Skip to content

Commit 0fb1dc8

Browse files
authored
feat(symfony): api:upgrade-filter codemod + filter fixture migration (#8344)
* feat(symfony): add api:upgrade-filter codemod Add an `api:upgrade-filter` command that rewrites legacy `#[ApiFilter]` attributes into parameter-based filters via a php-parser visitor. Includes the mapper/resolver/visitor pipeline, collision/skip/name-conversion handling, DI wiring, service config and unit tests. CI runs the codemod with --force before the functional suite. * test: migrate #[ApiFilter] fixtures to parameter filters + legacy suite Run api:upgrade-filter over the test fixtures to move them onto parameter filters, and relocate the un-migrated #[ApiFilter] fixtures (Boolean, Numeric, Enum, Order, Search, Date/Range/Exists, ExceptionToStatus) into a Legacy/ suite that keeps covering the deprecated path. * feat(metadata): deprecate #[ApiFilter] and Operation::$filters Emit a deprecation for the #[ApiFilter] attribute and for Operation::$filters consumption, surfaced through the metadata factory and OpenAPI factory with de-duplication. Baseline the resulting deprecations. * fix(state): cast empty/null scalar params, reject bad scalars with 400 ValueCaster now casts empty/null boolean params to false and empty/null numeric params consistently, and rejects empty values cast to a scalar native type with a 400 via BadRequestException (wired through the Laravel error renderer). * Apply suggestions from code review Co-authored-by: Antoine Bluchet <soyuka@users.noreply.github.com>
1 parent 75f9056 commit 0fb1dc8

66 files changed

Lines changed: 3837 additions & 344 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,50 @@ jobs:
122122
- name: Run container lint
123123
run: tests/Fixtures/app/console lint:container
124124

125+
upgrade-filter:
126+
name: Upgrade Filter Codemod
127+
runs-on: ubuntu-latest
128+
timeout-minutes: 20
129+
strategy:
130+
matrix:
131+
php:
132+
- '8.5'
133+
fail-fast: false
134+
steps:
135+
- name: Checkout
136+
uses: actions/checkout@v6
137+
- name: Setup PHP
138+
uses: shivammathur/setup-php@v2
139+
with:
140+
php-version: ${{ matrix.php }}
141+
extensions: intl, bcmath, curl, openssl, mbstring
142+
ini-values: memory_limit=-1
143+
tools: composer
144+
coverage: none
145+
- name: Get composer cache directory
146+
id: composercache
147+
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
148+
- name: Cache dependencies
149+
uses: actions/cache@v5
150+
with:
151+
path: ${{ steps.composercache.outputs.dir }}
152+
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
153+
restore-keys: ${{ runner.os }}-composer-
154+
- name: Update project dependencies
155+
run: |
156+
composer global require soyuka/pmu
157+
composer global config allow-plugins.soyuka/pmu true --no-interaction
158+
composer global link .
159+
- name: Codemod unit tests
160+
run: vendor/bin/phpunit src/Symfony/Tests/Bundle/Command
161+
# The #[ApiFilter] fixtures are already migrated in the tree, so --force only re-skips the
162+
# special cases (name conversion, service/#[ApiFilter] key overlap); it must not error.
163+
- name: Codemod force run
164+
run: tests/Fixtures/app/console api:upgrade-filter --force
165+
# Guard both paths: the migrated QueryParameter fixtures and the Legacy/ #[ApiFilter] fixtures.
166+
- name: Functional suite
167+
run: vendor/bin/phpunit tests/Functional
168+
125169
phpstan:
126170
name: PHPStan (PHP ${{ matrix.php }})
127171
runs-on: ubuntu-latest

phpunit.baseline.xml

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,68 @@
4848
<issue><![CDATA[Since api-platform/core 4.2: Having multiple "#[ApiResource]" attributes with the same "shortName" "OneToOneSubresourceAnswer" on class "ApiPlatform\Tests\Fixtures\TestBundle\Entity\OneToOneSubresourceAnswer" is deprecated and will result in automatic short name deduplication in API Platform 5.x. Set "defaults.extra_properties.deduplicate_resource_short_names" to "true" in the API Platform configuration to enable it now.]]></issue>
4949
</line>
5050
</file>
51+
<file path="src/Metadata/Resource/Factory/FiltersResourceMetadataCollectionFactory.php">
52+
<line number="63" hash="3f3dc5ed5b98671df2409a336d87db4f629303d2">
53+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "AbstractDummy" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
54+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "ArrayFilterValidator" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
55+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "ContainNonResource" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
56+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "ConvertedBoolean" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
57+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "ConvertedDate" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
58+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "ConvertedInteger" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
59+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "ConvertedOwner" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
60+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "ConvertedString" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
61+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "Dummy2" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
62+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "Dummy3" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
63+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "DummyDate" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
64+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "DummyExceptionToStatus" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
65+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "DummyForBackedEnumFilter" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
66+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "DummyGroup" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
67+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "DummyImmutableDate" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
68+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "Dummy" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
69+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "DummyProperty" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
70+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "DummyResource" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
71+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "DummyTravel" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
72+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "DummyWithArrayOfNotResourceObjects" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
73+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "EmbeddedDummy" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
74+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "FilteredAttributeParameter" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
75+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "FilterValidator" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
76+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "JsonApiFilteringProperty" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
77+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "MainResource" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
78+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "RelatedDummy2" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
79+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "RelatedDummy3" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
80+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "RelatedDummy4" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
81+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "RelatedDummy5" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
82+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "RelatedDummy6" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
83+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "RelatedDummy7" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
84+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "RelatedDummy8" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
85+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "RelatedDummy" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
86+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "RelatedToDummyFriend2" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
87+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "RelatedToDummyFriend3" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
88+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "RelatedToDummyFriend4" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
89+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "RelatedToDummyFriend5" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
90+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "RelatedToDummyFriend6" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
91+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "RelatedToDummyFriend" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
92+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "SeparatedDocument" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
93+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on the "SeparatedEntity" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.]]></issue>
94+
</line>
95+
</file>
96+
<file path="src/Symfony/Bundle/DependencyInjection/Compiler/AttributeFilterPass.php">
97+
<line number="60" hash="8001ad8a808628ce0a018b7798041837c9d07aa1">
98+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on "ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\AgentApi" with the "#[ApiFilter]" attribute is deprecated, use the "#[QueryParameter]" attribute instead. The "#[ApiFilter]" attribute will be removed in 6.0.]]></issue>
99+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on "ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\FilterWithStateOptions" with the "#[ApiFilter]" attribute is deprecated, use the "#[QueryParameter]" attribute instead. The "#[ApiFilter]" attribute will be removed in 6.0.]]></issue>
100+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on "ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue5605\MainResource" with the "#[ApiFilter]" attribute is deprecated, use the "#[QueryParameter]" attribute instead. The "#[ApiFilter]" attribute will be removed in 6.0.]]></issue>
101+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on "ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue5648\DummyResource" with the "#[ApiFilter]" attribute is deprecated, use the "#[QueryParameter]" attribute instead. The "#[ApiFilter]" attribute will be removed in 6.0.]]></issue>
102+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on "ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\ResourceWithSeparatedEntity" with the "#[ApiFilter]" attribute is deprecated, use the "#[QueryParameter]" attribute instead. The "#[ApiFilter]" attribute will be removed in 6.0.]]></issue>
103+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on "ApiPlatform\Tests\Fixtures\TestBundle\Entity\ConvertedBoolean" with the "#[ApiFilter]" attribute is deprecated, use the "#[QueryParameter]" attribute instead. The "#[ApiFilter]" attribute will be removed in 6.0.]]></issue>
104+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on "ApiPlatform\Tests\Fixtures\TestBundle\Entity\ConvertedDate" with the "#[ApiFilter]" attribute is deprecated, use the "#[QueryParameter]" attribute instead. The "#[ApiFilter]" attribute will be removed in 6.0.]]></issue>
105+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on "ApiPlatform\Tests\Fixtures\TestBundle\Entity\ConvertedInteger" with the "#[ApiFilter]" attribute is deprecated, use the "#[QueryParameter]" attribute instead. The "#[ApiFilter]" attribute will be removed in 6.0.]]></issue>
106+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on "ApiPlatform\Tests\Fixtures\TestBundle\Entity\ConvertedOwner" with the "#[ApiFilter]" attribute is deprecated, use the "#[QueryParameter]" attribute instead. The "#[ApiFilter]" attribute will be removed in 6.0.]]></issue>
107+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on "ApiPlatform\Tests\Fixtures\TestBundle\Entity\ConvertedString" with the "#[ApiFilter]" attribute is deprecated, use the "#[QueryParameter]" attribute instead. The "#[ApiFilter]" attribute will be removed in 6.0.]]></issue>
108+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on "ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyDate" with the "#[ApiFilter]" attribute is deprecated, use the "#[QueryParameter]" attribute instead. The "#[ApiFilter]" attribute will be removed in 6.0.]]></issue>
109+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on "ApiPlatform\Tests\Fixtures\TestBundle\Entity\Legacy\DummyExceptionToStatus" with the "#[ApiFilter]" attribute is deprecated, use the "#[QueryParameter]" attribute instead. The "#[ApiFilter]" attribute will be removed in 6.0.]]></issue>
110+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on "ApiPlatform\Tests\Fixtures\TestBundle\Entity\Legacy\DummyForBackedEnumFilter" with the "#[ApiFilter]" attribute is deprecated, use the "#[QueryParameter]" attribute instead. The "#[ApiFilter]" attribute will be removed in 6.0.]]></issue>
111+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on "ApiPlatform\Tests\Fixtures\TestBundle\Entity\Legacy\FilteredAttributeParameter" with the "#[ApiFilter]" attribute is deprecated, use the "#[QueryParameter]" attribute instead. The "#[ApiFilter]" attribute will be removed in 6.0.]]></issue>
112+
<issue><![CDATA[Since api-platform/core 4.4: Declaring filters on "ApiPlatform\Tests\Fixtures\TestBundle\Entity\Legacy\SearchFilterParameter" with the "#[ApiFilter]" attribute is deprecated, use the "#[QueryParameter]" attribute instead. The "#[ApiFilter]" attribute will be removed in 6.0.]]></issue>
113+
</line>
114+
</file>
51115
</files>

