Skip to content

Commit

Permalink
[Reflection] Use default typed property value when setting value to null
Browse files Browse the repository at this point in the history
Related to doctrine/orm#7999

I'm using typed properties with a default value that is not null. Example:
```php
class Entity {
    /**
     * @Orm\Column(name="id", type="integer")
     * @Orm\Id
     * @Orm\GeneratedValue(strategy="AUTO")
     */
    private int $id = 0;

    public function getId() : int
    {
        return $this->id;
    }
}
```

This works great. When inserting a new entity in the database, the auto incremented id is updated properly.

It means that entities that are not persisted will have a `0` as id and the rest will have another non-zero integer value.

But upon deleting an entity this becomes a problem. The ORM tries to set the `id` property value to `null` and that causes a TypeError:
```
TypeError: Typed property Entity::$id must be int, null used

vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php:1246
vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php:441
vendor/doctrine/orm/lib/Doctrine/ORM/EntityManager.php:376
```

With this change applied, the default value of the `id` property `0` is used again.

Solving the problem.
  • Loading branch information
ruudk committed Mar 10, 2021
1 parent a10be34 commit 32b1f6f
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 4 deletions.
9 changes: 7 additions & 2 deletions lib/Doctrine/Persistence/Mapping/RuntimeReflectionService.php
Expand Up @@ -4,6 +4,7 @@

use Doctrine\Persistence\Reflection\RuntimePublicReflectionProperty;
use Doctrine\Persistence\Reflection\TypedNoDefaultReflectionProperty;
use Doctrine\Persistence\Reflection\TypedWithDefaultReflectionProperty;
use ReflectionClass;
use ReflectionException;
use ReflectionMethod;
Expand Down Expand Up @@ -77,8 +78,12 @@ public function getAccessibleProperty($class, $property)

if ($reflectionProperty->isPublic()) {
$reflectionProperty = new RuntimePublicReflectionProperty($class, $property);
} elseif ($this->supportsTypedPropertiesWorkaround && ! array_key_exists($property, $this->getClass($class)->getDefaultProperties())) {
$reflectionProperty = new TypedNoDefaultReflectionProperty($class, $property);
} elseif ($this->supportsTypedPropertiesWorkaround) {
if (array_key_exists($property, $this->getClass($class)->getDefaultProperties())) {
$reflectionProperty = new TypedWithDefaultReflectionProperty($class, $property);
} else {
$reflectionProperty = new TypedNoDefaultReflectionProperty($class, $property);
}
}

$reflectionProperty->setAccessible(true);
Expand Down
@@ -0,0 +1,35 @@
<?php

namespace Doctrine\Persistence\Reflection;

use ReflectionProperty;

/**
* PHP Typed With Default Reflection Property - special override for typed properties with a default value.
*/
class TypedWithDefaultReflectionProperty extends ReflectionProperty
{
/**
* {@inheritDoc}
*
* Works around the problem with setting typed default properties to
* NULL which is not supported, instead assign default value to property.
*/
public function setValue($object, $value = null)
{
if ($value === null && $this->hasType() && ! $this->getType()->allowsNull()) {
$propertyName = $this->getName();
$defaultProperties = $this->getDeclaringClass()->getDefaultProperties();

$setter = function () use ($propertyName, $defaultProperties): void {
$this->$propertyName = $defaultProperties[$propertyName];
};
$setter = $setter->bindTo($object, $this->getDeclaringClass()->getName());
$setter();

return;
}

parent::setValue($object, $value);
}
}
Expand Up @@ -7,8 +7,8 @@
use Doctrine\Persistence\Mapping\RuntimeReflectionService;
use Doctrine\Persistence\Reflection\RuntimePublicReflectionProperty;
use Doctrine\Persistence\Reflection\TypedNoDefaultReflectionProperty;
use Doctrine\Persistence\Reflection\TypedWithDefaultReflectionProperty;
use PHPUnit\Framework\TestCase;
use ReflectionProperty;

/**
* @group DCOM-93
Expand Down Expand Up @@ -37,7 +37,7 @@ public function testGetTypedNoDefaultReflectionProperty(): void
public function testGetTypedDefaultReflectionProperty(): void
{
$reflProp = $this->reflectionService->getAccessibleProperty(self::class, 'typedDefaultProperty');
self::assertInstanceOf(ReflectionProperty::class, $reflProp);
self::assertInstanceOf(TypedWithDefaultReflectionProperty::class, $reflProp);
self::assertNotInstanceOf(TypedNoDefaultReflectionProperty::class, $reflProp);
}

Expand Down
@@ -0,0 +1,40 @@
<?php

namespace Doctrine\Tests_PHP74\Persistence\Reflection;

use Doctrine\Persistence\Reflection\TypedWithDefaultReflectionProperty;
use PHPUnit\Framework\TestCase;

class TypedWithDefaultReflectionPropertyTest extends TestCase
{
public function testSetValueNull(): void
{
$reflection = new TypedWithDefaultReflectionProperty(TypedFooWithDefault::class, 'id');
$reflection->setAccessible(true);

$object = new TypedFooWithDefault();
$object->setId(1);

self::assertTrue($reflection->isInitialized($object));

$reflection->setValue($object, null);

self::assertSame(0, $reflection->getValue($object));
self::assertTrue($reflection->isInitialized($object));
}
}

class TypedFooWithDefault
{
private int $id = 0;

public function setId(int $id): void
{
$this->id = $id;
}

public function getId(): int
{
return $this->id;
}
}

0 comments on commit 32b1f6f

Please sign in to comment.