Skip to content

Commit

Permalink
Merge pull request #57 from gsteel/v4/middleware-pipe-to-array
Browse files Browse the repository at this point in the history
Expose the internal middleware queue in `MiddlewarePipe` as an iterable
  • Loading branch information
Ocramius authored Jul 17, 2024
2 parents c8fea4e + e7c8a54 commit 176cf8d
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 7 deletions.
20 changes: 20 additions & 0 deletions src/IterableMiddlewarePipeInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace Laminas\Stratigility;

use IteratorAggregate;
use Psr\Http\Server\MiddlewareInterface;

/**
* Implementors of this interface expose composed middleware as an iterable.
*
* This is useful for inspecting the middleware that is configured to run within a pipeline. Iterating over the pipeline
* should *not* cause side effects such as de-queueing any composed middleware.
*
* @extends IteratorAggregate<int, MiddlewareInterface>
*/
interface IterableMiddlewarePipeInterface extends MiddlewarePipeInterface, IteratorAggregate
{
}
17 changes: 16 additions & 1 deletion src/MiddlewarePipe.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@

namespace Laminas\Stratigility;

use ArrayIterator;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use SplQueue;
use Traversable;

use function iterator_to_array;

/**
* Pipe middleware like unix pipes.
Expand All @@ -24,7 +28,7 @@
*
* @see https://github.com/senchalabs/connect
*/
final class MiddlewarePipe implements MiddlewarePipeInterface
final class MiddlewarePipe implements IterableMiddlewarePipeInterface
{
/** @var SplQueue<MiddlewareInterface> */
private SplQueue $pipeline;
Expand Down Expand Up @@ -84,4 +88,15 @@ public function pipe(MiddlewareInterface $middleware): void
{
$this->pipeline->enqueue($middleware);
}

/** @return Traversable<int, MiddlewareInterface> */
public function getIterator(): Traversable
{
return new ArrayIterator(
iterator_to_array(
clone $this->pipeline,
false,
),
);
}
}
51 changes: 45 additions & 6 deletions test/MiddlewarePipeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Laminas\Diactoros\Response;
use Laminas\Diactoros\ServerRequest as Request;
use Laminas\Stratigility\Exception;
use Laminas\Stratigility\IterableMiddlewarePipeInterface;
use Laminas\Stratigility\MiddlewarePipe;
use Laminas\Stratigility\MiddlewarePipeInterface;
use PHPUnit\Framework\TestCase;
Expand All @@ -17,7 +18,9 @@
use ReflectionClass;
use ReflectionMethod;
use ReflectionObject;
use SplQueue;

use function iterator_to_array;
use function sort;
use function spl_object_hash;
use function strpos;
Expand Down Expand Up @@ -64,15 +67,15 @@ public function testCanPipeInteropMiddleware(): void
static function ($argument1): bool {
self::assertInstanceOf(ServerRequestInterface::class, $argument1);
return true;
}
},
),
self::callback(
/** @psalm-suppress MissingClosureParamType */
static function ($argument2): bool {
self::assertInstanceOf(RequestHandlerInterface::class, $argument2);
return true;
}
)
},
),
)
->willReturn($response);

Expand Down Expand Up @@ -177,12 +180,12 @@ public function testHandleProcessesEnqueuedMiddleware(): void
$middleware1
->method('process')
->with(
$this->request
$this->request,
)
->willReturnCallback(
static fn(
ServerRequestInterface $request,
RequestHandlerInterface $handler): ResponseInterface => $handler->handle($request)
RequestHandlerInterface $handler): ResponseInterface => $handler->handle($request),
);
$middleware2 = $this->createMock(MiddlewareInterface::class);
$middleware2
Expand Down Expand Up @@ -211,7 +214,7 @@ public function testMiddlewarePipeOnlyImplementsMiddlewarePipeInterfaceApi(): vo
}
sort($actual);

$interfaceReflection = new ReflectionClass(MiddlewarePipeInterface::class);
$interfaceReflection = new ReflectionClass(IterableMiddlewarePipeInterface::class);
$interfaceMethods = $interfaceReflection->getMethods(ReflectionMethod::IS_PUBLIC);
$expected = [];
foreach ($interfaceMethods as $method) {
Expand All @@ -223,4 +226,40 @@ public function testMiddlewarePipeOnlyImplementsMiddlewarePipeInterfaceApi(): vo
self::assertEquals($expected, $actual);
self::assertInstanceOf(MiddlewarePipeInterface::class, $pipeline);
}

public function testThatTheIteratorYieldsEnqueuedMiddlewareInQueueOrder(): void
{
$pipeline = new MiddlewarePipe();
$a = $this->createMock(MiddlewareInterface::class);
$b = $this->createMock(MiddlewareInterface::class);
$c = $this->createMock(MiddlewareInterface::class);

$pipeline->pipe($c);
$pipeline->pipe($a);
$pipeline->pipe($b);

self::assertSame([$c, $a, $b], iterator_to_array($pipeline, false));
}

public function testThatCloningThePipelineAlsoClonesTheInternalQueue(): void
{
$pipe = new MiddlewarePipe();
$pipe->pipe($this->createMock(MiddlewareInterface::class));

$clone = clone $pipe;

$reflectionClass = new ReflectionClass(MiddlewarePipe::class);
$property = $reflectionClass->getProperty('pipeline');

$queue1 = $property->getValue($pipe);
self::assertInstanceOf(SplQueue::class, $queue1);
$queue2 = $property->getValue($clone);
self::assertInstanceOf(SplQueue::class, $queue2);

self::assertNotSame($queue1, $queue2);
self::assertSame(
iterator_to_array($queue1, false),
iterator_to_array($queue2, false),
);
}
}

0 comments on commit 176cf8d

Please sign in to comment.