diff --git a/features/main/attribute_resource.feature b/features/main/attribute_resource.feature index 890fccffd38..69224e54e9a 100644 --- a/features/main/attribute_resource.feature +++ b/features/main/attribute_resource.feature @@ -103,8 +103,17 @@ Feature: Resource attributes Scenario: Uri variables with Post operation When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/post_with_uri_variables/{id}" with body: + And I send a "POST" request to "/post_with_uri_variables_and_no_provider/{id}" with body: """ {} """ Then the response status code should be 201 + + Scenario: Throw validation exception in a provider + When I add "Content-Type" header equal to "application/ld+json" + And I send a "POST" request to "/post_with_uri_variables/{id}" with body: + """ + {} + """ + Then the response status code should be 422 + diff --git a/features/main/composite.feature b/features/main/composite.feature index cf0065085fa..ab99527bd7f 100644 --- a/features/main/composite.feature +++ b/features/main/composite.feature @@ -132,3 +132,8 @@ Feature: Retrieve data with Composite identifiers Then the response status code should be 200 And the response should be in JSON And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" + + Scenario: Get identifiers with different types + Given there are Composite identifier objects + When I send a "GET" request to "/composite_key_with_different_types/id=82133;verificationKey=7d75af772e637e45c36d041696e1128d" + Then the response status code should be 200 diff --git a/src/Api/UriVariablesConverter.php b/src/Api/UriVariablesConverter.php index e7c7334c53c..dadbd897473 100644 --- a/src/Api/UriVariablesConverter.php +++ b/src/Api/UriVariablesConverter.php @@ -44,7 +44,9 @@ public function convert(array $uriVariables, string $class, array $context = []) foreach ($uriVariables as $parameterName => $value) { $uriVariableDefinition = $uriVariablesDefinitions[$parameterName] ?? $uriVariablesDefinitions['id'] ?? new Link(); - if ([] === $types = $this->getIdentifierTypes($uriVariableDefinition->getFromClass() ?? $class, $uriVariableDefinition->getIdentifiers() ?? [$parameterName])) { + + $identifierTypes = $this->getIdentifierTypes($uriVariableDefinition->getFromClass() ?? $class, $uriVariableDefinition->getIdentifiers() ?? [$parameterName]); + if (!($types = $identifierTypes[$parameterName] ?? false)) { continue; } @@ -71,7 +73,7 @@ private function getIdentifierTypes(string $resourceClass, array $properties): a foreach ($properties as $property) { $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $property); foreach ($propertyMetadata->getBuiltinTypes() as $type) { - $types[] = Type::BUILTIN_TYPE_OBJECT === ($builtinType = $type->getBuiltinType()) ? $type->getClassName() : $builtinType; + $types[$property][] = Type::BUILTIN_TYPE_OBJECT === ($builtinType = $type->getBuiltinType()) ? $type->getClassName() : $builtinType; } } diff --git a/src/State/CallableProvider.php b/src/State/CallableProvider.php index 4926e7e6797..f669c079f6a 100644 --- a/src/State/CallableProvider.php +++ b/src/State/CallableProvider.php @@ -13,8 +13,8 @@ namespace ApiPlatform\State; -use ApiPlatform\Exception\RuntimeException; use ApiPlatform\Metadata\Operation; +use ApiPlatform\State\Exception\ProviderNotFoundException; use Psr\Container\ContainerInterface; final class CallableProvider implements ProviderInterface @@ -34,7 +34,7 @@ public function provide(Operation $operation, array $uriVariables = [], array $c if (\is_string($provider)) { if (!$this->locator->has($provider)) { - throw new RuntimeException(sprintf('Provider "%s" not found on operation "%s"', $provider, $operation->getName())); + throw new ProviderNotFoundException(sprintf('Provider "%s" not found on operation "%s"', $provider, $operation->getName())); } /** @var ProviderInterface $providerInstance */ @@ -43,6 +43,6 @@ public function provide(Operation $operation, array $uriVariables = [], array $c return $providerInstance->provide($operation, $uriVariables, $context); } - throw new RuntimeException(sprintf('Provider not found on operation "%s"', $operation->getName())); + throw new ProviderNotFoundException(sprintf('Provider not found on operation "%s"', $operation->getName())); } } diff --git a/src/State/Exception/ProviderNotFoundException.php b/src/State/Exception/ProviderNotFoundException.php new file mode 100644 index 00000000000..a12537f8bd9 --- /dev/null +++ b/src/State/Exception/ProviderNotFoundException.php @@ -0,0 +1,20 @@ + + * + * 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\Exception; + +use ApiPlatform\Metadata\Exception\RuntimeException; + +final class ProviderNotFoundException extends RuntimeException +{ +} diff --git a/src/Symfony/EventListener/ReadListener.php b/src/Symfony/EventListener/ReadListener.php index f0ee456d030..5b27165108c 100644 --- a/src/Symfony/EventListener/ReadListener.php +++ b/src/Symfony/EventListener/ReadListener.php @@ -16,11 +16,11 @@ use ApiPlatform\Api\UriVariablesConverterInterface; use ApiPlatform\Exception\InvalidIdentifierException; use ApiPlatform\Exception\InvalidUriVariableException; -use ApiPlatform\Exception\RuntimeException; use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Put; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Serializer\SerializerContextBuilderInterface; +use ApiPlatform\State\Exception\ProviderNotFoundException; use ApiPlatform\State\ProviderInterface; use ApiPlatform\State\UriVariablesResolverTrait; use ApiPlatform\Util\CloneTrait; @@ -93,7 +93,7 @@ public function onKernelRequest(RequestEvent $event): void $data = $this->provider->provide($operation, $uriVariables, $context); } catch (InvalidIdentifierException|InvalidUriVariableException $e) { throw new NotFoundHttpException('Invalid identifier value or configuration.', $e); - } catch (RuntimeException $e) { + } catch (ProviderNotFoundException $e) { $data = null; } diff --git a/tests/Fixtures/TestBundle/ApiResource/Issue5396/CompositeKeyWithDifferentType.php b/tests/Fixtures/TestBundle/ApiResource/Issue5396/CompositeKeyWithDifferentType.php new file mode 100644 index 00000000000..fbaa43fffea --- /dev/null +++ b/tests/Fixtures/TestBundle/ApiResource/Issue5396/CompositeKeyWithDifferentType.php @@ -0,0 +1,33 @@ + + * + * 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\Issue5396; + +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Operation; + +#[ApiResource(provider: [CompositeKeyWithDifferentType::class, 'provide'])] +class CompositeKeyWithDifferentType +{ + #[ApiProperty(identifier: true)] + public ?int $id; + + #[ApiProperty(identifier: true)] + public ?string $verificationKey; + + public static function provide(Operation $operation, array $uriVariables = [], array $context = []): array + { + return $context; + } +} diff --git a/tests/Fixtures/TestBundle/ApiResource/PostWithUriVariables.php b/tests/Fixtures/TestBundle/ApiResource/PostWithUriVariables.php index 463021e128b..8142768ced2 100644 --- a/tests/Fixtures/TestBundle/ApiResource/PostWithUriVariables.php +++ b/tests/Fixtures/TestBundle/ApiResource/PostWithUriVariables.php @@ -16,9 +16,12 @@ use ApiPlatform\Metadata\NotExposed; use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\Post; +use ApiPlatform\Symfony\Validator\Exception\ValidationException as ExceptionValidationException; +use Symfony\Component\Validator\ConstraintViolationList; #[NotExposed(uriTemplate: '/post_with_uri_variables/{id}')] -#[Post(uriTemplate: '/post_with_uri_variables/{id}', uriVariables: ['id'], processor: [PostWithUriVariables::class, 'process'])] +#[Post(uriTemplate: '/post_with_uri_variables_and_no_provider/{id}', uriVariables: ['id'], processor: [PostWithUriVariables::class, 'process'])] +#[Post(uriTemplate: '/post_with_uri_variables/{id}', uriVariables: ['id'], provider: [PostWithUriVariables::class, 'provide'])] final class PostWithUriVariables { public function __construct(public readonly ?int $id = null) @@ -29,4 +32,9 @@ public static function process(mixed $data, Operation $operation, array $uriVari { return new self(id: 1); } + + public static function provide(Operation $operation, array $uriVariables = [], array $context = []): void + { + throw new ExceptionValidationException(new ConstraintViolationList()); + } }