Skip to content

Commit

Permalink
Merge pull request #485 from patchlevel/improve-projection-traces
Browse files Browse the repository at this point in the history
improve projection errors and traces
  • Loading branch information
DavidBadura authored Feb 4, 2024
2 parents 137f05c + 8c8f691 commit abbabef
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 24 deletions.
22 changes: 1 addition & 21 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
@@ -1,35 +1,15 @@
parameters:
ignoreErrors:
-
message: "#^Method Patchlevel\\\\EventSourcing\\\\Console\\\\Command\\\\ProjectionStatusCommand\\:\\:displayError\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#"
count: 1
path: src/Console/Command/ProjectionStatusCommand.php

-
message: "#^Method Patchlevel\\\\EventSourcing\\\\EventBus\\\\Message\\:\\:headers\\(\\) should return array\\{aggregateClass\\?\\: class\\-string\\<Patchlevel\\\\EventSourcing\\\\Aggregate\\\\AggregateRoot\\>, aggregateId\\?\\: string, playhead\\?\\: int\\<1, max\\>, recordedOn\\?\\: DateTimeImmutable, newStreamStart\\?\\: bool, archived\\?\\: bool\\} but returns non\\-empty\\-array\\<string, mixed\\>\\.$#"
count: 1
path: src/EventBus/Message.php

-
message: "#^Method Patchlevel\\\\EventSourcing\\\\Projection\\\\Projection\\\\ProjectionError\\:\\:__construct\\(\\) has parameter \\$errorContext with no value type specified in iterable type array\\.$#"
count: 1
path: src/Projection/Projection/ProjectionError.php

-
message: "#^Parameter \\#2 \\$errorContext of class Patchlevel\\\\EventSourcing\\\\Projection\\\\Projection\\\\ProjectionError constructor expects array\\<int, array\\{message\\: string, code\\: int\\|string, file\\: string, line\\: int, trace\\: array\\<int, array\\{file\\?\\: string, line\\?\\: int, function\\?\\: string, class\\?\\: string, type\\?\\: string, args\\?\\: array\\}\\>\\}\\>\\|null, mixed given\\.$#"
message: "#^Parameter \\#2 \\$errorContext of class Patchlevel\\\\EventSourcing\\\\Projection\\\\Projection\\\\ProjectionError constructor expects array\\<int, array\\{class\\: class\\-string, message\\: string, code\\: int\\|string, file\\: string, line\\: int, trace\\: array\\<int, array\\{file\\?\\: string, line\\?\\: int, function\\?\\: string, class\\?\\: string, type\\?\\: string, args\\?\\: array\\}\\>\\}\\>\\|null, mixed given\\.$#"
count: 1
path: src/Projection/Projection/Store/DoctrineStore.php

-
message: "#^Method Patchlevel\\\\EventSourcing\\\\Projection\\\\Projection\\\\Store\\\\ErrorContext\\:\\:fromThrowable\\(\\) return type has no value type specified in iterable type array\\.$#"
count: 1
path: src/Projection/Projection/Store/ErrorContext.php

-
message: "#^Method Patchlevel\\\\EventSourcing\\\\Projection\\\\Projection\\\\Store\\\\ErrorContext\\:\\:transformThrowable\\(\\) return type has no value type specified in iterable type array\\.$#"
count: 1
path: src/Projection/Projection/Store/ErrorContext.php

-
message: "#^Method Patchlevel\\\\EventSourcing\\\\Projection\\\\Projector\\\\InMemoryProjectorRepository\\:\\:projectors\\(\\) should return array\\<int, object\\> but returns array\\<int\\|string, object\\>\\.$#"
count: 1
Expand Down
1 change: 1 addition & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
<env name="COLUMNS" value="120"/>
<env name="DB_URL" value="sqlite:///:memory:"/>
<ini name="error_reporting" value="E_ALL"/>
<ini name="zend.exception_ignore_args" value="0"/>
</php>
<source>
<include>
Expand Down
44 changes: 41 additions & 3 deletions src/Projection/Projection/Store/ErrorContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,17 @@

