diff --git a/.github/workflows/php-cs-fixer.yml b/.github/workflows/php-cs-fixer.yml index 5b8a976c..07ea4a2c 100644 --- a/.github/workflows/php-cs-fixer.yml +++ b/.github/workflows/php-cs-fixer.yml @@ -13,6 +13,10 @@ on: permissions: contents: write +concurrency: + group: ${{ github.head_ref || github.ref || github.run_id }}_php_cs_fixer + cancel-in-progress: true + jobs: php-cs-fixer: runs-on: ubuntu-latest diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index 3fdc2f7c..f7d36ed3 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -10,6 +10,10 @@ on: branches: - '*' +concurrency: + group: ${{ github.head_ref || github.ref || github.run_id }}_phpstan + cancel-in-progress: true + jobs: phpstan: name: phpstan diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index dc486425..5f82da34 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,6 +13,10 @@ on: permissions: contents: read +concurrency: + group: ${{ github.head_ref || github.ref || github.run_id }}_tests + cancel-in-progress: true + jobs: tests: runs-on: ${{ matrix.os }} diff --git a/src/Helpers/ObjectHelpers.php b/src/Helpers/ObjectHelpers.php new file mode 100644 index 00000000..ced87df4 --- /dev/null +++ b/src/Helpers/ObjectHelpers.php @@ -0,0 +1,37 @@ +{$dot})) { + $object = $object->{$dot}; + } elseif (is_array($object) && isset($object[$dot])) { + $object = $object[$dot]; + } else { + // Return null if the key doesn't exist + return null; + } + } + + return $object; + } +} diff --git a/src/Http/Response.php b/src/Http/Response.php index 2d84ae3a..bc2ed81b 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -10,6 +10,7 @@ use Saloon\Traits\Macroable; use InvalidArgumentException; use Saloon\Helpers\ArrayHelpers; +use Saloon\Helpers\ObjectHelpers; use Saloon\XmlWrangler\XmlReader; use Illuminate\Support\Collection; use Saloon\Contracts\FakeResponse; @@ -53,6 +54,11 @@ class Response */ protected array $decodedJson; + /** + * The decoded JSON response object. + */ + protected mixed $decodedJsonObject; + /** * The decoded XML response. */ @@ -223,11 +229,19 @@ public function array(int|string|null $key = null, mixed $default = null): mixed } /** - * Get the JSON decoded body of the response as an object. + * Get the JSON decoded body of the response as an object or scalar value. */ - public function object(): object + public function object(string|int|null $key = null, mixed $default = null): mixed { - return json_decode($this->body(), false, 512, JSON_THROW_ON_ERROR); + if (! isset($this->decodedJsonObject)) { + $this->decodedJsonObject = json_decode($this->body() ?: '{}', false, 512, JSON_THROW_ON_ERROR); + } + + if (is_null($key)) { + return $this->decodedJsonObject; + } + + return ObjectHelpers::get($this->decodedJsonObject, (string)$key, $default); } /** @@ -235,7 +249,7 @@ public function object(): object * * Suitable for reading small, simple XML responses but not suitable for * more advanced XML responses with namespaces and prefixes. Consider - * using the xmlReader method instead for better compatability. + * using the xmlReader method instead for better compatibility. * * @see https://www.php.net/manual/en/book.simplexml.php */ diff --git a/tests/Unit/ResponseTest.php b/tests/Unit/ResponseTest.php index 47d616b9..e63878d4 100644 --- a/tests/Unit/ResponseTest.php +++ b/tests/Unit/ResponseTest.php @@ -137,6 +137,23 @@ expect($response)->object()->toEqual($dataAsObject); }); +test('the object method with a dot notation key value will return a nested string', function () { + $data = [ + 'contacts' => [ + ['name' => 'Sam', 'work' => 'Codepotato'], + ['name' => 'Braunson', 'work' => 'Geekybeaver'], + ], + ]; + + $mockClient = new MockClient([ + MockResponse::make($data, 500), + ]); + + $response = connector()->send(new UserRequest, $mockClient); + + expect($response)->object('contacts.1.name')->toEqual('Braunson'); +}); + test('the collect method will return a collection', function () { $mockClient = new MockClient([ MockResponse::make(['name' => 'Sam', 'work' => 'Codepotato'], 500),