Skip to content

JsonApiProvider PR #8193 breaks #[ApiFilter] flat query params when combined with page= in JSON:API mode #8216

@ErnadoO

Description

@ErnadoO

API Platform version(s) affected

4.3.6+

Description

PR #8193 introduced a regression: JsonApiProvider now copies flat pagination params
(page, itemsPerPage, pagination, partial) into _api_filters even when no
filter[*] params are present. Since ReadProvider short-circuits on a non-null
_api_filters, any flat filter param (city_id, order[distance], etc.) is silently
dropped.

How to Reproduce

Custom filter reading from $context['filters']:

#[ApiResource]
#[ApiFilter(CityFilter::class)] // reads $context['filters']['city_id']
class Session {}

Request:

GET /api/sessions?city_id=3152&order[distance]=asc&page=1
Accept: application/vnd.api+json

Result: city_id and order[distance] are ignored, $context['filters']only contains['page' => '1']`.

Root Cause

With no filter[*] in the URL, $filters was empty before 4.3.6 and _api_filters was
never set. The new loop sets it to ['page' => '1'], which is enough to make ReadProvider
skip the raw query string entirely:

if (null === ($filters = $request?->attributes->get('_api_filters')) && $request) {
    // never reached when _api_filters = ['page' => '1']
    $filters = RequestParser::parseRequestParams($queryString);
}

Missing Test Coverage

PR #8193 tests only cover filter[*] + flat page. There is no test for flat custom
params without filter[*], which is the case that regressed.

Current Workaround

#[AsDecorator(decorates: 'api_platform.state_provider.read', priority: 1)]
final class JsonApiRawQueryMergeProvider implements ProviderInterface
{
    public function __construct(
        #[AutowireDecorated] private readonly ProviderInterface $decorated,                                                                                                                                    
    ) {}

    public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
    {
        $request = $context['request'] ?? null;

        if ($request instanceof Request && null !== ($apiFilters = $request->attributes->get('_api_filters'))) {
            $qs = $request->server->get('QUERY_STRING', '');
            if ($qs) {
                $rawParams = RequestParser::parseRequestParams($qs);
                $request->attributes->set('_api_filters', array_merge($rawParams, $apiFilters));
            }
        }

        return $this->decorated->provide($operation, $uriVariables, $context);                                                                                                                                 
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions