Skip to content

Commit

Permalink
Merge cc755bf into f40d9a1
Browse files Browse the repository at this point in the history
  • Loading branch information
GwendolenLynch committed Jun 18, 2024
2 parents f40d9a1 + cc755bf commit eee4348
Show file tree
Hide file tree
Showing 19 changed files with 912 additions and 60 deletions.
16 changes: 6 additions & 10 deletions src/Metadata/Property/Factory/AttributePropertyMetadataFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,11 @@ public function create(string $resourceClass, string $property, array $options =
return $this->handleNotFound($parentPropertyMetadata, $resourceClass, $property);
}

if ($reflectionEnum) {
if ($reflectionEnum->hasCase($property)) {
$reflectionCase = $reflectionEnum->getCase($property);
if ($attributes = $reflectionCase->getAttributes(ApiProperty::class)) {
return $this->createMetadata($attributes[0]->newInstance(), $parentPropertyMetadata);
}
if ($reflectionEnum && $reflectionEnum->hasCase($property)) {
$reflectionCase = $reflectionEnum->getCase($property);
if ($attributes = $reflectionCase->getAttributes(ApiProperty::class)) {
return $this->createMetadata($attributes[0]->newInstance(), $parentPropertyMetadata);
}

return $this->handleNotFound($parentPropertyMetadata, $resourceClass, $property);
}

if ($reflectionClass->hasProperty($property)) {
Expand All @@ -79,11 +75,11 @@ public function create(string $resourceClass, string $property, array $options =

foreach (array_merge(Reflection::ACCESSOR_PREFIXES, Reflection::MUTATOR_PREFIXES) as $prefix) {
$methodName = $prefix.ucfirst($property);
if (!$reflectionClass->hasMethod($methodName)) {
if (!$reflectionClass->hasMethod($methodName) && !$reflectionEnum?->hasMethod($methodName)) {
continue;
}

$reflectionMethod = $reflectionClass->getMethod($methodName);
$reflectionMethod = $reflectionClass->hasMethod($methodName) ? $reflectionClass->getMethod($methodName) : $reflectionEnum?->getMethod($methodName);
if (!$reflectionMethod->isPublic()) {
continue;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Metadata\Resource\Factory;

use ApiPlatform\Metadata\Operations;
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;

/**
* Triggers resource deprecations.
*
* @internal
*/
final class BackedEnumResourceMetadataCollectionFactory implements ResourceMetadataCollectionFactoryInterface
{
public const PROVIDER = 'api_platform.state_provider.backed_enum';

public function __construct(private readonly ResourceMetadataCollectionFactoryInterface $decorated)
{
}

public function create(string $resourceClass): ResourceMetadataCollection
{
$resourceMetadataCollection = $this->decorated->create($resourceClass);
if (!is_a($resourceClass, \BackedEnum::class, true)) {
return $resourceMetadataCollection;
}

foreach ($resourceMetadataCollection as $i => $resourceMetadata) {
$newOperations = [];
foreach ($resourceMetadata->getOperations() as $operationName => $operation) {
$newOperations[$operationName] = $operation;

if (null !== $operation->getProvider()) {
continue;
}

$newOperations[$operationName] = $operation->withProvider(self::PROVIDER);
}

$newGraphQlOperations = [];
foreach ($resourceMetadata->getGraphQlOperations() as $operationName => $operation) {
$newGraphQlOperations[$operationName] = $operation;

if (null !== $operation->getProvider()) {
continue;
}

$newGraphQlOperations[$operationName] = $operation->withProvider(self::PROVIDER);
}

$resourceMetadataCollection[$i] = $resourceMetadata->withOperations(new Operations($newOperations))->withGraphQlOperations($newGraphQlOperations);
}

return $resourceMetadataCollection;
}
}
7 changes: 7 additions & 0 deletions src/Metadata/Resource/Factory/LinkFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ public function createLinksFromIdentifiers(Metadata $operation): array

$link = (new Link())->withFromClass($resourceClass)->withIdentifiers($identifiers);
$parameterName = $identifiers[0];
if ('value' === $parameterName && enum_exists($resourceClass)) {
$parameterName = 'id';
}

if (1 < \count($identifiers)) {
$parameterName = 'id';
Expand Down Expand Up @@ -155,6 +158,10 @@ private function getIdentifiersFromResourceClass(string $resourceClass): array
return ['id'];
}

if (!$hasIdProperty && !$identifiers && enum_exists($resourceClass)) {
return ['value'];
}

return $identifiers;
}

Expand Down
7 changes: 6 additions & 1 deletion src/Metadata/Resource/Factory/OperationDefaultsTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ private function getResourceWithDefaults(string $resourceClass, string $shortNam

private function getDefaultHttpOperations($resource): iterable
{
if (enum_exists($resource->getClass())) {
return new Operations([new GetCollection(paginationEnabled: false), new Get()]);
}

if (($defaultOperations = $this->defaults['operations'] ?? null) && null === $resource->getOperations()) {
$operations = [];

Expand All @@ -108,8 +112,9 @@ private function getDefaultHttpOperations($resource): iterable

private function addDefaultGraphQlOperations(ApiResource $resource): ApiResource
{
$operations = enum_exists($resource->getClass()) ? [new QueryCollection(paginationEnabled: false), new Query()] : [new QueryCollection(), new Query(), (new Mutation())->withName('update'), (new DeleteMutation())->withName('delete'), (new Mutation())->withName('create')];
$graphQlOperations = [];
foreach ([new QueryCollection(), new Query(), (new Mutation())->withName('update'), (new DeleteMutation())->withName('delete'), (new Mutation())->withName('create')] as $operation) {
foreach ($operations as $operation) {
[$key, $operation] = $this->getOperationWithDefaults($resource, $operation);
$graphQlOperations[$key] = $operation;
}
Expand Down
69 changes: 69 additions & 0 deletions src/State/Provider/BackedEnumProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\State\Provider;

use ApiPlatform\Metadata\CollectionOperationInterface;
use ApiPlatform\Metadata\Exception\RuntimeException;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

final class BackedEnumProvider implements ProviderInterface
{
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
{
$resourceClass = $operation->getClass();
if (!$resourceClass || !is_a($resourceClass, \BackedEnum::class, true)) {
throw new RuntimeException('This resource is not an enum');
}

if ($operation instanceof CollectionOperationInterface) {
return $resourceClass::cases();
}

$id = $uriVariables['id'] ?? null;
if (null === $id) {
throw new NotFoundHttpException('Not Found');
}

if ($enum = $this->resolveEnum($resourceClass, $id)) {
return $enum;
}

throw new NotFoundHttpException('Not Found');
}

/**
* @param class-string $resourceClass
*/
private function resolveEnum(string $resourceClass, string|int $id): ?\BackedEnum
{
$reflectEnum = new \ReflectionEnum($resourceClass);
$type = (string) $reflectEnum->getBackingType();

if ('int' === $type) {
if (!is_numeric($id)) {
return null;
}
$enum = $resourceClass::tryFrom((int) $id);
} else {
$enum = $resourceClass::tryFrom($id);
}

// @deprecated enums will be indexable only by value in 4.0
$enum ??= array_reduce($resourceClass::cases(), static fn ($c, \BackedEnum $case) => $id === $case->name ? $case : $c, null);

return $enum;
}
}
4 changes: 4 additions & 0 deletions src/Symfony/Bundle/Resources/config/metadata/resource.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory.not_exposed_operation.inner" />
</service>

<service id="api_platform.metadata.resource.metadata_collection_factory.backed_enum" class="ApiPlatform\Metadata\Resource\Factory\BackedEnumResourceMetadataCollectionFactory" decorates="api_platform.metadata.resource.metadata_collection_factory" decoration-priority="500" public="false">
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory.backed_enum.inner" />
</service>

<service id="api_platform.metadata.resource.metadata_collection_factory.uri_template" class="ApiPlatform\Metadata\Resource\Factory\UriTemplateResourceMetadataCollectionFactory" decorates="api_platform.metadata.resource.metadata_collection_factory" decoration-priority="500" public="false">
<argument type="service" id="api_platform.metadata.resource.link_factory" />
<argument type="service" id="api_platform.path_segment_name_generator" />
Expand Down
5 changes: 5 additions & 0 deletions src/Symfony/Bundle/Resources/config/state/provider.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@
<argument type="service" id="translator" on-invalid="null" />
</service>

<service id="api_platform.state_provider.backed_enum" class="ApiPlatform\State\Provider\BackedEnumProvider">
<tag name="api_platform.state_provider" key="ApiPlatform\State\Provider\BackedEnumProvidevr" />
<tag name="api_platform.state_provider" key="api_platform.state_provider.backed_enum" />
</service>

<service id="api_platform.error_listener" class="ApiPlatform\Symfony\EventListener\ErrorListener">
<argument key="$controller">api_platform.symfony.main_controller</argument>
<argument key="$logger" type="service" id="logger" on-invalid="null" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Tests\Fixtures\TestBundle\ApiResource;

use ApiPlatform\Metadata\ApiResource;

#[ApiResource]
enum BackedEnumIntegerResource: int
{
case Yes = 1;
case No = 2;
case Maybe = 3;

public function getDescription(): string
{
return match ($this) {
self::Yes => 'We say yes',
self::No => 'Computer says no',
self::Maybe => 'Let me think about it',
};
}
}
33 changes: 33 additions & 0 deletions tests/Fixtures/TestBundle/ApiResource/BackedEnumStringResource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Tests\Fixtures\TestBundle\ApiResource;

use ApiPlatform\Metadata\ApiResource;

#[ApiResource]
enum BackedEnumStringResource: string
{
case Yes = 'yes';
case No = 'no';
case Maybe = 'maybe';

public function getDescription(): string
{
return match ($this) {
self::Yes => 'We say yes',
self::No => 'Computer says no',
self::Maybe => 'Let me think about it',
};
}
}
14 changes: 11 additions & 3 deletions tests/Fixtures/TestBundle/ApiResource/Issue6264/Availability.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,18 @@
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\GraphQl\Query;

#[ApiResource(normalizationContext: ['groups' => ['get']])]
#[GetCollection(provider: Availability::class.'::getCases')]
#[Get(provider: Availability::class.'::getCase')]
#[ApiResource(
normalizationContext: ['groups' => ['get']],
operations: [
new GetCollection(provider: Availability::class.'::getCases'),
new Get(provider: Availability::class.'::getCase'),
],
graphQlOperations: [
new Query(provider: Availability::class.'getCase'),
]
)]
enum Availability: int
{
use BackedEnumTrait;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
#[Get(provider: AvailabilityStatus::class.'::getCase')]
enum AvailabilityStatus: string
{
use BackedEnumTrait;
use BackedEnumStringTrait;

case Pending = 'pending';
case Reviewed = 'reviewed';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue6264;

use ApiPlatform\Metadata\Operation;
use Symfony\Component\Serializer\Attribute\Groups;

trait BackedEnumStringTrait
{
public static function values(): array
{
return array_map(static fn (\BackedEnum $feature) => $feature->value, self::cases());
}

public function getId(): string
{
return $this->value;
}

#[Groups(['get'])]
public function getValue(): string
{
return $this->value;
}

public static function getCases(): array
{
return self::cases();
}

/**
* @param array<string, string> $uriVariables
*/
public static function getCase(Operation $operation, array $uriVariables): ?self
{
return array_reduce(self::cases(), static fn ($c, \BackedEnum $case) => $case->value == $uriVariables['id'] ? $case : $c, null);
}
}
Loading

0 comments on commit eee4348

Please sign in to comment.