diff --git a/composer.json b/composer.json index 5adda6bc52..d4ad4ec422 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,6 @@ "doctrine/coding-standard": "^13.0", "ecodev/graphql-upload": "^7.0", "laminas/laminas-diactoros": "^3.5", - "myclabs/php-enum": "^1.6.6", "php-coveralls/php-coveralls": "^2.7", "phpstan/extension-installer": "^1.4", "phpstan/phpstan": "^2.0", diff --git a/src/AnnotationReader.php b/src/AnnotationReader.php index 39ebb6706c..c8cb9ec1f0 100644 --- a/src/AnnotationReader.php +++ b/src/AnnotationReader.php @@ -10,7 +10,6 @@ use ReflectionProperty; use TheCodingMachine\GraphQLite\Annotations\AbstractRequest; use TheCodingMachine\GraphQLite\Annotations\Decorate; -use TheCodingMachine\GraphQLite\Annotations\EnumType; use TheCodingMachine\GraphQLite\Annotations\Exceptions\ClassNotFoundException; use TheCodingMachine\GraphQLite\Annotations\Exceptions\InvalidParameterException; use TheCodingMachine\GraphQLite\Annotations\ExtendType; @@ -196,11 +195,6 @@ public function getExtendTypeAnnotation(ReflectionClass $refClass): ExtendType|n return $extendType; } - public function getEnumTypeAnnotation(ReflectionClass $refClass): EnumType|null - { - return $this->getClassAnnotation($refClass, EnumType::class); - } - /** @param class-string $annotationClass */ public function getRequestAnnotation(ReflectionMethod $refMethod, string $annotationClass): AbstractRequest|null { diff --git a/src/Annotations/EnumType.php b/src/Annotations/EnumType.php deleted file mode 100644 index e83431fbaf..0000000000 --- a/src/Annotations/EnumType.php +++ /dev/null @@ -1,48 +0,0 @@ -name = $name ?? $attributes['name'] ?? null; - $this->useValues = $useValues ?? $attributes['useValues'] ?? false; - } - - /** - * Returns the GraphQL name for this type. - */ - public function getName(): string|null - { - return $this->name; - } - - /** - * Returns true if the enum type should expose backed values instead of case names. - */ - public function useValues(): bool - { - return $this->useValues; - } -} diff --git a/src/Mappers/Root/EnumTypeMapper.php b/src/Mappers/Root/EnumTypeMapper.php index 87e575bb6c..77e8eb6794 100644 --- a/src/Mappers/Root/EnumTypeMapper.php +++ b/src/Mappers/Root/EnumTypeMapper.php @@ -8,7 +8,6 @@ use GraphQL\Type\Definition\NamedType; use GraphQL\Type\Definition\OutputType; use GraphQL\Type\Definition\Type as GraphQLType; -use MyCLabs\Enum\Enum; use phpDocumentor\Reflection\DocBlock; use phpDocumentor\Reflection\Type; use phpDocumentor\Reflection\Types\Object_; @@ -104,16 +103,12 @@ private function mapByClassName(string $enumClass): EnumType|null if (! enum_exists($enumClass)) { return null; } - /** @var class-string $enumClass */ + /** @var class-string $enumClass */ $enumClass = ltrim($enumClass, '\\'); if (isset($this->cacheByClass[$enumClass])) { return $this->cacheByClass[$enumClass]; } - // phpcs:disable SlevomatCodingStandard.Commenting.InlineDocCommentDeclaration.MissingVariable - /** @var class-string $enumClass */ - // phpcs:enable SlevomatCodingStandard.Commenting.InlineDocCommentDeclaration.MissingVariable - $reflectionEnum = new ReflectionEnum($enumClass); $typeAnnotation = $this->annotationReader->getTypeAnnotation($reflectionEnum); diff --git a/src/Mappers/Root/MyCLabsEnumTypeMapper.php b/src/Mappers/Root/MyCLabsEnumTypeMapper.php deleted file mode 100644 index 2a3bed7906..0000000000 --- a/src/Mappers/Root/MyCLabsEnumTypeMapper.php +++ /dev/null @@ -1,174 +0,0 @@ -, EnumType> */ - private array $cacheByClass = []; - /** @var array */ - private array $cacheByName = []; - - /** @var array> */ - private array $nameToClassMapping; - - public function __construct( - private readonly RootTypeMapperInterface $next, - private readonly AnnotationReader $annotationReader, - private readonly ClassFinder $classFinder, - private readonly ClassFinderComputedCache $classFinderComputedCache, - ) { - } - - public function toGraphQLOutputType( - Type $type, - OutputType|null $subType, - ReflectionMethod|ReflectionProperty $reflector, - DocBlock $docBlockObj, - ): OutputType&\GraphQL\Type\Definition\Type - { - $result = $this->map($type); - return $result ?? $this->next->toGraphQLOutputType($type, $subType, $reflector, $docBlockObj); - } - - public function toGraphQLInputType( - Type $type, - InputType|null $subType, - string $argumentName, - ReflectionMethod|ReflectionProperty $reflector, - DocBlock $docBlockObj, - ): InputType&\GraphQL\Type\Definition\Type - { - $result = $this->map($type); - return $result ?? $this->next->toGraphQLInputType( - $type, - $subType, - $argumentName, - $reflector, - $docBlockObj, - ); - } - - private function map(Type $type): EnumType|null - { - if (! $type instanceof Object_) { - return null; - } - $fqsen = $type->getFqsen(); - if ($fqsen === null) { - return null; - } - /** @var class-string $enumClass */ - $enumClass = (string) $fqsen; - - return $this->mapByClassName($enumClass); - } - - /** @param class-string $enumClass */ - private function mapByClassName(string $enumClass): EnumType|null - { - if (! is_a($enumClass, Enum::class, true)) { - return null; - } - /** @var class-string $enumClass */ - $enumClass = ltrim($enumClass, '\\'); - if (isset($this->cacheByClass[$enumClass])) { - return $this->cacheByClass[$enumClass]; - } - - $refClass = new ReflectionClass($enumClass); - $type = new MyCLabsEnumType($enumClass, $this->getTypeName($refClass)); - return $this->cacheByName[$type->name] = $this->cacheByClass[$enumClass] = $type; - } - - private function getTypeName(ReflectionClass $refClass): string - { - $enumType = $this->annotationReader->getEnumTypeAnnotation($refClass); - if ($enumType !== null) { - $name = $enumType->getName(); - if ($name !== null) { - return $name; - } - } - return $refClass->getShortName(); - } - - /** - * Returns a GraphQL type by name. - * If this root type mapper can return this type in "toGraphQLOutputType" or "toGraphQLInputType", - * it should also map these types by name in the "mapNameToType" method. - * - * @param string $typeName The name of the GraphQL type - */ - public function mapNameToType(string $typeName): NamedType&\GraphQL\Type\Definition\Type - { - // This is a hack to make sure "$schema->assertValid()" returns true. - // The mapNameToType will fail if the mapByClassName method was not called before. - // This is actually not an issue in real life scenarios where enum types are never queried - // by type name. - if (isset($this->cacheByName[$typeName])) { - return $this->cacheByName[$typeName]; - } - - $nameToClassMapping = $this->getNameToClassMapping(); - if (isset($this->nameToClassMapping[$typeName])) { - $className = $nameToClassMapping[$typeName]; - $type = $this->mapByClassName($className); - assert($type !== null); - return $type; - } - - return $this->next->mapNameToType($typeName); - } - - /** - * Go through all classes in the defined namespaces and loads the cache. - * - * @return array> - */ - private function getNameToClassMapping(): array - { - $this->nameToClassMapping ??= $this->classFinderComputedCache->compute( - $this->classFinder, - 'myclabsenum_name_to_class', - function (ReflectionClass $classReflection): array|null { - if (! $classReflection->isSubclassOf(Enum::class)) { - return null; - } - - return [$this->getTypeName($classReflection) => $classReflection->getName()]; - }, - static fn (array $entries) => array_merge(...array_values(array_filter($entries))), - ); - - return $this->nameToClassMapping; - } -} diff --git a/src/SchemaFactory.php b/src/SchemaFactory.php index 037910f281..3b875f4892 100644 --- a/src/SchemaFactory.php +++ b/src/SchemaFactory.php @@ -9,7 +9,6 @@ use Kcs\ClassFinder\FileFinder\DefaultFileFinder; use Kcs\ClassFinder\Finder\ComposerFinder; use Kcs\ClassFinder\Finder\FinderInterface; -use MyCLabs\Enum\Enum; use PackageVersions\Versions; use Psr\Container\ContainerInterface; use Psr\SimpleCache\CacheInterface; @@ -41,7 +40,6 @@ use TheCodingMachine\GraphQLite\Mappers\Root\FinalRootTypeMapper; use TheCodingMachine\GraphQLite\Mappers\Root\IteratorTypeMapper; use TheCodingMachine\GraphQLite\Mappers\Root\LastDelegatingTypeMapper; -use TheCodingMachine\GraphQLite\Mappers\Root\MyCLabsEnumTypeMapper; use TheCodingMachine\GraphQLite\Mappers\Root\NullableTypeMapperAdapter; use TheCodingMachine\GraphQLite\Mappers\Root\RootTypeMapperFactoryContext; use TheCodingMachine\GraphQLite\Mappers\Root\RootTypeMapperFactoryInterface; @@ -70,7 +68,6 @@ use TheCodingMachine\GraphQLite\Utils\NamespacedCache; use function array_reverse; -use function class_exists; use function implode; use function md5; use function substr; @@ -406,11 +403,6 @@ public function createSchema(): Schema $rootTypeMapper = new BaseTypeMapper($errorRootTypeMapper, $recursiveTypeMapper, $topRootTypeMapper); $rootTypeMapper = new EnumTypeMapper($rootTypeMapper, $annotationReader, $docBlockFactory, $classFinder, $classFinderComputedCache); - if (class_exists(Enum::class)) { - // Annotation support - deprecated - $rootTypeMapper = new MyCLabsEnumTypeMapper($rootTypeMapper, $annotationReader, $classFinder, $classFinderComputedCache); - } - if (! empty($this->rootTypeMapperFactories)) { $rootSchemaFactoryContext = new RootTypeMapperFactoryContext( $annotationReader, diff --git a/src/Types/MyCLabsEnumType.php b/src/Types/MyCLabsEnumType.php deleted file mode 100644 index 13f7a4cffa..0000000000 --- a/src/Types/MyCLabsEnumType.php +++ /dev/null @@ -1,40 +0,0 @@ - $value) { - $constInstances[$key] = ['value' => $enumClassName::$key()]; - } - - parent::__construct([ - 'name' => $typeName, - 'values' => $constInstances, - ]); - } - - public function serialize(mixed $value): mixed - { - if (! $value instanceof Enum) { - throw new InvalidArgumentException('Expected a Myclabs Enum instance'); - } - return $value->getKey(); - } -} diff --git a/tests/AbstractQueryProvider.php b/tests/AbstractQueryProvider.php index e9a25dc48e..a2dbf665c9 100644 --- a/tests/AbstractQueryProvider.php +++ b/tests/AbstractQueryProvider.php @@ -47,7 +47,6 @@ use TheCodingMachine\GraphQLite\Mappers\Root\FinalRootTypeMapper; use TheCodingMachine\GraphQLite\Mappers\Root\IteratorTypeMapper; use TheCodingMachine\GraphQLite\Mappers\Root\LastDelegatingTypeMapper; -use TheCodingMachine\GraphQLite\Mappers\Root\MyCLabsEnumTypeMapper; use TheCodingMachine\GraphQLite\Mappers\Root\NullableTypeMapperAdapter; use TheCodingMachine\GraphQLite\Mappers\Root\RootTypeMapperInterface; use TheCodingMachine\GraphQLite\Mappers\Root\VoidTypeMapper; @@ -369,14 +368,6 @@ protected function buildRootTypeMapper(): RootTypeMapperInterface $topRootTypeMapper, ); - // Annotation support - deprecated - $rootTypeMapper = new MyCLabsEnumTypeMapper( - $rootTypeMapper, - $this->getAnnotationReader(), - new StaticClassFinder([]), - new HardClassFinderComputedCache(new Psr16Cache($arrayAdapter)), - ); - $rootTypeMapper = new EnumTypeMapper( $rootTypeMapper, $this->getAnnotationReader(), diff --git a/tests/FieldsBuilderTest.php b/tests/FieldsBuilderTest.php index 60e9bd2ecd..454a7a4df0 100644 --- a/tests/FieldsBuilderTest.php +++ b/tests/FieldsBuilderTest.php @@ -124,7 +124,7 @@ public function testQueryProvider(): void 'dateTimeImmutable' => '2017-01-01 01:01:01', 'dateTime' => '2017-01-01 01:01:01', 'id' => 42, - 'enum' => TestEnum::ON(), + 'enum' => TestEnum::ON, ]; $resolve = $usersQuery->resolveFn; diff --git a/tests/Fixtures/Integration/Controllers/ProductController.php b/tests/Fixtures/Integration/Controllers/ProductController.php index 65b1c42408..049e05285b 100644 --- a/tests/Fixtures/Integration/Controllers/ProductController.php +++ b/tests/Fixtures/Integration/Controllers/ProductController.php @@ -22,7 +22,7 @@ class ProductController public function getProducts(): ArrayResult { return new ArrayResult([ - new Product('Foo', 42.0, ProductTypeEnum::NON_FOOD()), + new Product('Foo', 42.0, ProductTypeEnum::NON_FOOD), ]); } @@ -37,8 +37,8 @@ public function getProductsBadType(): array { return [ [ - new Product('Foo', 42.0, ProductTypeEnum::NON_FOOD()), - new Product('Foo', 42.0, ProductTypeEnum::NON_FOOD()), + new Product('Foo', 42.0, ProductTypeEnum::NON_FOOD), + new Product('Foo', 42.0, ProductTypeEnum::NON_FOOD), ], ]; } @@ -52,7 +52,7 @@ public function echoProductType(ProductTypeEnum $productType): ProductTypeEnum #[Query] public function echoSomeProductType(): ProductTypeEnum { - return ProductTypeEnum::FOOD(); + return ProductTypeEnum::FOOD; } #[Query] diff --git a/tests/Fixtures/Integration/Models/ProductTypeEnum.php b/tests/Fixtures/Integration/Models/ProductTypeEnum.php index 57cd6f2d2e..1d893bd889 100644 --- a/tests/Fixtures/Integration/Models/ProductTypeEnum.php +++ b/tests/Fixtures/Integration/Models/ProductTypeEnum.php @@ -1,12 +1,14 @@ getTest(); } - return new TestObject($string . $int . $str . ($boolean ? 'true' : 'false') . $float . $dateTimeImmutable->format('YmdHis') . $dateTime->format('YmdHis') . $withDefault . ($id?->val() ?? '') . $enum->getValue()); + return new TestObject($string . $int . $str . ($boolean ? 'true' : 'false') . $float . $dateTimeImmutable->format('YmdHis') . $dateTime->format('YmdHis') . $withDefault . ($id?->val() ?? '') . $enum->value); } #[Query] diff --git a/tests/Fixtures/TestEnum.php b/tests/Fixtures/TestEnum.php index 26e61bdc60..fe7130f036 100644 --- a/tests/Fixtures/TestEnum.php +++ b/tests/Fixtures/TestEnum.php @@ -1,17 +1,11 @@ get(RecursiveTypeMapperInterface::class)); $rootTypeMapper = new BaseTypeMapper($errorRootTypeMapper, $container->get(RecursiveTypeMapperInterface::class), $container->get(RootTypeMapperInterface::class)); - $rootTypeMapper = new MyCLabsEnumTypeMapper($rootTypeMapper, $container->get(AnnotationReader::class), $container->get(ClassFinder::class), $container->get(ClassFinderComputedCache::class)); $rootTypeMapper = new EnumTypeMapper($rootTypeMapper, $container->get(AnnotationReader::class), $container->get(DocBlockFactory::class), $container->get(ClassFinder::class), $container->get(ClassFinderComputedCache::class)); $rootTypeMapper = new CompoundTypeMapper($rootTypeMapper, $container->get(RootTypeMapperInterface::class), $container->get(NamingStrategyInterface::class), $container->get(TypeRegistry::class), $container->get(RecursiveTypeMapperInterface::class)); $rootTypeMapper = new IteratorTypeMapper($rootTypeMapper, $container->get(RootTypeMapperInterface::class)); diff --git a/tests/Mappers/Root/MyCLabsEnumTypeMapperTest.php b/tests/Mappers/Root/MyCLabsEnumTypeMapperTest.php deleted file mode 100644 index 8663d4f8a9..0000000000 --- a/tests/Mappers/Root/MyCLabsEnumTypeMapperTest.php +++ /dev/null @@ -1,24 +0,0 @@ -getTypeMapper()), $this->getAnnotationReader(), $this->getClassFinder([]), $this->getClassFinderComputedCache()); - - $this->expectException(CannotMapTypeException::class); - $this->expectExceptionMessage("don't know how to handle type object"); - $mapper->toGraphQLOutputType(new Object_(), null, new ReflectionMethod(self::class, 'testObjectTypeHint'), new DocBlock()); - } -} diff --git a/tests/Types/MyclabsEnumTypeTest.php b/tests/Types/MyclabsEnumTypeTest.php deleted file mode 100644 index 030d742e62..0000000000 --- a/tests/Types/MyclabsEnumTypeTest.php +++ /dev/null @@ -1,18 +0,0 @@ -expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Expected a Myclabs Enum instance'); - $enumType->serialize('foo'); - } -} diff --git a/website/docs/annotations-reference.md b/website/docs/annotations-reference.md index 485b1f561c..0d414f842d 100644 --- a/website/docs/annotations-reference.md +++ b/website/docs/annotations-reference.md @@ -305,16 +305,3 @@ Attribute | Compulsory | Type | Definition *for* | *yes* | string | The name of the PHP parameter *constraint* | *yes | annotation | One (or many) Symfony validation attributes. -## ~~@EnumType~~ - -*Deprecated: Use [PHP 8.1's native Enums](https://www.php.net/manual/en/language.types.enumerations.php) instead with a [#[Type]](#type-annotation).* - -The `@EnumType` annotation is used to change the name of a "Enum" type. -Note that if you do not want to change the name, the annotation is optionnal. Any object extending `MyCLabs\Enum\Enum` -is automatically mapped to a GraphQL enum type. - -**Applies on**: classes extending the `MyCLabs\Enum\Enum` base class. - -Attribute | Compulsory | Type | Definition ----------------|------------|------|-------- -name | *no* | string | The name of the enum type (in the GraphQL schema) diff --git a/website/docs/internals.md b/website/docs/internals.md index 93c7e958b8..a122a8ea2e 100644 --- a/website/docs/internals.md +++ b/website/docs/internals.md @@ -23,8 +23,7 @@ graph TD; NullableTypeMapperAdapter-->CompoundTypeMapper IteratorTypeMapper-->YourCustomRootTypeMapper CompoundTypeMapper-->IteratorTypeMapper - YourCustomRootTypeMapper-->MyCLabsEnumTypeMapper - MyCLabsEnumTypeMapper-->EnumTypeMapper + YourCustomRootTypeMapper-->EnumTypeMapper EnumTypeMapper-->BaseTypeMapper BaseTypeMapper-->FinalRootTypeMapper end @@ -56,13 +55,12 @@ They are responsible for: Root type mappers have access to the *context* of a type: they can access the PHP DocBlock and read annotations. If you want to write a custom type mapper that needs access to annotations, it needs to be a "root type mapper". -GraphQLite provides 6 classes implementing `RootTypeMapperInterface`: +GraphQLite provides 5 classes implementing `RootTypeMapperInterface`: - `NullableTypeMapperAdapter`: a type mapper in charge of making GraphQL types non-nullable if the PHP type is non-nullable - `IteratorTypeMapper`: a type mapper in charge of iterable types (for instance: `MyIterator|User[]`) - `CompoundTypeMapper`: a type mapper in charge of union types -- `MyCLabsEnumTypeMapper`: maps MyCLabs/enum types to GraphQL enum types (Deprecated: use native enums) -- `EnumTypeMapper`: maps PHP enums to GraphQL enum types +- `EnumTypeMapper`: maps PHP native enums to GraphQL enum types - `BaseTypeMapper`: maps scalar types and lists. Passes the control to the "recursive type mappers" if an object is encountered. - `FinalRootTypeMapper`: the last type mapper of the chain, used to throw error if no other type mapper managed to handle the type. @@ -75,8 +73,7 @@ graph TD; NullableTypeMapperAdapter-->CompoundTypeMapper CompoundTypeMapper-->IteratorTypeMapper IteratorTypeMapper-->YourCustomRootTypeMapper - YourCustomRootTypeMapper-->MyCLabsEnumTypeMapper - MyCLabsEnumTypeMapper-->EnumTypeMapper + YourCustomRootTypeMapper-->EnumTypeMapper EnumTypeMapper-->BaseTypeMapper BaseTypeMapper-->FinalRootTypeMapper end diff --git a/website/docs/type-mapping.mdx b/website/docs/type-mapping.mdx index 404ea28c24..b44147f468 100644 --- a/website/docs/type-mapping.mdx +++ b/website/docs/type-mapping.mdx @@ -217,73 +217,6 @@ enum Status: string } ``` -### Enum types with myclabs/php-enum - -
- This implementation is now deprecated and will be removed in the future. You are advised to use native enums instead. -
- -*Prior to version 5.1, GraphQLite only supported Enums through the 3rd party library, [myclabs/php-enum](https://github.com/myclabs/php-enum). If you'd like to use this implementation you'll first need to add this library as a dependency to your application.* - -```bash -$ composer require myclabs/php-enum -``` - -Now, any class extending the `MyCLabs\Enum\Enum` class will be mapped to a GraphQL enum: - -```php -use MyCLabs\Enum\Enum; - -class StatusEnum extends Enum -{ - private const ON = 'on'; - private const OFF = 'off'; - private const PENDING = 'pending'; -} -``` - -```php -/** - * @return User[] - */ -#[Query] -public function users(StatusEnum $status): array -{ - if ($status == StatusEnum::ON()) { - // Note that the "magic" ON() method returns an instance of the StatusEnum class. - // Also, note that we are comparing this instance using "==" (using "===" would fail as we have 2 different instances here) - // ... - } - // ... -} -``` - -```graphql -query users($status: StatusEnum!) {} - users(status: $status) { - id - } -} -``` - -By default, the name of the GraphQL enum type will be the name of the class. If you have a naming conflict (two classes -that live in different namespaces with the same class name), you can solve it using the `#[EnumType]` attribute: - -```php -use TheCodingMachine\GraphQLite\Annotations\EnumType; - -#[EnumType(name: "UserStatus")] -class StatusEnum extends Enum -{ - // ... -} -``` - -
GraphQLite must be able to find all the classes extending the "MyCLabs\Enum" class -in your project. By default, GraphQLite will look for "Enum" classes in the namespaces declared for the types. For this -reason, your enum classes MUST be in one of the namespaces declared for the types in your GraphQLite -configuration file.
- ## Deprecation of fields You can mark a field as deprecated in your GraphQL Schema by just annotating it with the `@deprecated` PHPDoc annotation. Note that a description (reason) is required for the annotation to be rendered.