Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions ORM/NullableEmbeddable/PropertyAccessorReflectionNullator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?php

declare(strict_types=1);

namespace Tarifhaus\Doctrine\ORM\NullableEmbeddable;

use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
use Symfony\Component\PropertyAccess\PropertyAccessor as DefaultPropertyAccessor;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;

final class PropertyAccessorReflectionNullator implements NullatorInterface
{
private $propertyAccessor;
private $closureNullator;

public function __construct(PropertyAccessorInterface $propertyAccessor, ClosureNullator $closureNullator)
{
$this->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;
}
}
9 changes: 9 additions & 0 deletions ORM/NullableEmbeddableListenerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

namespace Tarifhaus\Doctrine\Tests\NullableEmbeddable;

use PHPUnit\Framework\TestCase;
use Tarifhaus\Doctrine\ORM\NullableEmbeddableListenerFactory;
use Tarifhaus\Doctrine\ORM\NullableEmbeddableListener;
use Tarifhaus\Doctrine\Tests\EntityWithoutSetter;
use Tarifhaus\Doctrine\Tests\TopLevelEntityWithoutSetter;
use Tarifhaus\Tests\Doctrine\ORM\NullableEmbeddable;

/**
* @covers \Tarifhaus\Doctrine\ORM\NullableEmbeddable\PropertyAccessorReflectionNullator
*/
final class PropertyAccessorReflectionNullatorTest extends TestCase
{
/**
* @var NullableEmbeddableListener
*/
private $listener;

protected function setUp()
{
$this->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());
}
}
20 changes: 20 additions & 0 deletions Tests/TopLevelEntityWithoutSetter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace Tarifhaus\Doctrine\Tests;

final class TopLevelEntityWithoutSetter
{
private $childEntity;

public function __construct(EntityWithoutSetter $childEntity)
{
$this->childEntity = $childEntity;
}

public function getChildEntity()
{
return $this->childEntity;
}
}