Skip to content

Commit

Permalink
Merge b088d10 into 6d15e22
Browse files Browse the repository at this point in the history
  • Loading branch information
GwendolenLynch committed Apr 24, 2024
2 parents 6d15e22 + b088d10 commit e484df9
Show file tree
Hide file tree
Showing 13 changed files with 525 additions and 10 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
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
4 changes: 4 additions & 0 deletions 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(), new Get()]);
}

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

Expand Down
72 changes: 72 additions & 0 deletions src/State/Provider/BackedEnumProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?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\Operation;
use ApiPlatform\State\ProviderInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

final class BackedEnumProvider implements ProviderInterface
{
public function __construct(private ProviderInterface $decorated)
{
}

public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
{
$resourceClass = $operation->getClass();
if (!$resourceClass || !is_a($resourceClass, \BackedEnum::class, true)) {
return $this->decorated->provide($operation, $uriVariables, $context);
}

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/state/provider.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@
<argument type="tagged_locator" tag="api_platform.parameter_provider" index-by="key" />
</service>

<service id="api_platform.state_provider.backed_enum" class="ApiPlatform\State\Provider\BackedEnumProvider" decorates="api_platform.state_provider.main" decoration-priority="300">
<argument type="service" id="api_platform.state_provider.backed_enum.inner" />
</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',
};
}
}
48 changes: 48 additions & 0 deletions tests/Fixtures/TestBundle/ApiResource/Issue6317/Issue6317.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?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\Issue6317;

use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiResource;

#[ApiResource]
enum Issue6317: int
{
case First = 1;
case Second = 2;

#[ApiProperty(identifier: true, example: 'An example of an ID')]
public function getId(): int
{
return $this->value;
}

#[ApiProperty(jsonSchemaContext: ['example' => '/lisa/mary'])]
public function getName(): string
{
return $this->name;
}

#[ApiProperty(jsonldContext: ['example' => '24'])]
public function getOrdinal(): string
{
return 1 === $this->value ? '1st' : '2nd';
}

#[ApiProperty(openapiContext: ['example' => '42'])]
public function getCardinal(): int
{
return $this->value;
}
}
58 changes: 58 additions & 0 deletions tests/Fixtures/TestBundle/ApiResource/ResourceWithEnumProperty.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?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;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\Tests\Fixtures\TestBundle\Enum\GenderTypeEnum;

#[ApiResource()]
#[Get(
provider: self::class.'::providerItem',
)]
#[GetCollection(
provider: self::class.'::providerCollection',
)]
class ResourceWithEnumProperty
{
public int $id = 1;

public ?BackedEnumIntegerResource $intEnum = null;

/** @var BackedEnumStringResource[] */
public array $stringEnum = [];

public ?GenderTypeEnum $gender = null;

/** @var GenderTypeEnum[] */
public array $genders = [];

public static function providerItem(Operation $operation, array $uriVariables): self
{
$self = new self();
$self->intEnum = BackedEnumIntegerResource::Yes;
$self->stringEnum = [BackedEnumStringResource::Maybe, BackedEnumStringResource::No];
$self->gender = GenderTypeEnum::FEMALE;
$self->genders = [GenderTypeEnum::FEMALE, GenderTypeEnum::MALE];

return $self;
}

public static function providerCollection(Operation $operation, array $uriVariables): array
{
return [self::providerItem($operation, $uriVariables)];
}
}
Loading

0 comments on commit e484df9

Please sign in to comment.