src/Laravel/Exception/ErrorRenderer.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
use ApiPlatform\Laravel\ApiResource\Error;
1717
use ApiPlatform\Laravel\Controller\ApiPlatformController;
18+
use ApiPlatform\Metadata\Exception\HttpExceptionInterface;
1819
use ApiPlatform\Metadata\Exception\InvalidUriVariableException;
1920
use ApiPlatform\Metadata\Exception\ProblemExceptionInterface;
2021
use ApiPlatform\Metadata\Exception\StatusAwareExceptionInterface;
@@ -186,7 +187,7 @@ private function getStatusCode(?HttpOperation $apiOperation, ?HttpOperation $err
186187
return 403;
187188
}
188189

189-
if ($exception instanceof SymfonyHttpExceptionInterface) {
190+
if ($exception instanceof SymfonyHttpExceptionInterface || $exception instanceof HttpExceptionInterface) {
190191
return $exception->getStatusCode();
191192
}
192193

src/Metadata/ApiFilter.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
* Filter attribute.
2020
*
2121
* @author Antoine Bluchet <soyuka@gmail.com>
22+
*
23+
* @deprecated since API Platform 4.4, use the {@see QueryParameter} attribute instead. Will be removed in 6.0.
2224
*/
2325
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
2426
final class ApiFilter
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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\Metadata\Exception;
15+
16+
/**
17+
* Framework-agnostic 400 Bad Request, mapped by both the Symfony and Laravel error handlers.
18+
*/
19+
class BadRequestException extends \RuntimeException implements ExceptionInterface, HttpExceptionInterface
20+
{
21+
public function getStatusCode(): int
22+
{
23+
return 400;
24+
}
25+
26+
/**
27+
* @return array<string, string>
28+
*/
29+
public function getHeaders(): array
30+
{
31+
return [];
32+
}
33+
}

src/Metadata/Resource/Factory/FiltersResourceMetadataCollectionFactory.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,12 @@ public function create(string $resourceClass): ResourceMetadataCollection
5858

5959
foreach ($resourceMetadataCollection as $i => $resource) {
6060
foreach ($operations = $resource->getOperations() ?? [] as $operationName => $operation) {
61-
$operations->add($operationName, $operation->withFilters(array_unique(array_merge($resource->getFilters() ?? [], $operation->getFilters() ?? [], $filters))));
61+
$operationFilters = array_unique(array_merge($resource->getFilters() ?? [], $operation->getFilters() ?? [], $filters));
62+
if ($operationFilters) {
63+
trigger_deprecation('api-platform/core', '4.4', \sprintf('Declaring filters on the "%s" operation through "Operation::$filters" is deprecated, use the "parameters" argument instead. It will be removed in 6.0.', $operation->getShortName()));
64+
}
65+
66+
$operations->add($operationName, $operation->withFilters($operationFilters));
6267
}
6368

6469
if ($operations) {

src/Serializer/Filter/PropertyFilter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ public function getSchema(MetadataParameter $parameter): array
280280
public function getOpenApiParameters(MetadataParameter $parameter): Parameter
281281
{
282282
$example = \sprintf(
283-
'%1$s[]={propertyName}&%1$s[]={anotherPropertyName}',
283+
'%1$s[]={propertyName}&%1$s[]={anotherPropertyName}&%1$s[{nestedPropertyParent}][]={nestedProperty}',
284284
$parameter->getKey()
285285
);
286286

0 commit comments

Comments
 (0)