diff --git a/CHANGELOG.md b/CHANGELOG.md index ddbe0ce3..8e8aee24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## v8.8.3 (2026-08-26) + +- Preserves caller-provided plain PHP objects in request params so `(object) []` is sent as an empty JSON object (`{}`) instead of being stringified + ## v8.8.2 (2026-08-11) - Empty arrays and objects are no longer stripped when passed as params in the request diff --git a/composer.json b/composer.json index 4bdf8c51..5834ab09 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "easypost/easypost-php", "description": "EasyPost Shipping API Client Library for PHP", - "version": "8.8.2", + "version": "8.8.3", "keywords": [ "shipping", "api", diff --git a/lib/EasyPost/Constant/Constants.php b/lib/EasyPost/Constant/Constants.php index 19b40df2..ec7b1023 100644 --- a/lib/EasyPost/Constant/Constants.php +++ b/lib/EasyPost/Constant/Constants.php @@ -11,7 +11,7 @@ abstract class Constants const BETA_API_VERSION = 'beta'; // Library constants - const LIBRARY_VERSION = '8.8.2'; + const LIBRARY_VERSION = '8.8.3'; const SUPPORT_EMAIL = 'support@easypost.com'; // Validation diff --git a/lib/EasyPost/Http/Requestor.php b/lib/EasyPost/Http/Requestor.php index 02fab32a..5bdcfbeb 100644 --- a/lib/EasyPost/Http/Requestor.php +++ b/lib/EasyPost/Http/Requestor.php @@ -65,13 +65,13 @@ public static function utf8(mixed $value): string * Encodes an EasyPost object and prepares the data for the request. * * @param mixed $data - * @return array|string + * @return mixed */ - private static function encodeObjects(mixed $data): array|string + private static function encodeObjects(mixed $data): mixed { if (is_null($data)) { return []; - } elseif ($data instanceof EasypostObject) { + } elseif ($data instanceof EasyPostObject) { return ['id' => self::utf8($data->id)]; // @phpstan-ignore-line } elseif ($data === true) { return 'true'; @@ -86,6 +86,15 @@ private static function encodeObjects(mixed $data): array|string } return $resource; + } elseif ($data instanceof \stdClass) { + $resource = []; + foreach (get_object_vars($data) as $k => $v) { + if (!is_null($v) and ($v !== '')) { + $resource[$k] = self::encodeObjects($v); + } + } + + return (object)$resource; } else { return self::utf8(strval($data)); } diff --git a/test/EasyPost/RequestorEncodingTest.php b/test/EasyPost/RequestorEncodingTest.php new file mode 100644 index 00000000..bb61f5f5 --- /dev/null +++ b/test/EasyPost/RequestorEncodingTest.php @@ -0,0 +1,99 @@ +getMockClient(); + $requestPayload = null; + + $client->subscribeToRequestHook(function (array $args) use (&$requestPayload): void { + $requestPayload = $args['request_body']; + }); + + $client->makeApiCall('post', '/shipments', [ + 'shipment' => [ + 'options' => [], + ], + ]); + + $this->assertIsArray($requestPayload); + $this->assertArrayHasKey('shipment', $requestPayload); + $this->assertArrayHasKey('options', $requestPayload['shipment']); + $this->assertSame([], $requestPayload['shipment']['options']); + } + + public function testEmptyStdClassBecomesEmptyJsonObjectInRequestBody(): void + { + $client = $this->getMockClient(); + $requestPayload = null; + + $client->subscribeToRequestHook(function (array $args) use (&$requestPayload): void { + $requestPayload = $args['request_body']; + }); + + $client->makeApiCall('post', '/shipments', [ + 'shipment' => [ + 'options' => (object)[], + ], + ]); + + $this->assertIsArray($requestPayload); + $this->assertArrayHasKey('shipment', $requestPayload); + $this->assertArrayHasKey('options', $requestPayload['shipment']); + $this->assertIsObject($requestPayload['shipment']['options']); + $this->assertSame('{}', json_encode($requestPayload['shipment']['options'])); + } + + public function testNonEmptyStdClassIsEncodedAsObject(): void + { + $client = $this->getMockClient(); + $requestPayload = null; + + $client->subscribeToRequestHook(function (array $args) use (&$requestPayload): void { + $requestPayload = $args['request_body']; + }); + + $client->makeApiCall('post', '/shipments', [ + 'shipment' => [ + 'options' => (object)[ + 'incoterm' => 'DDP', + 'currency' => '', + ], + ], + ]); + + $this->assertIsArray($requestPayload); + $this->assertArrayHasKey('shipment', $requestPayload); + $this->assertArrayHasKey('options', $requestPayload['shipment']); + $this->assertIsObject($requestPayload['shipment']['options']); + $this->assertSame('DDP', $requestPayload['shipment']['options']->incoterm); // @phpstan-ignore-line + $this->assertFalse(property_exists($requestPayload['shipment']['options'], 'currency')); + } +}