Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
### v4.25.1 (2026-08-11)
* * *
### Bug Fixes:
- Filter operators `in`, `not_in` and `between` are now sent as a single form field by `URLFormEncoder`, at any nesting depth. Filters on export operations, such as `ramp[effective_from][between]`, were index-encoded and therefore ignored by the API.

### v4.25.0 (2026-07-30)
* * *
### New Resources:
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
4.25.0
4.25.1
12 changes: 12 additions & 0 deletions src/ValueObjects/Encoders/URLFormEncoder.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@

class URLFormEncoder implements ParamEncoderInterface
{
/**
* Filter operators whose value is the whole array, sent as a single field
* (e.g. updated_at[between]=[1704067200,1717199999]).
*/
private const ARRAY_OPERATORS = ['in', 'not_in', 'between'];

/**
* @param array $params.
* @param array $jsonKeys.
Expand Down Expand Up @@ -33,6 +39,12 @@ private static function serialize($value, $prefix = null, $idx = null, $jsonKeys
(!is_null($prefix) ? '[' . $usK . ']' : $usK) .
(!is_null($idx) ? '[' . $idx . ']' : '');
$serialized[$key] = is_string($v) ? $v : json_encode((is_array($v) && $v === []) ? (object)[] : $v);
} else if (is_array($v) && $v !== [] && !is_int($k) && in_array(Util::toUnderscoreFromCamelCase($k), self::ARRAY_OPERATORS, true)) {
// An empty filter is not a filter, so it falls through to the
// recursion below and leaves nothing in the request.
$usK = Util::toUnderscoreFromCamelCase($k);
$key = (!is_null($prefix) ? $prefix . '[' . $usK . ']' : $usK) . (!is_null($idx) ? '[' . $idx . ']' : '');
$serialized[$key] = json_encode($v);
} else if (is_array($v) && !is_int($k)) {
$tempPrefix = (!is_null($prefix)) ? $prefix . '[' . Util::toUnderscoreFromCamelCase($k) . ']' : Util::toUnderscoreFromCamelCase($k);
$serialized = array_merge($serialized, self::serialize($v, $tempPrefix, null, $jsonKeys, $level + 1));
Expand Down
2 changes: 1 addition & 1 deletion src/Version.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

final class Version
{
const VERSION = '4.25.0';
const VERSION = '4.25.1';
}

?>
52 changes: 52 additions & 0 deletions tests/ValueObjects/Encoder/URLFormEncoderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -217,4 +217,56 @@ public function testEncodeParamsShouldNotUseArrayBasedIndexingForJsonArray(): vo
$this->assertIsString($encoded);
$this->assertSame("id=foo&name=foo&discount_percentage=10&apply_on=each_specified_item&item_constraints%5Bconstraint%5D%5B0%5D=specific&item_constraints%5Bitem_type%5D%5B0%5D=plan&item_constraints%5Bitem_price_ids%5D%5B0%5D=%5B%22some_price_id%22%5D", $encoded);
}

/** Filter operators carry the whole array in one field, at any nesting depth. */
/** ramp[effective_from][between]=[1704067200,1717199999]&ramp[status][in]=["scheduled","draft"] */
public function testEncodeParamsWithFilterArrayOperators(): void
{
$params = [
'export_type' => 'import_friendly_data',
'ramp' => [
'effective_from' => [
'between' => [1704067200, 1717199999],
],
'status' => [
'in' => ['scheduled', 'draft'],
],
],
];
$encoded = URLFormEncoder::encode($params);
$this->assertIsString($encoded);
$this->assertSame(
"export_type=import_friendly_data&ramp%5Beffective_from%5D%5Bbetween%5D=%5B1704067200%2C1717199999%5D&ramp%5Bstatus%5D%5Bin%5D=%5B%22scheduled%22%2C%22draft%22%5D",
$encoded
);
}

/** Top-level filters on non-list requests get the same treatment. */
/** updated_at[between]=[1704067200,1717199999] */
public function testEncodeParamsWithTopLevelFilterArrayOperator(): void
{
$params = [
'updated_at' => [
'between' => [1704067200, 1717199999],
],
];
$encoded = URLFormEncoder::encode($params);
$this->assertIsString($encoded);
$this->assertSame("updated_at%5Bbetween%5D=%5B1704067200%2C1717199999%5D", $encoded);
}

/** An empty filter operator is left out of the request entirely. */
/** limit=5 */
public function testEncodeParamsWithEmptyFilterArrayOperator(): void
{
$params = [
'limit' => 5,
'updated_at' => [
'between' => [],
],
];
$encoded = URLFormEncoder::encode($params);
$this->assertIsString($encoded);
$this->assertSame("limit=5", $encoded);
}
}
Loading