Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic support for enums via use of strategies #57

Closed
wants to merge 3 commits into from
Closed
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
6 changes: 6 additions & 0 deletions src/DoctrineObject.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Doctrine\Laminas\Hydrator;

use BackedEnum;
use DateTime;
use DateTimeImmutable;
use Doctrine\Inflector\Inflector;
Expand Down Expand Up @@ -38,6 +39,7 @@
use function is_int;
use function is_object;
use function is_string;
use function interface_exists;
use function method_exists;
use function property_exists;
use function sprintf;
Expand Down Expand Up @@ -318,6 +320,10 @@ public function hydrateValue(string $name, $value, ?array $data = null)
return null;
}

if (PHP_VERSION_ID >= 80100 && interface_exists(BackedEnum::class) && $value instanceof BackedEnum) {
return $value;
}

return $this->handleTypeConversions($value, $this->metadata->getTypeOfField($name));
}

Expand Down
38 changes: 38 additions & 0 deletions tests/Assets/SimpleEntityWithEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace DoctrineTest\Laminas\Hydrator\Assets;

use const PHP_VERSION_ID;

if (PHP_VERSION_ID >= 80100) {
class SimpleEntityWithEnum
{
/** @var int */
protected $id;

/** @var SimpleEnum|null */
protected $enum;

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

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

public function setEnum(?SimpleEnum $enum = null): void
{
$this->enum = $enum;
}

public function getEnum(): ?SimpleEnum
{
return $this->enum;
}
}
}
15 changes: 15 additions & 0 deletions tests/Assets/SimpleEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace DoctrineTest\Laminas\Hydrator\Assets;

use const PHP_VERSION_ID;

if (PHP_VERSION_ID >= 80100) {
enum SimpleEnum: int
{
case One = 1;
case Two = 2;
}
}
41 changes: 41 additions & 0 deletions tests/Assets/SimpleEnumStrategy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace DoctrineTest\Laminas\Hydrator\Assets;

use Laminas\Hydrator\Strategy\StrategyInterface;

if (PHP_VERSION_ID >= 80100) {
class SimpleEnumStrategy implements StrategyInterface
{
/**
* @param mixed $value
*
* @return int|null
*/
public function extract($value, ?object $object = null)
{
if ($value === null) {
return null;
}

return SimpleEnum::tryFrom($value)->value;
}

/**
* @param mixed $value
* @param array<array-key, mixed>|null $data
*
* @return SimpleEnum|null
*/
public function hydrate($value, ?array $data)
{
if ($value === null) {
return null;
}

return SimpleEnum::tryFrom($value);
}
}
}
115 changes: 115 additions & 0 deletions tests/DoctrineObjectTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Doctrine\Laminas\Hydrator\Strategy;
use Doctrine\Persistence\Mapping\ClassMetadata;
use Doctrine\Persistence\ObjectManager;
use DoctrineTest\Laminas\Hydrator\Assets\SimpleEnum;
use InvalidArgumentException;
use Laminas\Hydrator\NamingStrategy\UnderscoreNamingStrategy;
use Laminas\Hydrator\Strategy\StrategyInterface;
Expand All @@ -20,13 +21,16 @@
use Prophecy\PhpUnit\ProphecyTrait;
use ReflectionClass;
use stdClass;
use TypeError;

use function array_keys;
use function assert;
use function explode;
use function implode;
use function time;

use const PHP_VERSION_ID;

class DoctrineObjectTest extends TestCase
{
use ProphecyTrait;
Expand Down Expand Up @@ -850,6 +854,63 @@ static function ($arg) {
);
}

public function configureObjectManagerForSimpleEntityWithEnum(): void
{
$refl = new ReflectionClass(Assets\SimpleEntityWithEnum::class);

$this
->metadata
->method('getAssociationNames')
->will($this->returnValue([]));

$this
->metadata
->method('getFieldNames')
->will($this->returnValue(['id', 'enum']));

$this
->metadata
->method('getTypeOfField')
->with($this->logicalOr($this->equalTo('id'), $this->equalTo('enum')))
->willReturnCallback(
static function ($arg) {
if ($arg === 'id') {
return 'integer';
}

if ($arg === 'enum') {
return 'enum';
}

throw new InvalidArgumentException();
}
);

$this
->metadata
->method('hasAssociation')
->will($this->returnValue(false));

$this
->metadata
->method('getIdentifierFieldNames')
->will($this->returnValue(['id']));

$this
->metadata
->method('getReflectionClass')
->will($this->returnValue($refl));

$this->hydratorByValue = new DoctrineObjectHydrator(
$this->objectManager,
true
);
$this->hydratorByReference = new DoctrineObjectHydrator(
$this->objectManager,
false
);
}

public function testObjectIsPassedForContextToStrategies()
{
$entity = new Assets\SimpleEntity();
Expand Down Expand Up @@ -2865,4 +2926,58 @@ public function testNestedHydrationByReference()
$this->assertSame('value', $entity->getToOne(false)->getField(false));
$this->assertSame('2019-01-24 12:00:00', $entity->getCreatedAt()->format('Y-m-d H:i:s'));
}

public function testHandleEnumConversionUsingByValue(): void
{
if (PHP_VERSION_ID < 80100) {
$this->markTestSkipped('PHP 8.1 required for enum compatibility');
}

// When using hydration by value, it will use the public API of the entity to set values (setters)
$entity = new Assets\SimpleEntityWithEnum();
$this->configureObjectManagerForSimpleEntityWithEnum();

$value = 1;
$data = ['enum' => $value];

$this->hydratorByValue->addStrategy('enum', new Assets\SimpleEnumStrategy());
$entity = $this->hydratorByValue->hydrate($data, $entity);

$this->assertInstanceOf(SimpleEnum::class, $entity->getEnum());
$this->assertEquals(SimpleEnum::tryFrom($value), $entity->getEnum());
}

public function testNullValueIsNotConvertedToEnum(): void
{
if (PHP_VERSION_ID < 80100) {
$this->markTestSkipped('PHP 8.1 required for enum compatibility');
}

$entity = new Assets\SimpleEntityWithEnum();
$this->configureObjectManagerForSimpleEntityWithEnum();

$data = ['enum' => null];

$this->hydratorByValue->addStrategy('enum', new Assets\SimpleEnumStrategy());
$entity = $this->hydratorByValue->hydrate($data, $entity);

$this->assertNull($entity->getEnum());
}

public function testWrongEnumBackedValueThrowsException(): void
{
if (PHP_VERSION_ID < 80100) {
$this->markTestSkipped('PHP 8.1 required for enum compatibility');
}

$entity = new Assets\SimpleEntityWithEnum();
$this->configureObjectManagerForSimpleEntityWithEnum();

$data = ['enum' => 'string'];

$this->expectException(TypeError::class);

$this->hydratorByValue->addStrategy('enum', new Assets\SimpleEnumStrategy());
$this->hydratorByValue->hydrate($data, $entity);
}
}