RFC: port JSON:API parameter providers to Symfony, deprecate translation listeners
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 5/5
- Tempo stimato
- Più di una settimana
- Idoneità per principianti
- 35/100
- Tipo di issue
- Funzionalità
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Tranquilla
- Stack tecnologico
- laravel, php, symfony
- Ambito
- api, backend, backend-api-design
Direzione di ricerca
Inizia da src/JsonApi/State/JsonApiProvider.php, SparseFieldsetParameterProvider.php e src/State/Provider/ParameterProvider.php per confrontare il percorso di mutazione attuale con la pipeline dei parametri. Implementa i provider e la metadata factory elencati, collega la registrazione di Symfony e Laravel e depreca i tre listener Symfony, preservando _api_filters e la precedenza dei parametri dell’utente.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
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
- Lingua principale
- PHP
- Stelle
- 2.6k
- Fork
- 982
- Merge medio
- 1g 16h
- PR unite (30g)
- 59
Guida per i contributori
Apri la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di api-platform/core
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 65/100
api-platform/core#8564 ·
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 84/100
api-platform/core#8495 ·
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 85/100
api-platform/core#8471 ·
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
api-platform/core#8475 ·
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
api-platform/core#8447 ·
Tutte le issue di api-platform/core
Issue simili
-
Solved site promotion gate fails on runner PHP patch drift (expects 8.2.33, runner installs 8.2.34) Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
Automattic/blocks-engine#2161 ·
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 70/100
Automattic/static-site-importer#1824 ·
-
[Chore] Keep one viget-block-generator skill and replicate it, instead of four tracked copies Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 70/100
-
Cannot reset column width Aperta0. Needs triage bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
-
0. Needs triage 35-feedback bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100