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
