On Tue, Jul 14, 2026 at 8:15 AM ignace nyamagana butera <[email protected]>
wrote:

>
>
> 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
>

Hi Máté,

As a follow up I would propose the following Enum but again names can be
change/improve

namespace Uri;

 enum QueryNullPolicy {
     case SkipTuple;
     case KeyOnly;
     case EmptyString;
}

and the class becomes

final readonly class QueryParamBuildingOptions
{
        public function __construct(
            public string $trueValue = "1",
            public string $falseValue = "0",
            public QueryNullPolicy $nullPolicy = QueryNullPolicy::SkipTuple
        ) {}
}

which is more readable and understandable IMHO

Best regards,
Ignace

Reply via email to