Bug #51188

Doctrine does not respect AOP-injected properties

Added by Andreas Wolf almost 2 years ago. Updated over 1 year ago.

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

related to TYPO3.Flow - Bug #27045: Introduced properties are not available in the reflection... New 2011-05-26

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 }

Also available in: Atom PDF