diff --git a/ORM/NullableEmbeddable/PropertyAccessorReflectionNullator.php b/ORM/NullableEmbeddable/PropertyAccessorReflectionNullator.php new file mode 100644 index 0000000..28647d7 --- /dev/null +++ b/ORM/NullableEmbeddable/PropertyAccessorReflectionNullator.php @@ -0,0 +1,98 @@ +propertyAccessor = $propertyAccessor; + $this->closureNullator = $closureNullator; + } + + public static function createWithDefault() + { + return new self(new DefaultPropertyAccessor(), new ClosureNullator()); + } + + public function setNull(&$object, $property) + { + try { + $this->propertyAccessor->setValue($object, $property, null); + } catch (NoSuchPropertyException $exception) { + $this->setNullByReflection($object, $property); + } + } + + private function setNullByReflection(&$object, $property) + { + $entity = clone $object; + + $this->makePropertyPublic($object, $property); + $this->closureNullator->setNull($object, $property); + + $object = $entity; + } + + private function makePropertyPublic(&$object, $property) + { + $reflectionProperty = $this->getReflectionProperty($object, $property); + + if (false !== strpos($property, '.')) { + $reflectionProperty = $this->getPropertyPathReflectionProperty($object, $property); + } + + if (null === $reflectionProperty) { + throw new NoSuchPropertyException(); + } + + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($object, null); + } + + private function getPropertyPathReflectionProperty(&$object, $property) + { + $reflectionProperty = null; + + $propertyPathProperties = explode('.', $property); + $numOfProperties = \count($propertyPathProperties); + + foreach ($propertyPathProperties as $propertyIndex => $pathProperty) { + $reflectionProperty = $this->getReflectionProperty($object, $pathProperty); + + if ($propertyIndex < $numOfProperties - 1) { + $object = $this->propertyAccessor->getValue($object, $pathProperty); + } + } + + return $reflectionProperty; + } + + private function getReflectionProperty($object, $property) + { + if (false === \is_object($object)) { + return null; + } + + $reflectionClass = (new \ReflectionClass(\get_class($object))); + + while ($reflectionClass instanceof \ReflectionClass) { + if ($reflectionClass->hasProperty($property)) { + return $reflectionClass->getProperty($property); + } + + $reflectionClass = $reflectionClass->getParentClass(); + } + + return null; + } +} diff --git a/ORM/NullableEmbeddableListenerFactory.php b/ORM/NullableEmbeddableListenerFactory.php index 913a091..3bb46fb 100644 --- a/ORM/NullableEmbeddableListenerFactory.php +++ b/ORM/NullableEmbeddableListenerFactory.php @@ -6,6 +6,7 @@ use Tarifhaus\Doctrine\ORM\NullableEmbeddable\ClosureNullator; use Tarifhaus\Doctrine\ORM\NullableEmbeddable\PropertyAccessor; +use Tarifhaus\Doctrine\ORM\NullableEmbeddable\PropertyAccessorReflectionNullator; final class NullableEmbeddableListenerFactory { @@ -23,4 +24,12 @@ public static function createWithClosureNullator(): NullableEmbeddableListener return new NullableEmbeddableListener($evaluator, $nullator); } + + public static function createWithPropertyAccessorReflectionNullator(): NullableEmbeddableListener + { + $evaluator = PropertyAccessor::createWithDefault(); + $nullator = PropertyAccessorReflectionNullator::createWithDefault(); + + return new NullableEmbeddableListener($evaluator, $nullator); + } } diff --git a/Tests/NullableEmbeddable/PropertyAccessorReflectionNullatorTest.php b/Tests/NullableEmbeddable/PropertyAccessorReflectionNullatorTest.php new file mode 100644 index 0000000..305238e --- /dev/null +++ b/Tests/NullableEmbeddable/PropertyAccessorReflectionNullatorTest.php @@ -0,0 +1,51 @@ +listener = NullableEmbeddableListenerFactory::createWithPropertyAccessorReflectionNullator(); + } + + public function test_it_nullates_without_using_setter() + { + $embeddable = new NullableEmbeddable(true); + $object = new EntityWithoutSetter($embeddable); + + $this->listener->addMapping(get_class($object), 'property'); + $this->listener->postLoad($object); + + static::assertNull($object->getProperty()); + } + + public function test_it_nullates_nested_embeddables_without_using_setter() + { + $embeddable = new NullableEmbeddable(true); + $childEntity = new EntityWithoutSetter($embeddable); + $entity = new TopLevelEntityWithoutSetter($childEntity); + + $this->listener->addMapping(get_class($entity), 'childEntity.property'); + $this->listener->postLoad($entity); + + static::assertNull($entity->getChildEntity()->getProperty()); + } +} diff --git a/Tests/TopLevelEntityWithoutSetter.php b/Tests/TopLevelEntityWithoutSetter.php new file mode 100644 index 0000000..0b0a8fe --- /dev/null +++ b/Tests/TopLevelEntityWithoutSetter.php @@ -0,0 +1,20 @@ +childEntity = $childEntity; + } + + public function getChildEntity() + { + return $this->childEntity; + } +}