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
2 changes: 1 addition & 1 deletion .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
6 changes: 6 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -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\<Patchlevel\\Hydrator\\Middleware\\Middleware\>, list\<Patchlevel\\Hydrator\\Middleware\\Middleware\> 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
Expand Down
16 changes: 14 additions & 2 deletions src/Middleware/NoMoreMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -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<Middleware> $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)),
),
);
}
}
4 changes: 2 additions & 2 deletions src/Middleware/Stack.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ final class Stack
{
private int $index = 0;

/** @param list<Middleware> $middlewares */
/** @param non-empty-list<Middleware> $middlewares */
public function __construct(
private readonly array $middlewares,
) {
Expand All @@ -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++;
Expand Down
16 changes: 16 additions & 0 deletions src/MissingMiddlewares.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Patchlevel\Hydrator;

use RuntimeException;

/** @experimental */
final class MissingMiddlewares extends RuntimeException implements HydratorException
{
public function __construct()
{
parent::__construct('Missing middlewares.');
}
}
3 changes: 3 additions & 0 deletions src/StackHydrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ public function __construct(
private readonly array $middlewares = [new TransformMiddleware()],
private readonly bool $defaultLazy = false,
) {
if ($middlewares === []) {
throw new MissingMiddlewares();
}
}

/**
Expand Down
36 changes: 36 additions & 0 deletions tests/Unit/Fixture/DummyMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace Patchlevel\Hydrator\Tests\Unit\Fixture;

use Patchlevel\Hydrator\Metadata\ClassMetadata;
use Patchlevel\Hydrator\Middleware\Middleware;
use Patchlevel\Hydrator\Middleware\Stack;

final class DummyMiddleware implements Middleware
{
/**
* @param ClassMetadata<T> $metadata
* @param array<string, mixed> $data
* @param array<string, mixed> $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<string, mixed> $context
*
* @return array<string, mixed>
*/
public function extract(ClassMetadata $metadata, object $object, array $context, Stack $stack): array
{
return $stack->next()->extract($metadata, $object, $context, $stack);
}
}
30 changes: 20 additions & 10 deletions tests/Unit/Middleware/StackTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
4 changes: 2 additions & 2 deletions tests/Unit/Middleware/TransformerMiddlewareTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -49,7 +49,7 @@ public function testExtract(): void
Email::fromString('info@patchlevel.de'),
),
[],
new Stack([]),
new Stack([$middleware]),
);

self::assertEquals($expected, $data);
Expand Down
8 changes: 8 additions & 0 deletions tests/Unit/StackHydratorBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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');
Expand Down Expand Up @@ -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');
Expand All @@ -128,6 +134,7 @@ public function testCachePsr6(): void

$builder = new StackHydratorBuilder();
$builder->setCache($cache);
$builder->addMiddleware($this->createMock(Middleware::class));

$hydrator = $builder->build();

Expand All @@ -143,6 +150,7 @@ public function testCachePsr16(): void

$builder = new StackHydratorBuilder();
$builder->setCache($cache);
$builder->addMiddleware($this->createMock(Middleware::class));

$hydrator = $builder->build();

Expand Down
13 changes: 13 additions & 0 deletions tests/Unit/StackHydratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(
Expand Down
Loading