Bug #51188
Doctrine does not respect AOP-injected properties
Status: | New | Start date: | 2013-08-19 | |
---|---|---|---|---|
Priority: | Should have | Due date: | ||
Assigned To: | - | % Done: | 0% |
|
Category: | Persistence | |||
Target version: | - | |||
PHP Version: | Complexity: | |||
Has patch: | No | Affected Flow version: | Git master |
Description
When I add a property to a model via an aspect, this property is not taken into account by the automagic Doctrine schema generation. Therefore, the database field is not created and the property is also not persisted if I manually add the field via a hand-written migration.
Doctrine does not respect the properties because the reflection components do not return the property that was woven in by the ProxyClassBuilder. This can be reproduced by examining the output of ReflectionService::getClassPropertyNames()
- the properties are missing there.
I think the root cause for this is that the reflection information is cached during compile-time before the proxy class files are built. Therefore, the injected properties are not available in the reflected (original) classes.
One potential caveat when fixing this is that both properties from MyClass and MyClass_Original have to be taken into account - I don't know how hard this will get using the PHP reflection mechanisms.
Related issues
History
#1 Updated by Rafael Kähm almost 2 years ago
You can use following dirty hack to show introduced properties in database:
You can put assignPropertiesToORM() method to your aspect class and modify rows 82-89.
1<?php
2namespace ..........
3
4use TYPO3\Flow\Annotations as Flow;
5use Doctrine\ORM\Mapping as ORM;
6
7/**
8 *
9 * @Flow\Scope("singleton")
10 * @Flow\Aspect
11 */
12class PropertyIntroductionAspect {
13
14 /**
15 * @Flow\Inject
16 * @var \TYPO3\Flow\Reflection\ReflectionService
17 */
18 protected $reflectionService;
19
20 /**
21 * @param \TYPO3\Flow\Reflection\ReflectionService $reflectionService Description
22 */
23 public function injectReflectionService(\TYPO3\Flow\Reflection\ReflectionService $reflectionService) {
24 $this->reflectionService = $reflectionService;
25 }
26
27 /**
28 * @var string
29 * @Flow\Introduce("some Pointcut")
30 */
31 protected $purpose;
32
33 /**
34 * @var string
35 * @Flow\Introduce("some other Pointcut")
36 */
37 protected $somethingElse;
38
39 /**
40 * Dirty hack for reflection service by introduced properties -> introduced properties are not in persistence layer
41 *
42 * @todo : Remove this advice if http://forge.typo3.org/issues/27045 is resolved.
43 *
44 * @param \TYPO3\Flow\Aop\JoinPointInterface $joinPoint The current join point
45 * @return void
46 * @Flow\Before("method(TYPO3\Flow\Persistence\Doctrine\EntityManagerFactory->create())")
47 */
48 public function assignPropertiesToORM(\TYPO3\Flow\Aop\JoinPointInterface $joinPoint) {
49 // those are added as property even if not tagged with entity/valueobject
50 $propertyTypeWhiteList = array(
51 'DateTime',
52 'SplObjectStorage',
53 'Doctrine\Common\Collections\Collection',
54 'Doctrine\Common\Collections\ArrayCollection'
55 );
56
57 $aspectClassName = get_class($this);
58 $aspectClassShema = $this->reflectionService->getClassSchema($aspectClassName);
59
60 $introducedPropertyNames = $this->reflectionService->getPropertyNamesByAnnotation($aspectClassName, 'TYPO3\Flow\Annotations\Introduce');
61
62 foreach ($introducedPropertyNames as $propertyName) {
63 $isTransientProperty = $this->reflectionService->isPropertyAnnotatedWith($aspectClassName, $propertyName, 'TYPO3\Flow\Annotations\Transient');
64 if ($isTransientProperty) {continue;}
65
66 $declaredType = trim(implode(' ', $this->reflectionService->getPropertyTagValues($aspectClassName, $propertyName, 'var')), ' \\');
67 if (preg_match('/\s/', $declaredType) === 1 || empty($declaredType)) {
68 throw new \TYPO3\Flow\Reflection\Exception\InvalidPropertyTypeException(sprintf('Introduced in "%s" property "%s" has no @var annotation or type is not defined or is not annotated as "TYPO3\Flow\Annotations\Transient". Please define type for "%s" or annotate it as "TYPO3\Flow\Annotations\Transient".', $aspectClassName, $propertyName, $propertyName), 1366547612);
69 }
70
71 try {
72 $parsedType = \TYPO3\Flow\Utility\TypeHandling::parseType($declaredType);
73 } catch (\TYPO3\Flow\Utility\Exception\InvalidTypeException $exception) {
74 throw new \InvalidArgumentException(sprintf($exception->getMessage(), 'class "' . $aspectClassName . '" for property "' . $propertyName . '"'), 1366551857);
75 }
76 if (!in_array($parsedType['type'], $propertyTypeWhiteList)
77 && (class_exists($parsedType['type']) || interface_exists($parsedType['type']))
78 && !($this->reflectionService->isClassAnnotatedWith($parsedType['type'], 'TYPO3\Flow\Annotations\Entity') || $this->isClassAnnotatedWith($parsedType['type'], 'Doctrine\ORM\Mapping\Entity') || $this->isClassAnnotatedWith($parsedType['type'], 'TYPO3\Flow\Annotations\ValueObject'))) {
79 continue;
80 }
81
82 // get all affected classes stuff is still not present but if it is possible here then you can add this Property to all affected classes
83// foreach ($affectedClasses as $affectedClass){
84// $classSchema = $this->reflectionService->getClassSchema($affectedClass);
85// $classSchema->addProperty($propertyName, $declaredType, $this->isPropertyAnnotatedWith($aspectClassName, $propertyName, 'TYPO3\Flow\Annotations\Lazy'));
86// if ($this->reflectionService->isPropertyAnnotatedWith($className, $propertyName, 'TYPO3\Flow\Annotations\Identity')) {
87// $classSchema->markAsIdentityProperty($propertyName);
88// }
89// }
90
91 }
92 }
93}
94?>
this code is https://gist.github.com/RafaelKa/6270217 here
#2 Updated by Rafael Kähm almost 2 years ago
if you know which class becomes new properties then you need only following inside of assignPropertiesToORM() method:
1<?php
2
3// ...
4 /**
5 * Dirty hack for reflection service by introduced properties -> introduced properties are not in persistence layer
6 *
7 * @todo : Remove this advice if http://forge.typo3.org/issues/27045 is resolved.
8 *
9 * param \TYPO3\Flow\Aop\JoinPointInterface $joinPoint The current join point
10 * @return void
11 * @Flow\Before("method(TYPO3\Flow\Persistence\Doctrine\EntityManagerFactory->create())")
12 */
13 public function assignPropertiesToORM() {
14// ...
15 $affectedProxy = $this->reflectionService->getClassSchema($affectedClassName);
16 $affectedProxy->addProperty('propertyName', 'string');
17// ...
18 }