Skip to content
Open
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 1 addition & 1 deletion lib/EasyPost/Constant/Constants.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 12 additions & 3 deletions lib/EasyPost/Http/Requestor.php
Original file line number Diff line number Diff line change
Expand Up @@ -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<mixed>|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';
Expand All @@ -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));
Comment thread
eschalburg-easypost marked this conversation as resolved.
}
Expand Down
99 changes: 99 additions & 0 deletions test/EasyPost/RequestorEncodingTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php

namespace EasyPost;

use EasyPost\Constant\Constants;
use EasyPost\Test\Mocking\MockingUtility;
use EasyPost\Test\Mocking\MockRequest;
use EasyPost\Test\Mocking\MockRequestMatchRule;
use EasyPost\Test\Mocking\MockRequestResponseInfo;
use PHPUnit\Framework\TestCase;

class RequestorEncodingTest extends TestCase
{
private function getMockClient(): EasyPostClient
{
$mockingUtility = new MockingUtility([
new MockRequest(
new MockRequestMatchRule('post', '/v2\/shipments$/'),
new MockRequestResponseInfo(200, '{}')
),
]);

return new EasyPostClient(
(string)getenv('EASYPOST_TEST_API_KEY'),
Constants::TIMEOUT,
Constants::API_BASE,
$mockingUtility
);
}

public function testEmptyArrayRemainsArrayInRequestBody(): void
{
$client = $this->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'));
}
}
Loading