Skip to content

Commit e2ca331

Browse files
committed
fix(laravel): property filter and _format
1 parent fcd8ef5 commit e2ca331

File tree

8 files changed

+49
-7
lines changed

8 files changed

+49
-7
lines changed

src/Laravel/ApiPlatformMiddleware.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
namespace ApiPlatform\Laravel;
1515

16+
use ApiPlatform\Metadata\HttpOperation;
1617
use ApiPlatform\Metadata\Operation\Factory\OperationMetadataFactory;
1718
use Illuminate\Http\Request;
1819
use Symfony\Component\HttpFoundation\Response;
@@ -30,10 +31,19 @@ public function __construct(
3031
public function handle(Request $request, \Closure $next, ?string $operationName = null): Response
3132
{
3233
if ($operationName) {
33-
$request->attributes->set('_api_operation', $this->operationMetadataFactory->create($operationName));
34+
$request->attributes->set('_api_operation', $operation = $this->operationMetadataFactory->create($operationName));
3435
}
3536

36-
$request->attributes->set('_format', str_replace('.', '', $request->route('_format') ?? ''));
37+
if (!($format = $request->route('_format')) && $operation instanceof HttpOperation && str_ends_with($operation->getUriTemplate(), '{._format}')) {
38+
$matches = [];
39+
if (preg_match('/\.[a-zA-Z]+$/', $request->getPathInfo(), $matches)) {
40+
$format = $matches[0];
41+
}
42+
}
43+
44+
if ($format) {
45+
$request->attributes->set('_format', substr($format, 1, \strlen($format) - 1));
46+
}
3747

3848
return $next($request);
3949
}

src/Laravel/ApiPlatformProvider.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,7 @@ public function register(): void
443443

444444
return new SerializerFilterParameterProvider(new ServiceLocator($tagged));
445445
});
446+
$this->app->alias(SerializerFilterParameterProvider::class, 'api_platform.serializer.filter_parameter_provider');
446447

447448
$this->app->tag([SerializerFilterParameterProvider::class], ParameterProviderInterface::class);
448449

@@ -455,6 +456,7 @@ public function register(): void
455456

456457
$this->app->singleton(ParameterProvider::class, function (Application $app) {
457458
$tagged = iterator_to_array($app->tagged(ParameterProviderInterface::class));
459+
$tagged['api_platform.serializer.filter_parameter_provider'] = $app->make(SerializerFilterParameterProvider::class);
458460

459461
return new ParameterProvider(
460462
new ParameterValidatorProvider(

src/Laravel/Controller/ApiPlatformController.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,12 @@ private function getUriVariables(Request $request, HttpOperation $operation): ar
104104
{
105105
$uriVariables = [];
106106
foreach ($operation->getUriVariables() ?? [] as $parameterName => $_) {
107-
$uriVariables[(string) $parameterName] = $request->route($parameterName);
107+
$parameter = $request->route($parameterName);
108+
if (\is_string($parameter) && ($format = $request->attributes->get('_format')) && str_contains($parameter, $format)) {
109+
$parameter = substr($parameter, 0, \strlen($parameter) - (\strlen($format) + 1));
110+
}
111+
112+
$uriVariables[(string) $parameterName] = $parameter;
108113
}
109114

110115
return $uriVariables;

src/Laravel/Eloquent/Serializer/SerializerContextBuilder.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,10 @@ public function createFromRequest(Request $request, bool $normalization, ?array
3737
return $context;
3838
}
3939

40-
// isWritable/isReadable is checked later on
41-
$context[AbstractNormalizer::ATTRIBUTES] = iterator_to_array($this->propertyNameCollectionFactory->create($context['resource_class'], ['serializer_groups' => $context['groups'] ?? null]));
40+
if (!isset($context[AbstractNormalizer::ATTRIBUTES])) {
41+
// isWritable/isReadable is checked later on
42+
$context[AbstractNormalizer::ATTRIBUTES] = iterator_to_array($this->propertyNameCollectionFactory->create($context['resource_class'], ['serializer_groups' => $context['groups'] ?? null]));
43+
}
4244

4345
return $context;
4446
}

src/Laravel/Tests/EloquentTest.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,19 @@ public function testSearchFilter(): void
3333
$this->assertSame($response->json()['member'][0], $book);
3434
}
3535

36+
public function testPropertyFilter(): void
37+
{
38+
$response = $this->get('/api/books', ['accept' => ['application/ld+json']]);
39+
$book = $response->json()['member'][0];
40+
41+
$response = $this->get(\sprintf('%s.jsonld?properties[]=author', $book['@id']));
42+
$book = $response->json();
43+
44+
$this->assertArrayHasKey('@id', $book);
45+
$this->assertArrayHasKey('author', $book);
46+
$this->assertArrayNotHasKey('name', $book);
47+
}
48+
3649
public function testPartialSearchFilter(): void
3750
{
3851
$response = $this->get('/api/books', ['accept' => ['application/ld+json']]);

src/Laravel/workbench/app/Models/Book.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
use ApiPlatform\Metadata\Post;
2626
use ApiPlatform\Metadata\Put;
2727
use ApiPlatform\Metadata\QueryParameter;
28+
use ApiPlatform\Serializer\Filter\PropertyFilter;
2829
use Illuminate\Database\Eloquent\Concerns\HasUlids;
2930
use Illuminate\Database\Eloquent\Factories\HasFactory;
3031
use Illuminate\Database\Eloquent\Model;
@@ -52,6 +53,7 @@
5253
filter: new OrFilter(new EqualsFilter()),
5354
property: 'name'
5455
)]
56+
#[QueryParameter(key: 'properties', filter: PropertyFilter::class)]
5557
class Book extends Model
5658
{
5759
use HasFactory;

src/Serializer/Filter/PropertyFilter.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313

1414
namespace ApiPlatform\Serializer\Filter;
1515

16+
use ApiPlatform\Metadata\HasOpenApiParameterFilterInterface;
17+
use ApiPlatform\Metadata\Parameter as MetadataParameter;
18+
use ApiPlatform\Metadata\QueryParameter;
1619
use ApiPlatform\OpenApi\Model\Parameter;
1720
use Symfony\Component\HttpFoundation\Request;
1821
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
@@ -114,7 +117,7 @@
114117
*
115118
* @author Baptiste Meyer <[email protected]>
116119
*/
117-
final class PropertyFilter implements FilterInterface
120+
final class PropertyFilter implements FilterInterface, HasOpenApiParameterFilterInterface
118121
{
119122
private ?array $whitelist;
120123

@@ -246,4 +249,9 @@ private function denormalizePropertyName($property): string
246249
{
247250
return null !== $this->nameConverter ? $this->nameConverter->denormalize($property) : $property;
248251
}
252+
253+
public function getOpenApiParameter(MetadataParameter $parameter): Parameter
254+
{
255+
return new Parameter(name: $parameter->getKey().'[]', in: $parameter instanceof QueryParameter ? 'query' : 'header');
256+
}
249257
}

src/Serializer/Parameter/SerializerFilterParameterProvider.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public function provide(Parameter $parameter, array $parameters = [], array $con
4545
return null;
4646
}
4747

48-
$context = $operation->getNormalizationContext();
48+
$context = $operation->getNormalizationContext() ?? [];
4949
$request->attributes->set('_api_parameter', $parameter);
5050
$filter->apply($request, true, RequestAttributesExtractor::extractAttributes($request), $context);
5151

0 commit comments

Comments
 (0)