Skip to content

Commit

Permalink
refactor: use provider/processor instead of event listeners (#5657)
Browse files Browse the repository at this point in the history
* refactor: use provider/processor instead of event listeners

* tests
  • Loading branch information
soyuka authored Sep 2, 2023
1 parent 819df1c commit 04b846b
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 14 deletions.
36 changes: 32 additions & 4 deletions SerializerContextBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@

namespace ApiPlatform\Serializer;

use ApiPlatform\Doctrine\Orm\State\Options;
use ApiPlatform\Metadata\CollectionOperationInterface;
use ApiPlatform\Metadata\Error as ErrorOperation;
use ApiPlatform\Metadata\Exception\RuntimeException;
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
use ApiPlatform\Symfony\Util\RequestAttributesExtractor;
Expand All @@ -29,7 +31,7 @@
*/
final class SerializerContextBuilder implements SerializerContextBuilderInterface
{
public function __construct(private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory)
public function __construct(private readonly ?ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory = null, private readonly bool $debug = false)
{
}

Expand All @@ -42,7 +44,14 @@ public function createFromRequest(Request $request, bool $normalization, array $
throw new RuntimeException('Request attributes are not valid.');
}

$operation = $attributes['operation'] ?? $this->resourceMetadataFactory->create($attributes['resource_class'])->getOperation($attributes['operation_name']);
if (!($operation = $attributes['operation'] ?? null)) {
if (!$this->resourceMetadataFactory) {
throw new RuntimeException('No operation');
}

$operation = $this->resourceMetadataFactory->create($attributes['resource_class'])->getOperation($attributes['operation_name'] ?? null);
}

$context = $normalization ? ($operation->getNormalizationContext() ?? []) : ($operation->getDenormalizationContext() ?? []);
$context['operation_name'] = $operation->getName();
$context['operation'] = $operation;
Expand All @@ -53,16 +62,18 @@ public function createFromRequest(Request $request, bool $normalization, array $
$context['uri'] = $request->getUri();
$context['input'] = $operation->getInput();
$context['output'] = $operation->getOutput();
$context['skip_deprecated_exception_normalizers'] = true;

// Special case as this is usually handled by our OperationContextTrait, here we want to force the IRI in the response
if (!$operation instanceof CollectionOperationInterface && method_exists($operation, 'getItemUriTemplate') && $operation->getItemUriTemplate()) {
$context['item_uri_template'] = $operation->getItemUriTemplate();
}

if ($operation->getTypes()) {
$context['types'] = $operation->getTypes();
if ($types = $operation->getTypes()) {
$context['types'] = $types;
}

// TODO: remove this as uri variables are available in the SerializerProcessor but correctly parsed
if ($operation->getUriVariables()) {
$context['uri_variables'] = [];

Expand All @@ -71,6 +82,18 @@ public function createFromRequest(Request $request, bool $normalization, array $
}
}

if (($options = $operation?->getStateOptions()) && $options instanceof Options && $options->getEntityClass()) {
$context['force_resource_class'] = $operation->getClass();
}

if ($this->debug && isset($context['groups']) && $operation instanceof ErrorOperation) {
if (!\is_array($context['groups'])) {
$context['groups'] = (array) $context['groups'];
}

$context['groups'][] = 'trace';
}

if (!$normalization) {
if (!isset($context['api_allow_update'])) {
$context['api_allow_update'] = \in_array($method = $request->getMethod(), ['PUT', 'PATCH'], true);
Expand All @@ -92,6 +115,11 @@ public function createFromRequest(Request $request, bool $normalization, array $
$context[AbstractObjectNormalizer::EXCLUDE_FROM_CACHE_KEY][] = 'root_operation';
$context[AbstractObjectNormalizer::EXCLUDE_FROM_CACHE_KEY][] = 'operation';

// JSON API see JsonApiProvider
if ($included = $request->attributes->get('_api_included')) {
$context['api_included'] = $included;
}

return $context;
}
}
6 changes: 5 additions & 1 deletion SerializerFilterContextBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@ public function createFromRequest(Request $request, bool $normalization, array $

$context = $this->decorated->createFromRequest($request, $normalization, $attributes);

$resourceFilters = $this->resourceMetadataCollectionFactory->create($attributes['resource_class'])->getOperation($attributes['operation_name'] ?? null)->getFilters();
if (!($operation = $context['operation'] ?? null)) {
$operation = $this->resourceMetadataCollectionFactory->create($attributes['resource_class'])->getOperation($attributes['operation_name'] ?? null);
}

$resourceFilters = $operation->getFilters();

if (!$resourceFilters) {
return $context;
Expand Down
18 changes: 9 additions & 9 deletions Tests/SerializerContextBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,42 +67,42 @@ public function testCreateFromRequest(): void
{
$request = Request::create('/foos/1');
$request->attributes->replace(['_api_resource_class' => 'Foo', '_api_operation_name' => 'get', '_api_format' => 'xml', '_api_mime_type' => 'text/xml']);
$expected = ['foo' => 'bar', 'operation_name' => 'get', 'resource_class' => 'Foo', 'request_uri' => '/foos/1', 'uri' => 'http://localhost/foos/1', 'output' => null, 'input' => null, 'iri_only' => false, 'skip_null_values' => true, 'operation' => $this->operation, 'exclude_from_cache_key' => ['root_operation', 'operation']];
$expected = ['foo' => 'bar', 'operation_name' => 'get', 'resource_class' => 'Foo', 'request_uri' => '/foos/1', 'uri' => 'http://localhost/foos/1', 'output' => null, 'input' => null, 'iri_only' => false, 'skip_null_values' => true, 'operation' => $this->operation, 'exclude_from_cache_key' => ['root_operation', 'operation'], 'skip_deprecated_exception_normalizers' => true];
$this->assertEquals($expected, $this->builder->createFromRequest($request, true));

$request = Request::create('/foos');
$request->attributes->replace(['_api_resource_class' => 'Foo', '_api_operation_name' => 'get_collection', '_api_format' => 'xml', '_api_mime_type' => 'text/xml']);
$expected = ['foo' => 'bar', 'operation_name' => 'get_collection', 'resource_class' => 'Foo', 'request_uri' => '/foos', 'uri' => 'http://localhost/foos', 'output' => null, 'input' => null, 'iri_only' => false, 'skip_null_values' => true, 'operation' => $this->operation->withName('get_collection'), 'exclude_from_cache_key' => ['root_operation', 'operation']];
$expected = ['foo' => 'bar', 'operation_name' => 'get_collection', 'resource_class' => 'Foo', 'request_uri' => '/foos', 'uri' => 'http://localhost/foos', 'output' => null, 'input' => null, 'iri_only' => false, 'skip_null_values' => true, 'operation' => $this->operation->withName('get_collection'), 'exclude_from_cache_key' => ['root_operation', 'operation'], 'skip_deprecated_exception_normalizers' => true];
$this->assertEquals($expected, $this->builder->createFromRequest($request, true));

$request = Request::create('/foos/1');
$request->attributes->replace(['_api_resource_class' => 'Foo', '_api_operation_name' => 'get', '_api_format' => 'xml', '_api_mime_type' => 'text/xml']);
$expected = ['bar' => 'baz', 'operation_name' => 'get', 'resource_class' => 'Foo', 'request_uri' => '/foos/1', 'api_allow_update' => false, 'uri' => 'http://localhost/foos/1', 'output' => null, 'input' => null, 'iri_only' => false, 'skip_null_values' => true, 'operation' => $this->operation, 'exclude_from_cache_key' => ['root_operation', 'operation']];
$expected = ['bar' => 'baz', 'operation_name' => 'get', 'resource_class' => 'Foo', 'request_uri' => '/foos/1', 'api_allow_update' => false, 'uri' => 'http://localhost/foos/1', 'output' => null, 'input' => null, 'iri_only' => false, 'skip_null_values' => true, 'operation' => $this->operation, 'exclude_from_cache_key' => ['root_operation', 'operation'], 'skip_deprecated_exception_normalizers' => true];
$this->assertEquals($expected, $this->builder->createFromRequest($request, false));

$request = Request::create('/foos', 'POST');
$request->attributes->replace(['_api_resource_class' => 'Foo', '_api_operation_name' => 'post', '_api_format' => 'xml', '_api_mime_type' => 'text/xml']);
$expected = ['bar' => 'baz', 'operation_name' => 'post', 'resource_class' => 'Foo', 'request_uri' => '/foos', 'api_allow_update' => false, 'uri' => 'http://localhost/foos', 'output' => null, 'input' => null, 'iri_only' => false, 'skip_null_values' => true, 'operation' => $this->operation->withName('post'), 'exclude_from_cache_key' => ['root_operation', 'operation']];
$expected = ['bar' => 'baz', 'operation_name' => 'post', 'resource_class' => 'Foo', 'request_uri' => '/foos', 'api_allow_update' => false, 'uri' => 'http://localhost/foos', 'output' => null, 'input' => null, 'iri_only' => false, 'skip_null_values' => true, 'operation' => $this->operation->withName('post'), 'exclude_from_cache_key' => ['root_operation', 'operation'], 'skip_deprecated_exception_normalizers' => true];
$this->assertEquals($expected, $this->builder->createFromRequest($request, false));

$request = Request::create('/foos', 'PUT');
$request->attributes->replace(['_api_resource_class' => 'Foo', '_api_operation_name' => 'put', '_api_format' => 'xml', '_api_mime_type' => 'text/xml']);
$expected = ['bar' => 'baz', 'operation_name' => 'put', 'resource_class' => 'Foo', 'request_uri' => '/foos', 'api_allow_update' => true, 'uri' => 'http://localhost/foos', 'output' => null, 'input' => null, 'iri_only' => false, 'skip_null_values' => true, 'operation' => (new Put(name: 'put'))->withOperation($this->operation), 'exclude_from_cache_key' => ['root_operation', 'operation']];
$expected = ['bar' => 'baz', 'operation_name' => 'put', 'resource_class' => 'Foo', 'request_uri' => '/foos', 'api_allow_update' => true, 'uri' => 'http://localhost/foos', 'output' => null, 'input' => null, 'iri_only' => false, 'skip_null_values' => true, 'operation' => (new Put(name: 'put'))->withOperation($this->operation), 'exclude_from_cache_key' => ['root_operation', 'operation'], 'skip_deprecated_exception_normalizers' => true];
$this->assertEquals($expected, $this->builder->createFromRequest($request, false));

$request = Request::create('/bars/1/foos');
$request->attributes->replace(['_api_resource_class' => 'Foo', '_api_operation_name' => 'get', '_api_format' => 'xml', '_api_mime_type' => 'text/xml']);
$expected = ['bar' => 'baz', 'operation_name' => 'get', 'resource_class' => 'Foo', 'request_uri' => '/bars/1/foos', 'api_allow_update' => false, 'uri' => 'http://localhost/bars/1/foos', 'output' => null, 'input' => null, 'iri_only' => false, 'skip_null_values' => true, 'operation' => $this->operation, 'exclude_from_cache_key' => ['root_operation', 'operation']];
$expected = ['bar' => 'baz', 'operation_name' => 'get', 'resource_class' => 'Foo', 'request_uri' => '/bars/1/foos', 'api_allow_update' => false, 'uri' => 'http://localhost/bars/1/foos', 'output' => null, 'input' => null, 'iri_only' => false, 'skip_null_values' => true, 'operation' => $this->operation, 'exclude_from_cache_key' => ['root_operation', 'operation'], 'skip_deprecated_exception_normalizers' => true];
$this->assertEquals($expected, $this->builder->createFromRequest($request, false));

$request = Request::create('/foowithpatch/1', 'PATCH');
$request->attributes->replace(['_api_resource_class' => 'FooWithPatch', '_api_operation_name' => 'patch', '_api_format' => 'json', '_api_mime_type' => 'application/json']);
$expected = ['operation_name' => 'patch', 'resource_class' => 'FooWithPatch', 'request_uri' => '/foowithpatch/1', 'api_allow_update' => true, 'uri' => 'http://localhost/foowithpatch/1', 'output' => null, 'input' => null, 'deep_object_to_populate' => true, 'skip_null_values' => true, 'iri_only' => false, 'operation' => $this->patchOperation, 'exclude_from_cache_key' => ['root_operation', 'operation']];
$expected = ['operation_name' => 'patch', 'resource_class' => 'FooWithPatch', 'request_uri' => '/foowithpatch/1', 'api_allow_update' => true, 'uri' => 'http://localhost/foowithpatch/1', 'output' => null, 'input' => null, 'deep_object_to_populate' => true, 'skip_null_values' => true, 'iri_only' => false, 'operation' => $this->patchOperation, 'exclude_from_cache_key' => ['root_operation', 'operation'], 'skip_deprecated_exception_normalizers' => true];
$this->assertEquals($expected, $this->builder->createFromRequest($request, false));

$request = Request::create('/bars/1/foos');
$request->attributes->replace(['_api_resource_class' => 'Foo', '_api_operation_name' => 'get', '_api_format' => 'xml', '_api_mime_type' => 'text/xml', 'id' => '1']);
$expected = ['bar' => 'baz', 'operation_name' => 'get', 'resource_class' => 'Foo', 'request_uri' => '/bars/1/foos', 'api_allow_update' => false, 'uri' => 'http://localhost/bars/1/foos', 'output' => null, 'input' => null, 'iri_only' => false, 'operation' => $this->operation, 'skip_null_values' => true, 'exclude_from_cache_key' => ['root_operation', 'operation']];
$expected = ['bar' => 'baz', 'operation_name' => 'get', 'resource_class' => 'Foo', 'request_uri' => '/bars/1/foos', 'api_allow_update' => false, 'uri' => 'http://localhost/bars/1/foos', 'output' => null, 'input' => null, 'iri_only' => false, 'operation' => $this->operation, 'skip_null_values' => true, 'exclude_from_cache_key' => ['root_operation', 'operation'], 'skip_deprecated_exception_normalizers' => true];
$this->assertEquals($expected, $this->builder->createFromRequest($request, false));
}

Expand All @@ -115,7 +115,7 @@ public function testThrowExceptionOnInvalidRequest(): void

public function testReuseExistingAttributes(): void
{
$expected = ['bar' => 'baz', 'operation_name' => 'get', 'resource_class' => 'Foo', 'request_uri' => '/foos/1', 'api_allow_update' => false, 'uri' => 'http://localhost/foos/1', 'output' => null, 'input' => null, 'iri_only' => false, 'skip_null_values' => true, 'operation' => $this->operation, 'exclude_from_cache_key' => ['root_operation', 'operation']];
$expected = ['bar' => 'baz', 'operation_name' => 'get', 'resource_class' => 'Foo', 'request_uri' => '/foos/1', 'api_allow_update' => false, 'uri' => 'http://localhost/foos/1', 'output' => null, 'input' => null, 'iri_only' => false, 'skip_null_values' => true, 'operation' => $this->operation, 'exclude_from_cache_key' => ['root_operation', 'operation'], 'skip_deprecated_exception_normalizers' => true];
$this->assertEquals($expected, $this->builder->createFromRequest(Request::create('/foos/1'), false, ['resource_class' => 'Foo', 'operation_name' => 'get']));
}

Expand Down

0 comments on commit 04b846b

Please sign in to comment.