-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
DBAL enum must be explicitly declared like so: ```php class LanguageType extends PhpEnumType { protected function getEnumType(): string { return Language::class; } } ``` GraphQL enums are automatically generated as long as `EnumAbstractFactory` is configured and enum lives in `Application\Enum` namespace. Then the model can use them like so: ```php #[ORM\Entity] class MyModel { #[ORM\Column(type: 'Language', options: ['default' => Language::fr])] private Language $language = Language::fr; public function getLanguage(): Language { return $this->language; } public function setLanguage(Language $language): void { $this->language = $language; } } ```
- Loading branch information
Showing
11 changed files
with
401 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Ecodev\Felix\Api\Enum; | ||
|
||
use BackedEnum; | ||
use Exception; | ||
use Laminas\ServiceManager\Factory\AbstractFactoryInterface; | ||
use Psr\Container\ContainerInterface; | ||
|
||
/** | ||
* Will create GraphQL Enums for a short name or a FQCN of a PHP native backed enum. | ||
* | ||
* The PHP enum must live in `Application\Enum` namespace. | ||
*/ | ||
class EnumAbstractFactory implements AbstractFactoryInterface | ||
{ | ||
/** | ||
* @var array<class-string<BackedEnum>, PhpEnumType> | ||
*/ | ||
private array $cache = []; | ||
|
||
public function canCreate(ContainerInterface $container, $requestedName) | ||
{ | ||
$class = $this->getClass($requestedName); | ||
|
||
return (bool) $class; | ||
} | ||
|
||
public function __invoke(ContainerInterface $container, $requestedName, ?array $options = null) | ||
{ | ||
$class = $this->getClass($requestedName); | ||
if (!$class) { | ||
throw new Exception('Cannot create a PhpEnumType for a name not matching a backed enum: ' . $requestedName); | ||
} | ||
|
||
// Share the same instance between short name and FQCN | ||
if (!array_key_exists($class, $this->cache)) { | ||
$this->cache[$class] = new PhpEnumType($class); | ||
} | ||
|
||
return $this->cache[$class]; | ||
} | ||
|
||
/** | ||
* @return null|class-string<BackedEnum> | ||
*/ | ||
private function getClass(string $requestedName): ?string | ||
{ | ||
$possibilities = [ | ||
$requestedName, | ||
'Application\Enum\\' . $requestedName, | ||
]; | ||
|
||
foreach ($possibilities as $class) { | ||
if (class_exists($class) && is_a($class, BackedEnum::class, true)) { | ||
return $class; | ||
} | ||
} | ||
|
||
return null; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Ecodev\Felix\Api\Enum; | ||
|
||
use BackedEnum; | ||
|
||
/** | ||
* An enum that has a localized description for each case to be shown to end-user. | ||
*/ | ||
interface LocalizedPhpEnumType extends BackedEnum | ||
{ | ||
/** | ||
* Returns the user-friendly, localized description for the case. | ||
*/ | ||
public function getDescription(): string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Ecodev\Felix\Api\Enum; | ||
|
||
use ReflectionClass; | ||
use ReflectionClassConstant; | ||
|
||
/** | ||
* Like `\GraphQL\Type\Definition\PhpEnumType` with added support for `LocalizedPhpEnumType`. | ||
*/ | ||
final class PhpEnumType extends \GraphQL\Type\Definition\PhpEnumType | ||
{ | ||
protected function extractDescription(ReflectionClassConstant|ReflectionClass $reflection): ?string | ||
{ | ||
if ($reflection instanceof ReflectionClassConstant) { | ||
$value = $reflection->getValue(); | ||
if ($value instanceof LocalizedPhpEnumType) { | ||
return $reflection->getValue()->getDescription(); | ||
} | ||
} | ||
|
||
return parent::extractDescription($reflection); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Ecodev\Felix\DBAL\Types; | ||
|
||
use BackedEnum; | ||
use Doctrine\DBAL\Platforms\AbstractPlatform; | ||
use GraphQL\Utils\Utils; | ||
use InvalidArgumentException; | ||
|
||
/** | ||
* Enum based on native PHP backed enum. | ||
*/ | ||
abstract class PhpEnumType extends EnumType | ||
{ | ||
/** | ||
* Returns the FQCN of the native PHP enum. | ||
* | ||
* @return class-string<BackedEnum> | ||
*/ | ||
abstract protected function getEnumType(): string; | ||
|
||
protected function getPossibleValues(): array | ||
{ | ||
return array_map(fn (BackedEnum $str) => $str->value, $this->getEnumType()::cases()); | ||
} | ||
|
||
/** | ||
* @param ?string $value | ||
*/ | ||
public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?BackedEnum | ||
{ | ||
if ($value === null || '' === $value) { | ||
return null; | ||
} | ||
|
||
if (!is_string($value)) { | ||
throw new InvalidArgumentException("Invalid '" . Utils::printSafe($value) . "' value fetched from database for enum " . $this->getName()); | ||
} | ||
|
||
return $this->getEnumType()::from($value); | ||
} | ||
|
||
public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): ?string | ||
{ | ||
if ($value === null) { | ||
return null; | ||
} | ||
|
||
if (!is_object($value) || !is_a($value, $this->getEnumType())) { | ||
throw new InvalidArgumentException("Invalid '" . Utils::printSafe($value) . "' value to be stored in database for enum " . $this->getName()); | ||
} | ||
|
||
return $value->value; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace EcodevTests\Felix\Api\Enum; | ||
|
||
use Ecodev\Felix\Api\Enum\PhpEnumType; | ||
use EcodevTests\Felix\Service\OtherTestEnum; | ||
use EcodevTests\Felix\Service\TestEnum; | ||
use PHPUnit\Framework\TestCase; | ||
|
||
class PhpEnumTypeTest extends TestCase | ||
{ | ||
public function testLocalizedDescription(): void | ||
{ | ||
$type = new PhpEnumType(TestEnum::class); | ||
self::assertSame('custom description for key 1', $type->getValues()[0]->description); | ||
self::assertSame('other for key 2', $type->getValues()[1]->description); | ||
|
||
$normalType = new PhpEnumType(OtherTestEnum::class); | ||
self::assertSame('static description via webonyx/graphql', $normalType->getValues()[0]->description, 'base features are still working'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace EcodevTests\Felix\DBAL\Types; | ||
|
||
use Doctrine\DBAL\Platforms\AbstractPlatform; | ||
use Doctrine\DBAL\Platforms\MySQLPlatform; | ||
use Ecodev\Felix\DBAL\Types\PhpEnumType; | ||
use EcodevTests\Felix\Service\OtherTestEnum; | ||
use EcodevTests\Felix\Service\TestEnum; | ||
use InvalidArgumentException; | ||
use PHPUnit\Framework\TestCase; | ||
use ValueError; | ||
|
||
class PhpEnumTypeTest extends TestCase | ||
{ | ||
private PhpEnumType $type; | ||
|
||
private AbstractPlatform $platform; | ||
|
||
protected function setUp(): void | ||
{ | ||
$this->type = new class() extends PhpEnumType { | ||
protected function getEnumType(): string | ||
{ | ||
return TestEnum::class; | ||
} | ||
}; | ||
|
||
$this->platform = new MySQLPlatform(); | ||
} | ||
|
||
public function testEnum(): void | ||
{ | ||
self::assertSame("ENUM('value1', 'value2')", $this->type->getSqlDeclaration(['foo'], $this->platform)); | ||
|
||
// Should always return string | ||
self::assertSame(TestEnum::key1, $this->type->convertToPHPValue('value1', $this->platform)); | ||
|
||
// Should support null values or empty string | ||
self::assertNull($this->type->convertToPHPValue(null, $this->platform)); | ||
self::assertNull($this->type->convertToPHPValue('', $this->platform)); | ||
self::assertNull($this->type->convertToDatabaseValue(null, $this->platform)); | ||
|
||
self::assertTrue($this->type->requiresSQLCommentHint($this->platform)); | ||
} | ||
|
||
public function testConvertToPHPValueThrowsWithInvalidValue(): void | ||
{ | ||
$this->expectException(ValueError::class); | ||
|
||
$this->type->convertToPHPValue('foo', $this->platform); | ||
} | ||
|
||
public function testConvertToDatabaseValueThrowsWithInvalidValue(): void | ||
{ | ||
$this->expectException(InvalidArgumentException::class); | ||
|
||
$this->type->convertToDatabaseValue('foo', $this->platform); | ||
} | ||
|
||
public function testConvertToDatabaseValueThrowsWithInvalidEnum(): void | ||
{ | ||
$this->expectException(InvalidArgumentException::class); | ||
|
||
$this->type->convertToDatabaseValue(OtherTestEnum::key1, $this->platform); | ||
} | ||
|
||
public function testConvertToPHPValueThrowsWithZero(): void | ||
{ | ||
$this->expectException(InvalidArgumentException::class); | ||
|
||
$this->type->convertToPHPValue(0, $this->platform); | ||
} | ||
|
||
public function testConvertToDatabaseValueThrowsWithZero(): void | ||
{ | ||
$this->expectException(InvalidArgumentException::class); | ||
|
||
$this->type->convertToDatabaseValue(0, $this->platform); | ||
} | ||
} |
Oops, something went wrong.