diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 60f97d2a..5316d72c 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -59,7 +59,7 @@ jobs: cat bench.txt >> $GITHUB_ENV echo 'EOF' >> $GITHUB_ENV - - uses: actions/github-script@v8 + - uses: actions/github-script@v9 with: script: | // Get the existing comments. diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 14b51ce2..26c9841e 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -132,6 +132,12 @@ parameters: count: 3 path: src/StackHydrator.php + - + message: '#^Parameter \#1 \$middlewares of class Patchlevel\\Hydrator\\Middleware\\Stack constructor expects non\-empty\-list\, list\ given\.$#' + identifier: argument.type + count: 4 + path: src/StackHydrator.php + - message: '#^Property Patchlevel\\Hydrator\\Tests\\Unit\\Extension\\Cryptography\\Fixture\\ChildWithSensitiveDataWithIdentifierDto\:\:\$email is never read, only written\.$#' identifier: property.onlyWritten diff --git a/src/Middleware/NoMoreMiddleware.php b/src/Middleware/NoMoreMiddleware.php index 8203ab3c..fa695bf0 100644 --- a/src/Middleware/NoMoreMiddleware.php +++ b/src/Middleware/NoMoreMiddleware.php @@ -7,10 +7,22 @@ use Patchlevel\Hydrator\HydratorException; use RuntimeException; +use function array_map; +use function count; +use function implode; +use function sprintf; + final class NoMoreMiddleware extends RuntimeException implements HydratorException { - public function __construct() + /** @param non-empty-list $middlewares */ + public function __construct(array $middlewares) { - parent::__construct('no more middlewares'); + parent::__construct( + sprintf( + 'The next middleware in %s was requested, but no further middleware exists. The following middlewares were executed: %s', + $middlewares[count($middlewares) - 1]::class, + implode(', ', array_map(static fn (Middleware $middleware): string => $middleware::class, $middlewares)), + ), + ); } } diff --git a/src/Middleware/Stack.php b/src/Middleware/Stack.php index 7a47f627..4591d84b 100644 --- a/src/Middleware/Stack.php +++ b/src/Middleware/Stack.php @@ -8,7 +8,7 @@ final class Stack { private int $index = 0; - /** @param list $middlewares */ + /** @param non-empty-list $middlewares */ public function __construct( private readonly array $middlewares, ) { @@ -19,7 +19,7 @@ public function next(): Middleware $next = $this->middlewares[$this->index] ?? null; if ($next === null) { - throw new NoMoreMiddleware(); + throw new NoMoreMiddleware($this->middlewares); } $this->index++; diff --git a/src/MissingMiddlewares.php b/src/MissingMiddlewares.php new file mode 100644 index 00000000..817ac6b4 --- /dev/null +++ b/src/MissingMiddlewares.php @@ -0,0 +1,16 @@ + $metadata + * @param array $data + * @param array $context + * + * @return T + * + * @template T of object + */ + public function hydrate(ClassMetadata $metadata, array $data, array $context, Stack $stack): object + { + return $stack->next()->hydrate($metadata, $data, $context, $stack); + } + + /** + * @param array $context + * + * @return array + */ + public function extract(ClassMetadata $metadata, object $object, array $context, Stack $stack): array + { + return $stack->next()->extract($metadata, $object, $context, $stack); + } +} diff --git a/tests/Unit/Middleware/StackTest.php b/tests/Unit/Middleware/StackTest.php index 31f30b23..1daf96dc 100644 --- a/tests/Unit/Middleware/StackTest.php +++ b/tests/Unit/Middleware/StackTest.php @@ -4,31 +4,41 @@ namespace Patchlevel\Hydrator\Tests\Unit\Middleware; -use Patchlevel\Hydrator\Middleware\Middleware; use Patchlevel\Hydrator\Middleware\NoMoreMiddleware; use Patchlevel\Hydrator\Middleware\Stack; +use Patchlevel\Hydrator\Tests\Unit\Fixture\DummyMiddleware; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; #[CoversClass(Stack::class)] final class StackTest extends TestCase { - public function testEmptyStack(): void + public function testStack(): void { - $this->expectException(NoMoreMiddleware::class); + $middleware1 = new DummyMiddleware(); + $middleware2 = new DummyMiddleware(); - $stack = new Stack([]); - $stack->next(); + $stack = new Stack([$middleware1, $middleware2]); + + self::assertSame($middleware1, $stack->next()); + self::assertSame($middleware2, $stack->next()); } - public function testStack(): void + public function testStackThrowsExceptionWhenNoMoreMiddlewareIsAvailable(): void { - $middleware1 = $this->createStub(Middleware::class); - $middleware2 = $this->createStub(Middleware::class); + $middleware1 = new DummyMiddleware(); + $middleware2 = new DummyMiddleware(); $stack = new Stack([$middleware1, $middleware2]); - self::assertSame($middleware1, $stack->next()); - self::assertSame($middleware2, $stack->next()); + $stack->next(); + $stack->next(); + + $this->expectException(NoMoreMiddleware::class); + $this->expectExceptionMessage( + 'The next middleware in Patchlevel\Hydrator\Tests\Unit\Fixture\DummyMiddleware was requested, but no further middleware exists. The following middlewares were executed: Patchlevel\Hydrator\Tests\Unit\Fixture\DummyMiddleware, Patchlevel\Hydrator\Tests\Unit\Fixture\DummyMiddleware', + ); + + $stack->next(); } } diff --git a/tests/Unit/Middleware/TransformerMiddlewareTest.php b/tests/Unit/Middleware/TransformerMiddlewareTest.php index b8c4c4ad..4190de41 100644 --- a/tests/Unit/Middleware/TransformerMiddlewareTest.php +++ b/tests/Unit/Middleware/TransformerMiddlewareTest.php @@ -30,7 +30,7 @@ public function testHydrate(): void $this->classMetadata(ProfileCreated::class), ['profileId' => '1', 'email' => 'info@patchlevel.de'], [], - new Stack([]), + new Stack([$middleware]), ); self::assertEquals($expected, $event); @@ -49,7 +49,7 @@ public function testExtract(): void Email::fromString('info@patchlevel.de'), ), [], - new Stack([]), + new Stack([$middleware]), ); self::assertEquals($expected, $data); diff --git a/tests/Unit/StackHydratorBuilderTest.php b/tests/Unit/StackHydratorBuilderTest.php index bb4b9e8a..274d8dbe 100644 --- a/tests/Unit/StackHydratorBuilderTest.php +++ b/tests/Unit/StackHydratorBuilderTest.php @@ -51,6 +51,8 @@ public function testAddMetadataEnricherWithPriority(): void $builder->addMetadataEnricher($enricher1, 10); $builder->addMetadataEnricher($enricher2, 20); + $builder->addMiddleware($this->createMock(Middleware::class)); + $hydrator = $builder->build(); $reflection = new ReflectionProperty(StackHydrator::class, 'metadataFactory'); @@ -74,6 +76,8 @@ public function testAddGuesserWithPriority(): void $builder->addGuesser($guesser1, 10); $builder->addGuesser($guesser2, 20); + $builder->addMiddleware($this->createMock(Middleware::class)); + $hydrator = $builder->build(); $reflection = new ReflectionProperty(StackHydrator::class, 'metadataFactory'); @@ -103,6 +107,8 @@ public function testEnableDefaultLazy(): void $builder = new StackHydratorBuilder(); $builder->enableDefaultLazy(); + $builder->addMiddleware($this->createMock(Middleware::class)); + $hydrator = $builder->build(); $reflection = new ReflectionProperty(StackHydrator::class, 'defaultLazy'); @@ -128,6 +134,7 @@ public function testCachePsr6(): void $builder = new StackHydratorBuilder(); $builder->setCache($cache); + $builder->addMiddleware($this->createMock(Middleware::class)); $hydrator = $builder->build(); @@ -143,6 +150,7 @@ public function testCachePsr16(): void $builder = new StackHydratorBuilder(); $builder->setCache($cache); + $builder->addMiddleware($this->createMock(Middleware::class)); $hydrator = $builder->build(); diff --git a/tests/Unit/StackHydratorTest.php b/tests/Unit/StackHydratorTest.php index 9dfbfeed..0d7c1579 100644 --- a/tests/Unit/StackHydratorTest.php +++ b/tests/Unit/StackHydratorTest.php @@ -12,10 +12,12 @@ use Patchlevel\Hydrator\ClassNotSupported; use Patchlevel\Hydrator\CoreExtension; use Patchlevel\Hydrator\DenormalizationFailure; +use Patchlevel\Hydrator\Metadata\AttributeMetadataFactory; use Patchlevel\Hydrator\Metadata\ClassMetadata; use Patchlevel\Hydrator\Middleware\Middleware; use Patchlevel\Hydrator\Middleware\Stack; use Patchlevel\Hydrator\Middleware\TransformMiddleware; +use Patchlevel\Hydrator\MissingMiddlewares; use Patchlevel\Hydrator\NormalizationFailure; use Patchlevel\Hydrator\Normalizer\HydratorAwareNormalizer; use Patchlevel\Hydrator\StackHydrator; @@ -60,6 +62,17 @@ public function setUp(): void $this->hydrator = new StackHydrator(); } + public function testMissingMiddlewares(): void + { + $this->expectException(MissingMiddlewares::class); + $this->expectExceptionMessage('Missing middlewares.'); + + new StackHydrator( + new AttributeMetadataFactory(), + [], + ); + } + public function testExtract(): void { $event = new ProfileCreated(