From 4c5e5953a3c1baa99563a85cc878b7000b4563e1 Mon Sep 17 00:00:00 2001 From: Braunson Yager Date: Thu, 23 May 2024 13:11:54 -0400 Subject: [PATCH 1/8] Added support to Response object method to now accept a dot notation string like json() --- .github/README.md | 1 + src/Helpers/ObjectHelpers.php | 39 +++++++++++++++++++++++++++++++++++ src/Http/Response.php | 27 ++++++++++++++++++++---- tests/Unit/ResponseTest.php | 17 +++++++++++++++ 4 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 src/Helpers/ObjectHelpers.php diff --git a/.github/README.md b/.github/README.md index 3f31a6ed..160234c9 100644 --- a/.github/README.md +++ b/.github/README.md @@ -51,6 +51,7 @@ Please see [here](../.github/SECURITY.md) for our security policy. ## Credits - [Sam Carré](https://github.com/Sammyjo20) +- [Braunson Yager](https://github.com/Braunson) - [All Contributors](https://github.com/Sammyjo20/Saloon/contributors) And a special thanks to [Caneco](https://twitter.com/caneco) for the logo ✨ diff --git a/src/Helpers/ObjectHelpers.php b/src/Helpers/ObjectHelpers.php new file mode 100644 index 00000000..e8b93c79 --- /dev/null +++ b/src/Helpers/ObjectHelpers.php @@ -0,0 +1,39 @@ + $object + * @param string|null $key + * @return ($key is null ? object : mixed) + */ + public static function get(object $object, string $key, mixed $default = null): mixed + { + // Split the dot notation into individual keys + $keys = explode('.', $key); + + // Navigate through the object properties + foreach ($keys as $key) { + // Check if the object is an array or object and if the key exists + if (is_object($object) && isset($object->{$key})) { + $object = $object->{$key}; + } elseif (is_array($object) && isset($object[$key])) { + $object = $object[$key]; + } 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..ac234f3d 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,13 @@ class Response */ protected array $decodedJson; + /** + * The decoded JSON response object. + * + * @var object + */ + protected object $decodedJsonObject; + /** * The decoded XML response. */ @@ -223,11 +231,22 @@ 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. + * + * @param string|null $key + * @return ($key is null ? object : mixed) */ - 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, $key, $default); } /** @@ -235,7 +254,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), From 33cbbfdf6b6001cb91fa0be9d9b59edf20098a86 Mon Sep 17 00:00:00 2001 From: Braunson Yager Date: Tue, 28 May 2024 16:44:44 -0400 Subject: [PATCH 2/8] Update Response.php --- src/Http/Response.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Http/Response.php b/src/Http/Response.php index ac234f3d..330717c3 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -57,9 +57,9 @@ class Response /** * The decoded JSON response object. * - * @var object + * @var mixed */ - protected object $decodedJsonObject; + protected mixed $decodedJsonObject; /** * The decoded XML response. From 241a39c94f480fefe0521183334c35d8b27beb3c Mon Sep 17 00:00:00 2001 From: Sammyjo20 <29132017+Sammyjo20@users.noreply.github.com> Date: Sun, 9 Jun 2024 11:23:21 +0100 Subject: [PATCH 3/8] Removed primary credit --- .github/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/README.md b/.github/README.md index 160234c9..3f31a6ed 100644 --- a/.github/README.md +++ b/.github/README.md @@ -51,7 +51,6 @@ Please see [here](../.github/SECURITY.md) for our security policy. ## Credits - [Sam Carré](https://github.com/Sammyjo20) -- [Braunson Yager](https://github.com/Braunson) - [All Contributors](https://github.com/Sammyjo20/Saloon/contributors) And a special thanks to [Caneco](https://twitter.com/caneco) for the logo ✨ From 4df4a0eca729705e81ae98c105c108f0169aee79 Mon Sep 17 00:00:00 2001 From: Sammyjo20 <29132017+Sammyjo20@users.noreply.github.com> Date: Sun, 9 Jun 2024 11:24:15 +0100 Subject: [PATCH 4/8] Code style --- src/Http/Response.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Http/Response.php b/src/Http/Response.php index 330717c3..4568a5d6 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -56,8 +56,6 @@ class Response /** * The decoded JSON response object. - * - * @var mixed */ protected mixed $decodedJsonObject; From bcb3fad8a5b3a800ed2c7ca6a62effb459307867 Mon Sep 17 00:00:00 2001 From: Sammyjo20 <29132017+Sammyjo20@users.noreply.github.com> Date: Sun, 9 Jun 2024 11:25:28 +0100 Subject: [PATCH 5/8] Added concurrency to workflows --- .github/workflows/php-cs-fixer.yml | 4 ++++ .github/workflows/phpstan.yml | 4 ++++ .github/workflows/tests.yml | 4 ++++ 3 files changed, 12 insertions(+) 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 }} From d5bc4c9449c1de4c72d678132f461b9c875a336b Mon Sep 17 00:00:00 2001 From: Sammyjo20 <29132017+Sammyjo20@users.noreply.github.com> Date: Sun, 9 Jun 2024 11:31:41 +0100 Subject: [PATCH 6/8] Fixed phpstan --- phpstan.baseline.neon | 10 ---------- src/Helpers/ObjectHelpers.php | 19 +++++++++++-------- src/Http/Response.php | 6 +++--- 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/phpstan.baseline.neon b/phpstan.baseline.neon index 23ea18cc..bb1b960e 100644 --- a/phpstan.baseline.neon +++ b/phpstan.baseline.neon @@ -5,11 +5,6 @@ parameters: count: 1 path: src/Http/Connector.php - - - message: "#^Parameter \\#1 \\$contents of static method Saloon\\\\Data\\\\RecordedResponse\\:\\:fromFile\\(\\) expects string, bool\\|string given\\.$#" - count: 1 - path: src/Http/Faking/Fixture.php - - message: "#^Method Saloon\\\\Http\\\\Faking\\\\MockClient\\:\\:getLastResponse\\(\\) should return Saloon\\\\Http\\\\Response\\|null but returns \\(callable\\)\\|Saloon\\\\Http\\\\Faking\\\\Fixture\\|Saloon\\\\Http\\\\Faking\\\\MockResponse\\.$#" count: 1 @@ -20,11 +15,6 @@ parameters: count: 1 path: src/Http/Faking/MockClient.php - - - message: "#^Result of \\&\\& is always false.$#" - count: 1 - path: src/Data/MultipartValue.php - - message: "#^Parameter \\#1 \\$response of method Saloon\\\\Http\\\\PendingRequest\\:\\:executeResponsePipeline\\(\\) expects Saloon\\\\Http\\\\Response\\, GuzzleHttp\\\\Promise\\\\PromiseInterface\\|Saloon\\\\Http\\\\Response given.$#" count: 1 diff --git a/src/Helpers/ObjectHelpers.php b/src/Helpers/ObjectHelpers.php index e8b93c79..88c9a6e9 100644 --- a/src/Helpers/ObjectHelpers.php +++ b/src/Helpers/ObjectHelpers.php @@ -12,22 +12,25 @@ final class ObjectHelpers /** * Get an item from an object using "dot" notation. * - * @param object $object - * @param string|null $key - * @return ($key is null ? object : mixed) + * @param object $object + * @param string $key + * @param mixed|null $default + * @return mixed */ public static function get(object $object, string $key, mixed $default = null): mixed { // Split the dot notation into individual keys + $keys = explode('.', $key); // Navigate through the object properties - foreach ($keys as $key) { + + foreach ($keys as $dot) { // Check if the object is an array or object and if the key exists - if (is_object($object) && isset($object->{$key})) { - $object = $object->{$key}; - } elseif (is_array($object) && isset($object[$key])) { - $object = $object[$key]; + if (is_object($object) && isset($object->{$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; diff --git a/src/Http/Response.php b/src/Http/Response.php index 4568a5d6..7bc30161 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -231,8 +231,8 @@ public function array(int|string|null $key = null, mixed $default = null): mixed /** * Get the JSON decoded body of the response as an object or scalar value. * - * @param string|null $key - * @return ($key is null ? object : mixed) + * @param string|int|null $key + * @return mixed */ public function object(string|int|null $key = null, mixed $default = null): mixed { @@ -244,7 +244,7 @@ public function object(string|int|null $key = null, mixed $default = null): mixe return $this->decodedJsonObject; } - return ObjectHelpers::get($this->decodedJsonObject, $key, $default); + return ObjectHelpers::get($this->decodedJsonObject, (string)$key, $default); } /** From f27e1b7514b2139c9f7e766d7e74a017be66e09f Mon Sep 17 00:00:00 2001 From: Sammyjo20 <29132017+Sammyjo20@users.noreply.github.com> Date: Sun, 9 Jun 2024 11:32:04 +0100 Subject: [PATCH 7/8] Code style --- src/Helpers/ObjectHelpers.php | 5 ----- src/Http/Response.php | 3 --- 2 files changed, 8 deletions(-) diff --git a/src/Helpers/ObjectHelpers.php b/src/Helpers/ObjectHelpers.php index 88c9a6e9..ced87df4 100644 --- a/src/Helpers/ObjectHelpers.php +++ b/src/Helpers/ObjectHelpers.php @@ -11,11 +11,6 @@ final class ObjectHelpers { /** * Get an item from an object using "dot" notation. - * - * @param object $object - * @param string $key - * @param mixed|null $default - * @return mixed */ public static function get(object $object, string $key, mixed $default = null): mixed { diff --git a/src/Http/Response.php b/src/Http/Response.php index 7bc30161..bc2ed81b 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -230,9 +230,6 @@ public function array(int|string|null $key = null, mixed $default = null): mixed /** * Get the JSON decoded body of the response as an object or scalar value. - * - * @param string|int|null $key - * @return mixed */ public function object(string|int|null $key = null, mixed $default = null): mixed { From 55d8fe0840d6a7edafd2d4b9e4e66eed74e0eb9d Mon Sep 17 00:00:00 2001 From: Sammyjo20 <29132017+Sammyjo20@users.noreply.github.com> Date: Sun, 9 Jun 2024 11:36:02 +0100 Subject: [PATCH 8/8] PHPStan --- phpstan.baseline.neon | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/phpstan.baseline.neon b/phpstan.baseline.neon index bb1b960e..23ea18cc 100644 --- a/phpstan.baseline.neon +++ b/phpstan.baseline.neon @@ -5,6 +5,11 @@ parameters: count: 1 path: src/Http/Connector.php + - + message: "#^Parameter \\#1 \\$contents of static method Saloon\\\\Data\\\\RecordedResponse\\:\\:fromFile\\(\\) expects string, bool\\|string given\\.$#" + count: 1 + path: src/Http/Faking/Fixture.php + - message: "#^Method Saloon\\\\Http\\\\Faking\\\\MockClient\\:\\:getLastResponse\\(\\) should return Saloon\\\\Http\\\\Response\\|null but returns \\(callable\\)\\|Saloon\\\\Http\\\\Faking\\\\Fixture\\|Saloon\\\\Http\\\\Faking\\\\MockResponse\\.$#" count: 1 @@ -15,6 +20,11 @@ parameters: count: 1 path: src/Http/Faking/MockClient.php + - + message: "#^Result of \\&\\& is always false.$#" + count: 1 + path: src/Data/MultipartValue.php + - message: "#^Parameter \\#1 \\$response of method Saloon\\\\Http\\\\PendingRequest\\:\\:executeResponsePipeline\\(\\) expects Saloon\\\\Http\\\\Response\\, GuzzleHttp\\\\Promise\\\\PromiseInterface\\|Saloon\\\\Http\\\\Response given.$#" count: 1