From e6668d4bfc3f123a40bf6fcc72dc754cb93d450f Mon Sep 17 00:00:00 2001 From: otsch Date: Fri, 16 May 2025 18:37:15 +0200 Subject: [PATCH 1/2] Result::toArray() convert all objects to arrays The `Result::toArray()` method now converts all objects contained in the Result array (at any level of the array) to arrays. Also, if the only element in a result array has some autogenerated key containing "unnamed", but the value also is an associative array with string keys, the method only returns that child array. --- CHANGELOG.md | 4 ++ src/Result.php | 14 +++++- src/Steps/BaseStep.php | 19 +------- src/Utils/OutputTypeHelper.php | 17 ++++++++ tests/ResultTest.php | 80 ++++++++++++++++++++++++++++++---- 5 files changed, 106 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35276da..e2f33eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [3.5.2] - 2025-05-16 +### Fixed +* The `Result::toArray()` method now converts all objects contained in the Result array (at any level of the array) to arrays. Also, if the only element in a result array has some autogenerated key containing "unnamed", but the value also is an associative array with string keys, the method only returns that child array. + ## [3.5.1] - 2025-04-23 ### Fixed * An issue that occurred, when a step uses the `PreStepInvocationLogger`. As refiners also use the logger, a newer logger (replacing the `PreStepInvocationLogger`) is now also passed to all registered refiners of a step. diff --git a/src/Result.php b/src/Result.php index 6e9aa22..1ac1e87 100644 --- a/src/Result.php +++ b/src/Result.php @@ -2,6 +2,8 @@ namespace Crwlr\Crawler; +use Crwlr\Crawler\Utils\OutputTypeHelper; + final class Result { /** @@ -54,7 +56,17 @@ public function get(string $key, mixed $default = null): mixed */ public function toArray(): array { - return $this->data; + $data = OutputTypeHelper::recursiveChildObjectsToArray($this->data); + + if ( + count($data) === 1 && + str_contains('unnamed', array_key_first($data)) && + OutputTypeHelper::isAssociativeArray($data[array_key_first($data)]) + ) { + return $data[array_key_first($data)]; + } + + return $data; } private function getUnnamedKey(): string diff --git a/src/Steps/BaseStep.php b/src/Steps/BaseStep.php index cc2b778..896da6e 100644 --- a/src/Steps/BaseStep.php +++ b/src/Steps/BaseStep.php @@ -704,30 +704,13 @@ protected function getOutputPropertyFromArray(string $key, array $data): mixed return $data[$this->getOutputKeyAliasRealKey($key)]; } - $data = $this->recursiveChildObjectsToArray($data); + $data = OutputTypeHelper::recursiveChildObjectsToArray($data); $dot = new Dot($data); return $dot->get($key); } - /** - * @param mixed[] $data - * @return mixed[] - */ - protected function recursiveChildObjectsToArray(array $data): array - { - foreach ($data as $key => $value) { - if (is_object($value)) { - $data[$key] = $this->recursiveChildObjectsToArray(OutputTypeHelper::objectToArray($value)); - } elseif (is_array($value)) { - $data[$key] = $this->recursiveChildObjectsToArray($value); - } - } - - return $data; - } - protected function isOutputKeyAlias(string $key): bool { return array_key_exists($key, $this->outputKeyAliases()); diff --git a/src/Utils/OutputTypeHelper.php b/src/Utils/OutputTypeHelper.php index 57710ad..d814930 100644 --- a/src/Utils/OutputTypeHelper.php +++ b/src/Utils/OutputTypeHelper.php @@ -42,4 +42,21 @@ public static function isAssociativeArray(mixed $output): bool return false; } + + /** + * @param mixed[] $data + * @return mixed[] + */ + public static function recursiveChildObjectsToArray(array $data): array + { + foreach ($data as $key => $value) { + if (is_object($value)) { + $data[$key] = self::recursiveChildObjectsToArray(self::objectToArray($value)); + } elseif (is_array($value)) { + $data[$key] = self::recursiveChildObjectsToArray($value); + } + } + + return $data; + } } diff --git a/tests/ResultTest.php b/tests/ResultTest.php index 35fd0b5..a43704e 100644 --- a/tests/ResultTest.php +++ b/tests/ResultTest.php @@ -2,7 +2,12 @@ namespace tests; +use Crwlr\Crawler\Loader\Http\Browser\Screenshot; +use Crwlr\Crawler\Loader\Http\Messages\RespondedRequest; use Crwlr\Crawler\Result; +use GuzzleHttp\Psr7\Request; +use GuzzleHttp\Psr7\Response; +use stdClass; test('You can set and get a property', function () { $result = new Result(); @@ -27,9 +32,8 @@ test('The get method has a default value that you can set yourself', function () { $result = new Result(); - expect($result->get('foo'))->toBeNull(); - - expect($result->get('foo', '123'))->toBe('123'); + expect($result->get('foo'))->toBeNull() + ->and($result->get('foo', '123'))->toBe('123'); }); test('You can convert it to a plain array', function () { @@ -47,6 +51,66 @@ ]); }); +test('Converting to an array, also converts all objects at any level in the array to arrays', function () { + $result = new Result(); + + $result->set('foo', 'one'); + + $result->set( + 'bar', + helper_getStdClassWithData([ + 'a' => 'b', + 'c' => helper_getStdClassWithData(['d' => 'e', 'f' => 'g']) + ]), + ); + + $resultArray = $result->toArray(); + + expect($resultArray)->toBe([ + 'foo' => 'one', + 'bar' => [ + 'a' => 'b', + 'c' => ['d' => 'e', 'f' => 'g'], + ], + ]); +}); + +test( + 'when the only element of the output array is some unnamed property, but the value is an array with keys, ' . + 'it returns only that child array', + function () { + $result = new Result(); + + $result->set('unnamed', new RespondedRequest( + new Request('GET', 'https://www.example.com/foo'), + new Response(200, [], 'Hello World!'), + [new Screenshot('/path/to/screenshot.png')], + )); + + $resultArray = $result->toArray(); + + expect($resultArray)->toBeArray() + ->and(count($resultArray))->toBeGreaterThanOrEqual(14) + ->and($resultArray['url'])->toBe('https://www.example.com/foo') + ->and($resultArray['status'])->toBe(200) + ->and($resultArray['body'])->toBe('Hello World!') + ->and($resultArray['screenshots'][0])->toBe('/path/to/screenshot.png'); + } +); + +test( + 'when the only element of the output array is an unnamed property, with a scalar value, it returns the unnamed key', + function () { + $result = new Result(); + + $result->set('unnamed', 'foo'); + + $resultArray = $result->toArray(); + + expect($resultArray)->toBe(['unnamed' => 'foo']); + } +); + test('when you add something with empty string as key it creates a name with incrementing number', function () { $result = new Result(); @@ -70,15 +134,13 @@ $instance2 = new Result($instance1); - expect($instance1->get('foo'))->toBe('bar'); - - expect($instance2->get('foo'))->toBe('bar'); + expect($instance1->get('foo'))->toBe('bar') + ->and($instance2->get('foo'))->toBe('bar'); $instance2->set('baz', 'quz'); - expect($instance1->get('baz'))->toBeNull(); - - expect($instance2->get('baz'))->toBe('quz'); + expect($instance1->get('baz'))->toBeNull() + ->and($instance2->get('baz'))->toBe('quz'); }); test('it makes a proper array of arrays if you repeatedly add (associative) arrays with the same key', function () { From a4a430d86d5f3cd472be60d95ee1b11087c01c1f Mon Sep 17 00:00:00 2001 From: otsch Date: Fri, 16 May 2025 18:40:54 +0200 Subject: [PATCH 2/2] PHP CS Fixer change --- tests/ResultTest.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/ResultTest.php b/tests/ResultTest.php index a43704e..83383b7 100644 --- a/tests/ResultTest.php +++ b/tests/ResultTest.php @@ -7,7 +7,6 @@ use Crwlr\Crawler\Result; use GuzzleHttp\Psr7\Request; use GuzzleHttp\Psr7\Response; -use stdClass; test('You can set and get a property', function () { $result = new Result(); @@ -60,7 +59,7 @@ 'bar', helper_getStdClassWithData([ 'a' => 'b', - 'c' => helper_getStdClassWithData(['d' => 'e', 'f' => 'g']) + 'c' => helper_getStdClassWithData(['d' => 'e', 'f' => 'g']), ]), ); @@ -95,7 +94,7 @@ function () { ->and($resultArray['status'])->toBe(200) ->and($resultArray['body'])->toBe('Hello World!') ->and($resultArray['screenshots'][0])->toBe('/path/to/screenshot.png'); - } + }, ); test( @@ -108,7 +107,7 @@ function () { $resultArray = $result->toArray(); expect($resultArray)->toBe(['unnamed' => 'foo']); - } + }, ); test('when you add something with empty string as key it creates a name with incrementing number', function () {