Skip to content
Merged
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
Expand Up @@ -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.
Expand Down
14 changes: 13 additions & 1 deletion src/Result.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace Crwlr\Crawler;

use Crwlr\Crawler\Utils\OutputTypeHelper;

final class Result
{
/**
Expand Down Expand Up @@ -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
Expand Down
19 changes: 1 addition & 18 deletions src/Steps/BaseStep.php
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
17 changes: 17 additions & 0 deletions src/Utils/OutputTypeHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
79 changes: 70 additions & 9 deletions tests/ResultTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

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;

test('You can set and get a property', function () {
$result = new Result();
Expand All @@ -27,9 +31,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 () {
Expand All @@ -47,6 +50,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();

Expand All @@ -70,15 +133,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 () {
Expand Down