use Throwable;

use function array_key_exists;
use function array_map;
use function array_walk_recursive;
use function get_resource_type;
use function is_object;
use function is_resource;
use function sprintf;

/**
* @psalm-type Trace = array{file?: string, line?: int, function?: string, class?: string, type?: string, args?: array}
* @psalm-type Context = array{message: string, code: int|string, file: string, line: int, trace: list<Trace>}
* @psalm-type Trace = array{file?: string, line?: int, function?: string, class?: string, type?: string, args?: array<array-key, mixed>}
* @psalm-type Context = array{class: class-string, message: string, code: int|string, file: string, line: int, trace: list<Trace>}
*/
final class ErrorContext
{
Expand All @@ -28,12 +36,42 @@ public static function fromThrowable(Throwable $error): array
/** @return Context */
private static function transformThrowable(Throwable $error): array
{
/** @var list<Trace> $traces */
$traces = $error->getTrace();

return [
'class' => $error::class,
'message' => $error->getMessage(),
'code' => $error->getCode(),
'file' => $error->getFile(),
'line' => $error->getLine(),
'trace' => $error->getTrace(),
'trace' => array_map(self::transformTrace(...), $traces),
];
}

/**
* @param Trace $trace
*
* @return Trace
*/
private static function transformTrace(array $trace): array
{
if (!array_key_exists('args', $trace)) {
return $trace;
}

array_walk_recursive($trace['args'], static function (mixed &$value): void {
if (is_object($value)) {
$value = sprintf('object(%s)', $value::class);
}

if (!is_resource($value)) {
return;
}

$value = sprintf('resource(%s)', get_resource_type($value));
});

return $trace;
}
}
75 changes: 75 additions & 0 deletions tests/Unit/Projection/Projection/ErrorContextTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

declare(strict_types=1);

namespace Patchlevel\EventSourcing\Tests\Unit\Projection\Projection;

use Patchlevel\EventSourcing\Aggregate\CustomId;
use Patchlevel\EventSourcing\Projection\Projection\Store\ErrorContext;
use PHPUnit\Framework\TestCase;
use RuntimeException;

use function count;
use function fclose;
use function fopen;

final class ErrorContextTest extends TestCase
{
public function testErrorContext(): void
{
$resource = fopen('php://memory', 'r');
$result = ErrorContext::fromThrowable(
$this->createException(
'test',
new CustomId('test'),
$resource,
['test' => [1, 2, 3]],
static fn () => null,
),
);
fclose($resource);

$this->assertCount(1, $result);
$error = $result[0];

$this->assertSame(RuntimeException::class, $error['class']);
$this->assertSame('test', $error['message']);
$this->assertSame(0, $error['code']);
$this->assertSame(__FILE__, $error['file']);
$this->assertGreaterThan(0, count($error['trace']));
$this->assertArrayHasKey(0, $error['trace']);

$firstTrace = $error['trace'][0];

$this->assertArrayHasKey('file', $firstTrace);
$this->assertSame(__FILE__, $firstTrace['file'] ?? null);
$this->assertArrayHasKey('line', $firstTrace);
$this->assertSame('createException', $firstTrace['function'] ?? null);
$this->assertArrayHasKey('class', $firstTrace);
$this->assertSame(self::class, $firstTrace['class'] ?? null);
$this->assertArrayHasKey('type', $firstTrace);
$this->assertSame('->', $firstTrace['type'] ?? null);
$this->assertArrayHasKey('args', $firstTrace);
$this->assertSame([
'test',
'object(Patchlevel\EventSourcing\Aggregate\CustomId)',
'resource(stream)',
['test' => [1, 2, 3]],
'object(Closure)',
], $firstTrace['args'] ?? null);
}

/**
* @param resource $resource
* @param array<array-key, mixed> $array
*/
private function createException(
string $message,
CustomId $id,
$resource,
array $array,
callable $callable,
): RuntimeException {
return new RuntimeException($message);
}
}

0 comments on commit abbabef

Please sign in to comment.