On Wed, Jul 8, 2026 at 11:31 PM Máté Kocsis <[email protected]> wrote:

> Hello Tim et All,
>
> I managed to implement a considerable part of the proposal efficiently, as
> I planned. However, I came to a different conclusion
> regarding the "Array API" (https://wiki.php.net/rfc/query_params#array_api)
> than what I originally wrote:
>
> First and foremost, I wasn't happy that the Array API would try to mimic
> native array handling. For example, QueryParams::getArray("foo[bar][baz]")
> would try to parse the supplied array dimensions and then find index "baz"
> of index "bar" of array "foo". QueryParams::appendArray() and
> QueryParams::setArray() would need a similar behavior. I wasn't keen on
> reimplementing array operations within query params. In order to solve
> this Gordian knot, I want to simplify the Array API to only two (or maybe
> three) methods:
> - QueryParams::fromArray(): Creates a new QueryParams object from an array
> - QueryParams::withArray(): Does the same as above, but from an existing
> instance (I'm not 100% sure about this method yet)
> - QueryParams::toArray(): Creates an array from the QueryParams instance
>
> If someone wants to do any kind of array modifications, then they can do
> it by retrieving the query params array first via toArray(), change what's
> needed
> by using PHP's native array manipulation functionalities, and finally,
> convert the.array back to a QueryParams instance. I think this behavior is
> simple, clear,
> and also extensible later, if we learn about a better approach...
>
> Besides, I also realized that supporting some kind of configuration will
> also be necessary mostly for security reasons. These are the options
> I'm considering to add:
>
> - parsingMaxQueryStringLength: the maximum length of the input query
> string that can be parsed by the parse*() methods
> - parsingMaxParamCount: The maximum number of parameters that the input
> query string can contain when calling any parse*() methods (similar
> to the "max_input_vars" php.ini option).
> - parsingMaxNestingLevel: The maximum nesting level of arrays that can be
> parsed when calling any parse*() methods (similar
> to the "max_input_nesting_level" php.ini option)
>
> I figured that it might be useful to control how some specific type of
> query parameter values are represented when adding a new query
> parameter via  calling e.g. QueryParams::append("param", $value).
>
> - boolean values: by default, true would be represented as "1" and false
> would be represented as "0"
> - null values: there are three ways to handle them: 1) omitting these; 2)
> adding them without a "=", containing only the param name (e.g. "?foo");
> 3) and converting the null value to an empty string (e.g. "?foo=")
>
> I'm not really sure how useful these options are, so I'm curious to get
> feedback about them. It's also an option to only accept string or null
> values by the manipulator methods, but I think it would provide a better
> UX to support all valid types (e.g. an int is a valid one, but an object
> isn't).
>
> It's also a question how and where these configs should be passed.
> Currently, I group them together in a dedicated QueryParamOptions object
> which has the following signature:
>
> final readonly class QueryParamOptions
> {
>         public function __construct(
>             public int $parsingMaxQueryStringLength = 10000,
>             public int $parsingMaxParamCount = 1000,
>             public int $parsingMaxNestingLevel = 64,
>             public string $trueValue = "0",
>             public string $falseValue = "0",
>             public bool $useNullAsEmptyString = false
>         ) {}}
>
> However, the properties prefixed by "parsing" are only applicable during
> parsing, so maybe they should be passed separately to the parse*() methods?
> What are your thoughts?
>
> Regards,
> Máté
>
> Hi Máté,
thanks for the feedback and for the work already done on this RFC.

>
>  - QueryParams::fromArray(): Creates a new QueryParams object from an
> array
> - QueryParams::withArray(): Does the same as above, but from an existing
> instance (I'm not 100% sure about this method yet)
> - QueryParams::toArray(): Creates an array from the QueryParams instance


IMHO the suffix "Array" is an implementation detail I would prefer
something like "fromQueryParams", "toQueryParams" and
"(with|merge)QueryParams" . This conveys more the nature of the operations
than the type of operation.

I think your reasoning to simplify this part of the RFC makes sense instead
of adding a complex API users may want to rely on their own "Collection"
like object structure to deal with Query parameters
modification as long as they adhere to the principle of only
allowing scalar type and null as possible values.

final readonly class QueryParamOptions
> {
>         public function __construct(
>             public int $parsingMaxQueryStringLength = 10000,
>             public int $parsingMaxParamCount = 1000,
>             public int $parsingMaxNestingLevel = 64,
>             public string $trueValue = "0",
>             public string $falseValue = "0",
>             public bool $useNullAsEmptyString = false
>         ) {}}


This seems to me 2 pairs of options: One used for parsing, the other for
building. I would split this class into two QueryParamParsingOptions and
QueryParamsBuildingOptions, names can be improved.

final readonly class QueryParamParsingOptions
{
        public function __construct(
            public int $parsingMaxQueryStringLength = 10000,
            public int $parsingMaxParamCount = 1000,
            public int $parsingMaxNestingLevel = 64,
        ) {}
}

And

final readonly class QueryParamBuildingOptions
{
        public function __construct(
            public string $trueValue = "1",
            public string $falseValue = "0",
            public bool $useNullAsEmptyString = false
        ) {}
}

The QueryParamParsingOptions should be an argument for all your `parse*`
methods with sensible default value the same for the QueryParamBuildingOptions
but for the`to*` methods. The behaviour toward
using `null` seems unclear to me when checking the class on itself. partly
because it is a boolean, partly because you state that there's 3 ways to
handle null values and a boolean only allows for 2 ways. Perhaps
using an Enum would be a better fit in this case ?

Best regards,
Ignace

Reply via email to