From bf5a037608bfaa008b2182f08ed8a677124275f9 Mon Sep 17 00:00:00 2001 From: George Steel Date: Thu, 18 Jul 2024 13:48:59 +0100 Subject: [PATCH 1/2] Drop compatibility with callable stream factories on `ImplicitHeadMiddleware` Signed-off-by: George Steel --- psalm-baseline.xml | 18 +--- src/Middleware/ImplicitHeadMiddleware.php | 22 +---- .../ImplicitHeadMiddlewareFactory.php | 40 ++------ ...AbstractImplicitMethodsIntegrationTest.php | 6 +- .../ImplicitHeadMiddlewareFactoryTest.php | 95 +++---------------- .../Middleware/ImplicitHeadMiddlewareTest.php | 4 +- test/ServiceManagerIntegrationTest.php | 7 +- 7 files changed, 32 insertions(+), 160 deletions(-) diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 82aa0a8..e419653 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,21 +1,5 @@ - - - - new CallableStreamFactoryDecorator($streamFactory) - - - - - get(StreamInterface::class))]]> - - - detectStreamFactory - - - get(StreamInterface::class)]]> - - + services[$id]]]> diff --git a/src/Middleware/ImplicitHeadMiddleware.php b/src/Middleware/ImplicitHeadMiddleware.php index 373a45c..c9ebfc4 100644 --- a/src/Middleware/ImplicitHeadMiddleware.php +++ b/src/Middleware/ImplicitHeadMiddleware.php @@ -7,16 +7,12 @@ use Fig\Http\Message\RequestMethodInterface as RequestMethod; use Mezzio\Router\RouteResult; use Mezzio\Router\RouterInterface; -use Mezzio\Router\Stream\CallableStreamFactoryDecorator; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\StreamFactoryInterface; -use Psr\Http\Message\StreamInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -use function is_callable; - /** * Handle implicit HEAD requests. * @@ -46,20 +42,10 @@ final class ImplicitHeadMiddleware implements MiddlewareInterface { public const FORWARDED_HTTP_METHOD_ATTRIBUTE = 'forwarded_http_method'; - private StreamFactoryInterface $streamFactory; - - /** - * @param (callable(): StreamInterface)|StreamFactoryInterface $streamFactory A factory capable of returning - * an empty StreamInterface instance to - * inject in a response. - */ - public function __construct(private RouterInterface $router, callable|StreamFactoryInterface $streamFactory) - { - if (is_callable($streamFactory)) { - $streamFactory = new CallableStreamFactoryDecorator($streamFactory); - } - - $this->streamFactory = $streamFactory; + public function __construct( + private readonly RouterInterface $router, + private readonly StreamFactoryInterface $streamFactory, + ) { } /** diff --git a/src/Middleware/ImplicitHeadMiddlewareFactory.php b/src/Middleware/ImplicitHeadMiddlewareFactory.php index 9e66738..22e62f3 100644 --- a/src/Middleware/ImplicitHeadMiddlewareFactory.php +++ b/src/Middleware/ImplicitHeadMiddlewareFactory.php @@ -6,10 +6,8 @@ use Mezzio\Router\Exception\MissingDependencyException; use Mezzio\Router\RouterInterface; -use Mezzio\Router\Stream\CallableStreamFactoryDecorator; use Psr\Container\ContainerInterface; use Psr\Http\Message\StreamFactoryInterface; -use Psr\Http\Message\StreamInterface; /** * Create and return an ImplicitHeadMiddleware instance. @@ -17,14 +15,13 @@ * This factory depends on two other services: * * - Mezzio\Router\RouterInterface, which should resolve to an instance of that interface. - * - Either Psr\Http\Message\StreamFactoryInterface or Psr\Http\Message\StreamInterface, which should resolve to a - * callable that will produce an empty Psr\Http\Message\StreamInterface instance. + * - Psr\Http\Message\StreamFactoryInterface which should resolve to an instance of that interface. */ final class ImplicitHeadMiddlewareFactory { /** * @throws MissingDependencyException If either the Mezzio\Router\RouterInterface - * or Psr\Http\Message\StreamInterface services are missing. + * or Psr\Http\Message\StreamFactoryInterface services are missing. */ public function __invoke(ContainerInterface $container): ImplicitHeadMiddleware { @@ -35,37 +32,16 @@ public function __invoke(ContainerInterface $container): ImplicitHeadMiddleware ); } - return new ImplicitHeadMiddleware( - $container->get(RouterInterface::class), - $this->detectStreamFactory($container), - ); - } - - /** - * BC Preserving StreamFactoryInterface Retrieval - * - * Preserves existing behaviour in the 3.x series by fetching a `StreamInterface` callable and wrapping it in a - * decorator that implements StreamFactoryInterface. If `StreamInterface` callable is unavailable, attempt to - * fetch a `StreamFactoryInterface`, throwing a MissingDependencyException if neither are found. - * - * @deprecated Will be removed in version 4.0.0 - */ - private function detectStreamFactory(ContainerInterface $container): StreamFactoryInterface - { - $hasStreamFactory = $container->has(StreamFactoryInterface::class); - $hasDeprecatedCallable = $container->has(StreamInterface::class); - - if (! $hasStreamFactory && ! $hasDeprecatedCallable) { + if (! $container->has(StreamFactoryInterface::class)) { throw MissingDependencyException::dependencyForService( - StreamInterface::class, + StreamFactoryInterface::class, ImplicitHeadMiddleware::class ); } - if ($hasDeprecatedCallable) { - return new CallableStreamFactoryDecorator($container->get(StreamInterface::class)); - } - - return $container->get(StreamFactoryInterface::class); + return new ImplicitHeadMiddleware( + $container->get(RouterInterface::class), + $container->get(StreamFactoryInterface::class), + ); } } diff --git a/src/Test/AbstractImplicitMethodsIntegrationTest.php b/src/Test/AbstractImplicitMethodsIntegrationTest.php index 3f9e98e..3251548 100644 --- a/src/Test/AbstractImplicitMethodsIntegrationTest.php +++ b/src/Test/AbstractImplicitMethodsIntegrationTest.php @@ -9,7 +9,7 @@ use Generator; use Laminas\Diactoros\Response; use Laminas\Diactoros\ServerRequest; -use Laminas\Diactoros\Stream; +use Laminas\Diactoros\StreamFactory; use Laminas\Stratigility\MiddlewarePipe; use Mezzio\Router\Middleware\DispatchMiddleware; use Mezzio\Router\Middleware\ImplicitHeadMiddleware; @@ -56,9 +56,7 @@ public function getImplicitHeadMiddleware(RouterInterface $router): ImplicitHead { return new ImplicitHeadMiddleware( $router, - function () { - return new Stream('php://temp', 'rw'); - } + new StreamFactory(), ); } diff --git a/test/Middleware/ImplicitHeadMiddlewareFactoryTest.php b/test/Middleware/ImplicitHeadMiddlewareFactoryTest.php index 1a22289..eafc748 100644 --- a/test/Middleware/ImplicitHeadMiddlewareFactoryTest.php +++ b/test/Middleware/ImplicitHeadMiddlewareFactoryTest.php @@ -8,122 +8,49 @@ use Mezzio\Router\Exception\MissingDependencyException; use Mezzio\Router\Middleware\ImplicitHeadMiddlewareFactory; use Mezzio\Router\RouterInterface; +use MezzioTest\Router\InMemoryContainer; use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Psr\Container\ContainerInterface; use Psr\Http\Message\StreamFactoryInterface; -use Psr\Http\Message\StreamInterface; - -use function in_array; #[CoversClass(ImplicitHeadMiddlewareFactory::class)] final class ImplicitHeadMiddlewareFactoryTest extends TestCase { - /** @var ContainerInterface&MockObject */ - private ContainerInterface $container; - + private InMemoryContainer $container; private ImplicitHeadMiddlewareFactory $factory; protected function setUp(): void { parent::setUp(); - $this->container = $this->createMock(ContainerInterface::class); + $this->container = new InMemoryContainer(); $this->factory = new ImplicitHeadMiddlewareFactory(); } public function testFactoryRaisesExceptionIfRouterInterfaceServiceIsMissing(): void { - $this->container - ->expects(self::once()) - ->method('has') - ->with(RouterInterface::class) - ->willReturn(false); - $this->expectException(MissingDependencyException::class); + $this->expectExceptionMessage(RouterInterface::class); ($this->factory)($this->container); } - public function testFactoryRaisesExceptionIfStreamFactoryServiceIsMissing(): void + public function testFactoryRaisesExceptionIfStreamFactoryInterfaceServiceIsMissing(): void { - $this->container - ->expects(self::exactly(3)) - ->method('has') - ->with(self::callback(function (string $arg): bool { - self::assertTrue(in_array($arg, [ - RouterInterface::class, - StreamFactoryInterface::class, - StreamInterface::class, - ])); - return true; - })) - ->willReturnOnConsecutiveCalls(true, false, false); + $this->container->set(RouterInterface::class, $this->createMock(RouterInterface::class)); $this->expectException(MissingDependencyException::class); - - ($this->factory)($this->container); - } - - public function testFactoryProducesImplicitHeadMiddlewareWithCallableStreamFactory(): void - { - $router = $this->createMock(RouterInterface::class); - $streamFactory = static function (): void { - }; - - $this->container - ->expects(self::exactly(3)) - ->method('has') - ->with(self::callback(function (string $arg): bool { - self::assertTrue(in_array($arg, [ - RouterInterface::class, - StreamFactoryInterface::class, - StreamInterface::class, - ])); - return true; - })) - ->willReturnOnConsecutiveCalls(true, false, true); - - $this->container - ->expects(self::exactly(2)) - ->method('get') - ->with(self::callback(function (string $arg): bool { - self::assertTrue(in_array($arg, [RouterInterface::class, StreamInterface::class])); - return true; - })) - ->willReturnOnConsecutiveCalls($router, $streamFactory); + $this->expectExceptionMessage(StreamFactoryInterface::class); ($this->factory)($this->container); } public function testFactoryProducesImplicitHeadMiddlewareWithStreamFactoryInterface(): void { - $router = $this->createMock(RouterInterface::class); - $streamFactory = new StreamFactory(); - - $this->container - ->expects(self::exactly(3)) - ->method('has') - ->with(self::callback(function (string $arg): bool { - self::assertTrue(in_array($arg, [ - RouterInterface::class, - StreamFactoryInterface::class, - StreamInterface::class, - ])); - return true; - })) - ->willReturn(true, true, false); - - $this->container - ->expects(self::exactly(2)) - ->method('get') - ->with(self::callback(function (string $arg): bool { - self::assertTrue(in_array($arg, [RouterInterface::class, StreamFactoryInterface::class])); - return true; - })) - ->willReturnOnConsecutiveCalls($router, $streamFactory); - + $this->container->set(RouterInterface::class, $this->createMock(RouterInterface::class)); + $this->container->set(StreamFactoryInterface::class, new StreamFactory()); ($this->factory)($this->container); + + $this->expectNotToPerformAssertions(); } } diff --git a/test/Middleware/ImplicitHeadMiddlewareTest.php b/test/Middleware/ImplicitHeadMiddlewareTest.php index 2d3170b..ccf1f12 100644 --- a/test/Middleware/ImplicitHeadMiddlewareTest.php +++ b/test/Middleware/ImplicitHeadMiddlewareTest.php @@ -7,6 +7,7 @@ use Fig\Http\Message\RequestMethodInterface as RequestMethod; use Laminas\Diactoros\Response\TextResponse; use Laminas\Diactoros\ServerRequest; +use Laminas\Diactoros\StreamFactory; use Mezzio\Router\Middleware\ImplicitHeadMiddleware; use Mezzio\Router\Route; use Mezzio\Router\RouteResult; @@ -16,7 +17,6 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Message\StreamInterface; use Psr\Http\Server\MiddlewareInterface; /** @covers \Mezzio\Router\Middleware\ImplicitHeadMiddleware */ @@ -34,7 +34,7 @@ protected function setUp(): void $this->router = $this->createMock(RouterInterface::class); $this->middleware = new ImplicitHeadMiddleware( $this->router, - fn (): StreamInterface => $this->createMock(StreamInterface::class), + new StreamFactory(), ); } diff --git a/test/ServiceManagerIntegrationTest.php b/test/ServiceManagerIntegrationTest.php index 56e7a38..60b6594 100644 --- a/test/ServiceManagerIntegrationTest.php +++ b/test/ServiceManagerIntegrationTest.php @@ -4,13 +4,14 @@ namespace MezzioTest\Router; +use Laminas\Diactoros\StreamFactory; use Laminas\ServiceManager\ServiceManager; use Mezzio\Router; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Psr\Container\ContainerInterface; use Psr\Http\Message\ResponseFactoryInterface; -use Psr\Http\Message\StreamInterface; +use Psr\Http\Message\StreamFactoryInterface; use function array_merge_recursive; @@ -34,8 +35,8 @@ protected function setUp(): void Router\RouterInterface::class => function (): Router\RouterInterface { return $this->createMock(Router\RouterInterface::class); }, - StreamInterface::class => function (): callable { - return fn (): StreamInterface => $this->createMock(StreamInterface::class); + StreamFactoryInterface::class => function (): StreamFactoryInterface { + return new StreamFactory(); }, ], ], From fddec0c412492377bdea7bd667c4797a3c8d51b0 Mon Sep 17 00:00:00 2001 From: George Steel Date: Thu, 18 Jul 2024 14:42:33 +0100 Subject: [PATCH 2/2] Remove unused `CallableStreamFactoryDecorator` Signed-off-by: George Steel --- src/Stream/CallableStreamFactoryDecorator.php | 43 ------------- .../CallableStreamFactoryDecoratorTest.php | 61 ------------------- 2 files changed, 104 deletions(-) delete mode 100644 src/Stream/CallableStreamFactoryDecorator.php delete mode 100644 test/Stream/CallableStreamFactoryDecoratorTest.php diff --git a/src/Stream/CallableStreamFactoryDecorator.php b/src/Stream/CallableStreamFactoryDecorator.php deleted file mode 100644 index 4db838f..0000000 --- a/src/Stream/CallableStreamFactoryDecorator.php +++ /dev/null @@ -1,43 +0,0 @@ -streamFactory = $streamFactory; - } - - /** @inheritDoc */ - public function createStream(string $content = ''): StreamInterface - { - return ($this->streamFactory)(); - } - - /** @inheritDoc */ - public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface - { - throw new RuntimeException('This method will not be implemented'); - } - - /** @inheritDoc */ - public function createStreamFromResource($resource): StreamInterface - { - throw new RuntimeException('This method will not be implemented'); - } -} diff --git a/test/Stream/CallableStreamFactoryDecoratorTest.php b/test/Stream/CallableStreamFactoryDecoratorTest.php deleted file mode 100644 index 674fc2c..0000000 --- a/test/Stream/CallableStreamFactoryDecoratorTest.php +++ /dev/null @@ -1,61 +0,0 @@ -stream = (new StreamFactory())->createStream(); - - $this->decorator = new CallableStreamFactoryDecorator(fn (): StreamInterface => $this->stream); - } - - public function testThatCreateStreamWillProduceStream(): void - { - self::assertSame($this->stream, $this->decorator->createStream()); - } - - public function testThatTheStreamDoesNotReceiveContentArgument(): void - { - $result = $this->decorator->createStream('some content'); - - self::assertSame('', $result->getContents()); - } - - public function testCreateStreamFromFileIsNotImplemented(): void - { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('This method will not be implemented'); - - $this->decorator->createStreamFromFile('/foo'); - } - - public function testCreateStreamFromResourceIsNotImplemented(): void - { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('This method will not be implemented'); - - $this->decorator->createStreamFromResource(fopen(__FILE__, 'r')); - } -}