RFC: port JSON:API parameter providers to Symfony, deprecate translation listeners
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
- 難易度
- 5/5
- 見積もり時間
- 1週間以上
- 初心者へのやさしさ
- 35/100
調査の方向性
src/JsonApi/State/JsonApiProvider.php、SparseFieldsetParameterProvider.php、src/State/Provider/ParameterProvider.php から始めて、現在の mutation path と Parameter pipeline を比較します。記載されている provider と metadata factory を実装し、Symfony と Laravel の登録を接続し、_api_filters とユーザーパラメーターの優先順位を維持しながら、3 つの Symfony listener を非推奨にします。
索引モデルが issue の本文から書いたものです。
説明
RFC: port JSON:API parameter providers to Symfony, deprecate translation listeners
Summary
Symfony still translates JSON:API sort=, filter[...], page[...] through an
inline mutation block in ApiPlatform\JsonApi\State\JsonApiProvider plus three
legacy listeners. This is the only remaining JSON:API surface that has not been
moved to the QueryParameter + ParameterProvider architecture used elsewhere.
The mutation-based approach is brittle (see #7888, fixed by #8193 on 4.3),
ships partial state into _api_filters, and duplicates logic that is already
solved in Laravel for the same protocol.
This RFC proposes porting the missing pieces to Symfony, auto-attaching them
as default Parameters on JSON:API operations through a metadata factory, and
deprecating the three legacy listeners.
Target branch: main (4.4 / 5.0). Listener removal: 5.0. Final cleanup
(filters: [...] shorthand on ApiResource): 6.0 (already planned).
Current state
Symfony
-
src/JsonApi/State/JsonApiProvider.php- Reads
$request->querydirectly - Translates
sort=a,-btoorderfilter - Merges
filter[...]into_api_filters - Merges
page[...]into_api_filters(bracket form only — flat
page=2anditemsPerPage=Nwere silently dropped, fixed in #8193) - Computes sparse fieldset (
fields[type]=a,b) andinclude - Sets
_api_filter_property,_api_included,_api_filtersrequest attrs
- Reads
-
src/Symfony/EventListener/JsonApi/TransformSortingParametersListener.php -
src/Symfony/EventListener/JsonApi/TransformFilteringParametersListener.php -
src/Symfony/EventListener/JsonApi/TransformPaginationParametersListener.php- Same translation logic, used on the legacy listener-based controller path
(use_symfony_listeners: true) - Both paths exist because
main_controller(default) uses providers and the
legacy controller path uses listeners
- Same translation logic, used on the legacy listener-based controller path
-
src/JsonApi/Filter/SparseFieldset.php+SparseFieldsetParameterProvider.php- The only JSON:API piece already on the new architecture
- Implements
ParameterProviderFilterInterfaceand writes to the operation's
normalizationContext[ATTRIBUTES] - Proves the pattern is viable
Laravel
-
src/Laravel/JsonApi/State/JsonApiProvider.php- Stripped down: only handles
include - Docstring explicitly states: "This is a copy of
ApiPlatform\JsonApi\State\JsonApiProviderwithout the support of sort,
filter and fields as these should be implemented using QueryParameters and
specific Filters."
- Stripped down: only handles
-
src/Laravel/Eloquent/Filter/JsonApi/SortFilter.php -
src/Laravel/Eloquent/Filter/JsonApi/SortFilterParameterProvider.php- Provider parses
sort=a,-binto['a' => 'asc', 'b' => 'desc'] - Filter applies the array to an Eloquent
Builder
- Provider parses
What is not there on either side
- An automatic way to declare these parameters on every JSON:API resource. On
Laravel users still hand-write#[QueryParameter(...)]on each model (see
src/Laravel/workbench/app/Models/Book.php:60-84for the pattern with the
Eloquent filters). - No
FilterParameterProvider(filter syntax) on either side. - No
PageParameterProvideron either side. - Symfony has no JSON:API equivalent of
SortFilterParameterProvider.
Problem (concrete)
Bug #7888: ?filter[custom]=true&page=2 returns page 1.
Sequence:
JsonApiProviderreads$request->query- Sees
filteras an array → merges into_api_filters - Sees
pageas a string →is_array()check fails → drops it - Sets
_api_filters = ['custom' => 'true'] ReadProvidersees_api_filtersnon-null → uses as-is (skips full
RequestParser::parseRequestParams)Pagination::getPage()looks forpagein filters → not found → returns
default1- Hydra link generation advertises the user-requested page in URLs, so the
client receivesmeta.currentPage = 1butlinks.nextreferences
?page=3. Pagination de-syncs from links.
Class of bug: a single writer of _api_filters setting partial state silently
suppresses the RequestParser fallback that would otherwise produce correct
values.
Patch shipped on 4.3 papers over this by passing flat scalars through. Real
fix is to stop mutating _api_filters from an inline provide() block and
move translation into discrete, testable, declared ParameterProviders
attached per operation.
_api_filters itself stays — request attributes are the correct cross-layer
propagation channel.
Proposed design (Symfony)
New classes
All under src/JsonApi/State/ (storage-agnostic, no Doctrine dependency):
-
SortParameterProvider- Input:
Parameterwith value fromsort=a,-b,c - Output: writes structured
[$field => 'asc'|'desc']array into
_api_filters[order_parameter_name](or into the parameter value for
downstream filter consumption)
- Input:
-
FilterParameterProvider- Input:
Parameterwith value fromfilter[name]=foo&filter[age]=42 - Output: writes the array into
_api_filtersunder each property key
- Input:
-
PageParameterProvider- Input:
Parameterwith value from any of:
page[number],page[size],page[offset], flatpage, flat
itemsPerPage, flatpagination, flatpartial - Output: writes canonical
page,itemsPerPage,pagination,partial
keys into_api_filters - Subsumes the 4.3 patch's scalar passthrough
- Input:
These mirror the role of SparseFieldsetParameterProvider and live next to
it. They write to _api_filters exactly like the current translation block,
so the rest of the read pipeline (Pagination, Doctrine filters,
PropertyFilter, GroupFilter) is unchanged.
Auto-attach metadata factory
src/JsonApi/Metadata/Resource/Factory/JsonApiParameterResourceMetadataCollectionFactory.php
Decorates ResourceMetadataCollectionFactoryInterface. For each operation:
- If
$operation->getFormats()containsjsonapi, or no format restriction
is set andjsonapiis globally enabled, attach as defaults:new QueryParameter(key: 'sort', provider: SortParameterProvider::class)new QueryParameter(key: 'filter', provider: FilterParameterProvider::class)new QueryParameter(key: 'page', provider: PageParameterProvider::class)new QueryParameter(key: 'fields', provider: SparseFieldsetParameterProvider::class)
(replaces inlinetransformFieldsetsParameters())new QueryParameter(key: 'include', provider: IncludeParameterProvider::class)
(replaces remaining inline block — last thing to leaveJsonApiProvider)
- User-declared parameters on the same key win (no override)
Wire in src/Symfony/Bundle/Resources/config/metadata.php and equivalent for
Laravel.
JsonApiProvider becomes a no-op
After the factory does its job, the Symfony JsonApiProvider::provide()
contains nothing — the file can be removed in 5.0 (or kept as a deprecation
shim).
Listener deprecation
TransformSortingParametersListenerTransformFilteringParametersListenerTransformPaginationParametersListener
Marked @deprecated in 5.0, removed in 6.0. The parameter providers run
under both controller paths (main controller and the legacy controller) once
they are wired as Parameters, because the Parameter pipeline runs in
ParameterProvider (in src/State/Provider/ParameterProvider.php), which is
called by both paths.
Migration note: users who relied on these listeners being firing-points for
custom subscribers need to subscribe to KernelEvents::REQUEST directly or
move their logic into a ParameterProvider.
Laravel gaps
Laravel already has the architecture but is missing:
-
FilterParameterProvider— there is no equivalent offilter[name]=foo
parsing. Users must declare each filter as a discreteQueryParameter
(#[QueryParameter(key: 'name', filter: PartialSearchFilter::class)]).
That works but is not JSON:API spec compliant on the client side, which
sends?filter[name]=foo, not?name=foo. -
PageParameterProvider— same story; Laravel relies on default
page/itemsPerPagehandling. Bracket-formpage[number]is silently
ignored. -
Auto-attach metadata factory equivalent. Today every Laravel model
declares its filters manually; even the JSON:API-specificSortFilteris
only present when the user writes
new QueryParameter(filter: SortFilter::class).
Recommendation: add the same JsonApiParameterResourceMetadataCollectionFactory
in src/Laravel/JsonApi/ and the same three providers under
src/Laravel/JsonApi/State/. They can share interfaces with Symfony — only
the apply-step (Doctrine vs Eloquent) differs, and the apply-step is the
filter classes, not the parameter providers. The providers themselves should
be 100% shared in src/JsonApi/State/ if possible (no storage dependency).
filters: [...] removal in 6.0
Already planned. Once parameter providers are the way, the legacy
#[ApiResource(filters: ['my.filter.service.id'])] shorthand is fully
redundant — every legacy filter has a QueryParameter equivalent. The
FiltersResourceMetadataCollectionFactory
(src/Metadata/Resource/Factory/FiltersResourceMetadataCollectionFactory.php)
that resolves service ids can be deleted with the array shorthand.
This RFC is a prerequisite: as long as JSON:API translation lives outside
the Parameter system, the filters: [...] array still has a role.
File-level plan
+ src/JsonApi/State/SortParameterProvider.php
+ src/JsonApi/State/FilterParameterProvider.php
+ src/JsonApi/State/PageParameterProvider.php
+ src/JsonApi/State/IncludeParameterProvider.php
+ src/JsonApi/Metadata/Resource/Factory/JsonApiParameterResourceMetadataCollectionFactory.php
~ src/JsonApi/State/JsonApiProvider.php # shrink, then deprecate
~ src/Symfony/EventListener/JsonApi/TransformSortingParametersListener.php # @deprecated
~ src/Symfony/EventListener/JsonApi/TransformFilteringParametersListener.php # @deprecated
~ src/Symfony/EventListener/JsonApi/TransformPaginationParametersListener.php # @deprecated
~ src/Symfony/Bundle/Resources/config/state/jsonapi.php # wire new factory
+ src/Laravel/JsonApi/State/FilterParameterProvider.php # parity
+ src/Laravel/JsonApi/State/PageParameterProvider.php # parity
~ src/Laravel/JsonApi/State/JsonApiProvider.php # may become no-op
~ src/Laravel/ApiPlatformDeferredProvider.php # register new providers + factory
BC considerations
_api_filterskeeps its current shape. No change for downstream consumers
(PropertyFilter,GroupFilter, Doctrine extensions).- User-declared
QueryParameters on the same keys (sort,filter,page,
fields,include) take precedence over auto-attached defaults. This is
the same contract the existingParameterResourceMetadataCollectionFactory
expects. - Listeners stay functional in 5.0 with a
trigger_deprecation()notice.
Out of scope
- Removing
_api_filters. Decided not to. Request attributes are the correct
propagation channel; the bug was about who writes them, not whether they
exist. - Rewriting Doctrine ORM filters for JSON:API. Existing filters
(OrderFilter,SearchFilter, etc.) already accept the array shape the
new providers will produce. - GraphQL parity. JSON:API is a REST-only concern.
References
- Bug fix on 4.3: #8193 (resolves #7888)
- Existing pattern:
src/JsonApi/Filter/SparseFieldset.php - Laravel reference:
src/Laravel/Eloquent/Filter/JsonApi/SortFilter.php+
SortFilterParameterProvider.php
- 主要言語
- PHP
- スター
- 2.6k
- フォーク
- 989
- 平均マージ
- 1日 19時間
- マージ済み PR(30日)
- 87
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートあり
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
api-platform/core のほかの issue
-
難易度 1/5 1時間未満 初心者へのやさしさ 85/100
api-platform/core#8680 ·
メンテナーはふだん 1 日以内に返信
-
難易度 4/5 3〜5日 初心者へのやさしさ 54/100
api-platform/core#8664 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
[Metadata] Incorrect api schema for path variable対応中かも @bram123 が 2 日前に担当しました。 オープン
難易度 3/5 1〜2日 初心者へのやさしさ 68/100
api-platform/core#8646 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
DenormalizationViolationFactory crash when GroupSequence is used対応中かも このイシューにリンクされたプルリクエストがオープン中、またはマージ済みです。 オープン
難易度 3/5 1〜2日 初心者へのやさしさ 64/100
api-platform/core#8639 ·
メンテナーはふだん 1 日以内に返信
-
HTTP cache purgers error handling and performance issues対応中かも このイシューにリンクされたプルリクエストがオープン中、またはマージ済みです。 オープン
難易度 4/5 3〜5日 初心者へのやさしさ 38/100
api-platform/core#8591 ·
メンテナーはふだん 1 日以内に返信
api-platform/core の issue をすべて見る
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 62/100
woocommerce/woocommerce-google-analytics-integration#699 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 60/100
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
ProfessionalWiki/NeoWiki#1675 ·
メンテナーはふだん 1 日以内に返信
-
難易度 1/5 1時間未満 初心者へのやさしさ 88/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100