Skip to content

Commit 11d846b

Browse files
committed
Adding QueryComposeMode::EnumLenient
1 parent 5083caa commit 11d846b

File tree

3 files changed

+58
-4
lines changed

3 files changed

+58
-4
lines changed

QueryComposeMode.php

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,26 @@ enum QueryComposeMode
2525
*/
2626
case Compatible;
2727

28+
/**
29+
* PHP 8.4+ enum-compatible lenient mode.
30+
*
31+
* Provides stable support for BackedEnum values.
32+
* UnitEnum values are skipped.
33+
* Uses get_object_vars() for non-enum objects.
34+
* Unserializable values are skipped.
35+
*
36+
* Behaves like {@see QueryComposeMode::EnumCompatible}
37+
* but does not throw for UnitEnum values.
38+
*
39+
* Mirrors http_build_query behavior in PHP 8.4+,
40+
* except that error cases are silently ignored
41+
* instead of throwing.
42+
*
43+
* This mode is tolerant by design and skips entries that would otherwise
44+
* result in an exception in {@see QueryComposeMode::EnumCompatible}.
45+
*/
46+
case EnumLenient;
47+
2848
/**
2949
* PHP 8.4+ mode.
3050
*
@@ -34,8 +54,6 @@ enum QueryComposeMode
3454
* Unserializable values are skipped.
3555
*
3656
* http_build_query behavior in PHP 8.4+.
37-
*
38-
* Non-enum behavior may evolve in future versions.
3957
*/
4058
case EnumCompatible;
4159

