From 798ad12f499db62c07547ba8086bb24a738a7cac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Lescallier?= Date: Sun, 15 Mar 2020 22:14:16 +0100 Subject: [PATCH 1/4] Copy and adapte detach entity from doctrine; Fix multiple bug --- CHANGELOG.md | 8 + README.md | 7 +- composer.json | 3 +- docs/usage.md | 7 +- src/Doctrine/DetachEntity.php | 195 ++++++++++ .../Doctrine/OnFlushListener.php | 252 ++++--------- .../Serializer/MapSubscriber.php | 337 ------------------ src/LinkEntity/LinkEntityDoctrine.php | 25 +- src/Model/VersionWorkflowTrait.php | 8 + src/Repository/VersionWorkflowRepository.php | 9 +- src/Resources/config/services.yml | 8 + src/Utils/AutoMergeCheck.php | 111 ++++++ tests/AbstractTestCase.php | 35 +- tests/Model/News.php | 3 + tests/Serializer/SerializerTest.php | 84 ----- tests/Utils/AutoMergeCheckTest.php | 51 +++ 16 files changed, 512 insertions(+), 631 deletions(-) create mode 100644 src/Doctrine/DetachEntity.php delete mode 100644 src/EventSubscriber/Serializer/MapSubscriber.php create mode 100644 src/Utils/AutoMergeCheck.php delete mode 100644 tests/Serializer/SerializerTest.php create mode 100644 tests/Utils/AutoMergeCheckTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index a8df774..5b7125e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# 0.1.5 + +* Move mapping serializer to own package (``coosos/jms-serializer-bidirectional-relation``) + * Fix array keys who an auto reset +* Fix array link entity to original +* DetachEntity (based on doctrine 2.7) class for recursive detach directly in properties of UnitOfWork +* Reorganize / Optimize code + # 0.1.4 * Update test diff --git a/README.md b/README.md index 18d0e11..82ab547 100644 --- a/README.md +++ b/README.md @@ -30,5 +30,10 @@ If an old entity yet exists the same identifier, it will be replaced while keepi * [Installation](docs/install.md) * [Configuration](docs/config.md) * [Basic usage](docs/usage.md) -* [Serializer](https://github.com/schmittjoh/serializer) : I propose directly to you to see the documentation of JMS. +* [Serializer](https://github.com/schmittjoh/serializer) * [Use with doctrine](docs/doctrine.md) + +## TODO + +* Replace SingleStateMarkingStore by MethodMarkingStore +* Replace setInitialPlace by setInitialPlaces diff --git a/composer.json b/composer.json index 87dbad2..3f3cf8d 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,8 @@ "symfony/http-kernel": "^4.2", "symfony/property-info": "^4.2", "doctrine/orm": "^2.6", - "jms/serializer-bundle": "^2.4||^3.0" + "jms/serializer-bundle": "^2.4||^3.0", + "coosos/jms-serializer-bidirectional-relation": "^1.0.0" }, "require-dev": { "edgedesign/phpqa": "^1.23", diff --git a/docs/usage.md b/docs/usage.md index d5caf9a..1cc2c05 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -1,9 +1,14 @@ # Usage -# Prepare your model +This bundle use ``coosos/jms-serializer-bidirectional-relation`` package, see the documentation for more configuration. + +## Prepare your model For your model to work with bundle, is necessary to use VersionWorkflowTrait. + /** + * @Coosos\BidirectionalRelation\Annotations\SerializerBidirectionalRelation() + */ class News { use \Coosos\VersionWorkflowBundle\Model\VersionWorkflowTrait; diff --git a/src/Doctrine/DetachEntity.php b/src/Doctrine/DetachEntity.php new file mode 100644 index 0000000..86f5a95 --- /dev/null +++ b/src/Doctrine/DetachEntity.php @@ -0,0 +1,195 @@ + + */ +class DetachEntity +{ + /** + * @var EntityManagerInterface + */ + private $em; + + /** + * DetachEntity constructor. + * + * @param EntityManagerInterface $em + */ + public function __construct(EntityManagerInterface $em) + { + $this->em = $em; + } + + /** + * Detach doctrine entity + * + * @param VersionWorkflowTrait $entity + * @param UnitOfWork $unitOfWork + * @param array $entitiesDetached + * @param array $invokes + * + * @throws ReflectionException + */ + public function detach($entity, UnitOfWork $unitOfWork, &$entitiesDetached = [], array $invokes = []) + { + $visited = []; + + $this->doDetach($entity, $unitOfWork, $visited, $entitiesDetached, $invokes); + } + + /** + * Executes a detach operation on the given entity. + * + * @param object $entity + * @param UnitOfWork $unitOfWork + * @param array $visited + * @param array $entitiesDetached + * @param array $invokes + * @param boolean $noCascade if true, don't cascade detach operation. + * + * @return void + * @throws ReflectionException + */ + private function doDetach( + $entity, + UnitOfWork $unitOfWork, + array &$visited, + array &$entitiesDetached, + array $invokes, + $noCascade = false + ) { + if ($entity instanceof VersionWorkflow) { + return; + } + + $oid = spl_object_hash($entity); + + if (isset($visited[$oid])) { + return; // Prevent infinite recursion + } + + $visited[$oid] = $entity; // mark visited + switch ($unitOfWork->getEntityState($entity, UnitOfWork::STATE_DETACHED)) { + case UnitOfWork::STATE_MANAGED: + if ($unitOfWork->isInIdentityMap($entity)) { + $unitOfWork->removeFromIdentityMap($entity); + } + + if (!$entity instanceof VersionWorkflow && + isset($invokes['preUpdate']) && + is_callable($invokes['preUpdate']) + ) { + $invokes['preUpdate']($entity); + } + + $entitiesDetached[$oid] = true; + $this->unsetFromUnitOfWork($unitOfWork, 'entityInsertions', $oid); + $this->unsetFromUnitOfWork($unitOfWork, 'entityUpdates', $oid); + $this->unsetFromUnitOfWork($unitOfWork, 'entityDeletions', $oid); + $this->unsetFromUnitOfWork($unitOfWork, 'entityIdentifiers', $oid); + $this->unsetFromUnitOfWork($unitOfWork, 'entityStates', $oid); + $this->unsetFromUnitOfWork($unitOfWork, 'originalEntityData', $oid); + + break; + case UnitOfWork::STATE_NEW: + case UnitOfWork::STATE_DETACHED: + return; + } + + if (!$noCascade) { + $this->cascadeDetach($entity, $unitOfWork, $visited, $entitiesDetached, $invokes); + } + } + + /** + * Cascades a detach operation to associated entities. + * + * @param object $entity + * @param UnitOfWork $unitOfWork + * @param array $visited + * @param array $entitiesDetached + * @param array $invokes + * + * @return void + * @throws ReflectionException + */ + private function cascadeDetach( + $entity, + UnitOfWork + $unitOfWork, + array &$visited, + array &$entitiesDetached, + array $invokes + ) { + $class = $this->em->getClassMetadata(get_class($entity)); + + foreach ($class->associationMappings as $assoc) { + $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); + if ($relatedEntities instanceof PersistentCollection || + $relatedEntities instanceof Collection || + $relatedEntities instanceof ArrayCollection + ) { + $this->unsetFromUnitOfWork($unitOfWork, 'collectionUpdates', spl_object_hash($relatedEntities)); + $this->unsetFromUnitOfWork($unitOfWork, 'visitedCollections', spl_object_hash($relatedEntities)); + } + + if ($relatedEntities instanceof PersistentCollection) { + $relatedEntities = $relatedEntities->unwrap(); + } + + switch (true) { + case ($relatedEntities instanceof Collection): + case (is_array($relatedEntities)): + foreach ($relatedEntities as $relatedEntity) { + $this->doDetach($relatedEntity, $unitOfWork, $visited, $entitiesDetached, $invokes); + } + break; + case ($relatedEntities !== null): + $this->doDetach($relatedEntities, $unitOfWork, $visited, $entitiesDetached, $invokes); + break; + + default: + // Do nothing + } + } + } + + /** + * Use reflection for unset property data from unit of work class + * + * @param UnitOfWork $unitOfWork + * @param string $property + * @param string $oid + * + * @throws ReflectionException + */ + public function unsetFromUnitOfWork(UnitOfWork $unitOfWork, string $property, string $oid) + { + $filterCallback = function ($key) use ($oid) { + return $key !== $oid; + }; + + $propery = (new ReflectionClass($unitOfWork))->getProperty($property); + $propery->setAccessible(true); + + $properyValueFiltered = array_filter($propery->getValue($unitOfWork), $filterCallback, ARRAY_FILTER_USE_KEY); + $propery->setValue($unitOfWork, $properyValueFiltered); + } +} diff --git a/src/EventListener/Doctrine/OnFlushListener.php b/src/EventListener/Doctrine/OnFlushListener.php index 807f206..71f5ab0 100644 --- a/src/EventListener/Doctrine/OnFlushListener.php +++ b/src/EventListener/Doctrine/OnFlushListener.php @@ -2,22 +2,19 @@ namespace Coosos\VersionWorkflowBundle\EventListener\Doctrine; -use Coosos\VersionWorkflowBundle\Entity\VersionWorkflow; +use Coosos\VersionWorkflowBundle\Doctrine\DetachEntity; use Coosos\VersionWorkflowBundle\Model\VersionWorkflowConfiguration; use Coosos\VersionWorkflowBundle\Model\VersionWorkflowTrait; use Coosos\VersionWorkflowBundle\Service\SerializerService; use Coosos\VersionWorkflowBundle\Service\VersionWorkflowService; +use Coosos\VersionWorkflowBundle\Utils\AutoMergeCheck; use Coosos\VersionWorkflowBundle\Utils\ClassContains; -use Doctrine\Common\Collections\ArrayCollection; -use Doctrine\Common\Collections\Collection; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Event\ListenersInvoker; use Doctrine\ORM\Event\OnFlushEventArgs; use Doctrine\ORM\Event\PreUpdateEventArgs; use Doctrine\ORM\Events; -use Doctrine\ORM\ORMException; use Doctrine\ORM\ORMInvalidArgumentException; -use Doctrine\ORM\PersistentCollection; use ReflectionException; use Symfony\Component\Workflow\Registry; @@ -26,7 +23,6 @@ * * @package Coosos\VersionWorkflowBundle\EventListener\Doctrine * @author Remy Lescallier - * * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ @@ -71,6 +67,16 @@ class OnFlushListener */ protected $serializer; + /** + * @var AutoMergeCheck + */ + protected $autoMergeCheck; + + /** + * @var DetachEntity + */ + protected $detachEntity; + /** * OnFlushListener constructor. * @@ -79,7 +85,9 @@ class OnFlushListener * @param Registry $registry * @param VersionWorkflowService $versionWorkflowService * @param EntityManagerInterface $entityManager - * @param SerializerService $serializer + * @param SerializerService $serializer + * @param AutoMergeCheck $autoMergeCheck + * @param DetachEntity $detachEntity */ public function __construct( ClassContains $classContains, @@ -87,7 +95,9 @@ public function __construct( Registry $registry, VersionWorkflowService $versionWorkflowService, EntityManagerInterface $entityManager, - SerializerService $serializer + SerializerService $serializer, + AutoMergeCheck $autoMergeCheck, + DetachEntity $detachEntity ) { $this->classContains = $classContains; $this->versionWorkflowConfiguration = $versionWorkflowConfiguration; @@ -96,6 +106,8 @@ public function __construct( $this->listenersInvoker = new ListenersInvoker($entityManager); $this->versionWorkflowService = $versionWorkflowService; $this->serializer = $serializer; + $this->autoMergeCheck = $autoMergeCheck; + $this->detachEntity = $detachEntity; } /** @@ -104,179 +116,58 @@ public function __construct( * @param OnFlushEventArgs $args * * @throws ReflectionException - * @throws ORMException - * * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ public function onFlush(OnFlushEventArgs $args) { + $entityManager = $args->getEntityManager(); $unitOfWork = $args->getEntityManager()->getUnitOfWork(); - $entityFlushList = [ + $entitiesDetached = []; + $scheduledEntityList = [ 'entityUpdates' => $unitOfWork->getScheduledEntityUpdates(), 'entityInsertions' => $unitOfWork->getScheduledEntityInsertions(), 'entityDeletions' => $unitOfWork->getScheduledEntityDeletions(), - 'collectionUpdates' => $unitOfWork->getScheduledCollectionUpdates(), - 'collectionDeletions' => $unitOfWork->getScheduledCollectionDeletions(), ]; - foreach ($entityFlushList as $scheduledType => $item) { - if ($scheduledType === 'entityUpdates' || $scheduledType === 'entityInsertions') { - /** @var VersionWorkflowTrait $scheduledEntity */ - foreach ($item as $scheduledEntity) { - if ($this->hasVersionWorkflowTrait($scheduledEntity) - && !is_null($scheduledEntity->getWorkflowName()) - && $this->hasConfigurationByWorkflowName($scheduledEntity)) { - $currentPlace = $this->getCurrentPlace($scheduledEntity); - $autoMerge = false; - - if ($currentPlace) { - if (is_array($currentPlace)) { - foreach (array_keys($currentPlace) as $status) { - if ($this->isAutoMerge($scheduledEntity, $status)) { - $autoMerge = true; - break; - } - } - } elseif (is_string($currentPlace)) { - if ($this->isAutoMerge($scheduledEntity, $currentPlace)) { - $autoMerge = true; - } - } - } - - if (!$autoMerge || $scheduledEntity->isVersionWorkflowFakeEntity()) { - $this->detachRecursive($args, $scheduledEntity, $scheduledType); - $this->updateSerializedObject($scheduledEntity, $args->getEntityManager()); - } - } - } - } - - if ($scheduledType === 'entityDeletions' || $scheduledType === 'collectionDeletions') { - foreach ($item as $scheduledEntity) { - $this->detachDeletionsHash = []; - $detachDeletions = $this->checkDetachDeletionsRecursive($args, $scheduledEntity); - if ($detachDeletions && !$scheduledEntity instanceof PersistentCollection) { - $args->getEntityManager()->persist($scheduledEntity); - $this->detachRecursive($args, $scheduledEntity, $scheduledType); - } - - if ($detachDeletions && $scheduledEntity instanceof PersistentCollection) { - $mapping = $scheduledEntity->getMapping(); - $mapping['orphanRemoval'] = false; - $scheduledEntity->setOwner($scheduledEntity->getOwner(), $mapping); - } - } - } - } - - if ($unitOfWork->getScheduledEntityInsertions()) { - foreach ($unitOfWork->getScheduledEntityInsertions() as $entityInsertion) { - if (property_exists($entityInsertion, self::PROPERTY_DETACH) - && $entityInsertion->{self::PROPERTY_DETACH}) { - $args->getEntityManager()->detach($entityInsertion); + foreach ($scheduledEntityList as $entityType => $scheduledEntities) { + foreach ($scheduledEntities as $scheduledEntity) { + if (!$this->hasVersionWorkflowTrait($scheduledEntity) + || empty($scheduledEntity->getWorkflowName()) + || !$this->hasConfigurationByWorkflowName($scheduledEntity) + ) { + continue; } - } - } - } - - /** - * Check if relation use object detached - * - * @param OnFlushEventArgs $args - * @param VersionWorkflowTrait|mixed $entity - * - * @return bool - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - protected function checkDetachDeletionsRecursive(OnFlushEventArgs $args, $entity) - { - if (is_null($entity)) { - return false; - } - - if (property_exists($entity, self::PROPERTY_DETACH) && $entity->{self::PROPERTY_DETACH}) { - return true; - } - - if (in_array(spl_object_hash($entity), $this->detachDeletionsHash)) { - return false; - } - $this->detachDeletionsHash[] = spl_object_hash($entity); - - if ($entity instanceof PersistentCollection) { - $isDetach = $this->checkDetachDeletionsRecursive($args, $entity->getOwner()); - if ($isDetach) { - return true; - } - } + if (!$this->isAutoMergeEntity($scheduledEntity) || $scheduledEntity->isVersionWorkflowFakeEntity()) { + $preUpdateInvoke = function ($entity) use ($entityType, $entityManager) { + if ($entityType === 'entityUpdates') { + $this->invokePreUpdateEvent($entityManager, $entity); + } + }; - $meta = $args->getEntityManager()->getClassMetadata(get_class($entity)); - foreach (array_keys($meta->getAssociationMappings()) as $fieldName) { - if (!$entity->{'get' . ucfirst($fieldName)}() instanceof Collection) { - $isDetach = $this->checkDetachDeletionsRecursive($args, $entity->{'get' . ucfirst($fieldName)}()); - if ($isDetach) { - return true; + $invokes = ['preUpdate' => $preUpdateInvoke]; + $this->detachEntity->detach($scheduledEntity, $unitOfWork, $entitiesDetached, $invokes); + $this->updateSerializedObject($scheduledEntity, $args->getEntityManager()); } } } - return false; - } - - /** - * Recursive detach - * - * @param OnFlushEventArgs $args - * @param VersionWorkflowTrait|mixed $entity - * @param string|null $scheduledType - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - protected function detachRecursive(OnFlushEventArgs $args, $entity, ?string $scheduledType = null) - { - $entityManager = $args->getEntityManager(); - $classMetaData = $entityManager->getClassMetadata(get_class($entity)); - if (!$entity instanceof VersionWorkflow && $scheduledType && $scheduledType === 'entityUpdates') { - $this->invokePreUpdateEvent($entityManager, $entity); - } - - $entityManager->detach($entity); - $entity->{self::PROPERTY_DETACH} = true; - - foreach (array_keys($classMetaData->getAssociationMappings()) as $key) { - if ($entity->{'get' . ucfirst($key)}() instanceof PersistentCollection) { - /** @var PersistentCollection $getCollectionMethod */ - $getCollectionMethod = $entity->{'get' . ucfirst($key)}(); - foreach ($getCollectionMethod as $item) { - if (property_exists($item, self::PROPERTY_DETACH) && $item->{self::PROPERTY_DETACH}) { - continue; - } - - $this->detachRecursive($args, $item, $scheduledType); - - continue; - } - - /** @var PersistentCollection $tags */ - $mapping = $getCollectionMethod->getMapping(); - $mapping['isOwningSide'] = false; - $getCollectionMethod->setOwner($entity, $mapping); - } elseif (!$entity->{'get' . ucfirst($key)}() instanceof ArrayCollection - && $key !== self::VERSION_WORKFLOW_PROPERTY) { - $item = $entity->{'get' . ucfirst($key)}(); - if (!is_null($item)) { - if (property_exists($item, self::PROPERTY_DETACH) && $item->{self::PROPERTY_DETACH}) { - continue; + $entityDeletions = $unitOfWork->getScheduledEntityDeletions(); + foreach ([$entityDeletions] as $scheduledEntities) { + foreach ($scheduledEntities as $scheduledEntity) { + foreach ($unitOfWork->getEntityChangeSet($scheduledEntity) as $changeSet) { + foreach ($changeSet as $value) { + if (is_object($value) && isset($entitiesDetached[spl_object_hash($value)])) { + $entitiesDetached[spl_object_hash($scheduledEntity)] = true; + $this->detachEntity->unsetFromUnitOfWork( + $unitOfWork, + 'entityDeletions', + spl_object_hash($scheduledEntity) + ); + } } - - $this->detachRecursive($args, $item, $scheduledType); } - - continue; } } } @@ -296,6 +187,7 @@ protected function hasVersionWorkflowTrait($model) /** * @param VersionWorkflowTrait $model + * * @return bool */ protected function hasConfigurationByWorkflowName($model) @@ -304,33 +196,15 @@ protected function hasConfigurationByWorkflowName($model) } /** - * @param VersionWorkflowTrait $model - * @return mixed|null - */ - protected function getCurrentPlace($model) - { - $workflow = $this->registry->get($model, $model->getWorkflowName()); - - $getterMethod = $this->classContains->getGetterMethod($model, $workflow->getMarkingStore()->getProperty()); - - if ($getterMethod) { - return $model->{$getterMethod}(); - } - - return null; - } - - /** - * @param VersionWorkflowTrait $model - * @param string $place + * Check is auto merge by entity + * + * @param VersionWorkflowTrait $scheduledEntity + * * @return bool */ - protected function isAutoMerge($model, $place) + protected function isAutoMergeEntity($scheduledEntity) { - return $this->versionWorkflowConfiguration->isAutoMerge( - $model->getWorkflowName(), - $place - ); + return $this->autoMergeCheck->isAutoMergeEntity($scheduledEntity); } /** @@ -339,7 +213,6 @@ protected function isAutoMerge($model, $place) * @param EntityManagerInterface $entityManager * @param mixed $entity * @param bool $recompute - * * @SuppressWarnings(PHPMD.EmptyCatchBlock) */ protected function invokePreUpdateEvent($entityManager, $entity, $recompute = false) @@ -377,12 +250,7 @@ protected function updateSerializedObject($entity, $entityManager) { if ($entity->getVersionWorkflow()) { $entity->getVersionWorkflow()->setObjectSerialized($this->serializer->serialize($entity)); - - $this->invokePreUpdateEvent( - $entityManager, - $entity->getVersionWorkflow(), - true - ); + $this->invokePreUpdateEvent($entityManager, $entity->getVersionWorkflow(), true); } return $entity; diff --git a/src/EventSubscriber/Serializer/MapSubscriber.php b/src/EventSubscriber/Serializer/MapSubscriber.php deleted file mode 100644 index c6d340c..0000000 --- a/src/EventSubscriber/Serializer/MapSubscriber.php +++ /dev/null @@ -1,337 +0,0 @@ - - * - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class MapSubscriber implements EventSubscriberInterface -{ - const ATTR_DATA_NAME = 'version_workflow_mappings'; - - /** - * @var array - */ - protected $alreadyHashObject; - - /** - * @var array - */ - protected $map; - - /** - * @var ClassContains - */ - private $classContains; - - /** - * @var mixed|null - */ - private $currentObject; - - /** - * @var array - */ - private $currentMappings; - - /** - * TransformRelationSubscriber constructor. - * - * @param ClassContains $classContains - */ - public function __construct(ClassContains $classContains) - { - $this->classContains = $classContains; - $this->currentObject = null; - $this->currentMappings = []; - } - - /** - * {@inheritDoc} - */ - public static function getSubscribedEvents() - { - return [ - [ - 'event' => 'serializer.pre_serialize', - 'method' => 'onPreSerialize', - ], - [ - 'event' => 'serializer.post_deserialize', - 'method' => 'onPostDeserialize', - ], - [ - 'event' => 'serializer.post_serialize', - 'method' => 'onPostSerialize', - ], - [ - 'event' => 'serializer.pre_deserialize', - 'method' => 'onPreDeserialize', - ], - ]; - } - - /** - * @param PreSerializeEvent $event - * - * @throws ReflectionException - */ - public function onPreSerialize(PreSerializeEvent $event) - { - if (is_object($event->getObject()) - && $this->classContains->hasTrait($event->getObject(), VersionWorkflowTrait::class) - && $event->getContext()->getDepth() === 1 - ) { - $this->alreadyHashObject = []; - $this->currentObject = $event->getObject(); - $this->currentMappings = $this->optimizeMappingSerialize( - $this->buildMap($event->getObject(), $event->getContext()) - ); - } - } - - /** - * @param ObjectEvent $event - */ - public function onPostSerialize(ObjectEvent $event) - { - if ($event->getObject() === $this->currentObject) { - $visitor = $event->getVisitor(); - $data = [ - new StaticPropertyMetadata('', self::ATTR_DATA_NAME, $this->currentMappings), - $this->currentMappings - ]; - - if (!$visitor instanceof SerializationVisitorInterface) { - $data[] = $event->getContext(); - } - - $visitor->visitProperty(...$data); - } - } - - /** - * @param PreDeserializeEvent $event - */ - public function onPreDeserialize(PreDeserializeEvent $event) - { - if ($event->getContext()->getDepth() === 1 && isset($event->getData()[self::ATTR_DATA_NAME])) { - $this->currentMappings = $event->getData()[self::ATTR_DATA_NAME]; - } - } - - /** - * @param ObjectEvent $event - * - * @throws ReflectionException - */ - public function onPostDeserialize(ObjectEvent $event) - { - /** @var VersionWorkflowTrait $object */ - $object = $event->getObject(); - if ($this->classContains->hasTrait($object, VersionWorkflowTrait::class) - && $event->getContext()->getDepth() === 0 - ) { - $map = $this->currentMappings; - $this->parseDeserialize($object, $map); - } - } - - /** - * Build map - * - * @param VersionWorkflowTrait $object - * @param Context $context - * @param string|null $prev - * - * @return mixed - * @throws ReflectionException - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - protected function buildMap($object, Context $context, string $prev = null) - { - $splObjectHash = spl_object_hash($object); - if (isset($this->alreadyHashObject[$splObjectHash])) { - return [$prev => $splObjectHash]; - } - - $this->alreadyHashObject[$splObjectHash] = true; - - if (!$prev) { - $prev = 'root'; - } - - $map = []; - $map[$prev] = $splObjectHash; - - $propertyMetadata = $context->getMetadataFactory()->getMetadataForClass(get_class($object))->propertyMetadata; - $properties = (new ReflectionClass($object))->getProperties( - ReflectionProperty::IS_PUBLIC | - ReflectionProperty::IS_PROTECTED | - ReflectionProperty::IS_PRIVATE - ); - - foreach ($properties as $property) { - if (!in_array($property->getName(), array_keys($propertyMetadata)) - || in_array($property->getName(), FieldsListExclusionStrategy::IGNORE_FIELDS)) { - continue; - } - - $property->setAccessible(true); - $propertyValue = $property->getValue($object); - - if (is_object($propertyValue) && !$propertyValue instanceof ArrayAccess) { - $map = array_merge( - $map, - $this->buildMap($propertyValue, $context, sprintf('%s,%s', $prev, $property->getName())) - ); - } elseif ((is_array($propertyValue) || $propertyValue instanceof ArrayAccess) - && $property->getName() !== 'versionWorkflowMap' - ) { - foreach ($propertyValue as $key => $item) { - $map = array_merge( - $map, - $this->buildMap($item, $context, sprintf('%s,%s,__array,%s', $prev, $property->getName(), $key)) - ); - } - } - } - - return $map; - } - - /** - * Optmize mapping - * - * @param array $mappings - * - * @return array - */ - protected function optimizeMappingSerialize(array $mappings): array - { - $tempMapping = $newMapping = []; - - $i = 0; - foreach ($mappings as $path => $mapping) { - if (!isset($tempMapping[$mapping])) { - $tempMapping[$mapping] = ++$i; - } - - $newMapping[$path] = $tempMapping[$mapping]; - } - - return $newMapping; - } - - /** - * Parse deserialize - * - * @param VersionWorkflowTrait|mixed $object - * @param array $map - * @param string $currentMap - * @param array $already - * - * @return mixed - * @throws ReflectionException - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - protected function parseDeserialize( - $object, - array &$map, - string $currentMap = 'root', - array &$already = [] - ) { - if (!isset($map[$currentMap])) { - return $object; - } - - if (isset($already[$map[$currentMap]])) { - return $already[$map[$currentMap]]; - } - - $already[$map[$currentMap]] = $object; - unset($map[$currentMap]); - - $properties = (new ReflectionClass($object))->getProperties( - ReflectionProperty::IS_PUBLIC | - ReflectionProperty::IS_PROTECTED | - ReflectionProperty::IS_PRIVATE - ); - - foreach ($properties as $property) { - $property->setAccessible(true); - $propertyValue = $property->getValue($object); - $propertyName = $property->getName(); - - if (is_object($propertyValue) && !$propertyValue instanceof ArrayAccess) { - $property->setValue( - $object, - $this->parseDeserialize( - $propertyValue, - $map, - sprintf('%s,%s', $currentMap, $propertyName), - $already - ) - ); - - unset($map[sprintf('%s,%s', $currentMap, $propertyName)]); - } elseif ((is_array($propertyValue) || $propertyValue instanceof ArrayAccess) - && $propertyName !== 'versionWorkflowMap' - ) { - $list = ($propertyValue instanceof ArrayCollection) ? $propertyValue : []; - foreach ($propertyValue as $key => $item) { - if ($list instanceof ArrayCollection) { - $list->set( - $key, - $this->parseDeserialize( - $item, - $map, - sprintf('%s,%s,__array,%s', $currentMap, $propertyName, $key), - $already - ) - ); - } elseif (is_array($propertyValue)) { - $list[$key] = $this->parseDeserialize( - $item, - $map, - sprintf('%s,%s,__array,%s', $currentMap, $propertyName, $key), - $already - ); - } - } - - $property->setValue($object, $list); - } elseif ($propertyValue === null - && isset($map[$currentMap . ',' . $propertyName]) - && isset($already[$map[$currentMap . ',' . $propertyName]]) - ) { - $property->setValue($object, $already[$map[$currentMap . ',' . $propertyName]]); - unset($map[$currentMap . ',' . $propertyName]); - } - } - - return $object; - } -} diff --git a/src/LinkEntity/LinkEntityDoctrine.php b/src/LinkEntity/LinkEntityDoctrine.php index a3c0633..d1d5ffa 100644 --- a/src/LinkEntity/LinkEntityDoctrine.php +++ b/src/LinkEntity/LinkEntityDoctrine.php @@ -4,6 +4,7 @@ use Coosos\VersionWorkflowBundle\Annotation\IgnoreChange; use Coosos\VersionWorkflowBundle\Annotation\OnlyId; +use Coosos\VersionWorkflowBundle\Entity\VersionWorkflow; use Coosos\VersionWorkflowBundle\Model\VersionWorkflowTrait; use Coosos\VersionWorkflowBundle\Utils\ClassContains; use Doctrine\Common\Annotations\Reader; @@ -11,6 +12,7 @@ use Doctrine\Common\Persistence\Mapping\ClassMetadata; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata as ORMClassMetadata; +use Doctrine\ORM\PersistentCollection; use ReflectionProperty; /** @@ -74,7 +76,7 @@ public function linkFakeEntityWithOriginalEntity( array $annotations = [], ClassMetadata $classMetadata = null ) { - if (is_null($model)) { + if (is_null($model) || $model instanceof VersionWorkflow) { return $model; } elseif (in_array(spl_object_hash($model), array_keys($this->originalObjectByModelHash))) { return $this->originalObjectByModelHash[spl_object_hash($model)]; @@ -215,6 +217,12 @@ protected function parseListRelation( $metadataField, $classMetadata ) { + $getterMethod = $this->classContains->getGetterMethod($originalEntity, $metadataField); + $setterMethod = $this->classContains->getSetterMethod($originalEntity, $metadataField); + if (spl_object_hash($originalEntity->{$getterMethod}()) === spl_object_hash($model->{$getterMethod}())) { + $originalEntity->{$setterMethod}(clone $originalEntity->{$getterMethod}()); + } + $compare = $this->compareRelationList($originalEntity, $model, $metadataField, $classMetadata); $this->parseRemoveElementFromList($originalEntity, $compare, $classMetadata, $metadataField); @@ -227,8 +235,8 @@ protected function parseListRelation( $metadataField ); - $this->parseAddElementFromList($compare, $metadataField, $originalEntity); $this->parseKeyChangedFromList($compare, $metadataField, $originalEntity); + $this->parseAddElementFromList($compare, $metadataField, $originalEntity); return true; } @@ -255,8 +263,8 @@ protected function parseUpdateElementFromList( $modelEntityList = $model->{$getterMethod}(); $originalEntityList = $originalEntity->{$getterMethod}(); - foreach ($modelEntityList as $key => $item) { - foreach ($originalEntityList as $itemOriginal) { + foreach ($modelEntityList as $item) { + foreach ($originalEntityList as $key => $itemOriginal) { if ($this->compareIdentifierModel($classMetadata, $item, $itemOriginal)) { $originalEntityList[$key] = $this->linkFakeEntityWithOriginalEntity( $item, @@ -349,9 +357,14 @@ protected function parseKeyChangedFromList(array $compare, string $field, $entit { $getterMethod = $this->classContains->getGetterMethod($entity, $field); $originalEntityList = $entity->{$getterMethod}(); + if ($originalEntityList instanceof PersistentCollection) { + $originalEntityList = $originalEntityList->unwrap(); + } + if (!empty($compare['keyChanged'])) { - foreach (array_keys($compare['keyChanged']) as $key) { - unset($originalEntityList[$key]); + foreach ($compare['keyChanged'] as $sourceKey => $destinationKey) { + $originalEntityList[$destinationKey] = $originalEntityList[$sourceKey]; + unset($originalEntityList[$sourceKey]); } } diff --git a/src/Model/VersionWorkflowTrait.php b/src/Model/VersionWorkflowTrait.php index c658485..eb900ef 100644 --- a/src/Model/VersionWorkflowTrait.php +++ b/src/Model/VersionWorkflowTrait.php @@ -2,6 +2,8 @@ namespace Coosos\VersionWorkflowBundle\Model; +use Coosos\BidirectionalRelation\Annotations\ExcludeFromMapping; + /** * Trait VersionWorkflowTrait * @@ -11,16 +13,22 @@ trait VersionWorkflowTrait { /** * @var VersionWorkflowModel|null + * + * @ExcludeFromMapping() */ protected $versionWorkflow; /** * @var string|null + * + * @ExcludeFromMapping() */ protected $workflowName; /** * @var bool + * + * @ExcludeFromMapping() */ protected $versionWorkflowFakeEntity = false; diff --git a/src/Repository/VersionWorkflowRepository.php b/src/Repository/VersionWorkflowRepository.php index 941039b..624d9ba 100644 --- a/src/Repository/VersionWorkflowRepository.php +++ b/src/Repository/VersionWorkflowRepository.php @@ -16,10 +16,11 @@ class VersionWorkflowRepository extends EntityRepository /** * Get list * - * @param string $classPath - * @param string $workflowName - * @param string $marking - * @param bool $lastOfInstance + * @param string $classPath + * @param string $workflowName + * @param string|array $marking + * @param bool $lastOfInstance + * * @return mixed */ public function getList(string $classPath, string $workflowName, $marking, $lastOfInstance = true) diff --git a/src/Resources/config/services.yml b/src/Resources/config/services.yml index 9da982b..c3b9018 100644 --- a/src/Resources/config/services.yml +++ b/src/Resources/config/services.yml @@ -19,3 +19,11 @@ services: Coosos\VersionWorkflowBundle\EventListener\Doctrine\OnFlushListener: tags: - { name: doctrine.event_listener, event: onFlush } + + Coosos\BidirectionalRelation\EventSubscriber\MapDeserializerSubscriber: + tags: + - { name: jms_serializer.event_subscriber } + + Coosos\BidirectionalRelation\EventSubscriber\MapSerializerSubscriber: + tags: + - { name: jms_serializer.event_subscriber } diff --git a/src/Utils/AutoMergeCheck.php b/src/Utils/AutoMergeCheck.php new file mode 100644 index 0000000..1e502ab --- /dev/null +++ b/src/Utils/AutoMergeCheck.php @@ -0,0 +1,111 @@ + + */ +class AutoMergeCheck +{ + /** + * @var Registry + */ + private $registry; + + /** + * @var ClassContains + */ + private $classContains; + + /** + * @var VersionWorkflowConfiguration + */ + private $versionWorkflowConfiguration; + + /** + * AutoMergeCheck constructor. + * + * @param Registry $registry + * @param ClassContains $classContains + * @param VersionWorkflowConfiguration $versionWorkflowConfiguration + */ + public function __construct( + Registry $registry, + ClassContains $classContains, + VersionWorkflowConfiguration $versionWorkflowConfiguration + ) { + $this->registry = $registry; + $this->classContains = $classContains; + $this->versionWorkflowConfiguration = $versionWorkflowConfiguration; + } + + /** + * Check is auto merge by entity + * + * @param VersionWorkflowTrait $scheduledEntity + * + * @return bool + */ + public function isAutoMergeEntity($scheduledEntity) + { + if ($currentPlace = $this->getCurrentPlace($scheduledEntity)) { + if (is_array($currentPlace)) { + foreach (array_keys($currentPlace) as $status) { + if ($this->isAutoMerge($scheduledEntity, $status)) { + return true; + } + } + } elseif (is_string($currentPlace)) { + if ($this->isAutoMerge($scheduledEntity, $currentPlace)) { + return true; + } + } + } + + return false; + } + + /** + * Get current place + * + * @param VersionWorkflowTrait $model + * + * @return mixed|null + */ + protected function getCurrentPlace($model) + { + $workflow = $this->registry->get($model, $model->getWorkflowName()); + + $getterMethod = $this->classContains->getGetterMethod($model, $workflow->getMarkingStore()->getProperty()); + + if ($getterMethod) { + return $model->{$getterMethod}(); + } + + return null; + } + + /** + * Check is auto merge by place + * + * @param VersionWorkflowTrait $model + * @param string $place + * + * @return bool + */ + protected function isAutoMerge($model, $place) + { + return $this->versionWorkflowConfiguration->isAutoMerge( + $model->getWorkflowName(), + $place + ); + } +} diff --git a/tests/AbstractTestCase.php b/tests/AbstractTestCase.php index 70d6bf9..c12e38a 100644 --- a/tests/AbstractTestCase.php +++ b/tests/AbstractTestCase.php @@ -2,7 +2,9 @@ namespace Coosos\VersionWorkflowBundle\Tests; -use Coosos\VersionWorkflowBundle\EventSubscriber\Serializer\MapSubscriber; +use Coosos\BidirectionalRelation\EventSubscriber\MapDeserializerSubscriber; +use Coosos\BidirectionalRelation\EventSubscriber\MapSerializerSubscriber; +use Coosos\VersionWorkflowBundle\Model\VersionWorkflowConfiguration; use Coosos\VersionWorkflowBundle\Serializer\Exclusion\FieldsListExclusionStrategy; use Coosos\VersionWorkflowBundle\Service\SerializerService; use Coosos\VersionWorkflowBundle\Service\VersionWorkflowService; @@ -45,6 +47,11 @@ abstract class AbstractTestCase extends TestCase */ protected $serializerContext; + /** + * @var Registry + */ + protected $registryWorkflow; + /** * Set up */ @@ -53,20 +60,20 @@ protected function setUp(): void $classContains = new ClassContains(); $builder = SerializerBuilder::create(); $builder->configureListeners(function (EventDispatcher $dispatcher) { - $dispatcher->addSubscriber(new MapSubscriber(new ClassContains())); + $dispatcher->addSubscriber(new MapSerializerSubscriber()); + $dispatcher->addSubscriber(new MapDeserializerSubscriber()); }); - $this->jmsSerializer = $builder->build(); if (!$this->serializerContext) { $this->serializerContext = (SerializationContext::create()) ->addExclusionStrategy(new FieldsListExclusionStrategy($classContains)); } - $registry = $this->getRegistryMock(); + $this->registryWorkflow = $this->getRegistryMock(); $this->versionWorkflowService = new VersionWorkflowService( new SerializerService($this->jmsSerializer, $classContains), - $registry, + $this->registryWorkflow, $classContains ); } @@ -123,4 +130,22 @@ protected function getDeserializerContext() return (DeserializationContext::create()) ->addExclusionStrategy(new FieldsListExclusionStrategy(new ClassContains())); } + + /** + * Get version workflow configuration model + * + * @return VersionWorkflowConfiguration + */ + protected function getVersionWorkflowConfiguration() + { + $config = [ + 'workflows' => [ + self::DEFAULT_WORKFLOW_NAME => [ + 'auto_merge' => ['publish'], + ], + ], + ]; + + return new VersionWorkflowConfiguration($config); + } } diff --git a/tests/Model/News.php b/tests/Model/News.php index b22b4a1..db7a407 100644 --- a/tests/Model/News.php +++ b/tests/Model/News.php @@ -2,6 +2,7 @@ namespace Coosos\VersionWorkflowBundle\Tests\Model; +use Coosos\BidirectionalRelation\Annotations\SerializerBidirectionalRelation; use Coosos\VersionWorkflowBundle\Model\VersionWorkflowTrait; use DateTime; use Doctrine\Common\Collections\ArrayCollection; @@ -13,6 +14,8 @@ * * @package Coosos\VersionWorkflowBundle\Tests\Model * @author Remy Lescallier + * + * @SerializerBidirectionalRelation() */ class News { diff --git a/tests/Serializer/SerializerTest.php b/tests/Serializer/SerializerTest.php deleted file mode 100644 index fbcfb2f..0000000 --- a/tests/Serializer/SerializerTest.php +++ /dev/null @@ -1,84 +0,0 @@ - - */ -class SerializerTest extends AbstractTestCase -{ - /** - * Example 1 - * - * @dataProvider getExampleProviderList - * - * @param int $nb - */ - public function testSerializeExample($nb) - { - $example = $this->getExample($nb); - $build = $this->jmsSerializer; - $newsSerialized = $build->serialize( - $example->generate(), - SerializerService::SERIALIZE_FORMAT, - $this->getSerializerContext() - ); - - $newsDeserialized = $build->deserialize( - $newsSerialized, - News::class, - SerializerService::SERIALIZE_FORMAT, - $this->getDeserializerContext() - ); - - $this->assertEquals($newsDeserialized, $example->resultDeserialied()); - } - - /** - * Example - * - * @dataProvider getExampleProviderList - * - * @param int $nb - */ - public function testSerializeExampleWithVersionWorkflowModel($nb) - { - $example = $this->getExample($nb); - $exampleGenerate = $example->generate(); - $exampleGenerate->setVersionWorkflow(new VersionWorkflowModel()); - - $newsSerialized = $this->jmsSerializer->serialize( - $exampleGenerate, - SerializerService::SERIALIZE_FORMAT, - $this->getSerializerContext() - ); - - $newsDeserialized = $this->jmsSerializer->deserialize( - $newsSerialized, - News::class, - SerializerService::SERIALIZE_FORMAT, - $this->getDeserializerContext() - ); - - $this->assertEquals($newsDeserialized, $example->resultDeserialied()); - } - - /** - * @return Generator - */ - public function getExampleProviderList() - { - yield [1]; - yield [2]; - yield [3]; - } -} diff --git a/tests/Utils/AutoMergeCheckTest.php b/tests/Utils/AutoMergeCheckTest.php new file mode 100644 index 0000000..d1b9ce6 --- /dev/null +++ b/tests/Utils/AutoMergeCheckTest.php @@ -0,0 +1,51 @@ + + */ +class AutoMergeCheckTest extends AbstractTestCase +{ + /** + * @var AutoMergeCheck + */ + protected $autoMergeChecker; + + /** + * Test is auto merge entity from AutoMergeCheck class + */ + public function testIsAutoMergeEntity() + { + $example = $this->getExample(1); + $news = $example->generate(); + $news->setWorkflowName(self::DEFAULT_WORKFLOW_NAME); + + $news->setMarking('draft'); + $this->assertFalse($this->autoMergeChecker->isAutoMergeEntity($news)); + + $news->setMarking('publish'); + $this->assertTrue($this->autoMergeChecker->isAutoMergeEntity($news)); + } + + /** + * @inheritDoc + */ + protected function setUp(): void + { + parent::setUp(); + + $this->autoMergeChecker = new AutoMergeCheck( + $this->registryWorkflow, + new ClassContains(), + $this->getVersionWorkflowConfiguration() + ); + } +} From 7e2395b8ad8adbee146ac0cacd55cd6cba603fd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Lescallier?= Date: Sun, 5 Apr 2020 19:44:43 +0200 Subject: [PATCH 2/4] Fix detach remove entity from collection --- src/Doctrine/DetachEntity.php | 109 ++++++++++++++------------ src/LinkEntity/LinkEntityDoctrine.php | 11 --- 2 files changed, 58 insertions(+), 62 deletions(-) diff --git a/src/Doctrine/DetachEntity.php b/src/Doctrine/DetachEntity.php index 86f5a95..f12e7f7 100644 --- a/src/Doctrine/DetachEntity.php +++ b/src/Doctrine/DetachEntity.php @@ -86,38 +86,48 @@ private function doDetach( } $visited[$oid] = $entity; // mark visited - switch ($unitOfWork->getEntityState($entity, UnitOfWork::STATE_DETACHED)) { - case UnitOfWork::STATE_MANAGED: - if ($unitOfWork->isInIdentityMap($entity)) { - $unitOfWork->removeFromIdentityMap($entity); - } - - if (!$entity instanceof VersionWorkflow && - isset($invokes['preUpdate']) && - is_callable($invokes['preUpdate']) - ) { - $invokes['preUpdate']($entity); - } + if ($unitOfWork->getEntityState($entity, UnitOfWork::STATE_DETACHED) === UnitOfWork::STATE_MANAGED) { + if ($unitOfWork->isInIdentityMap($entity)) { + $unitOfWork->removeFromIdentityMap($entity); + } - $entitiesDetached[$oid] = true; - $this->unsetFromUnitOfWork($unitOfWork, 'entityInsertions', $oid); - $this->unsetFromUnitOfWork($unitOfWork, 'entityUpdates', $oid); - $this->unsetFromUnitOfWork($unitOfWork, 'entityDeletions', $oid); - $this->unsetFromUnitOfWork($unitOfWork, 'entityIdentifiers', $oid); - $this->unsetFromUnitOfWork($unitOfWork, 'entityStates', $oid); - $this->unsetFromUnitOfWork($unitOfWork, 'originalEntityData', $oid); - - break; - case UnitOfWork::STATE_NEW: - case UnitOfWork::STATE_DETACHED: - return; + if (!$entity instanceof VersionWorkflow && + isset($invokes['preUpdate']) && + is_callable($invokes['preUpdate']) + ) { + $invokes['preUpdate']($entity); + } } + $this->unsetEntity($entity, $unitOfWork, $entitiesDetached); + if (!$noCascade) { $this->cascadeDetach($entity, $unitOfWork, $visited, $entitiesDetached, $invokes); } } + /** + * Use reflection for unset property data from unit of work class + * + * @param UnitOfWork $unitOfWork + * @param string $property + * @param string $oid + * + * @throws ReflectionException + */ + public function unsetFromUnitOfWork(UnitOfWork $unitOfWork, string $property, string $oid) + { + $filterCallback = function ($key) use ($oid) { + return $key !== $oid; + }; + + $propery = (new ReflectionClass($unitOfWork))->getProperty($property); + $propery->setAccessible(true); + + $properyValueFiltered = array_filter($propery->getValue($unitOfWork), $filterCallback, ARRAY_FILTER_USE_KEY); + $propery->setValue($unitOfWork, $properyValueFiltered); + } + /** * Cascades a detach operation to associated entities. * @@ -132,8 +142,7 @@ private function doDetach( */ private function cascadeDetach( $entity, - UnitOfWork - $unitOfWork, + UnitOfWork $unitOfWork, array &$visited, array &$entitiesDetached, array $invokes @@ -151,45 +160,43 @@ private function cascadeDetach( } if ($relatedEntities instanceof PersistentCollection) { + foreach ($relatedEntities->getDeleteDiff() as $entityDeleted) { + $this->doDetach($entityDeleted, $unitOfWork, $visited, $entitiesDetached, $invokes); + } + $relatedEntities = $relatedEntities->unwrap(); } - switch (true) { - case ($relatedEntities instanceof Collection): - case (is_array($relatedEntities)): - foreach ($relatedEntities as $relatedEntity) { - $this->doDetach($relatedEntity, $unitOfWork, $visited, $entitiesDetached, $invokes); - } - break; - case ($relatedEntities !== null): - $this->doDetach($relatedEntities, $unitOfWork, $visited, $entitiesDetached, $invokes); - break; - - default: - // Do nothing + if ($relatedEntities instanceof Collection || is_array($relatedEntities)) { + foreach ($relatedEntities as $relatedEntity) { + $this->doDetach($relatedEntity, $unitOfWork, $visited, $entitiesDetached, $invokes); + } + } elseif ($relatedEntities !== null) { + $this->doDetach($relatedEntities, $unitOfWork, $visited, $entitiesDetached, $invokes); } } } /** - * Use reflection for unset property data from unit of work class + * Unset entity * + * @param mixed $entity * @param UnitOfWork $unitOfWork - * @param string $property - * @param string $oid + * @param array $entitiesDetached * * @throws ReflectionException */ - public function unsetFromUnitOfWork(UnitOfWork $unitOfWork, string $property, string $oid) + private function unsetEntity($entity, UnitOfWork $unitOfWork, array &$entitiesDetached) { - $filterCallback = function ($key) use ($oid) { - return $key !== $oid; - }; - - $propery = (new ReflectionClass($unitOfWork))->getProperty($property); - $propery->setAccessible(true); + $oid = spl_object_hash($entity); - $properyValueFiltered = array_filter($propery->getValue($unitOfWork), $filterCallback, ARRAY_FILTER_USE_KEY); - $propery->setValue($unitOfWork, $properyValueFiltered); + $entitiesDetached[$oid] = true; + $this->unsetFromUnitOfWork($unitOfWork, 'entityInsertions', $oid); + $this->unsetFromUnitOfWork($unitOfWork, 'entityUpdates', $oid); + $this->unsetFromUnitOfWork($unitOfWork, 'entityDeletions', $oid); + $this->unsetFromUnitOfWork($unitOfWork, 'entityIdentifiers', $oid); + $this->unsetFromUnitOfWork($unitOfWork, 'entityStates', $oid); + $this->unsetFromUnitOfWork($unitOfWork, 'originalEntityData', $oid); + $this->unsetFromUnitOfWork($unitOfWork, 'orphanRemovals', $oid); } } diff --git a/src/LinkEntity/LinkEntityDoctrine.php b/src/LinkEntity/LinkEntityDoctrine.php index d1d5ffa..9009b22 100644 --- a/src/LinkEntity/LinkEntityDoctrine.php +++ b/src/LinkEntity/LinkEntityDoctrine.php @@ -326,17 +326,6 @@ protected function parseRemoveElementFromList($originalEntity, $compare, $classM if ($identifiers == $this->getIdentifiers($classMetadata, $item)) { if ($list instanceof Collection) { $originalEntity->{$getterMethod}()->removeElement($item); - - $metadata = $this->entityManager->getClassMetadata(get_class($item)); - $relationToOriginal = $metadata->getAssociationsByTargetClass(get_class($originalEntity)); - if (!empty($relationToOriginal)) { - $relationSetter = $this->classContains->getSetterMethod( - $item, - array_keys($relationToOriginal)[0] - ); - - $item->{$relationSetter}(null); - } } } } From c403bdd274a440cb7860efd0c693f55b9654237f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Lescallier?= Date: Sun, 5 Apr 2020 19:53:48 +0200 Subject: [PATCH 3/4] Update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b7125e..dcb5d7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# 0.1.5 +# 0.2.0 * Move mapping serializer to own package (``coosos/jms-serializer-bidirectional-relation``) * Fix array keys who an auto reset From e66b203658c80be311cdc09bd7dd9f90a237ea44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Lescallier?= Date: Sun, 5 Apr 2020 21:59:26 +0200 Subject: [PATCH 4/4] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dcb5d7a..11d216a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ * Move mapping serializer to own package (``coosos/jms-serializer-bidirectional-relation``) * Fix array keys who an auto reset + * Necessary to add the annotation ``Coosos\BidirectionalRelation\Annotations\SerializerBidirectionalRelation`` + to your root entity which will be serialized * Fix array link entity to original * DetachEntity (based on doctrine 2.7) class for recursive detach directly in properties of UnitOfWork * Reorganize / Optimize code