Bug #51188

Doctrine does not respect AOP-injected properties

Added by Andreas Wolf about 8 years ago. Updated almost 8 years ago.

Status:
New
Priority:
Should have
Assignee:
-
Category:
Persistence
Target version:
-
Start date:
2013-08-19
Due date:
% Done:

0%

Estimated time:
PHP Version:
Has patch:
No
Complexity:

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 service during a compile runNew2011-05-26

Actions
#1

Updated by Rafael Kähm about 8 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.

<?php namespace .......... use TYPO3\Flow\Annotations as Flow; use Doctrine\ORM\Mapping as ORM; /** * * @Flow\Scope("singleton") * @Flow\Aspect */ class PropertyIntroductionAspect { /** * @Flow\Inject * @var \TYPO3\Flow\Reflection\ReflectionService */ protected $reflectionService; /** * @param \TYPO3\Flow\Reflection\ReflectionService $reflectionService Description */ public function injectReflectionService(\TYPO3\Flow\Reflection\ReflectionService $reflectionService) { $this->reflectionService = $reflectionService; } /** * @var string * @Flow\Introduce("some Pointcut") */ protected $purpose; /** * @var string * @Flow\Introduce("some other Pointcut") */ protected $somethingElse; /** * Dirty hack for reflection service by introduced properties -> introduced properties are not in persistence layer * * @todo : Remove this advice if http://forge.typo3.org/issues/27045 is resolved. * * @param \TYPO3\Flow\Aop\JoinPointInterface $joinPoint The current join point * @return void * @Flow\Before("method(TYPO3\Flow\Persistence\Doctrine\EntityManagerFactory->create())") */ public function assignPropertiesToORM(\TYPO3\Flow\Aop\JoinPointInterface $joinPoint) { // those are added as property even if not tagged with entity/valueobject $propertyTypeWhiteList = array( 'DateTime', 'SplObjectStorage', 'Doctrine\Common\Collections\Collection', 'Doctrine\Common\Collections\ArrayCollection' ); $aspectClassName = get_class($this); $aspectClassShema = $this->reflectionService->getClassSchema($aspectClassName); $introducedPropertyNames = $this->reflectionService->getPropertyNamesByAnnotation($aspectClassName, 'TYPO3\Flow\Annotations\Introduce'); foreach ($introducedPropertyNames as $propertyName) { $isTransientProperty = $this->reflectionService->isPropertyAnnotatedWith($aspectClassName, $propertyName, 'TYPO3\Flow\Annotations\Transient'); if ($isTransientProperty) {continue;} $declaredType = trim(implode(' ', $this->reflectionService->getPropertyTagValues($aspectClassName, $propertyName, 'var')), ' \\'); if (preg_match('/\s/', $declaredType) === 1 || empty($declaredType)) { 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); } try { $parsedType = \TYPO3\Flow\Utility\TypeHandling::parseType($declaredType); } catch (\TYPO3\Flow\Utility\Exception\InvalidTypeException $exception) { throw new \InvalidArgumentException(sprintf($exception->getMessage(), 'class "' . $aspectClassName . '" for property "' . $propertyName . '"'), 1366551857); } if (!in_array($parsedType['type'], $propertyTypeWhiteList) && (class_exists($parsedType['type']) || interface_exists($parsedType['type'])) && !($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'))) { continue; } // 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 // foreach ($affectedClasses as $affectedClass){ // $classSchema = $this->reflectionService->getClassSchema($affectedClass); // $classSchema->addProperty($propertyName, $declaredType, $this->isPropertyAnnotatedWith($aspectClassName, $propertyName, 'TYPO3\Flow\Annotations\Lazy')); // if ($this->reflectionService->isPropertyAnnotatedWith($className, $propertyName, 'TYPO3\Flow\Annotations\Identity')) { // $classSchema->markAsIdentityProperty($propertyName); // } // } } } } ?>

this code is https://gist.github.com/RafaelKa/6270217 here

#2

Updated by Rafael Kähm about 8 years ago

if you know which class becomes new properties then you need only following inside of assignPropertiesToORM() method:

<?php // ... /** * Dirty hack for reflection service by introduced properties -> introduced properties are not in persistence layer * * @todo : Remove this advice if http://forge.typo3.org/issues/27045 is resolved. * * param \TYPO3\Flow\Aop\JoinPointInterface $joinPoint The current join point * @return void * @Flow\Before("method(TYPO3\Flow\Persistence\Doctrine\EntityManagerFactory->create())") */ public function assignPropertiesToORM() { // ... $affectedProxy = $this->reflectionService->getClassSchema($affectedClassName); $affectedProxy->addProperty('propertyName', 'string'); // ... }

Also available in: Atom PDF