QueryString.php

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,10 @@ public static function composeFromValue(
147147
?Converter $converter = null,
148148
QueryComposeMode $composeMode = QueryComposeMode::Native,
149149
): ?string {
150+
if (QueryComposeMode::EnumLenient === $composeMode && $data instanceof UnitEnum && !$data instanceof BackedEnum) {
151+
return '';
152+
}
153+
150154
QueryComposeMode::Safe !== $composeMode || is_array($data) || throw new TypeError('In safe mode only arrays are supported.');
151155

152156
$converter ??= Converter::fromRFC3986();
@@ -173,7 +177,7 @@ private static function composeRecursive(
173177
SplObjectStorage $seenObjects = new SplObjectStorage(),
174178
): iterable {
175179
QueryComposeMode::Safe !== $composeMode || is_array($data) || throw new TypeError('In safe mode only arrays are supported.');
176-
QueryComposeMode::EnumCompatible !== $composeMode || !$data instanceof UnitEnum || throw new TypeError('Argument #1 ($data) must not be an enum, '.((new ReflectionEnum($data::class))->isBacked() ? 'Backed' : 'Pure').' given') ;
180+
in_array($composeMode, [QueryComposeMode::EnumCompatible, QueryComposeMode::EnumLenient], true) || !$data instanceof UnitEnum || throw new TypeError('Argument #1 ($data) must not be an enum, '.((new ReflectionEnum($data::class))->isBacked() ? 'Backed' : 'Pure').' given') ;
177181

178182
if (is_object($data)) {
179183
if ($seenObjects->contains($data)) {
@@ -205,12 +209,20 @@ private static function composeRecursive(
205209
continue;
206210
}
207211

208-
if (null === $value || is_scalar($value)) {
212+
if (is_scalar($value)) {
209213
yield [$name, $value];
210214

211215
continue;
212216
}
213217

218+
if (null === $value) {
219+
if (QueryComposeMode::Safe === $composeMode) {
220+
yield [$name, $value];
221+
}
222+
223+
continue;
224+
}
225+
214226
if ($value instanceof BackedEnum) {
215227
if (QueryComposeMode::Compatible !== $composeMode) {
216228
yield [$name, $value->value];
@@ -222,6 +234,10 @@ private static function composeRecursive(
222234
}
223235

224236
if ($value instanceof UnitEnum) {
237+
if (QueryComposeMode::EnumLenient === $composeMode) {
238+
continue;
239+
}
240+
225241
QueryComposeMode::Compatible === $composeMode || throw new TypeError('Unbacked enum '.$value::class.' cannot be converted to a string');
226242

227243
$value = get_object_vars($value);

QueryStringTest.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use ValueError;
2424

2525
use function date_create;
26+
use function http_build_query;
2627
use function tmpfile;
2728

2829
use const PHP_QUERY_RFC1738;
@@ -807,6 +808,11 @@ public function test_it_throws_if_a_non_backed_enum_is_given_in_enum_native_mode
807808
QueryString::compose(['pure' => PureEnum::One], composeMode: QueryComposeMode::EnumCompatible);
808809
}
809810

811+
public function test_it_silently_ignore_if_a_non_backed_enum_is_given_in_enum_lenient_mode(): void
812+
{
813+
self::assertSame('foo=bar', QueryString::compose(['pure' => PureEnum::One, 'foo' => 'bar'], composeMode: QueryComposeMode::EnumLenient));
814+
}
815+
810816
public function test_it_throws_if_a_non_backed_enum_is_given_in_strict_mode(): void
811817
{
812818
$this->expectException(TypeError::class);
@@ -827,6 +833,7 @@ public function test_it_handles_backed_enums(): void
827833

828834
self::assertSame((PHP_VERSION_ID < 80400 ? $compatible : $enumNative).'&baz=1', QueryString::compose($params, composeMode: QueryComposeMode::Native));
829835
self::assertSame($compatible.'&baz=1', QueryString::compose($params, composeMode: QueryComposeMode::Compatible));
836+
self::assertSame($enumNative.'&baz=1', QueryString::compose($params, composeMode: QueryComposeMode::EnumLenient));
830837
self::assertSame($enumNative.'&baz=1', QueryString::compose($params, composeMode: QueryComposeMode::EnumCompatible));
831838
self::assertSame($enumNative.'&baz=1', QueryString::compose($params, composeMode: QueryComposeMode::Safe));
832839

@@ -836,6 +843,7 @@ public function test_it_can_handles_empty_array(): void
836843
{
837844
self::assertSame('', QueryString::compose([], composeMode: QueryComposeMode::Native));
838845
self::assertSame('', QueryString::compose([], composeMode: QueryComposeMode::Compatible));
846+
self::assertSame('', QueryString::compose([], composeMode: QueryComposeMode::EnumLenient));
839847
self::assertSame('', QueryString::compose([], composeMode: QueryComposeMode::EnumCompatible));
840848
self::assertNull(QueryString::compose([], composeMode: QueryComposeMode::Safe));
841849
}
@@ -847,6 +855,18 @@ public function test_it_can_convert_list_without_indices_in_safe_mode(): void
847855
self::assertSame('a%5B%5D=foo&a%5B%5D=0&a%5B%5D=1.23', QueryString::compose($data, composeMode: QueryComposeMode::Safe));
848856
self::assertSame('a%5B0%5D=foo&a%5B1%5D=0&a%5B2%5D=1.23', QueryString::compose($data, composeMode: QueryComposeMode::Native));
849857
}
858+
859+
public function test_it_can_handle_null_value_differently_with_composed_mode(): void
860+
{
861+
$data = ['module' => null, 'action' => '', 'page' => true];
862+
863+
self::assertSame('module&action=&page=1', QueryString::compose($data, composeMode: QueryComposeMode::Safe));
864+
self::assertSame('action=&page=1', QueryString::compose($data, composeMode: QueryComposeMode::EnumLenient));
865+
self::assertSame('action=&page=1', QueryString::compose($data, composeMode: QueryComposeMode::EnumCompatible));
866+
self::assertSame('action=&page=1', QueryString::compose($data, composeMode: QueryComposeMode::Compatible));
867+
self::assertSame('action=&page=1', QueryString::compose($data, composeMode: QueryComposeMode::Native));
868+
self::assertSame('action=&page=1', http_build_query($data));
869+
}
850870
}
851871

852872
enum PureEnum

0 commit comments

Comments
 (0)