Skip to content

Commit 0d04f28

Browse files
feat(metadata): improve CreateProvider (#5770)
1 parent 731ceac commit 0d04f28

File tree

7 files changed

+345
-38
lines changed

7 files changed

+345
-38
lines changed

features/main/sub_resource.feature

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,3 +607,23 @@ Feature: Sub-resource support
607607
| invalid_uri | collection_uri | item_uri |
608608
| /subresource_organizations/invalid/subresource_employees | /subresource_organizations/1/subresource_employees | /subresource_organizations/1/subresource_employees/1 |
609609
| /subresource_organizations/invalid/subresource_factories | /subresource_organizations/1/subresource_factories | /subresource_organizations/1/subresource_factories/1 |
610+
611+
@!mongodb
612+
@createSchema
613+
Scenario: I can POST on a subresource using CreateProvider with parent_uri_template
614+
Given I add "Content-Type" header equal to "application/ld+json"
615+
And I send a "POST" request to "/subresource_categories/1/subresource_bikes" with body:
616+
"""
617+
{
618+
"name": "Hello World!"
619+
}
620+
"""
621+
Then the response status code should be 404
622+
Given I add "Content-Type" header equal to "application/ld+json"
623+
And I send a "POST" request to "/subresource_categories_with_create_provider/1/subresource_bikes" with body:
624+
"""
625+
{
626+
"name": "Hello World!"
627+
}
628+
"""
629+
Then the response status code should be 201

src/State/CreateProvider.php

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@
1414
namespace ApiPlatform\State;
1515

1616
use ApiPlatform\Exception\RuntimeException;
17-
use ApiPlatform\Metadata\Get;
1817
use ApiPlatform\Metadata\HttpOperation;
19-
use ApiPlatform\Metadata\Link;
2018
use ApiPlatform\Metadata\Operation;
19+
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
20+
use ApiPlatform\State\Exception\ProviderNotFoundException;
2121
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
2222
use Symfony\Component\PropertyAccess\PropertyAccess;
2323
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
@@ -30,11 +30,16 @@
3030
* @author Antoine Bluchet <soyuka@gmail.com>
3131
*
3232
* @experimental
33+
*
34+
* @internal
3335
*/
3436
final class CreateProvider implements ProviderInterface
3537
{
36-
public function __construct(private ProviderInterface $decorated, private ?PropertyAccessorInterface $propertyAccessor = null)
37-
{
38+
public function __construct(
39+
private ProviderInterface $decorated,
40+
private ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory,
41+
private ?PropertyAccessorInterface $propertyAccessor = null,
42+
) {
3843
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
3944
}
4045

@@ -47,18 +52,15 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
4752
$operationUriVariables = $operation->getUriVariables();
4853
$relationClass = current($operationUriVariables)->getFromClass();
4954
$key = key($operationUriVariables);
50-
$relationUriVariables = [];
51-
52-
foreach ($operationUriVariables as $parameterName => $value) {
53-
if ($key === $parameterName) {
54-
$relationUriVariables['id'] = new Link(identifiers: $value->getIdentifiers(), fromClass: $value->getFromClass(), parameterName: $key);
55-
continue;
56-
}
5755

58-
$relationUriVariables[$parameterName] = $value;
56+
$parentOperation = $this->resourceMetadataCollectionFactory
57+
->create($relationClass)
58+
->getOperation($operation->getExtraProperties()['parent_uri_template'] ?? null);
59+
try {
60+
$relation = $this->decorated->provide($parentOperation, $uriVariables);
61+
} catch (ProviderNotFoundException) {
62+
$relation = null;
5963
}
60-
61-
$relation = $this->decorated->provide(new Get(uriVariables: $relationUriVariables, class: $relationClass), $uriVariables);
6264
if (!$relation) {
6365
throw new NotFoundHttpException('Not Found');
6466
}
@@ -68,6 +70,7 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
6870
} catch (\Throwable $e) {
6971
throw new RuntimeException(sprintf('An error occurred while trying to create an instance of the "%s" resource. Consider writing your own "%s" implementation and setting it as `provider` on your operation instead.', $operation->getClass(), ProviderInterface::class), 0, $e);
7072
}
73+
7174
$property = $operationUriVariables[$key]->getToProperty() ?? $key;
7275
$this->propertyAccessor->setValue($resource, $property, $relation);
7376

src/Symfony/Bundle/Resources/config/state.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@
8181
<service id="ApiPlatform\State\Pagination\PaginationOptions" alias="api_platform.pagination_options" />
8282

8383
<service id="api_platform.state_provider.create" class="ApiPlatform\State\CreateProvider">
84-
<argument type="service" id="api_platform.state.item_provider" />
84+
<argument type="service" id="api_platform.state_provider" />
85+
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory" />
8586

8687
<tag name="api_platform.state_provider" key="ApiPlatform\State\CreateProvider" />
8788
<tag name="api_platform.state_provider" key="api_platform.state_provider.create" />

tests/Fixtures/TestBundle/ApiResource/PostWithUriVariables.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
namespace ApiPlatform\Tests\Fixtures\TestBundle\ApiResource;
1515

1616
use ApiPlatform\Metadata\NotExposed;
17-
use ApiPlatform\Metadata\Operation;
1817
use ApiPlatform\Metadata\Post;
1918
use ApiPlatform\Symfony\Validator\Exception\ValidationException as ExceptionValidationException;
2019
use Symfony\Component\Validator\ConstraintViolationList;
@@ -28,12 +27,12 @@ public function __construct(public readonly ?int $id = null)
2827
{
2928
}
3029

31-
public static function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = [])
30+
public static function process(): self
3231
{
3332
return new self(id: 1);
3433
}
3534

36-
public static function provide(Operation $operation, array $uriVariables = [], array $context = []): void
35+
public static function provide(): void
3736
{
3837
throw new ExceptionValidationException(new ConstraintViolationList());
3938
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <dunglas@gmail.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Tests\Fixtures\TestBundle\ApiResource;
15+
16+
use ApiPlatform\Metadata\ApiProperty;
17+
use ApiPlatform\Metadata\Link;
18+
use ApiPlatform\Metadata\Post;
19+
use ApiPlatform\State\CreateProvider;
20+
use Symfony\Component\Validator\Constraints as Assert;
21+
22+
#[Post(
23+
uriTemplate: '/subresource_categories/{id}/subresource_bikes',
24+
uriVariables: [
25+
'id' => new Link(
26+
fromClass: SubresourceCategory::class,
27+
toProperty: 'category',
28+
identifiers: ['id']
29+
),
30+
],
31+
provider: CreateProvider::class,
32+
processor: [SubresourceBike::class, 'process']
33+
)]
34+
#[Post(
35+
uriTemplate: '/subresource_categories_with_create_provider/{id}/subresource_bikes',
36+
uriVariables: [
37+
'id' => new Link(
38+
fromClass: SubresourceCategory::class,
39+
toProperty: 'category',
40+
identifiers: ['id']
41+
),
42+
],
43+
provider: CreateProvider::class,
44+
processor: [SubresourceBike::class, 'process'],
45+
extraProperties: ['parent_uri_template' => '/subresource_categories_with_create_provider/{id}']
46+
)]
47+
/**
48+
* @see SubresourceCategory
49+
*/
50+
class SubresourceBike
51+
{
52+
#[ApiProperty(identifier: true)]
53+
public ?int $id = null;
54+
55+
#[Assert\NotBlank]
56+
public ?string $name = null;
57+
58+
#[Assert\NotNull]
59+
public ?SubresourceCategory $category = null;
60+
61+
public static function process(mixed $data): self
62+
{
63+
$data->id = 1;
64+
65+
return $data;
66+
}
67+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <dunglas@gmail.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Tests\Fixtures\TestBundle\ApiResource;
15+
16+
use ApiPlatform\Metadata\ApiProperty;
17+
use ApiPlatform\Metadata\Get;
18+
19+
#[Get(
20+
uriTemplate: '/subresource_categories/{id}',
21+
provider: [SubresourceCategory::class, 'provideNull']
22+
)]
23+
#[Get(
24+
uriTemplate: '/subresource_categories_with_create_provider/{id}',
25+
provider: [SubresourceCategory::class, 'provide']
26+
)]
27+
/**
28+
* @see SubresourceBike
29+
*/
30+
final class SubresourceCategory
31+
{
32+
public function __construct(
33+
#[ApiProperty(identifier: true)]
34+
public ?int $id = null,
35+
public ?string $name = null
36+
) {
37+
}
38+
39+
public static function provideNull()
40+
{
41+
return null;
42+
}
43+
44+
public static function provide(): self
45+
{
46+
return new self(1, 'Hello World!');
47+
}
48+
}

0 commit comments

Comments
 (0)