Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions features/main/sub_resource.feature
Original file line number Diff line number Diff line change
Expand Up @@ -607,3 +607,23 @@ Feature: Sub-resource support
| invalid_uri | collection_uri | item_uri |
| /subresource_organizations/invalid/subresource_employees | /subresource_organizations/1/subresource_employees | /subresource_organizations/1/subresource_employees/1 |
| /subresource_organizations/invalid/subresource_factories | /subresource_organizations/1/subresource_factories | /subresource_organizations/1/subresource_factories/1 |

@!mongodb
@createSchema
Scenario: I can POST on a subresource using CreateProvider with parent_uri_template
Given I add "Content-Type" header equal to "application/ld+json"
And I send a "POST" request to "/subresource_categories/1/subresource_bikes" with body:
"""
{
"name": "Hello World!"
}
"""
Then the response status code should be 404
Given I add "Content-Type" header equal to "application/ld+json"
And I send a "POST" request to "/subresource_categories_with_create_provider/1/subresource_bikes" with body:
"""
{
"name": "Hello World!"
}
"""
Then the response status code should be 201
31 changes: 17 additions & 14 deletions src/State/CreateProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
namespace ApiPlatform\State;

use ApiPlatform\Exception\RuntimeException;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\HttpOperation;
use ApiPlatform\Metadata\Link;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
use ApiPlatform\State\Exception\ProviderNotFoundException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
Expand All @@ -30,11 +30,16 @@
* @author Antoine Bluchet <soyuka@gmail.com>
*
* @experimental
*
* @internal
*/
final class CreateProvider implements ProviderInterface
{
public function __construct(private ProviderInterface $decorated, private ?PropertyAccessorInterface $propertyAccessor = null)
{
public function __construct(
private ProviderInterface $decorated,
private ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory,
private ?PropertyAccessorInterface $propertyAccessor = null,
) {
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
}

Expand All @@ -47,18 +52,15 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
$operationUriVariables = $operation->getUriVariables();
$relationClass = current($operationUriVariables)->getFromClass();
$key = key($operationUriVariables);
$relationUriVariables = [];

foreach ($operationUriVariables as $parameterName => $value) {
if ($key === $parameterName) {
$relationUriVariables['id'] = new Link(identifiers: $value->getIdentifiers(), fromClass: $value->getFromClass(), parameterName: $key);
continue;
}

$relationUriVariables[$parameterName] = $value;
$parentOperation = $this->resourceMetadataCollectionFactory
->create($relationClass)
->getOperation($operation->getExtraProperties()['parent_uri_template'] ?? null);
try {
$relation = $this->decorated->provide($parentOperation, $uriVariables);
} catch (ProviderNotFoundException) {
$relation = null;
}

$relation = $this->decorated->provide(new Get(uriVariables: $relationUriVariables, class: $relationClass), $uriVariables);
if (!$relation) {
throw new NotFoundHttpException('Not Found');
}
Expand All @@ -68,6 +70,7 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
} catch (\Throwable $e) {
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);
}

$property = $operationUriVariables[$key]->getToProperty() ?? $key;
$this->propertyAccessor->setValue($resource, $property, $relation);

Expand Down
3 changes: 2 additions & 1 deletion src/Symfony/Bundle/Resources/config/state.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@
<service id="ApiPlatform\State\Pagination\PaginationOptions" alias="api_platform.pagination_options" />

<service id="api_platform.state_provider.create" class="ApiPlatform\State\CreateProvider">
<argument type="service" id="api_platform.state.item_provider" />
<argument type="service" id="api_platform.state_provider" />
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory" />

<tag name="api_platform.state_provider" key="ApiPlatform\State\CreateProvider" />
<tag name="api_platform.state_provider" key="api_platform.state_provider.create" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
namespace ApiPlatform\Tests\Fixtures\TestBundle\ApiResource;

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;
Expand All @@ -28,12 +27,12 @@ public function __construct(public readonly ?int $id = null)
{
}

public static function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = [])
public static function process(): self
{
return new self(id: 1);
}

public static function provide(Operation $operation, array $uriVariables = [], array $context = []): void
public static function provide(): void
{
throw new ExceptionValidationException(new ConstraintViolationList());
}
Expand Down
67 changes: 67 additions & 0 deletions tests/Fixtures/TestBundle/ApiResource/SubresourceBike.php
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\Tests\Fixtures\TestBundle\ApiResource;

use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\Link;
use ApiPlatform\Metadata\Post;
use ApiPlatform\State\CreateProvider;
use Symfony\Component\Validator\Constraints as Assert;

#[Post(
uriTemplate: '/subresource_categories/{id}/subresource_bikes',
uriVariables: [
'id' => new Link(
fromClass: SubresourceCategory::class,
toProperty: 'category',
identifiers: ['id']
),
],
provider: CreateProvider::class,
processor: [SubresourceBike::class, 'process']
)]
#[Post(
uriTemplate: '/subresource_categories_with_create_provider/{id}/subresource_bikes',
uriVariables: [
'id' => new Link(
fromClass: SubresourceCategory::class,
toProperty: 'category',
identifiers: ['id']
),
],
provider: CreateProvider::class,
processor: [SubresourceBike::class, 'process'],
extraProperties: ['parent_uri_template' => '/subresource_categories_with_create_provider/{id}']
)]
/**
* @see SubresourceCategory
*/
class SubresourceBike
{
#[ApiProperty(identifier: true)]
public ?int $id = null;

#[Assert\NotBlank]
public ?string $name = null;

#[Assert\NotNull]
public ?SubresourceCategory $category = null;

public static function process(mixed $data): self
{
$data->id = 1;

return $data;
}
}
48 changes: 48 additions & 0 deletions tests/Fixtures/TestBundle/ApiResource/SubresourceCategory.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;

use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\Get;

#[Get(
uriTemplate: '/subresource_categories/{id}',
provider: [SubresourceCategory::class, 'provideNull']
)]
#[Get(
uriTemplate: '/subresource_categories_with_create_provider/{id}',
provider: [SubresourceCategory::class, 'provide']
)]
/**
* @see SubresourceBike
*/
final class SubresourceCategory
{
public function __construct(
#[ApiProperty(identifier: true)]
public ?int $id = null,
public ?string $name = null
) {
}

public static function provideNull()
{
return null;
}

public static function provide(): self
{
return new self(1, 'Hello World!');
}
}
Loading