From 3a8dcf6d34c10643692f3ed6fcae414c2f3a104b Mon Sep 17 00:00:00 2001 From: Geert Eltink Date: Sun, 11 Oct 2020 12:51:59 +0200 Subject: [PATCH 01/11] feat: add PHP 8 support Signed-off-by: Geert Eltink Signed-off-by: Alejandro Celaya Signed-off-by: Alejandro Celaya --- .gitignore | 1 + .travis.yml | 18 +- composer.json | 9 +- phpunit.xml.dist | 26 +- .../ProblemDetailsExceptionInterface.php | 2 + test/ProblemDetailsAssertionsTrait.php | 33 +- test/ProblemDetailsMiddlewareFactoryTest.php | 32 +- test/ProblemDetailsMiddlewareTest.php | 94 ++++-- ...oblemDetailsNotFoundHandlerFactoryTest.php | 27 +- test/ProblemDetailsNotFoundHandlerTest.php | 52 +-- ...oblemDetailsResponseFactoryFactoryTest.php | 135 +++++--- test/ProblemDetailsResponseFactoryTest.php | 317 ++++++++++-------- 12 files changed, 421 insertions(+), 325 deletions(-) diff --git a/.gitignore b/.gitignore index 87904ec..46466ef 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +/.phpunit.result.cache /clover.xml /composer.lock /coveralls-upload.json diff --git a/.travis.yml b/.travis.yml index e810782..7cceb3f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,30 +6,30 @@ cache: env: global: - - COMPOSER_ARGS="--no-interaction" + - COMPOSER_ARGS="--no-interaction --ignore-platform-reqs" - COVERAGE_DEPS="php-coveralls/php-coveralls" matrix: fast_finish: true include: - - php: 7.1 + - php: 7.3 env: - DEPS=lowest - - php: 7.1 + - php: 7.3 env: - DEPS=latest - - CS_CHECK=true - - TEST_COVERAGE=true - - php: 7.2 + - php: 7.4 env: - DEPS=lowest - - php: 7.2 + - php: 7.4 env: - DEPS=latest - - php: 7.3 + - CS_CHECK=true + - TEST_COVERAGE=true + - php: nightly env: - DEPS=lowest - - php: 7.3 + - php: nightly env: - DEPS=latest diff --git a/composer.json b/composer.json index 4875051..3629e6b 100644 --- a/composer.json +++ b/composer.json @@ -22,16 +22,12 @@ "sort-packages": true }, "extra": { - "branch-alias": { - "dev-master": "1.1.x-dev", - "dev-develop": "1.2.x-dev" - }, "laminas": { "config-provider": "Mezzio\\ProblemDetails\\ConfigProvider" } }, "require": { - "php": "^7.1", + "php": "^7.3 || ~8.0.0", "ext-json": "*", "fig/http-message-util": "^1.1.2", "laminas/laminas-zendframework-bridge": "^1.0", @@ -43,8 +39,7 @@ }, "require-dev": { "laminas/laminas-coding-standard": "~1.0.0", - "phpspec/prophecy": "^1.8.0", - "phpunit/phpunit": "^7.0.1" + "phpunit/phpunit": "^9.3" }, "autoload": { "psr-4": { diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 7fb861f..c996de4 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,17 +1,13 @@ - - - - ./test - - - - - - ./src - - + + + + ./src + + + + + ./test + + diff --git a/src/Exception/ProblemDetailsExceptionInterface.php b/src/Exception/ProblemDetailsExceptionInterface.php index b72c472..21fe6f4 100644 --- a/src/Exception/ProblemDetailsExceptionInterface.php +++ b/src/Exception/ProblemDetailsExceptionInterface.php @@ -35,4 +35,6 @@ public function getAdditionalData() : array; * for cases where the XML variant is desired. */ public function toArray() : array; + + public function jsonSerialize() : array; } diff --git a/test/ProblemDetailsAssertionsTrait.php b/test/ProblemDetailsAssertionsTrait.php index eead62a..375e10e 100644 --- a/test/ProblemDetailsAssertionsTrait.php +++ b/test/ProblemDetailsAssertionsTrait.php @@ -11,8 +11,7 @@ namespace MezzioTest\ProblemDetails; use PHPUnit\Framework\Assert; -use Prophecy\Argument; -use Prophecy\Prophecy\ObjectProphecy; +use PHPUnit\Framework\MockObject\MockObject; use Psr\Http\Message\StreamInterface; use Throwable; @@ -65,11 +64,11 @@ public function assertExceptionDetails(Throwable $e, array $details) : void } /** - * @param StreamInterface|ObjectProphecy $stream + * @param StreamInterface|MockObject $stream */ public function prepareResponsePayloadAssertions( string $contentType, - ObjectProphecy $stream, + MockObject $stream, callable $assertion ) : void { if ('application/problem+json' === $contentType) { @@ -84,33 +83,35 @@ public function prepareResponsePayloadAssertions( } /** - * @param StreamInterface|ObjectProphecy $stream + * @param StreamInterface|MockObject $stream */ - public function preparePayloadForJsonResponse(ObjectProphecy $stream, callable $assertion) : void + public function preparePayloadForJsonResponse(MockObject $stream, callable $assertion) : void { $stream - ->write(Argument::that(function ($body) use ($assertion) { - Assert::assertInternalType('string', $body); + ->expects($this->any()) + ->method('write') + ->with($this->callback(function ($body) use ($assertion) { + Assert::assertIsString($body); $data = json_decode($body, true); $assertion($data); return $body; - })) - ->shouldBeCalled(); + })); } /** - * @param StreamInterface|ObjectProphecy $stream + * @param StreamInterface|MockObject $stream */ - public function preparePayloadForXmlResponse(ObjectProphecy $stream, callable $assertion) : void + public function preparePayloadForXmlResponse(MockObject $stream, callable $assertion) : void { $stream - ->write(Argument::that(function ($body) use ($assertion) { - Assert::assertInternalType('string', $body); + ->expects($this->any()) + ->method('write') + ->with($this->callback(function ($body) use ($assertion) { + Assert::assertIsString($body); $data = $this->deserializeXmlPayload($body); $assertion($data); return $body; - })) - ->shouldBeCalled(); + })); } public function deserializeXmlPayload(string $xml) : array diff --git a/test/ProblemDetailsMiddlewareFactoryTest.php b/test/ProblemDetailsMiddlewareFactoryTest.php index d14fe15..cbbf0f7 100644 --- a/test/ProblemDetailsMiddlewareFactoryTest.php +++ b/test/ProblemDetailsMiddlewareFactoryTest.php @@ -13,39 +13,49 @@ use Mezzio\ProblemDetails\ProblemDetailsMiddleware; use Mezzio\ProblemDetails\ProblemDetailsMiddlewareFactory; use Mezzio\ProblemDetails\ProblemDetailsResponseFactory; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Container\ContainerInterface; use RuntimeException; class ProblemDetailsMiddlewareFactoryTest extends TestCase { + /** @var ContainerInterface|MockObject */ + private $container; + protected function setUp() : void { - $this->container = $this->prophesize(ContainerInterface::class); + $this->container = $this->createMock(ContainerInterface::class); $this->factory = new ProblemDetailsMiddlewareFactory(); } public function testRaisesExceptionWhenProblemDetailsResponseFactoryServiceIsNotAvailable() { $e = new RuntimeException(); - $this->container->get(ProblemDetailsResponseFactory::class)->willThrow($e); + $this->container + ->method('get') + ->with(ProblemDetailsResponseFactory::class) + ->willThrowException($e); $this->expectException(RuntimeException::class); - $this->factory->__invoke($this->container->reveal()); + $this->factory->__invoke($this->container); } public function testCreatesMiddlewareUsingResponseFactoryService() : void { - $responseFactory = $this->prophesize(ProblemDetailsResponseFactory::class)->reveal(); - $this->container->get(ProblemDetailsResponseFactory::class)->willReturn($responseFactory); + $responseFactory = $this->createMock(ProblemDetailsResponseFactory::class); + + $this->container + ->method('get') + ->with(ProblemDetailsResponseFactory::class) + ->willReturn($responseFactory); + + $middleware = ($this->factory)($this->container); - $middleware = ($this->factory)($this->container->reveal()); + $r = (new \ReflectionObject($middleware))->getProperty('responseFactory'); + $r->setAccessible(true); $this->assertInstanceOf(ProblemDetailsMiddleware::class, $middleware); - $this->assertAttributeSame( - $responseFactory, - 'responseFactory', - $middleware - ); + $this->assertSame($responseFactory, $r->getValue($middleware)); } } diff --git a/test/ProblemDetailsMiddlewareTest.php b/test/ProblemDetailsMiddlewareTest.php index 81fd57a..37d2b6c 100644 --- a/test/ProblemDetailsMiddlewareTest.php +++ b/test/ProblemDetailsMiddlewareTest.php @@ -29,9 +29,9 @@ class ProblemDetailsMiddlewareTest extends TestCase protected function setUp() : void { - $this->request = $this->prophesize(ServerRequestInterface::class); - $this->responseFactory = $this->prophesize(ProblemDetailsResponseFactory::class); - $this->middleware = new ProblemDetailsMiddleware($this->responseFactory->reveal()); + $this->request = $this->createMock(ServerRequestInterface::class); + $this->responseFactory = $this->createMock(ProblemDetailsResponseFactory::class); + $this->middleware = new ProblemDetailsMiddleware($this->responseFactory); } public function acceptHeaders() : array @@ -47,16 +47,16 @@ public function acceptHeaders() : array public function testSuccessfulDelegationReturnsHandlerResponse() : void { - $response = $this->prophesize(ResponseInterface::class); - $handler = $this->prophesize(RequestHandlerInterface::class); + $response = $this->createMock(ResponseInterface::class); + $handler = $this->createMock(RequestHandlerInterface::class); $handler - ->handle(Argument::that([$this->request, 'reveal'])) - ->will([$response, 'reveal']); + ->method('handle') + ->with($this->request) + ->willReturn($response); + $result = $this->middleware->process($this->request, $handler); - $result = $this->middleware->process($this->request->reveal(), $handler->reveal()); - - $this->assertSame($response->reveal(), $result); + $this->assertSame($response, $result); } /** @@ -64,21 +64,26 @@ public function testSuccessfulDelegationReturnsHandlerResponse() : void */ public function testThrowableRaisedByHandlerResultsInProblemDetails(string $accept) : void { - $this->request->getHeaderLine('Accept')->willReturn($accept); + $this->request + ->method('getHeaderLine') + ->with('Accept') + ->willReturn($accept); $exception = new TestAsset\RuntimeException('Thrown!', 507); - $handler = $this->prophesize(RequestHandlerInterface::class); + $handler = $this->createMock(RequestHandlerInterface::class); $handler - ->handle(Argument::that([$this->request, 'reveal'])) - ->willThrow($exception); + ->method('handle') + ->with($this->request) + ->willThrowException($exception); - $expected = $this->prophesize(ResponseInterface::class)->reveal(); + $expected = $this->createMock(ResponseInterface::class); $this->responseFactory - ->createResponseFromThrowable($this->request->reveal(), $exception) + ->method('createResponseFromThrowable') + ->with($this->request, $exception) ->willReturn($expected); - $result = $this->middleware->process($this->request->reveal(), $handler->reveal()); + $result = $this->middleware->process($this->request, $handler); $this->assertSame($expected, $result); } @@ -88,18 +93,23 @@ public function testThrowableRaisedByHandlerResultsInProblemDetails(string $acce */ public function testMiddlewareRegistersErrorHandlerToConvertErrorsToProblemDetails(string $accept) : void { - $this->request->getHeaderLine('Accept')->willReturn($accept); + $this->request + ->method('getHeaderLine') + ->with('Accept') + ->willReturn($accept); - $handler = $this->prophesize(RequestHandlerInterface::class); + $handler = $this->createMock(RequestHandlerInterface::class); $handler - ->handle(Argument::that([$this->request, 'reveal'])) - ->will(function () { + ->method('handle') + ->with($this->request) + ->willReturnCallback(function () { trigger_error('Triggered error!', E_USER_ERROR); }); - $expected = $this->prophesize(ResponseInterface::class)->reveal(); + $expected = $this->createMock(ResponseInterface::class); $this->responseFactory - ->createResponseFromThrowable($this->request->reveal(), Argument::that(function ($e) { + ->method('createResponseFromThrowable') + ->with($this->request, $this->callback(function ($e) { $this->assertInstanceOf(ErrorException::class, $e); $this->assertEquals(E_USER_ERROR, $e->getSeverity()); $this->assertEquals('Triggered error!', $e->getMessage()); @@ -107,24 +117,29 @@ public function testMiddlewareRegistersErrorHandlerToConvertErrorsToProblemDetai })) ->willReturn($expected); - $result = $this->middleware->process($this->request->reveal(), $handler->reveal()); + $result = $this->middleware->process($this->request, $handler); $this->assertSame($expected, $result); } public function testRethrowsCaughtExceptionIfUnableToNegotiateAcceptHeader() : void { - $this->request->getHeaderLine('Accept')->willReturn('text/html'); + $this->request + ->method('getHeaderLine') + ->with('Accept') + ->willReturn('text/html'); + $exception = new TestAsset\RuntimeException('Thrown!', 507); - $handler = $this->prophesize(RequestHandlerInterface::class); + $handler = $this->createMock(RequestHandlerInterface::class); $handler - ->handle(Argument::that([$this->request, 'reveal'])) - ->willThrow($exception); + ->method('handle') + ->with($this->request) + ->willThrowException($exception); $this->expectException(TestAsset\RuntimeException::class); $this->expectExceptionMessage('Thrown!'); $this->expectExceptionCode(507); - $this->middleware->process($this->request->reveal(), $handler->reveal()); + $this->middleware->process($this->request, $handler); } /** @@ -132,30 +147,35 @@ public function testRethrowsCaughtExceptionIfUnableToNegotiateAcceptHeader() : v */ public function testErrorHandlingTriggersListeners(string $accept) : void { - $this->request->getHeaderLine('Accept')->willReturn($accept); + $this->request + ->method('getHeaderLine') + ->with('Accept') + ->willReturn($accept); $exception = new TestAsset\RuntimeException('Thrown!', 507); - $handler = $this->prophesize(RequestHandlerInterface::class); + $handler = $this->createMock(RequestHandlerInterface::class); $handler - ->handle(Argument::that([$this->request, 'reveal'])) - ->willThrow($exception); + ->method('handle') + ->with($this->request) + ->willThrowException($exception); - $expected = $this->prophesize(ResponseInterface::class)->reveal(); + $expected = $this->createMock(ResponseInterface::class); $this->responseFactory - ->createResponseFromThrowable($this->request->reveal(), $exception) + ->method('createResponseFromThrowable') + ->with($this->request, $exception) ->willReturn($expected); $listener = function ($error, $request, $response) use ($exception, $expected) { $this->assertSame($exception, $error, 'Listener did not receive same exception as was raised'); - $this->assertSame($this->request->reveal(), $request, 'Listener did not receive same request'); + $this->assertSame($this->request, $request, 'Listener did not receive same request'); $this->assertSame($expected, $response, 'Listener did not receive same response'); }; $listener2 = clone $listener; $this->middleware->attachListener($listener); $this->middleware->attachListener($listener2); - $result = $this->middleware->process($this->request->reveal(), $handler->reveal()); + $result = $this->middleware->process($this->request, $handler); $this->assertSame($expected, $result); } diff --git a/test/ProblemDetailsNotFoundHandlerFactoryTest.php b/test/ProblemDetailsNotFoundHandlerFactoryTest.php index ec1c41c..c6ea3d8 100644 --- a/test/ProblemDetailsNotFoundHandlerFactoryTest.php +++ b/test/ProblemDetailsNotFoundHandlerFactoryTest.php @@ -21,31 +21,36 @@ class ProblemDetailsNotFoundHandlerFactoryTest extends TestCase { protected function setUp() : void { - $this->container = $this->prophesize(ContainerInterface::class); + $this->container = $this->createMock(ContainerInterface::class); $this->factory = new ProblemDetailsNotFoundHandlerFactory(); } public function testRaisesExceptionWhenProblemDetailsResponseFactoryServiceIsNotAvailable() { $e = new RuntimeException(); - $this->container->get(ProblemDetailsResponseFactory::class)->willThrow($e); + $this->container + ->method('get') + ->with(ProblemDetailsResponseFactory::class) + ->willThrowException($e); $this->expectException(RuntimeException::class); - $this->factory->__invoke($this->container->reveal()); + $this->factory->__invoke($this->container); } public function testCreatesNotFoundHandlerUsingResponseFactoryService() : void { - $responseFactory = $this->prophesize(ProblemDetailsResponseFactory::class)->reveal(); - $this->container->get(ProblemDetailsResponseFactory::class)->willReturn($responseFactory); + $responseFactory = $this->createMock(ProblemDetailsResponseFactory::class); + $this->container + ->method('get') + ->with(ProblemDetailsResponseFactory::class) + ->willReturn($responseFactory); - $notFoundHandler = ($this->factory)($this->container->reveal()); + $notFoundHandler = ($this->factory)($this->container); + + $r = (new \ReflectionObject($notFoundHandler))->getProperty('responseFactory'); + $r->setAccessible(true); $this->assertInstanceOf(ProblemDetailsNotFoundHandler::class, $notFoundHandler); - $this->assertAttributeSame( - $responseFactory, - 'responseFactory', - $notFoundHandler - ); + $this->assertSame($responseFactory, $r->getValue($notFoundHandler)); } } diff --git a/test/ProblemDetailsNotFoundHandlerTest.php b/test/ProblemDetailsNotFoundHandlerTest.php index 354c68b..27bf9e0 100644 --- a/test/ProblemDetailsNotFoundHandlerTest.php +++ b/test/ProblemDetailsNotFoundHandlerTest.php @@ -22,9 +22,9 @@ class ProblemDetailsNotFoundHandlerTest extends TestCase { use ProblemDetailsAssertionsTrait; - public function setUp() + protected function setUp() : void { - $this->responseFactory = $this->prophesize(ProblemDetailsResponseFactory::class); + $this->responseFactory = $this->createMock(ProblemDetailsResponseFactory::class); } public function acceptHeaders() : array @@ -40,44 +40,46 @@ public function acceptHeaders() : array */ public function testResponseFactoryPassedInConstructorGeneratesTheReturnedResponse(string $acceptHeader) : void { - $request = $this->prophesize(ServerRequestInterface::class); - $request->getMethod()->willReturn('POST'); - $request->getHeaderLine('Accept')->willReturn($acceptHeader); - $request->getUri()->willReturn('https://example.com/foo'); + $request = $this->createMock(ServerRequestInterface::class); + $request->method('getMethod')->willReturn('POST'); + $request->method('getHeaderLine')->with('Accept')->willReturn($acceptHeader); + $request->method('getUri')->willReturn('https://example.com/foo'); - $response = $this->prophesize(ResponseInterface::class); + $response = $this->createMock(ResponseInterface::class); - $this->responseFactory->createResponse( - Argument::that([$request, 'reveal']), - 404, - 'Cannot POST https://example.com/foo!' - )->will([$response, 'reveal']); + $this->responseFactory + ->method('createResponse') + ->with( + $request, + 404, + 'Cannot POST https://example.com/foo!' + )->willReturn($response); - $notFoundHandler = new ProblemDetailsNotFoundHandler($this->responseFactory->reveal()); + $notFoundHandler = new ProblemDetailsNotFoundHandler($this->responseFactory); $this->assertSame( - $response->reveal(), - $notFoundHandler->process($request->reveal(), $this->prophesize(RequestHandlerInterface::class)->reveal()) + $response, + $notFoundHandler->process($request, $this->createMock(RequestHandlerInterface::class)) ); } public function testHandlerIsCalledIfAcceptHeaderIsUnacceptable() : void { - $request = $this->prophesize(ServerRequestInterface::class); - $request->getMethod()->willReturn('POST'); - $request->getHeaderLine('Accept')->willReturn('text/html'); - $request->getUri()->willReturn('https://example.com/foo'); + $request = $this->createMock(ServerRequestInterface::class); + $request->method('getMethod')->willReturn('POST'); + $request->method('getHeaderLine')->with('Accept')->willReturn('text/html'); + $request->method('getUri')->willReturn('https://example.com/foo'); - $response = $this->prophesize(ResponseInterface::class); + $response = $this->createMock(ResponseInterface::class); - $handler = $this->prophesize(RequestHandlerInterface::class); - $handler->handle($request->reveal())->will([$response, 'reveal']); + $handler = $this->createMock(RequestHandlerInterface::class); + $handler->method('handle')->with($request)->willReturn($response); - $notFoundHandler = new ProblemDetailsNotFoundHandler($this->responseFactory->reveal()); + $notFoundHandler = new ProblemDetailsNotFoundHandler($this->responseFactory); $this->assertSame( - $response->reveal(), - $notFoundHandler->process($request->reveal(), $handler->reveal()) + $response, + $notFoundHandler->process($request, $handler) ); } } diff --git a/test/ProblemDetailsResponseFactoryFactoryTest.php b/test/ProblemDetailsResponseFactoryFactoryTest.php index 8a83f4b..4892fe3 100644 --- a/test/ProblemDetailsResponseFactoryFactoryTest.php +++ b/test/ProblemDetailsResponseFactoryFactoryTest.php @@ -31,7 +31,7 @@ class ProblemDetailsResponseFactoryFactoryTest extends TestCase { protected function setUp() : void { - $this->container = $this->prophesize(ContainerInterface::class); + $this->container = $this->createMock(ContainerInterface::class); } public function assertResponseFactoryReturns(ResponseInterface $expected, ProblemDetailsResponseFactory $factory) @@ -48,97 +48,129 @@ public function testLackOfResponseServiceResultsInException() $factory = new ProblemDetailsResponseFactoryFactory(); $e = new RuntimeException(); - $this->container->has('config')->willReturn(false); - $this->container->get('config')->shouldNotBeCalled(); - $this->container->get(ResponseInterface::class)->willThrow($e); + $this->container->method('has')->with('config')->willReturn(false); + $this->container->method('get')->with(ResponseInterface::class)->willThrowException($e); $this->expectException(RuntimeException::class); - $factory($this->container->reveal()); + $factory($this->container); } public function testNonCallableResponseServiceResultsInException() { $factory = new ProblemDetailsResponseFactoryFactory(); - $this->container->has('config')->willReturn(false); - $this->container->get('config')->shouldNotBeCalled(); - $this->container->get(ResponseInterface::class)->willReturn(new stdClass); + $this->container->method('has')->with('config')->willReturn(false); + $this->container->method('get')->with(ResponseInterface::class)->willReturn(new stdClass); $this->expectException(TypeError::class); - $factory($this->container->reveal()); + $factory($this->container); } public function testLackOfConfigServiceResultsInFactoryUsingDefaults() : void { - $this->container->has('config')->willReturn(false); + $this->container->method('has')->with('config')->willReturn(false); - $response = $this->prophesize(ResponseInterface::class)->reveal(); - $this->container->get(ResponseInterface::class)->willReturn(function () use ($response) { - return $response; - }); + $response = $this->createMock(ResponseInterface::class); + $this->container + ->method('get') + ->with(ResponseInterface::class) + ->willReturn(function () use ($response) { + return $response; + }); $factoryFactory = new ProblemDetailsResponseFactoryFactory(); - $factory = $factoryFactory($this->container->reveal()); + $factory = $factoryFactory($this->container); + + $isDebug = (new \ReflectionObject($factory))->getProperty('isDebug'); + $isDebug->setAccessible(true); + + $jsonFlags = (new \ReflectionObject($factory))->getProperty('jsonFlags'); + $jsonFlags->setAccessible(true); + + $responseFactory = (new \ReflectionObject($factory))->getProperty('responseFactory'); + $responseFactory->setAccessible(true); $this->assertInstanceOf(ProblemDetailsResponseFactory::class, $factory); - $this->assertAttributeSame(ProblemDetailsResponseFactory::EXCLUDE_THROWABLE_DETAILS, 'isDebug', $factory); - $this->assertAttributeSame( + $this->assertSame(ProblemDetailsResponseFactory::EXCLUDE_THROWABLE_DETAILS, $isDebug->getValue($factory)); + $this->assertSame( JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION | JSON_PARTIAL_OUTPUT_ON_ERROR, - 'jsonFlags', - $factory + $jsonFlags->getValue($factory) ); - $this->assertAttributeInstanceOf(Closure::class, 'responseFactory', $factory); + $this->assertInstanceOf(Closure::class, $responseFactory->getValue($factory)); $this->assertResponseFactoryReturns($response, $factory); } public function testUsesPrettyPrintFlagOnEnabledDebugMode() : void { - $this->container->has('config')->willReturn(true); - $this->container->get('config')->willReturn([ - 'debug' => true, - ]); - $this->container->get(ResponseInterface::class)->willReturn(function () { - }); + $this->container->method('has')->with('config')->willReturn(true); + + $this->container + ->method('get') + ->willReturnMap([ + ['config', ['debug' => true]], + [ResponseInterface::class, function () { + }] + ]); $factoryFactory = new ProblemDetailsResponseFactoryFactory(); - $factory = $factoryFactory($this->container->reveal()); + $factory = $factoryFactory($this->container); + + $jsonFlags = (new \ReflectionObject($factory))->getProperty('jsonFlags'); + $jsonFlags->setAccessible(true); - $this->assertSame(JSON_PRETTY_PRINT, Assert::readAttribute($factory, 'jsonFlags') & JSON_PRETTY_PRINT); + $this->assertSame(JSON_PRETTY_PRINT, $jsonFlags->getValue($factory) & JSON_PRETTY_PRINT); } public function testUsesDebugSettingFromConfigWhenPresent() : void { - $this->container->has('config')->willReturn(true); - $this->container->get('config')->willReturn(['debug' => true]); + $this->container->method('has')->with('config')->willReturn(true); - $this->container->get(ResponseInterface::class)->willReturn(function () { - }); + $this->container + ->method('get') + ->willReturnMap([ + ['config', ['debug' => true]], + [ResponseInterface::class, function () { + }] + ]); $factoryFactory = new ProblemDetailsResponseFactoryFactory(); - $factory = $factoryFactory($this->container->reveal()); + $factory = $factoryFactory($this->container); + + $isDebug = (new \ReflectionObject($factory))->getProperty('isDebug'); + $isDebug->setAccessible(true); + + $exceptionDetailsInResponse = (new \ReflectionObject($factory))->getProperty('exceptionDetailsInResponse'); + $exceptionDetailsInResponse->setAccessible(true); $this->assertInstanceOf(ProblemDetailsResponseFactory::class, $factory); - $this->assertAttributeSame(ProblemDetailsResponseFactory::INCLUDE_THROWABLE_DETAILS, 'isDebug', $factory); - $this->assertAttributeSame(true, 'exceptionDetailsInResponse', $factory); + $this->assertSame(ProblemDetailsResponseFactory::INCLUDE_THROWABLE_DETAILS, $isDebug->getValue($factory)); + $this->assertSame(true, $exceptionDetailsInResponse->getValue($factory)); } public function testUsesJsonFlagsSettingFromConfigWhenPresent() : void { - $this->container->has('config')->willReturn(true); - $this->container->get('config')->willReturn(['problem-details' => ['json_flags' => JSON_PRETTY_PRINT]]); + $this->container->method('has')->with('config')->willReturn(true); - $this->container->get(ResponseInterface::class)->willReturn(function () { - }); + $this->container + ->method('get') + ->willReturnMap([ + ['config', ['problem-details' => ['json_flags' => JSON_PRETTY_PRINT]]], + [ResponseInterface::class, function () { + }] + ]); $factoryFactory = new ProblemDetailsResponseFactoryFactory(); - $factory = $factoryFactory($this->container->reveal()); + $factory = $factoryFactory($this->container); + + $jsonFlags = (new \ReflectionObject($factory))->getProperty('jsonFlags'); + $jsonFlags->setAccessible(true); $this->assertInstanceOf(ProblemDetailsResponseFactory::class, $factory); - $this->assertAttributeSame(JSON_PRETTY_PRINT, 'jsonFlags', $factory); + $this->assertSame(JSON_PRETTY_PRINT, $jsonFlags->getValue($factory)); } public function testUsesDefaultTypesSettingFromConfigWhenPresent() : void @@ -147,18 +179,23 @@ public function testUsesDefaultTypesSettingFromConfigWhenPresent() : void 404 => 'https://example.com/problem-details/error/not-found', ]; - $this->container->has('config')->willReturn(true); - $this->container->get('config')->willReturn( - ['problem-details' => ['default_types_map' => $expectedDefaultTypes]] - ); + $this->container->method('has')->with('config')->willReturn(true); - $this->container->get(ResponseInterface::class)->willReturn(function () { - }); + $this->container + ->method('get') + ->willReturnMap([ + ['config', ['problem-details' => ['default_types_map' => $expectedDefaultTypes]]], + [ResponseInterface::class, function () { + }] + ]); $factoryFactory = new ProblemDetailsResponseFactoryFactory(); - $factory = $factoryFactory($this->container->reveal()); + $factory = $factoryFactory($this->container); + + $defaultTypesMap = (new \ReflectionObject($factory))->getProperty('defaultTypesMap'); + $defaultTypesMap->setAccessible(true); $this->assertInstanceOf(ProblemDetailsResponseFactory::class, $factory); - $this->assertAttributeSame($expectedDefaultTypes, 'defaultTypesMap', $factory); + $this->assertSame($expectedDefaultTypes, $defaultTypesMap->getValue($factory)); } } diff --git a/test/ProblemDetailsResponseFactoryTest.php b/test/ProblemDetailsResponseFactoryTest.php index 62ba6a3..3c0c6d8 100644 --- a/test/ProblemDetailsResponseFactoryTest.php +++ b/test/ProblemDetailsResponseFactoryTest.php @@ -14,9 +14,8 @@ use Mezzio\ProblemDetails\Exception\ProblemDetailsExceptionInterface; use Mezzio\ProblemDetails\ProblemDetailsResponseFactory; use PHPUnit\Framework\Assert; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\Prophecy\ObjectProphecy; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\StreamInterface; @@ -32,10 +31,10 @@ class ProblemDetailsResponseFactoryTest extends TestCase { use ProblemDetailsAssertionsTrait; - /** @var ServerRequestInterface|ObjectProphecy */ + /** @var ServerRequestInterface|MockObject */ private $request; - /** @var ResponseInterface|ObjectProphecy */ + /** @var ResponseInterface|MockObject */ private $response; /** @var ProblemDetailsResponseFactory */ @@ -45,10 +44,10 @@ class ProblemDetailsResponseFactoryTest extends TestCase protected function setUp() : void { - $this->request = $this->prophesize(ServerRequestInterface::class); - $this->response = $this->prophesize(ResponseInterface::class); + $this->request = $this->createMock(ServerRequestInterface::class); + $this->response = $this->createMock(ResponseInterface::class); $this->factory = new ProblemDetailsResponseFactory(function () { - return $this->response->reveal(); + return $this->response; }); } @@ -68,22 +67,22 @@ public function acceptHeaders() : array */ public function testCreateResponseCreatesExpectedType(string $header, string $expectedType) : void { - $this->request->getHeaderLine('Accept')->willReturn($header); + $this->request->method('getHeaderLine')->with('Accept')->willReturn($header); - $stream = $this->prophesize(StreamInterface::class); - $stream->write(Argument::type('string'))->shouldBeCalled(); + $stream = $this->createMock(StreamInterface::class); + $stream->expects($this->atLeastOnce())->method('write')->with($this->isType('string')); - $this->response->getBody()->will([$stream, 'reveal']); - $this->response->withStatus(500)->will([$this->response, 'reveal']); - $this->response->withHeader('Content-Type', $expectedType)->will([$this->response, 'reveal']); + $this->response->method('getBody')->willReturn($stream); + $this->response->method('withStatus')->with(500)->willReturn($this->response); + $this->response->method('withHeader')->with('Content-Type', $expectedType)->willReturn($this->response); $response = $this->factory->createResponse( - $this->request->reveal(), + $this->request, 500, 'Unknown error occurred' ); - $this->assertSame($this->response->reveal(), $response); + $this->assertSame($this->response, $response); } /** @@ -91,22 +90,22 @@ public function testCreateResponseCreatesExpectedType(string $header, string $ex */ public function testCreateResponseFromThrowableCreatesExpectedType(string $header, string $expectedType) : void { - $this->request->getHeaderLine('Accept')->willReturn($header); + $this->request->method('getHeaderLine')->with('Accept')->willReturn($header); - $stream = $this->prophesize(StreamInterface::class); - $stream->write(Argument::type('string'))->shouldBeCalled(); + $stream = $this->createMock(StreamInterface::class); + $stream->expects($this->atLeastOnce())->method('write')->with($this->isType('string')); - $this->response->getBody()->will([$stream, 'reveal']); - $this->response->withStatus(500)->will([$this->response, 'reveal']); - $this->response->withHeader('Content-Type', $expectedType)->will([$this->response, 'reveal']); + $this->response->method('getBody')->willReturn($stream); + $this->response->method('withStatus')->with(500)->willReturn($this->response); + $this->response->method('withHeader')->with('Content-Type', $expectedType)->willReturn($this->response); $exception = new RuntimeException(); $response = $this->factory->createResponseFromThrowable( - $this->request->reveal(), + $this->request, $exception ); - $this->assertSame($this->response->reveal(), $response); + $this->assertSame($this->response, $response); } /** @@ -116,31 +115,31 @@ public function testCreateResponseFromThrowableCreatesExpectedTypeWithExtraInfor string $header, string $expectedType ) : void { - $this->request->getHeaderLine('Accept')->willReturn($header); + $this->request->method('getHeaderLine')->with('Accept')->willReturn($header); - $stream = $this->prophesize(StreamInterface::class); + $stream = $this->createMock(StreamInterface::class); $this->prepareResponsePayloadAssertions($expectedType, $stream, function (array $payload) { Assert::assertArrayHasKey('exception', $payload); }); - $this->response->getBody()->will([$stream, 'reveal']); - $this->response->withStatus(500)->will([$this->response, 'reveal']); - $this->response->withHeader('Content-Type', $expectedType)->will([$this->response, 'reveal']); + $this->response->method('getBody')->willReturn($stream); + $this->response->method('withStatus')->with(500)->willReturn($this->response); + $this->response->method('withHeader')->with('Content-Type', $expectedType)->willReturn($this->response); $factory = new ProblemDetailsResponseFactory( function () { - return $this->response->reveal(); + return $this->response; }, ProblemDetailsResponseFactory::INCLUDE_THROWABLE_DETAILS ); $exception = new RuntimeException(); $response = $factory->createResponseFromThrowable( - $this->request->reveal(), + $this->request, $exception ); - $this->assertSame($this->response->reveal(), $response); + $this->assertSame($this->response, $response); } /** @@ -148,7 +147,7 @@ function () { */ public function testCreateResponseRemovesInvalidCharactersFromXmlKeys(string $header, string $expectedType) : void { - $this->request->getHeaderLine('Accept')->willReturn($header); + $this->request->method('getHeaderLine')->with('Accept')->willReturn($header); $additional = [ 'foo' => [ @@ -172,7 +171,7 @@ public function testCreateResponseRemovesInvalidCharactersFromXmlKeys(string $he $expectedKeyNames = array_keys($additional['foo']); } - $stream = $this->prophesize(StreamInterface::class); + $stream = $this->createMock(StreamInterface::class); $this->prepareResponsePayloadAssertions( $expectedType, $stream, @@ -181,12 +180,12 @@ function (array $payload) use ($expectedKeyNames) { } ); - $this->response->getBody()->will([$stream, 'reveal']); - $this->response->withStatus(500)->will([$this->response, 'reveal']); - $this->response->withHeader('Content-Type', $expectedType)->will([$this->response, 'reveal']); + $this->response->method('getBody')->willReturn($stream); + $this->response->method('withStatus')->with(500)->willReturn($this->response); + $this->response->method('withHeader')->with('Content-Type', $expectedType)->willReturn($this->response); $response = $this->factory->createResponse( - $this->request->reveal(), + $this->request, 500, 'Unknown error occurred', 'Title', @@ -194,21 +193,19 @@ function (array $payload) use ($expectedKeyNames) { $additional ); - $this->assertSame($this->response->reveal(), $response); + $this->assertSame($this->response, $response); } public function testCreateResponseFromThrowableWillPullDetailsFromProblemDetailsExceptionInterface() : void { - $e = $this->prophesize(ProblemDetailsExceptionInterface::class); - $e->getStatus()->willReturn(400); - $e->getDetail()->willReturn('Exception details'); - $e->getTitle()->willReturn('Invalid client request'); - $e->getType()->willReturn('https://example.com/api/doc/invalid-client-request'); - $e->getAdditionalData()->willReturn(['foo' => 'bar']); - - $this->request->getHeaderLine('Accept')->willReturn('application/json'); - - $stream = $this->prophesize(StreamInterface::class); + $e = $this->createMock(ProblemDetailsExceptionInterface::class); + $e->method('getStatus')->willReturn(400); + $e->method('getDetail')->willReturn('Exception details'); + $e->method('getTitle')->willReturn('Invalid client request'); + $e->method('getType')->willReturn('https://example.com/api/doc/invalid-client-request'); + $e->method('getAdditionalData')->willReturn(['foo' => 'bar']); + + $stream = $this->createMock(StreamInterface::class); $this->preparePayloadForJsonResponse( $stream, function (array $payload) { @@ -220,20 +217,23 @@ function (array $payload) { } ); - $this->response->getBody()->will([$stream, 'reveal']); - $this->response->withStatus(400)->will([$this->response, 'reveal']); - $this->response->withHeader('Content-Type', 'application/problem+json')->will([$this->response, 'reveal']); + $this->response->method('getBody')->willReturn($stream); + $this->response->method('withStatus')->with(500)->willReturn($this->response); + $this->response + ->method('withHeader') + ->with('Content-Type', 'application/problem+json') + ->willReturn($this->response); $factory = new ProblemDetailsResponseFactory(function () { - return $this->response->reveal(); + return $this->response; }); $response = $factory->createResponseFromThrowable( - $this->request->reveal(), - $e->reveal() + $this->request, + $e ); - $this->assertSame($this->response->reveal(), $response); + $this->assertSame($this->response, $response); } /** @@ -241,23 +241,24 @@ function (array $payload) { */ public function testCreateResponseRemovesResourcesFromInputData(string $header, string $expectedType) : void { - $this->request->getHeaderLine('Accept')->willReturn($header); + $this->request->method('getHeaderLine')->with('Accept')->willReturn($header); - $stream = $this->prophesize(StreamInterface::class); + $stream = $this->createMock(StreamInterface::class); $stream - ->write(Argument::that(function ($body) { + ->expects($this->atLeastOnce()) + ->method('write') + ->with($this->callback(function ($body) { Assert::assertNotEmpty($body); return $body; - })) - ->shouldBeCalled(); + })); - $this->response->getBody()->will([$stream, 'reveal']); - $this->response->withStatus(500)->will([$this->response, 'reveal']); - $this->response->withHeader('Content-Type', $expectedType)->will([$this->response, 'reveal']); + $this->response->method('getBody')->willReturn($stream); + $this->response->method('withStatus')->with(500)->willReturn($this->response); + $this->response->method('withHeader')->with('Content-Type', $expectedType)->willReturn($this->response); $fh = fopen(__FILE__, 'r'); $response = $this->factory->createResponse( - $this->request->reveal(), + $this->request, 500, 'Unknown error occurred', 'Title', @@ -270,36 +271,37 @@ public function testCreateResponseRemovesResourcesFromInputData(string $header, ); fclose($fh); - $this->assertSame($this->response->reveal(), $response); + $this->assertSame($this->response, $response); } public function testFactoryGeneratesXmlResponseIfNegotiationFails() : void { - $this->request->getHeaderLine('Accept')->willReturn('text/plain'); + $this->request->method('getHeaderLine')->with('Accept')->willReturn('text/plain'); - $stream = $this->prophesize(StreamInterface::class); - $stream - ->write(Argument::type('string')) - ->shouldBeCalled(); + $stream = $this->createMock(StreamInterface::class); + $stream->expects($this->atLeastOnce())->method('write')->with($this->isType('string')); - $this->response->getBody()->will([$stream, 'reveal']); - $this->response->withStatus(500)->will([$this->response, 'reveal']); - $this->response->withHeader('Content-Type', 'application/problem+xml')->will([$this->response, 'reveal']); + $this->response->method('getBody')->willReturn($stream); + $this->response->method('withStatus')->with(500)->willReturn($this->response); + $this->response + ->method('withHeader') + ->with('Content-Type', 'application/problem+xml') + ->willReturn($this->response); $response = $this->factory->createResponse( - $this->request->reveal(), + $this->request, 500, 'Unknown error occurred' ); - $this->assertSame($this->response->reveal(), $response); + $this->assertSame($this->response, $response); } public function testFactoryRendersPreviousExceptionsInDebugMode() : void { - $this->request->getHeaderLine('Accept')->willReturn('application/json'); + $this->request->method('getHeaderLine')->with('Accept')->willReturn('application/json'); - $stream = $this->prophesize(StreamInterface::class); + $stream = $this->createMock(StreamInterface::class); $this->preparePayloadForJsonResponse( $stream, function (array $payload) { @@ -307,32 +309,35 @@ function (array $payload) { Assert::assertEquals(101011, $payload['exception']['code']); Assert::assertEquals('second', $payload['exception']['message']); Assert::assertArrayHasKey('stack', $payload['exception']); - Assert::assertInternalType('array', $payload['exception']['stack']); + Assert::assertIsArray($payload['exception']['stack']); Assert::assertEquals(101010, $payload['exception']['stack'][0]['code']); Assert::assertEquals('first', $payload['exception']['stack'][0]['message']); } ); - $this->response->getBody()->will([$stream, 'reveal']); - $this->response->withStatus(500)->will([$this->response, 'reveal']); - $this->response->withHeader('Content-Type', 'application/problem+json')->will([$this->response, 'reveal']); + $this->response->method('getBody')->willReturn($stream); + $this->response->method('withStatus')->with(500)->willReturn($this->response); + $this->response + ->method('withHeader') + ->with('Content-Type', 'application/problem+json') + ->willReturn($this->response); $first = new RuntimeException('first', 101010); $second = new RuntimeException('second', 101011, $first); $factory = new ProblemDetailsResponseFactory( function () { - return $this->response->reveal(); + return $this->response; }, ProblemDetailsResponseFactory::INCLUDE_THROWABLE_DETAILS ); $response = $factory->createResponseFromThrowable( - $this->request->reveal(), + $this->request, $second ); - $this->assertSame($this->response->reveal(), $response); + $this->assertSame($this->response, $response); } public function testFragileDataInExceptionMessageShouldBeHiddenInResponseBodyInNoDebugMode() @@ -340,29 +345,33 @@ public function testFragileDataInExceptionMessageShouldBeHiddenInResponseBodyInN $fragileMessage = 'Your SQL or password here'; $exception = new Exception($fragileMessage); - $stream = $this->prophesize(StreamInterface::class); + $stream = $this->createMock(StreamInterface::class); $stream - ->write(Argument::that(function ($body) use ($fragileMessage) { + ->expects($this->atLeastOnce()) + ->method('write') + ->with($this->callback(function ($body) use ($fragileMessage) { Assert::assertNotContains($fragileMessage, $body); Assert::assertContains(ProblemDetailsResponseFactory::DEFAULT_DETAIL_MESSAGE, $body); return $body; - })) - ->shouldBeCalled(); + })); - $this->response->getBody()->will([$stream, 'reveal']); - $this->response->withStatus(500)->will([$this->response, 'reveal']); - $this->response->withHeader('Content-Type', 'application/problem+json')->will([$this->response, 'reveal']); + $this->response->method('getBody')->willReturn($stream); + $this->response->method('withStatus')->with(500)->willReturn($this->response); + $this->response + ->method('withHeader') + ->with('Content-Type', 'application/problem+json') + ->willReturn($this->response); - $response = $this->factory->createResponseFromThrowable($this->request->reveal(), $exception); + $response = $this->factory->createResponseFromThrowable($this->request, $exception); - $this->assertSame($this->response->reveal(), $response); + $this->assertSame($this->response, $response); } public function testExceptionCodeShouldBeIgnoredAnd500ServedInResponseBodyInNonDebugMode() { $exception = new Exception('', 400); - $stream = $this->prophesize(StreamInterface::class); + $stream = $this->createMock(StreamInterface::class); $this->preparePayloadForJsonResponse( $stream, function (array $payload) { @@ -370,13 +379,16 @@ function (array $payload) { } ); - $this->response->getBody()->will([$stream, 'reveal']); - $this->response->withStatus(500)->will([$this->response, 'reveal']); - $this->response->withHeader('Content-Type', 'application/problem+json')->will([$this->response, 'reveal']); + $this->response->method('getBody')->willReturn($stream); + $this->response->method('withStatus')->with(500)->willReturn($this->response); + $this->response + ->method('withHeader') + ->with('Content-Type', 'application/problem+json') + ->willReturn($this->response); - $response = $this->factory->createResponseFromThrowable($this->request->reveal(), $exception); + $response = $this->factory->createResponseFromThrowable($this->request, $exception); - $this->assertSame($this->response->reveal(), $response); + $this->assertSame($this->response, $response); } public function testFragileDataInExceptionMessageShouldBeVisibleInResponseBodyInNonDebugModeWhenAllowToShowByFlag() @@ -384,7 +396,7 @@ public function testFragileDataInExceptionMessageShouldBeVisibleInResponseBodyIn $fragileMessage = 'Your SQL or password here'; $exception = new Exception($fragileMessage); - $stream = $this->prophesize(StreamInterface::class); + $stream = $this->createMock(StreamInterface::class); $this->preparePayloadForJsonResponse( $stream, function (array $payload) use ($fragileMessage) { @@ -392,29 +404,32 @@ function (array $payload) use ($fragileMessage) { } ); - $this->response->getBody()->will([$stream, 'reveal']); - $this->response->withStatus(500)->will([$this->response, 'reveal']); - $this->response->withHeader('Content-Type', 'application/problem+json')->will([$this->response, 'reveal']); + $this->response->method('getBody')->willReturn($stream); + $this->response->method('withStatus')->with(500)->willReturn($this->response); + $this->response + ->method('withHeader') + ->with('Content-Type', 'application/problem+json') + ->willReturn($this->response); $factory = new ProblemDetailsResponseFactory( function () { - return $this->response->reveal(); + return $this->response; }, false, null, true ); - $response = $factory->createResponseFromThrowable($this->request->reveal(), $exception); + $response = $factory->createResponseFromThrowable($this->request, $exception); - $this->assertSame($this->response->reveal(), $response); + $this->assertSame($this->response, $response); } public function testCustomDetailMessageShouldBeVisible() { $detailMessage = 'Custom detail message'; - $stream = $this->prophesize(StreamInterface::class); + $stream = $this->createMock(StreamInterface::class); $this->preparePayloadForJsonResponse( $stream, function (array $payload) use ($detailMessage) { @@ -422,13 +437,16 @@ function (array $payload) use ($detailMessage) { } ); - $this->response->getBody()->will([$stream, 'reveal']); - $this->response->withStatus(500)->will([$this->response, 'reveal']); - $this->response->withHeader('Content-Type', 'application/problem+json')->will([$this->response, 'reveal']); + $this->response->method('getBody')->willReturn($stream); + $this->response->method('withStatus')->with(500)->willReturn($this->response); + $this->response + ->method('withHeader') + ->with('Content-Type', 'application/problem+json') + ->willReturn($this->response); $factory = new ProblemDetailsResponseFactory( function () { - return $this->response->reveal(); + return $this->response; }, false, null, @@ -436,25 +454,25 @@ function () { $detailMessage ); - $response = $factory->createResponseFromThrowable($this->request->reveal(), new Exception()); + $response = $factory->createResponseFromThrowable($this->request, new Exception()); - $this->assertSame($this->response->reveal(), $response); + $this->assertSame($this->response, $response); } public function testRenderWithMalformedUtf8Sequences(): void { - $e = $this->prophesize(ProblemDetailsExceptionInterface::class); - $e->getStatus()->willReturn(400); - $e->getDetail()->willReturn('Exception details'); - $e->getTitle()->willReturn('Invalid client request'); - $e->getType()->willReturn('https://example.com/api/doc/invalid-client-request'); - $e->getAdditionalData()->willReturn([ + $e = $this->createMock(ProblemDetailsExceptionInterface::class); + $e->method('getStatus')->willReturn(400); + $e->method('getDetail')->willReturn('Exception details'); + $e->method('getTitle')->willReturn('Invalid client request'); + $e->method('getType')->willReturn('https://example.com/api/doc/invalid-client-request'); + $e->method('getAdditionalData')->willReturn([ 'malformed-utf8' => self::UTF_8_INVALID_2_OCTET_SEQUENCE, ]); - $this->request->getHeaderLine('Accept')->willReturn('application/json'); + $this->request->method('getHeaderLine')->with('Accept')->willReturn('application/json'); - $stream = $this->prophesize(StreamInterface::class); + $stream = $this->createMock(StreamInterface::class); $this->preparePayloadForJsonResponse( $stream, function (array $payload) { @@ -462,20 +480,23 @@ function (array $payload) { } ); - $this->response->getBody()->will([$stream, 'reveal']); - $this->response->withStatus(400)->will([$this->response, 'reveal']); - $this->response->withHeader('Content-Type', 'application/problem+json')->will([$this->response, 'reveal']); + $this->response->method('getBody')->willReturn($stream); + $this->response->method('withStatus')->with(400)->willReturn($this->response); + $this->response + ->method('withHeader') + ->with('Content-Type', 'application/problem+json') + ->willReturn($this->response); $factory = new ProblemDetailsResponseFactory(function () { - return $this->response->reveal(); + return $this->response; }); $response = $factory->createResponseFromThrowable( - $this->request->reveal(), - $e->reveal() + $this->request, + $e ); - $this->assertSame($this->response->reveal(), $response); + $this->assertSame($this->response, $response); } public function provideMappedStatuses() : array @@ -498,23 +519,32 @@ public function provideMappedStatuses() : array */ public function testTypeIsInferredFromDefaultTypesMap(array $map, int $status, string $expectedType) : void { - $this->request->getHeaderLine('Accept')->willReturn('application/json'); - - $stream = $this->prophesize(StreamInterface::class); - $writeStream = $stream->write(Argument::that(function (string $body) use ($expectedType) { - $payload = json_decode($body, true); - Assert::assertEquals($expectedType, $payload['type']); + $this->request->method('getHeaderLine')->with('Accept')->willReturn('application/json'); - return $body; - })); - - $this->response->getBody()->will([$stream, 'reveal']); - $withStatus = $this->response->withStatus($status)->will([$this->response, 'reveal']); - $this->response->withHeader('Content-Type', 'application/problem+json')->will([$this->response, 'reveal']); + $stream = $this->createMock(StreamInterface::class); + $stream + ->expects($this->atLeastOnce()) + ->method('write') + ->with($this->callback(function (string $body) use ($expectedType) { + $payload = json_decode($body, true); + Assert::assertEquals($expectedType, $payload['type']); + return $body; + })); + + $this->response->method('getBody')->willReturn($stream); + $this->response + ->expects($this->atLeastOnce()) + ->method('withStatus') + ->with($status) + ->willReturn($this->response); + $this->response + ->method('withStatus') + ->with('Content-Type', 'application/problem+json') + ->willReturn($this->response); $factory = new ProblemDetailsResponseFactory( function () { - return $this->response->reveal(); + return $this->response; }, false, null, @@ -523,9 +553,6 @@ function () { $map ); - $factory->createResponse($this->request->reveal(), $status, 'detail'); - - $writeStream->shouldHaveBeenCalled(); - $withStatus->shouldHaveBeenCalled(); + $factory->createResponse($this->request, $status, 'detail'); } } From b257b68f4390ae3b137dbbf926a3433b9f09017e Mon Sep 17 00:00:00 2001 From: Geert Eltink Date: Sun, 11 Oct 2020 13:13:01 +0200 Subject: [PATCH 02/11] feat: laminas coding standard 2 Signed-off-by: Geert Eltink Signed-off-by: Alejandro Celaya Signed-off-by: Alejandro Celaya --- .gitignore | 1 + composer.json | 2 +- phpcs.xml | 16 ++- src/ConfigProvider.php | 14 ++- .../CommonProblemDetailsExceptionTrait.php | 32 ++--- .../ProblemDetailsExceptionInterface.php | 14 +-- src/ProblemDetailsMiddleware.php | 21 ++-- src/ProblemDetailsMiddlewareFactory.php | 2 +- src/ProblemDetailsNotFoundHandler.php | 8 +- src/ProblemDetailsNotFoundHandlerFactory.php | 2 +- src/ProblemDetailsResponseFactory.php | 114 +++++++++--------- src/ProblemDetailsResponseFactoryFactory.php | 8 +- test/ConfigProviderTest.php | 4 +- .../ProblemDetailsExceptionInterfaceTest.php | 26 ++-- test/ProblemDetailsAssertionsTrait.php | 16 +-- test/ProblemDetailsMiddlewareFactoryTest.php | 9 +- test/ProblemDetailsMiddlewareTest.php | 29 +++-- ...oblemDetailsNotFoundHandlerFactoryTest.php | 9 +- test/ProblemDetailsNotFoundHandlerTest.php | 9 +- ...oblemDetailsResponseFactoryFactoryTest.php | 74 +++++++----- test/ProblemDetailsResponseFactoryTest.php | 43 +++---- test/TestAsset/RuntimeException.php | 4 +- 22 files changed, 236 insertions(+), 221 deletions(-) diff --git a/.gitignore b/.gitignore index 46466ef..30e934c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +/.phpcs-cache /.phpunit.result.cache /clover.xml /composer.lock diff --git a/composer.json b/composer.json index 3629e6b..067959c 100644 --- a/composer.json +++ b/composer.json @@ -38,7 +38,7 @@ "willdurand/negotiation": "^2.3" }, "require-dev": { - "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-coding-standard": "~2.1.0", "phpunit/phpunit": "^9.3" }, "autoload": { diff --git a/phpcs.xml b/phpcs.xml index 4da1eed..1efe663 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -1,8 +1,20 @@ - - + + + + + + + + + + src test + + + diff --git a/src/ConfigProvider.php b/src/ConfigProvider.php index 69ccc93..896c7bc 100644 --- a/src/ConfigProvider.php +++ b/src/ConfigProvider.php @@ -14,13 +14,15 @@ * Configuration provider for the package. * * @see https://docs.laminas.dev/laminas-component-installer/ + * + * phpcs:disable WebimpressCodingStandard.PHP.DisallowFqn.FileName */ class ConfigProvider { /** * Returns the configuration array. */ - public function __invoke() : array + public function __invoke(): array { return [ 'dependencies' => $this->getDependencies(), @@ -30,17 +32,17 @@ public function __invoke() : array /** * Returns the container dependencies. */ - public function getDependencies() : array + public function getDependencies(): array { return [ // Legacy Zend Framework aliases - 'aliases' => [ - \Zend\ProblemDetails\ProblemDetailsMiddleware::class => ProblemDetailsMiddleware::class, + 'aliases' => [ + \Zend\ProblemDetails\ProblemDetailsMiddleware::class => ProblemDetailsMiddleware::class, \Zend\ProblemDetails\ProblemDetailsNotFoundHandler::class => ProblemDetailsNotFoundHandler::class, \Zend\ProblemDetails\ProblemDetailsResponseFactory::class => ProblemDetailsResponseFactory::class, ], - 'factories' => [ - ProblemDetailsMiddleware::class => ProblemDetailsMiddlewareFactory::class, + 'factories' => [ + ProblemDetailsMiddleware::class => ProblemDetailsMiddlewareFactory::class, ProblemDetailsNotFoundHandler::class => ProblemDetailsNotFoundHandlerFactory::class, ProblemDetailsResponseFactory::class => ProblemDetailsResponseFactoryFactory::class, ], diff --git a/src/Exception/CommonProblemDetailsExceptionTrait.php b/src/Exception/CommonProblemDetailsExceptionTrait.php index ee2e0ae..3f07e7b 100644 --- a/src/Exception/CommonProblemDetailsExceptionTrait.php +++ b/src/Exception/CommonProblemDetailsExceptionTrait.php @@ -25,52 +25,42 @@ */ trait CommonProblemDetailsExceptionTrait { - /** - * @var int - */ + /** @var int */ private $status; - /** - * @var string - */ + /** @var string */ private $detail; - /** - * @var string - */ + /** @var string */ private $title; - /** - * @var string - */ + /** @var string */ private $type; - /** - * @var array - */ + /** @var array */ private $additional = []; - public function getStatus() : int + public function getStatus(): int { return $this->status; } - public function getType() : string + public function getType(): string { return $this->type; } - public function getTitle() : string + public function getTitle(): string { return $this->title; } - public function getDetail() : string + public function getDetail(): string { return $this->detail; } - public function getAdditionalData() : array + public function getAdditionalData(): array { return $this->additional; } @@ -81,7 +71,7 @@ public function getAdditionalData() : array * Likely useful for the JsonSerializable implementation, but also * for cases where the XML variant is desired. */ - public function toArray() : array + public function toArray(): array { $problem = [ 'status' => $this->status, diff --git a/src/Exception/ProblemDetailsExceptionInterface.php b/src/Exception/ProblemDetailsExceptionInterface.php index 21fe6f4..ceb3309 100644 --- a/src/Exception/ProblemDetailsExceptionInterface.php +++ b/src/Exception/ProblemDetailsExceptionInterface.php @@ -18,15 +18,15 @@ */ interface ProblemDetailsExceptionInterface extends JsonSerializable, Throwable { - public function getStatus() : int; + public function getStatus(): int; - public function getType() : string; + public function getType(): string; - public function getTitle() : string; + public function getTitle(): string; - public function getDetail() : string; + public function getDetail(): string; - public function getAdditionalData() : array; + public function getAdditionalData(): array; /** * Serialize the exception to an array of problem details. @@ -34,7 +34,7 @@ public function getAdditionalData() : array; * Likely useful for the JsonSerializable implementation, but also * for cases where the XML variant is desired. */ - public function toArray() : array; + public function toArray(): array; - public function jsonSerialize() : array; + public function jsonSerialize(): array; } diff --git a/src/ProblemDetailsMiddleware.php b/src/ProblemDetailsMiddleware.php index c5dc05b..ec37005 100644 --- a/src/ProblemDetailsMiddleware.php +++ b/src/ProblemDetailsMiddleware.php @@ -30,14 +30,10 @@ */ class ProblemDetailsMiddleware implements MiddlewareInterface { - /** - * @var callable[] - */ + /** @var callable[] */ private $listeners = []; - /** - * @var ProblemDetailsResponseFactory - */ + /** @var ProblemDetailsResponseFactory */ private $responseFactory; public function __construct(ProblemDetailsResponseFactory $responseFactory) @@ -48,7 +44,7 @@ public function __construct(ProblemDetailsResponseFactory $responseFactory) /** * {@inheritDoc} */ - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { // If we cannot provide a representation, act as a no-op. if (! $this->canActAsErrorHandler($request)) { @@ -81,7 +77,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface * listeners are ignored; use listeners for reporting purposes * only. */ - public function attachListener(callable $listener) : void + public function attachListener(callable $listener): void { if (in_array($listener, $this->listeners, true)) { return; @@ -95,7 +91,7 @@ public function attachListener(callable $listener) : void * * Returns a boolean false if negotiation fails. */ - private function canActAsErrorHandler(ServerRequestInterface $request) : bool + private function canActAsErrorHandler(ServerRequestInterface $request): bool { $accept = $request->getHeaderLine('Accept') ?: '*/*'; @@ -108,17 +104,16 @@ private function canActAsErrorHandler(ServerRequestInterface $request) : bool * * Only raises exceptions for errors that are within the error_reporting mask. */ - private function createErrorHandler() : callable + private function createErrorHandler(): callable { /** * @param int $errno * @param string $errstr * @param string $errfile * @param int $errline - * @return void * @throws ErrorException if error is not within the error_reporting mask. */ - return function (int $errno, string $errstr, string $errfile, int $errline) : void { + return function (int $errno, string $errstr, string $errfile, int $errline): void { if (! (error_reporting() & $errno)) { // error_reporting does not include this error return; @@ -135,7 +130,7 @@ private function triggerListeners( Throwable $error, ServerRequestInterface $request, ResponseInterface $response - ) : void { + ): void { array_walk($this->listeners, function ($listener) use ($error, $request, $response) { $listener($error, $request, $response); }); diff --git a/src/ProblemDetailsMiddlewareFactory.php b/src/ProblemDetailsMiddlewareFactory.php index b2da786..c9d9710 100644 --- a/src/ProblemDetailsMiddlewareFactory.php +++ b/src/ProblemDetailsMiddlewareFactory.php @@ -14,7 +14,7 @@ class ProblemDetailsMiddlewareFactory { - public function __invoke(ContainerInterface $container) : ProblemDetailsMiddleware + public function __invoke(ContainerInterface $container): ProblemDetailsMiddleware { return new ProblemDetailsMiddleware($container->get(ProblemDetailsResponseFactory::class)); } diff --git a/src/ProblemDetailsNotFoundHandler.php b/src/ProblemDetailsNotFoundHandler.php index 20f8555..8b02268 100644 --- a/src/ProblemDetailsNotFoundHandler.php +++ b/src/ProblemDetailsNotFoundHandler.php @@ -20,9 +20,7 @@ class ProblemDetailsNotFoundHandler implements MiddlewareInterface { - /** - * @var ProblemDetailsResponseFactory - */ + /** @var ProblemDetailsResponseFactory */ private $responseFactory; /** @@ -37,7 +35,7 @@ public function __construct(ProblemDetailsResponseFactory $responseFactory) /** * Creates and returns a 404 response. */ - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { // If we cannot provide a representation, act as a no-op. if (! $this->canActAsErrorHandler($request)) { @@ -54,7 +52,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface /** * Can the middleware act as an error handler? */ - private function canActAsErrorHandler(ServerRequestInterface $request) : bool + private function canActAsErrorHandler(ServerRequestInterface $request): bool { $accept = $request->getHeaderLine('Accept') ?: '*/*'; diff --git a/src/ProblemDetailsNotFoundHandlerFactory.php b/src/ProblemDetailsNotFoundHandlerFactory.php index 8a4f31f..804119e 100644 --- a/src/ProblemDetailsNotFoundHandlerFactory.php +++ b/src/ProblemDetailsNotFoundHandlerFactory.php @@ -14,7 +14,7 @@ class ProblemDetailsNotFoundHandlerFactory { - public function __invoke(ContainerInterface $container) : ProblemDetailsNotFoundHandler + public function __invoke(ContainerInterface $container): ProblemDetailsNotFoundHandler { return new ProblemDetailsNotFoundHandler($container->get(ProblemDetailsResponseFactory::class)); } diff --git a/src/ProblemDetailsResponseFactory.php b/src/ProblemDetailsResponseFactory.php index 0c91680..e98a4e4 100644 --- a/src/ProblemDetailsResponseFactory.php +++ b/src/ProblemDetailsResponseFactory.php @@ -4,6 +4,8 @@ * @see https://github.com/mezzio/mezzio-problem-details for the canonical source repository * @copyright https://github.com/mezzio/mezzio-problem-details/blob/master/COPYRIGHT.md * @license https://github.com/mezzio/mezzio-problem-details/blob/master/LICENSE.md New BSD License + * + * phpcs:disable WebimpressCodingStandard.Namespaces.UnusedUseStatement.UnusedUse */ declare(strict_types=1); @@ -30,6 +32,7 @@ use function preg_replace; use function print_r; use function sprintf; +use function str_replace; use function strpos; use const JSON_PARTIAL_OUTPUT_ON_ERROR; @@ -79,36 +82,36 @@ class ProblemDetailsResponseFactory */ public const DEFAULT_TITLE_MAP = [ // 4×× Client Error - StatusCode::STATUS_BAD_REQUEST => 'Bad Request', - StatusCode::STATUS_UNAUTHORIZED => 'Unauthorized', - StatusCode::STATUS_PAYMENT_REQUIRED => 'Payment Required', - StatusCode::STATUS_FORBIDDEN => 'Forbidden', - StatusCode::STATUS_NOT_FOUND => 'Not Found', - StatusCode::STATUS_METHOD_NOT_ALLOWED => 'Method Not Allowed', - StatusCode::STATUS_NOT_ACCEPTABLE => 'Not Acceptable', - StatusCode::STATUS_PROXY_AUTHENTICATION_REQUIRED => 'Proxy Authentication Required', - StatusCode::STATUS_REQUEST_TIMEOUT => 'Request Timeout', - StatusCode::STATUS_CONFLICT => 'Conflict', - StatusCode::STATUS_GONE => 'Gone', - StatusCode::STATUS_LENGTH_REQUIRED => 'Length Required', - StatusCode::STATUS_PRECONDITION_FAILED => 'Precondition Failed', - StatusCode::STATUS_PAYLOAD_TOO_LARGE => 'Payload Too Large', - StatusCode::STATUS_URI_TOO_LONG => 'Request-URI Too Long', - StatusCode::STATUS_UNSUPPORTED_MEDIA_TYPE => 'Unsupported Media Type', - StatusCode::STATUS_RANGE_NOT_SATISFIABLE => 'Requested Range Not Satisfiable', - StatusCode::STATUS_EXPECTATION_FAILED => 'Expectation Failed', - StatusCode::STATUS_IM_A_TEAPOT => 'I\'m a teapot', - StatusCode::STATUS_MISDIRECTED_REQUEST => 'Misdirected Request', - StatusCode::STATUS_UNPROCESSABLE_ENTITY => 'Unprocessable Entity', - StatusCode::STATUS_LOCKED => 'Locked', - StatusCode::STATUS_FAILED_DEPENDENCY => 'Failed Dependency', - StatusCode::STATUS_UPGRADE_REQUIRED => 'Upgrade Required', - StatusCode::STATUS_PRECONDITION_REQUIRED => 'Precondition Required', - StatusCode::STATUS_TOO_MANY_REQUESTS => 'Too Many Requests', - StatusCode::STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE => 'Request Header Fields Too Large', - 444 => 'Connection Closed Without Response', - StatusCode::STATUS_UNAVAILABLE_FOR_LEGAL_REASONS => 'Unavailable For Legal Reasons', - 499 => 'Client Closed Request', + StatusCode::STATUS_BAD_REQUEST => 'Bad Request', + StatusCode::STATUS_UNAUTHORIZED => 'Unauthorized', + StatusCode::STATUS_PAYMENT_REQUIRED => 'Payment Required', + StatusCode::STATUS_FORBIDDEN => 'Forbidden', + StatusCode::STATUS_NOT_FOUND => 'Not Found', + StatusCode::STATUS_METHOD_NOT_ALLOWED => 'Method Not Allowed', + StatusCode::STATUS_NOT_ACCEPTABLE => 'Not Acceptable', + StatusCode::STATUS_PROXY_AUTHENTICATION_REQUIRED => 'Proxy Authentication Required', + StatusCode::STATUS_REQUEST_TIMEOUT => 'Request Timeout', + StatusCode::STATUS_CONFLICT => 'Conflict', + StatusCode::STATUS_GONE => 'Gone', + StatusCode::STATUS_LENGTH_REQUIRED => 'Length Required', + StatusCode::STATUS_PRECONDITION_FAILED => 'Precondition Failed', + StatusCode::STATUS_PAYLOAD_TOO_LARGE => 'Payload Too Large', + StatusCode::STATUS_URI_TOO_LONG => 'Request-URI Too Long', + StatusCode::STATUS_UNSUPPORTED_MEDIA_TYPE => 'Unsupported Media Type', + StatusCode::STATUS_RANGE_NOT_SATISFIABLE => 'Requested Range Not Satisfiable', + StatusCode::STATUS_EXPECTATION_FAILED => 'Expectation Failed', + StatusCode::STATUS_IM_A_TEAPOT => 'I\'m a teapot', + StatusCode::STATUS_MISDIRECTED_REQUEST => 'Misdirected Request', + StatusCode::STATUS_UNPROCESSABLE_ENTITY => 'Unprocessable Entity', + StatusCode::STATUS_LOCKED => 'Locked', + StatusCode::STATUS_FAILED_DEPENDENCY => 'Failed Dependency', + StatusCode::STATUS_UPGRADE_REQUIRED => 'Upgrade Required', + StatusCode::STATUS_PRECONDITION_REQUIRED => 'Precondition Required', + StatusCode::STATUS_TOO_MANY_REQUESTS => 'Too Many Requests', + StatusCode::STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE => 'Request Header Fields Too Large', + 444 => 'Connection Closed Without Response', + StatusCode::STATUS_UNAVAILABLE_FOR_LEGAL_REASONS => 'Unavailable For Legal Reasons', + 499 => 'Client Closed Request', // 5×× Server Error StatusCode::STATUS_INTERNAL_SERVER_ERROR => 'Internal Server Error', StatusCode::STATUS_NOT_IMPLEMENTED => 'Not Implemented', @@ -217,16 +220,16 @@ class ProblemDetailsResponseFactory public function __construct( callable $responseFactory, bool $isDebug = self::EXCLUDE_THROWABLE_DETAILS, - int $jsonFlags = null, + ?int $jsonFlags = null, bool $exceptionDetailsInResponse = false, string $defaultDetailMessage = self::DEFAULT_DETAIL_MESSAGE, array $defaultTypesMap = [] ) { // Ensures type safety of the composed factory - $this->responseFactory = function () use ($responseFactory) : ResponseInterface { + $this->responseFactory = function () use ($responseFactory): ResponseInterface { return $responseFactory(); }; - $this->isDebug = $isDebug; + $this->isDebug = $isDebug; if (! $jsonFlags) { $jsonFlags = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE @@ -236,10 +239,10 @@ public function __construct( $jsonFlags = JSON_PRETTY_PRINT | $jsonFlags; } } - $this->jsonFlags = $jsonFlags; + $this->jsonFlags = $jsonFlags; $this->exceptionDetailsInResponse = $exceptionDetailsInResponse; - $this->defaultDetailMessage = $defaultDetailMessage; - $this->defaultTypesMap = $defaultTypesMap; + $this->defaultDetailMessage = $defaultDetailMessage; + $this->defaultTypesMap = $defaultTypesMap; } public function createResponse( @@ -249,7 +252,7 @@ public function createResponse( string $title = '', string $type = '', array $additional = [] - ) : ResponseInterface { + ): ResponseInterface { $status = $this->normalizeStatus($status); $title = $title ?: $this->createTitleFromStatus($status); $type = $type ?: $this->createTypeFromStatus($status); @@ -280,7 +283,7 @@ public function createResponse( public function createResponseFromThrowable( ServerRequestInterface $request, Throwable $e - ) : ResponseInterface { + ): ResponseInterface { if ($e instanceof Exception\ProblemDetailsExceptionInterface) { return $this->createResponse( $request, @@ -292,9 +295,10 @@ public function createResponseFromThrowable( ); } - $detail = $this->isDebug || $this->exceptionDetailsInResponse ? $e->getMessage() : $this->defaultDetailMessage; + $detail = $this->isDebug + || $this->exceptionDetailsInResponse ? $e->getMessage() : $this->defaultDetailMessage; $additionalDetails = $this->isDebug ? $this->createThrowableDetail($e) : []; - $code = $this->isDebug || $this->exceptionDetailsInResponse ? $this->getThrowableCode($e) : 500; + $code = $this->isDebug || $this->exceptionDetailsInResponse ? $this->getThrowableCode($e) : 500; return $this->createResponse( $request, @@ -306,14 +310,14 @@ public function createResponseFromThrowable( ); } - protected function getThrowableCode(Throwable $e) : int + protected function getThrowableCode(Throwable $e): int { $code = $e->getCode(); return is_int($code) ? $code : 0; } - protected function generateJsonResponse(array $payload) : ResponseInterface + protected function generateJsonResponse(array $payload): ResponseInterface { return $this->generateResponse( $payload['status'], @@ -330,15 +334,15 @@ private function cleanKeysForXml(array $input): array { $return = []; foreach ($input as $key => $value) { - $key = str_replace("\n", '_', $key); + $key = str_replace("\n", '_', $key); $startCharacterPattern = '[A-Z]|_|[a-z]|[\xC0-\xD6]|[\xD8-\xF6]|[\xF8-\x{2FF}]|[\x{370}-\x{37D}]|[\x{37F}-\x{1FFF}]|' . '[\x{200C}-\x{200D}]|[\x{2070}-\x{218F}]|[\x{2C00}-\x{2FEF}]|[\x{3001}-\x{D7FF}]|[\x{F900}-\x{FDCF}]' . '|[\x{FDF0}-\x{FFFD}]'; - $characterPattern = $startCharacterPattern . '|\-|\.|[0-9]|\xB7|[\x{300}-\x{36F}]|[\x{203F}-\x{2040}]'; + $characterPattern = $startCharacterPattern . '|\-|\.|[0-9]|\xB7|[\x{300}-\x{36F}]|[\x{203F}-\x{2040}]'; - $key = preg_replace('/(?!'.$characterPattern.')./u', '_', $key); - $key = preg_replace('/^(?!'.$startCharacterPattern.')./u', '_', $key); + $key = preg_replace('/(?!' . $characterPattern . ')./u', '_', $key); + $key = preg_replace('/^(?!' . $startCharacterPattern . ')./u', '_', $key); if (is_array($value)) { $value = $this->cleanKeysForXml($value); @@ -348,7 +352,7 @@ private function cleanKeysForXml(array $input): array return $return; } - protected function generateXmlResponse(array $payload) : ResponseInterface + protected function generateXmlResponse(array $payload): ResponseInterface { // Ensure any objects are flattened to arrays first $content = json_decode(json_encode($payload), true); @@ -357,8 +361,8 @@ protected function generateXmlResponse(array $payload) : ResponseInterface $cleanedContent = $this->cleanKeysForXml($content); $converter = new ArrayToXml($cleanedContent, 'problem'); - $dom = $converter->toDom(); - $root = $dom->firstChild; + $dom = $converter->toDom(); + $root = $dom->firstChild; $root->setAttribute('xmlns', 'urn:ietf:rfc:7807'); return $this->generateResponse( @@ -368,7 +372,7 @@ protected function generateXmlResponse(array $payload) : ResponseInterface ); } - protected function generateResponse(int $status, string $contentType, string $payload) : ResponseInterface + protected function generateResponse(int $status, string $contentType, string $payload): ResponseInterface { $response = ($this->responseFactory)(); $response->getBody()->write($payload); @@ -378,7 +382,7 @@ protected function generateResponse(int $status, string $contentType, string $pa ->withHeader('Content-Type', $contentType); } - private function getResponseGenerator(ServerRequestInterface $request) : callable + private function getResponseGenerator(ServerRequestInterface $request): callable { $accept = $request->getHeaderLine('Accept') ?: '*/*'; $mediaType = (new Negotiator())->getBest($accept, self::NEGOTIATION_PRIORITIES); @@ -388,7 +392,7 @@ private function getResponseGenerator(ServerRequestInterface $request) : callabl : Closure::fromCallable([$this, 'generateJsonResponse']); } - private function normalizeStatus(int $status) : int + private function normalizeStatus(int $status): int { if ($status < 400 || $status > 599) { return 500; @@ -397,17 +401,17 @@ private function normalizeStatus(int $status) : int return $status; } - private function createTitleFromStatus(int $status) : string + private function createTitleFromStatus(int $status): string { return self::DEFAULT_TITLE_MAP[$status] ?? 'Unknown Error'; } - private function createTypeFromStatus(int $status) : string + private function createTypeFromStatus(int $status): string { return $this->defaultTypesMap[$status] ?? sprintf('https://httpstatus.es/%s', $status); } - private function createThrowableDetail(Throwable $e) : array + private function createThrowableDetail(Throwable $e): array { $detail = [ 'class' => get_class($e), diff --git a/src/ProblemDetailsResponseFactoryFactory.php b/src/ProblemDetailsResponseFactoryFactory.php index 265f86e..e7d1d58 100644 --- a/src/ProblemDetailsResponseFactoryFactory.php +++ b/src/ProblemDetailsResponseFactoryFactory.php @@ -15,14 +15,14 @@ class ProblemDetailsResponseFactoryFactory { - public function __invoke(ContainerInterface $container) : ProblemDetailsResponseFactory + public function __invoke(ContainerInterface $container): ProblemDetailsResponseFactory { - $config = $container->has('config') ? $container->get('config') : []; + $config = $container->has('config') ? $container->get('config') : []; $includeThrowableDetail = $config['debug'] ?? ProblemDetailsResponseFactory::EXCLUDE_THROWABLE_DETAILS; $problemDetailsConfig = $config['problem-details'] ?? []; - $jsonFlags = $problemDetailsConfig['json_flags'] ?? null; - $defaultTypesMap = $problemDetailsConfig['default_types_map'] ?? []; + $jsonFlags = $problemDetailsConfig['json_flags'] ?? null; + $defaultTypesMap = $problemDetailsConfig['default_types_map'] ?? []; return new ProblemDetailsResponseFactory( $container->get(ResponseInterface::class), diff --git a/test/ConfigProviderTest.php b/test/ConfigProviderTest.php index dbf4cdc..cdb6524 100644 --- a/test/ConfigProviderTest.php +++ b/test/ConfigProviderTest.php @@ -19,10 +19,10 @@ class ConfigProviderTest extends TestCase { - public function testReturnsExpectedDependencies() : void + public function testReturnsExpectedDependencies(): void { $provider = new ConfigProvider(); - $config = $provider(); + $config = $provider(); $this->assertArrayHasKey('dependencies', $config); diff --git a/test/Exception/ProblemDetailsExceptionInterfaceTest.php b/test/Exception/ProblemDetailsExceptionInterfaceTest.php index 039ca18..19fd540 100644 --- a/test/Exception/ProblemDetailsExceptionInterfaceTest.php +++ b/test/Exception/ProblemDetailsExceptionInterfaceTest.php @@ -18,17 +18,17 @@ use function json_decode; use function json_encode; -class ProblemDetailsExceptionTest extends TestCase +class ProblemDetailsExceptionInterfaceTest extends TestCase { - protected $status = 403; - protected $detail = 'You are not authorized to do that'; - protected $title = 'Unauthorized'; - protected $type = 'https://httpstatus.es/403'; + protected $status = 403; + protected $detail = 'You are not authorized to do that'; + protected $title = 'Unauthorized'; + protected $type = 'https://httpstatus.es/403'; protected $additional = [ 'foo' => 'bar', ]; - protected function setUp() : void + protected function setUp(): void { $this->exception = new class ( $this->status, @@ -41,16 +41,16 @@ protected function setUp() : void public function __construct(int $status, string $detail, string $title, string $type, array $additional) { - $this->status = $status; - $this->detail = $detail; - $this->title = $title; - $this->type = $type; + $this->status = $status; + $this->detail = $detail; + $this->title = $title; + $this->type = $type; $this->additional = $additional; } }; } - public function testCanPullDetailsIndividually() : void + public function testCanPullDetailsIndividually(): void { $this->assertEquals($this->status, $this->exception->getStatus()); $this->assertEquals($this->detail, $this->exception->getDetail()); @@ -59,7 +59,7 @@ public function testCanPullDetailsIndividually() : void $this->assertEquals($this->additional, $this->exception->getAdditionalData()); } - public function testCanCastDetailsToArray() : void + public function testCanCastDetailsToArray(): void { $this->assertEquals([ 'status' => $this->status, @@ -70,7 +70,7 @@ public function testCanCastDetailsToArray() : void ], $this->exception->toArray()); } - public function testIsJsonSerializable() : void + public function testIsJsonSerializable(): void { $problem = json_decode(json_encode($this->exception), true); diff --git a/test/ProblemDetailsAssertionsTrait.php b/test/ProblemDetailsAssertionsTrait.php index 375e10e..97d23c5 100644 --- a/test/ProblemDetailsAssertionsTrait.php +++ b/test/ProblemDetailsAssertionsTrait.php @@ -25,7 +25,7 @@ trait ProblemDetailsAssertionsTrait { - public function assertProblemDetails(array $expected, array $details) : void + public function assertProblemDetails(array $expected, array $details): void { foreach ($expected as $key => $value) { $this->assertArrayHasKey( @@ -43,7 +43,7 @@ public function assertProblemDetails(array $expected, array $details) : void } } - public function assertExceptionDetails(Throwable $e, array $details) : void + public function assertExceptionDetails(Throwable $e, array $details): void { $this->assertArrayHasKey('class', $details); $this->assertSame(get_class($e), $details['class']); @@ -70,7 +70,7 @@ public function prepareResponsePayloadAssertions( string $contentType, MockObject $stream, callable $assertion - ) : void { + ): void { if ('application/problem+json' === $contentType) { $this->preparePayloadForJsonResponse($stream, $assertion); return; @@ -85,7 +85,7 @@ public function prepareResponsePayloadAssertions( /** * @param StreamInterface|MockObject $stream */ - public function preparePayloadForJsonResponse(MockObject $stream, callable $assertion) : void + public function preparePayloadForJsonResponse(MockObject $stream, callable $assertion): void { $stream ->expects($this->any()) @@ -101,7 +101,7 @@ public function preparePayloadForJsonResponse(MockObject $stream, callable $asse /** * @param StreamInterface|MockObject $stream */ - public function preparePayloadForXmlResponse(MockObject $stream, callable $assertion) : void + public function preparePayloadForXmlResponse(MockObject $stream, callable $assertion): void { $stream ->expects($this->any()) @@ -114,10 +114,10 @@ public function preparePayloadForXmlResponse(MockObject $stream, callable $asser })); } - public function deserializeXmlPayload(string $xml) : array + public function deserializeXmlPayload(string $xml): array { - $xml = simplexml_load_string($xml); - $json = json_encode($xml); + $xml = simplexml_load_string($xml); + $json = json_encode($xml); $payload = json_decode($json, true); // Ensure ints and floats are properly represented diff --git a/test/ProblemDetailsMiddlewareFactoryTest.php b/test/ProblemDetailsMiddlewareFactoryTest.php index cbbf0f7..c06e2fd 100644 --- a/test/ProblemDetailsMiddlewareFactoryTest.php +++ b/test/ProblemDetailsMiddlewareFactoryTest.php @@ -16,6 +16,7 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Container\ContainerInterface; +use ReflectionObject; use RuntimeException; class ProblemDetailsMiddlewareFactoryTest extends TestCase @@ -23,10 +24,10 @@ class ProblemDetailsMiddlewareFactoryTest extends TestCase /** @var ContainerInterface|MockObject */ private $container; - protected function setUp() : void + protected function setUp(): void { $this->container = $this->createMock(ContainerInterface::class); - $this->factory = new ProblemDetailsMiddlewareFactory(); + $this->factory = new ProblemDetailsMiddlewareFactory(); } public function testRaisesExceptionWhenProblemDetailsResponseFactoryServiceIsNotAvailable() @@ -41,7 +42,7 @@ public function testRaisesExceptionWhenProblemDetailsResponseFactoryServiceIsNot $this->factory->__invoke($this->container); } - public function testCreatesMiddlewareUsingResponseFactoryService() : void + public function testCreatesMiddlewareUsingResponseFactoryService(): void { $responseFactory = $this->createMock(ProblemDetailsResponseFactory::class); @@ -52,7 +53,7 @@ public function testCreatesMiddlewareUsingResponseFactoryService() : void $middleware = ($this->factory)($this->container); - $r = (new \ReflectionObject($middleware))->getProperty('responseFactory'); + $r = (new ReflectionObject($middleware))->getProperty('responseFactory'); $r->setAccessible(true); $this->assertInstanceOf(ProblemDetailsMiddleware::class, $middleware); diff --git a/test/ProblemDetailsMiddlewareTest.php b/test/ProblemDetailsMiddlewareTest.php index 37d2b6c..98c779d 100644 --- a/test/ProblemDetailsMiddlewareTest.php +++ b/test/ProblemDetailsMiddlewareTest.php @@ -14,7 +14,6 @@ use Mezzio\ProblemDetails\ProblemDetailsMiddleware; use Mezzio\ProblemDetails\ProblemDetailsResponseFactory; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; @@ -27,14 +26,14 @@ class ProblemDetailsMiddlewareTest extends TestCase { use ProblemDetailsAssertionsTrait; - protected function setUp() : void + protected function setUp(): void { - $this->request = $this->createMock(ServerRequestInterface::class); + $this->request = $this->createMock(ServerRequestInterface::class); $this->responseFactory = $this->createMock(ProblemDetailsResponseFactory::class); - $this->middleware = new ProblemDetailsMiddleware($this->responseFactory); + $this->middleware = new ProblemDetailsMiddleware($this->responseFactory); } - public function acceptHeaders() : array + public function acceptHeaders(): array { return [ 'empty' => [''], @@ -45,10 +44,10 @@ public function acceptHeaders() : array ]; } - public function testSuccessfulDelegationReturnsHandlerResponse() : void + public function testSuccessfulDelegationReturnsHandlerResponse(): void { $response = $this->createMock(ResponseInterface::class); - $handler = $this->createMock(RequestHandlerInterface::class); + $handler = $this->createMock(RequestHandlerInterface::class); $handler ->method('handle') ->with($this->request) @@ -62,7 +61,7 @@ public function testSuccessfulDelegationReturnsHandlerResponse() : void /** * @dataProvider acceptHeaders */ - public function testThrowableRaisedByHandlerResultsInProblemDetails(string $accept) : void + public function testThrowableRaisedByHandlerResultsInProblemDetails(string $accept): void { $this->request ->method('getHeaderLine') @@ -71,7 +70,7 @@ public function testThrowableRaisedByHandlerResultsInProblemDetails(string $acce $exception = new TestAsset\RuntimeException('Thrown!', 507); - $handler = $this->createMock(RequestHandlerInterface::class); + $handler = $this->createMock(RequestHandlerInterface::class); $handler ->method('handle') ->with($this->request) @@ -91,7 +90,7 @@ public function testThrowableRaisedByHandlerResultsInProblemDetails(string $acce /** * @dataProvider acceptHeaders */ - public function testMiddlewareRegistersErrorHandlerToConvertErrorsToProblemDetails(string $accept) : void + public function testMiddlewareRegistersErrorHandlerToConvertErrorsToProblemDetails(string $accept): void { $this->request ->method('getHeaderLine') @@ -122,7 +121,7 @@ public function testMiddlewareRegistersErrorHandlerToConvertErrorsToProblemDetai $this->assertSame($expected, $result); } - public function testRethrowsCaughtExceptionIfUnableToNegotiateAcceptHeader() : void + public function testRethrowsCaughtExceptionIfUnableToNegotiateAcceptHeader(): void { $this->request ->method('getHeaderLine') @@ -130,7 +129,7 @@ public function testRethrowsCaughtExceptionIfUnableToNegotiateAcceptHeader() : v ->willReturn('text/html'); $exception = new TestAsset\RuntimeException('Thrown!', 507); - $handler = $this->createMock(RequestHandlerInterface::class); + $handler = $this->createMock(RequestHandlerInterface::class); $handler ->method('handle') ->with($this->request) @@ -145,7 +144,7 @@ public function testRethrowsCaughtExceptionIfUnableToNegotiateAcceptHeader() : v /** * @dataProvider acceptHeaders */ - public function testErrorHandlingTriggersListeners(string $accept) : void + public function testErrorHandlingTriggersListeners(string $accept): void { $this->request ->method('getHeaderLine') @@ -154,7 +153,7 @@ public function testErrorHandlingTriggersListeners(string $accept) : void $exception = new TestAsset\RuntimeException('Thrown!', 507); - $handler = $this->createMock(RequestHandlerInterface::class); + $handler = $this->createMock(RequestHandlerInterface::class); $handler ->method('handle') ->with($this->request) @@ -166,7 +165,7 @@ public function testErrorHandlingTriggersListeners(string $accept) : void ->with($this->request, $exception) ->willReturn($expected); - $listener = function ($error, $request, $response) use ($exception, $expected) { + $listener = function ($error, $request, $response) use ($exception, $expected) { $this->assertSame($exception, $error, 'Listener did not receive same exception as was raised'); $this->assertSame($this->request, $request, 'Listener did not receive same request'); $this->assertSame($expected, $response, 'Listener did not receive same response'); diff --git a/test/ProblemDetailsNotFoundHandlerFactoryTest.php b/test/ProblemDetailsNotFoundHandlerFactoryTest.php index c6ea3d8..473d4fc 100644 --- a/test/ProblemDetailsNotFoundHandlerFactoryTest.php +++ b/test/ProblemDetailsNotFoundHandlerFactoryTest.php @@ -15,14 +15,15 @@ use Mezzio\ProblemDetails\ProblemDetailsResponseFactory; use PHPUnit\Framework\TestCase; use Psr\Container\ContainerInterface; +use ReflectionObject; use RuntimeException; class ProblemDetailsNotFoundHandlerFactoryTest extends TestCase { - protected function setUp() : void + protected function setUp(): void { $this->container = $this->createMock(ContainerInterface::class); - $this->factory = new ProblemDetailsNotFoundHandlerFactory(); + $this->factory = new ProblemDetailsNotFoundHandlerFactory(); } public function testRaisesExceptionWhenProblemDetailsResponseFactoryServiceIsNotAvailable() @@ -37,7 +38,7 @@ public function testRaisesExceptionWhenProblemDetailsResponseFactoryServiceIsNot $this->factory->__invoke($this->container); } - public function testCreatesNotFoundHandlerUsingResponseFactoryService() : void + public function testCreatesNotFoundHandlerUsingResponseFactoryService(): void { $responseFactory = $this->createMock(ProblemDetailsResponseFactory::class); $this->container @@ -47,7 +48,7 @@ public function testCreatesNotFoundHandlerUsingResponseFactoryService() : void $notFoundHandler = ($this->factory)($this->container); - $r = (new \ReflectionObject($notFoundHandler))->getProperty('responseFactory'); + $r = (new ReflectionObject($notFoundHandler))->getProperty('responseFactory'); $r->setAccessible(true); $this->assertInstanceOf(ProblemDetailsNotFoundHandler::class, $notFoundHandler); diff --git a/test/ProblemDetailsNotFoundHandlerTest.php b/test/ProblemDetailsNotFoundHandlerTest.php index 27bf9e0..aed32f6 100644 --- a/test/ProblemDetailsNotFoundHandlerTest.php +++ b/test/ProblemDetailsNotFoundHandlerTest.php @@ -13,7 +13,6 @@ use Mezzio\ProblemDetails\ProblemDetailsNotFoundHandler; use Mezzio\ProblemDetails\ProblemDetailsResponseFactory; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; @@ -22,12 +21,12 @@ class ProblemDetailsNotFoundHandlerTest extends TestCase { use ProblemDetailsAssertionsTrait; - protected function setUp() : void + protected function setUp(): void { $this->responseFactory = $this->createMock(ProblemDetailsResponseFactory::class); } - public function acceptHeaders() : array + public function acceptHeaders(): array { return [ 'application/json' => ['application/json', 'application/problem+json'], @@ -38,7 +37,7 @@ public function acceptHeaders() : array /** * @dataProvider acceptHeaders */ - public function testResponseFactoryPassedInConstructorGeneratesTheReturnedResponse(string $acceptHeader) : void + public function testResponseFactoryPassedInConstructorGeneratesTheReturnedResponse(string $acceptHeader): void { $request = $this->createMock(ServerRequestInterface::class); $request->method('getMethod')->willReturn('POST'); @@ -63,7 +62,7 @@ public function testResponseFactoryPassedInConstructorGeneratesTheReturnedRespon ); } - public function testHandlerIsCalledIfAcceptHeaderIsUnacceptable() : void + public function testHandlerIsCalledIfAcceptHeaderIsUnacceptable(): void { $request = $this->createMock(ServerRequestInterface::class); $request->method('getMethod')->willReturn('POST'); diff --git a/test/ProblemDetailsResponseFactoryFactoryTest.php b/test/ProblemDetailsResponseFactoryFactoryTest.php index 4892fe3..acd613e 100644 --- a/test/ProblemDetailsResponseFactoryFactoryTest.php +++ b/test/ProblemDetailsResponseFactoryFactoryTest.php @@ -17,11 +17,13 @@ use PHPUnit\Framework\TestCase; use Psr\Container\ContainerInterface; use Psr\Http\Message\ResponseInterface; +use ReflectionObject; use ReflectionProperty; use RuntimeException; use stdClass; use TypeError; +use const JSON_PARTIAL_OUTPUT_ON_ERROR; use const JSON_PRESERVE_ZERO_FRACTION; use const JSON_PRETTY_PRINT; use const JSON_UNESCAPED_SLASHES; @@ -29,7 +31,7 @@ class ProblemDetailsResponseFactoryFactoryTest extends TestCase { - protected function setUp() : void + protected function setUp(): void { $this->container = $this->createMock(ContainerInterface::class); } @@ -46,7 +48,7 @@ public function assertResponseFactoryReturns(ResponseInterface $expected, Proble public function testLackOfResponseServiceResultsInException() { $factory = new ProblemDetailsResponseFactoryFactory(); - $e = new RuntimeException(); + $e = new RuntimeException(); $this->container->method('has')->with('config')->willReturn(false); $this->container->method('get')->with(ResponseInterface::class)->willThrowException($e); @@ -60,13 +62,13 @@ public function testNonCallableResponseServiceResultsInException() $factory = new ProblemDetailsResponseFactoryFactory(); $this->container->method('has')->with('config')->willReturn(false); - $this->container->method('get')->with(ResponseInterface::class)->willReturn(new stdClass); + $this->container->method('get')->with(ResponseInterface::class)->willReturn(new stdClass()); $this->expectException(TypeError::class); $factory($this->container); } - public function testLackOfConfigServiceResultsInFactoryUsingDefaults() : void + public function testLackOfConfigServiceResultsInFactoryUsingDefaults(): void { $this->container->method('has')->with('config')->willReturn(false); @@ -79,15 +81,15 @@ public function testLackOfConfigServiceResultsInFactoryUsingDefaults() : void }); $factoryFactory = new ProblemDetailsResponseFactoryFactory(); - $factory = $factoryFactory($this->container); + $factory = $factoryFactory($this->container); - $isDebug = (new \ReflectionObject($factory))->getProperty('isDebug'); + $isDebug = (new ReflectionObject($factory))->getProperty('isDebug'); $isDebug->setAccessible(true); - $jsonFlags = (new \ReflectionObject($factory))->getProperty('jsonFlags'); + $jsonFlags = (new ReflectionObject($factory))->getProperty('jsonFlags'); $jsonFlags->setAccessible(true); - $responseFactory = (new \ReflectionObject($factory))->getProperty('responseFactory'); + $responseFactory = (new ReflectionObject($factory))->getProperty('responseFactory'); $responseFactory->setAccessible(true); $this->assertInstanceOf(ProblemDetailsResponseFactory::class, $factory); @@ -104,7 +106,7 @@ public function testLackOfConfigServiceResultsInFactoryUsingDefaults() : void $this->assertResponseFactoryReturns($response, $factory); } - public function testUsesPrettyPrintFlagOnEnabledDebugMode() : void + public function testUsesPrettyPrintFlagOnEnabledDebugMode(): void { $this->container->method('has')->with('config')->willReturn(true); @@ -112,20 +114,23 @@ public function testUsesPrettyPrintFlagOnEnabledDebugMode() : void ->method('get') ->willReturnMap([ ['config', ['debug' => true]], - [ResponseInterface::class, function () { - }] + [ + ResponseInterface::class, + function () { + }, + ], ]); $factoryFactory = new ProblemDetailsResponseFactoryFactory(); - $factory = $factoryFactory($this->container); + $factory = $factoryFactory($this->container); - $jsonFlags = (new \ReflectionObject($factory))->getProperty('jsonFlags'); + $jsonFlags = (new ReflectionObject($factory))->getProperty('jsonFlags'); $jsonFlags->setAccessible(true); $this->assertSame(JSON_PRETTY_PRINT, $jsonFlags->getValue($factory) & JSON_PRETTY_PRINT); } - public function testUsesDebugSettingFromConfigWhenPresent() : void + public function testUsesDebugSettingFromConfigWhenPresent(): void { $this->container->method('has')->with('config')->willReturn(true); @@ -133,17 +138,20 @@ public function testUsesDebugSettingFromConfigWhenPresent() : void ->method('get') ->willReturnMap([ ['config', ['debug' => true]], - [ResponseInterface::class, function () { - }] + [ + ResponseInterface::class, + function () { + }, + ], ]); $factoryFactory = new ProblemDetailsResponseFactoryFactory(); - $factory = $factoryFactory($this->container); + $factory = $factoryFactory($this->container); - $isDebug = (new \ReflectionObject($factory))->getProperty('isDebug'); + $isDebug = (new ReflectionObject($factory))->getProperty('isDebug'); $isDebug->setAccessible(true); - $exceptionDetailsInResponse = (new \ReflectionObject($factory))->getProperty('exceptionDetailsInResponse'); + $exceptionDetailsInResponse = (new ReflectionObject($factory))->getProperty('exceptionDetailsInResponse'); $exceptionDetailsInResponse->setAccessible(true); $this->assertInstanceOf(ProblemDetailsResponseFactory::class, $factory); @@ -151,7 +159,7 @@ public function testUsesDebugSettingFromConfigWhenPresent() : void $this->assertSame(true, $exceptionDetailsInResponse->getValue($factory)); } - public function testUsesJsonFlagsSettingFromConfigWhenPresent() : void + public function testUsesJsonFlagsSettingFromConfigWhenPresent(): void { $this->container->method('has')->with('config')->willReturn(true); @@ -159,21 +167,24 @@ public function testUsesJsonFlagsSettingFromConfigWhenPresent() : void ->method('get') ->willReturnMap([ ['config', ['problem-details' => ['json_flags' => JSON_PRETTY_PRINT]]], - [ResponseInterface::class, function () { - }] + [ + ResponseInterface::class, + function () { + }, + ], ]); $factoryFactory = new ProblemDetailsResponseFactoryFactory(); - $factory = $factoryFactory($this->container); + $factory = $factoryFactory($this->container); - $jsonFlags = (new \ReflectionObject($factory))->getProperty('jsonFlags'); + $jsonFlags = (new ReflectionObject($factory))->getProperty('jsonFlags'); $jsonFlags->setAccessible(true); $this->assertInstanceOf(ProblemDetailsResponseFactory::class, $factory); - $this->assertSame(JSON_PRETTY_PRINT, $jsonFlags->getValue($factory)); + $this->assertSame(JSON_PRETTY_PRINT, $jsonFlags->getValue($factory)); } - public function testUsesDefaultTypesSettingFromConfigWhenPresent() : void + public function testUsesDefaultTypesSettingFromConfigWhenPresent(): void { $expectedDefaultTypes = [ 404 => 'https://example.com/problem-details/error/not-found', @@ -185,14 +196,17 @@ public function testUsesDefaultTypesSettingFromConfigWhenPresent() : void ->method('get') ->willReturnMap([ ['config', ['problem-details' => ['default_types_map' => $expectedDefaultTypes]]], - [ResponseInterface::class, function () { - }] + [ + ResponseInterface::class, + function () { + }, + ], ]); $factoryFactory = new ProblemDetailsResponseFactoryFactory(); - $factory = $factoryFactory($this->container); + $factory = $factoryFactory($this->container); - $defaultTypesMap = (new \ReflectionObject($factory))->getProperty('defaultTypesMap'); + $defaultTypesMap = (new ReflectionObject($factory))->getProperty('defaultTypesMap'); $defaultTypesMap->setAccessible(true); $this->assertInstanceOf(ProblemDetailsResponseFactory::class, $factory); diff --git a/test/ProblemDetailsResponseFactoryTest.php b/test/ProblemDetailsResponseFactoryTest.php index 3c0c6d8..7fbf55c 100644 --- a/test/ProblemDetailsResponseFactoryTest.php +++ b/test/ProblemDetailsResponseFactoryTest.php @@ -22,6 +22,7 @@ use RuntimeException; use function array_keys; +use function chr; use function fclose; use function fopen; use function json_decode; @@ -42,16 +43,16 @@ class ProblemDetailsResponseFactoryTest extends TestCase private const UTF_8_INVALID_2_OCTET_SEQUENCE = "\xc3\x28"; - protected function setUp() : void + protected function setUp(): void { - $this->request = $this->createMock(ServerRequestInterface::class); + $this->request = $this->createMock(ServerRequestInterface::class); $this->response = $this->createMock(ResponseInterface::class); - $this->factory = new ProblemDetailsResponseFactory(function () { + $this->factory = new ProblemDetailsResponseFactory(function () { return $this->response; }); } - public function acceptHeaders() : array + public function acceptHeaders(): array { return [ 'empty' => ['', 'application/problem+json'], @@ -65,7 +66,7 @@ public function acceptHeaders() : array /** * @dataProvider acceptHeaders */ - public function testCreateResponseCreatesExpectedType(string $header, string $expectedType) : void + public function testCreateResponseCreatesExpectedType(string $header, string $expectedType): void { $this->request->method('getHeaderLine')->with('Accept')->willReturn($header); @@ -88,7 +89,7 @@ public function testCreateResponseCreatesExpectedType(string $header, string $ex /** * @dataProvider acceptHeaders */ - public function testCreateResponseFromThrowableCreatesExpectedType(string $header, string $expectedType) : void + public function testCreateResponseFromThrowableCreatesExpectedType(string $header, string $expectedType): void { $this->request->method('getHeaderLine')->with('Accept')->willReturn($header); @@ -100,7 +101,7 @@ public function testCreateResponseFromThrowableCreatesExpectedType(string $heade $this->response->method('withHeader')->with('Content-Type', $expectedType)->willReturn($this->response); $exception = new RuntimeException(); - $response = $this->factory->createResponseFromThrowable( + $response = $this->factory->createResponseFromThrowable( $this->request, $exception ); @@ -114,7 +115,7 @@ public function testCreateResponseFromThrowableCreatesExpectedType(string $heade public function testCreateResponseFromThrowableCreatesExpectedTypeWithExtraInformation( string $header, string $expectedType - ) : void { + ): void { $this->request->method('getHeaderLine')->with('Accept')->willReturn($header); $stream = $this->createMock(StreamInterface::class); @@ -134,7 +135,7 @@ function () { ); $exception = new RuntimeException(); - $response = $factory->createResponseFromThrowable( + $response = $factory->createResponseFromThrowable( $this->request, $exception ); @@ -145,7 +146,7 @@ function () { /** * @dataProvider acceptHeaders */ - public function testCreateResponseRemovesInvalidCharactersFromXmlKeys(string $header, string $expectedType) : void + public function testCreateResponseRemovesInvalidCharactersFromXmlKeys(string $header, string $expectedType): void { $this->request->method('getHeaderLine')->with('Accept')->willReturn($header); @@ -196,7 +197,7 @@ function (array $payload) use ($expectedKeyNames) { $this->assertSame($this->response, $response); } - public function testCreateResponseFromThrowableWillPullDetailsFromProblemDetailsExceptionInterface() : void + public function testCreateResponseFromThrowableWillPullDetailsFromProblemDetailsExceptionInterface(): void { $e = $this->createMock(ProblemDetailsExceptionInterface::class); $e->method('getStatus')->willReturn(400); @@ -239,7 +240,7 @@ function (array $payload) { /** * @dataProvider acceptHeaders */ - public function testCreateResponseRemovesResourcesFromInputData(string $header, string $expectedType) : void + public function testCreateResponseRemovesResourcesFromInputData(string $header, string $expectedType): void { $this->request->method('getHeaderLine')->with('Accept')->willReturn($header); @@ -256,7 +257,7 @@ public function testCreateResponseRemovesResourcesFromInputData(string $header, $this->response->method('withStatus')->with(500)->willReturn($this->response); $this->response->method('withHeader')->with('Content-Type', $expectedType)->willReturn($this->response); - $fh = fopen(__FILE__, 'r'); + $fh = fopen(__FILE__, 'r'); $response = $this->factory->createResponse( $this->request, 500, @@ -266,7 +267,7 @@ public function testCreateResponseRemovesResourcesFromInputData(string $header, [ 'args' => [ 'resource' => $fh, - ] + ], ] ); fclose($fh); @@ -274,7 +275,7 @@ public function testCreateResponseRemovesResourcesFromInputData(string $header, $this->assertSame($this->response, $response); } - public function testFactoryGeneratesXmlResponseIfNegotiationFails() : void + public function testFactoryGeneratesXmlResponseIfNegotiationFails(): void { $this->request->method('getHeaderLine')->with('Accept')->willReturn('text/plain'); @@ -297,7 +298,7 @@ public function testFactoryGeneratesXmlResponseIfNegotiationFails() : void $this->assertSame($this->response, $response); } - public function testFactoryRendersPreviousExceptionsInDebugMode() : void + public function testFactoryRendersPreviousExceptionsInDebugMode(): void { $this->request->method('getHeaderLine')->with('Accept')->willReturn('application/json'); @@ -322,7 +323,7 @@ function (array $payload) { ->with('Content-Type', 'application/problem+json') ->willReturn($this->response); - $first = new RuntimeException('first', 101010); + $first = new RuntimeException('first', 101010); $second = new RuntimeException('second', 101011, $first); $factory = new ProblemDetailsResponseFactory( @@ -343,7 +344,7 @@ function () { public function testFragileDataInExceptionMessageShouldBeHiddenInResponseBodyInNoDebugMode() { $fragileMessage = 'Your SQL or password here'; - $exception = new Exception($fragileMessage); + $exception = new Exception($fragileMessage); $stream = $this->createMock(StreamInterface::class); $stream @@ -394,7 +395,7 @@ function (array $payload) { public function testFragileDataInExceptionMessageShouldBeVisibleInResponseBodyInNonDebugModeWhenAllowToShowByFlag() { $fragileMessage = 'Your SQL or password here'; - $exception = new Exception($fragileMessage); + $exception = new Exception($fragileMessage); $stream = $this->createMock(StreamInterface::class); $this->preparePayloadForJsonResponse( @@ -499,7 +500,7 @@ function (array $payload) { $this->assertSame($this->response, $response); } - public function provideMappedStatuses() : array + public function provideMappedStatuses(): array { $defaultTypesMap = [ 404 => 'https://example.com/problem-details/error/not-found', @@ -517,7 +518,7 @@ public function provideMappedStatuses() : array /** * @dataProvider provideMappedStatuses */ - public function testTypeIsInferredFromDefaultTypesMap(array $map, int $status, string $expectedType) : void + public function testTypeIsInferredFromDefaultTypesMap(array $map, int $status, string $expectedType): void { $this->request->method('getHeaderLine')->with('Accept')->willReturn('application/json'); diff --git a/test/TestAsset/RuntimeException.php b/test/TestAsset/RuntimeException.php index 33f284b..973b66b 100644 --- a/test/TestAsset/RuntimeException.php +++ b/test/TestAsset/RuntimeException.php @@ -16,11 +16,9 @@ class RuntimeException extends BaseRuntimeException { /** - * @param string $message * @param mixed $code Mimic PHP internal exceptions, and allow any code. - * @param Throwable $previous */ - public function __construct(string $message, $code = 0, Throwable $previous = null) + public function __construct(string $message, $code = 0, ?Throwable $previous = null) { parent::__construct($message, 0, $previous); $this->code = $code; From 47e76968b19cb457607956dd6cda3b74452d1e01 Mon Sep 17 00:00:00 2001 From: Geert Eltink Date: Sun, 11 Oct 2020 13:25:15 +0200 Subject: [PATCH 03/11] fix: use correct declaration Signed-off-by: Geert Eltink Signed-off-by: Alejandro Celaya Signed-off-by: Alejandro Celaya --- src/Exception/ProblemDetailsExceptionInterface.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Exception/ProblemDetailsExceptionInterface.php b/src/Exception/ProblemDetailsExceptionInterface.php index ceb3309..2b2f698 100644 --- a/src/Exception/ProblemDetailsExceptionInterface.php +++ b/src/Exception/ProblemDetailsExceptionInterface.php @@ -36,5 +36,5 @@ public function getAdditionalData(): array; */ public function toArray(): array; - public function jsonSerialize(): array; + public function jsonSerialize(); } From f576cee8900fcf0bdfd2a6edc6423a2dd5a8de44 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 23 Jan 2021 16:38:27 +0100 Subject: [PATCH 04/11] Updated willdurand/negotiation to v3 Signed-off-by: Alejandro Celaya Signed-off-by: Alejandro Celaya --- .travis.yml | 4 ++-- composer.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7cceb3f..b617b55 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,10 +26,10 @@ matrix: - DEPS=latest - CS_CHECK=true - TEST_COVERAGE=true - - php: nightly + - php: 8.0 env: - DEPS=lowest - - php: nightly + - php: 8.0 env: - DEPS=latest diff --git a/composer.json b/composer.json index 067959c..448be49 100644 --- a/composer.json +++ b/composer.json @@ -35,7 +35,7 @@ "psr/http-message": "^1.0", "psr/http-server-middleware": "^1.0", "spatie/array-to-xml": "^2.3", - "willdurand/negotiation": "^2.3" + "willdurand/negotiation": "^3.0" }, "require-dev": { "laminas/laminas-coding-standard": "~2.1.0", From 47966f3756b22ccff751fe53044028a3890bbc9f Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 23 Jan 2021 16:44:48 +0100 Subject: [PATCH 05/11] Explicitly imported legacy classes with the Legacy prefix Signed-off-by: Alejandro Celaya Signed-off-by: Alejandro Celaya --- .travis.yml | 2 +- src/ConfigProvider.php | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index b617b55..385dde6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ cache: env: global: - - COMPOSER_ARGS="--no-interaction --ignore-platform-reqs" + - COMPOSER_ARGS="--no-interaction" - COVERAGE_DEPS="php-coveralls/php-coveralls" matrix: diff --git a/src/ConfigProvider.php b/src/ConfigProvider.php index 896c7bc..91aa89a 100644 --- a/src/ConfigProvider.php +++ b/src/ConfigProvider.php @@ -10,12 +10,14 @@ namespace Mezzio\ProblemDetails; +use Zend\ProblemDetails\ProblemDetailsMiddleware as LegacyProblemDetailsMiddleware; +use Zend\ProblemDetails\ProblemDetailsNotFoundHandler as LegacyProblemDetailsNotFoundHandler; +use Zend\ProblemDetails\ProblemDetailsResponseFactory as LegacyProblemDetailsResponseFactory; + /** * Configuration provider for the package. * * @see https://docs.laminas.dev/laminas-component-installer/ - * - * phpcs:disable WebimpressCodingStandard.PHP.DisallowFqn.FileName */ class ConfigProvider { @@ -37,9 +39,9 @@ public function getDependencies(): array return [ // Legacy Zend Framework aliases 'aliases' => [ - \Zend\ProblemDetails\ProblemDetailsMiddleware::class => ProblemDetailsMiddleware::class, - \Zend\ProblemDetails\ProblemDetailsNotFoundHandler::class => ProblemDetailsNotFoundHandler::class, - \Zend\ProblemDetails\ProblemDetailsResponseFactory::class => ProblemDetailsResponseFactory::class, + LegacyProblemDetailsMiddleware::class => ProblemDetailsMiddleware::class, + LegacyProblemDetailsNotFoundHandler::class => ProblemDetailsNotFoundHandler::class, + LegacyProblemDetailsResponseFactory::class => ProblemDetailsResponseFactory::class, ], 'factories' => [ ProblemDetailsMiddleware::class => ProblemDetailsMiddlewareFactory::class, From bdbe3cbf2bde59d4e1c7c896b90275c4a3330ce6 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 23 Jan 2021 16:56:44 +0100 Subject: [PATCH 06/11] Fixed coding styles in ProblemDetailsExceptionInterfaceTest Signed-off-by: Alejandro Celaya Signed-off-by: Alejandro Celaya --- .../ProblemDetailsExceptionInterfaceTest.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/test/Exception/ProblemDetailsExceptionInterfaceTest.php b/test/Exception/ProblemDetailsExceptionInterfaceTest.php index 19fd540..c44c803 100644 --- a/test/Exception/ProblemDetailsExceptionInterfaceTest.php +++ b/test/Exception/ProblemDetailsExceptionInterfaceTest.php @@ -20,10 +20,15 @@ class ProblemDetailsExceptionInterfaceTest extends TestCase { - protected $status = 403; - protected $detail = 'You are not authorized to do that'; - protected $title = 'Unauthorized'; - protected $type = 'https://httpstatus.es/403'; + /** @var int */ + protected $status = 403; + /** @var string */ + protected $detail = 'You are not authorized to do that'; + /** @var string */ + protected $title = 'Unauthorized'; + /** @var string */ + protected $type = 'https://httpstatus.es/403'; + /** @var string[] */ protected $additional = [ 'foo' => 'bar', ]; From 4644b381fcbcc7322c8df5201d90e0c90acc8650 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 23 Jan 2021 17:17:08 +0100 Subject: [PATCH 07/11] Fixed TypeError in tests due to wrong return type used in mocks callback Signed-off-by: Alejandro Celaya Signed-off-by: Alejandro Celaya --- test/ProblemDetailsAssertionsTrait.php | 4 ++-- test/ProblemDetailsResponseFactoryTest.php | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/ProblemDetailsAssertionsTrait.php b/test/ProblemDetailsAssertionsTrait.php index 97d23c5..9f0b5d4 100644 --- a/test/ProblemDetailsAssertionsTrait.php +++ b/test/ProblemDetailsAssertionsTrait.php @@ -94,7 +94,7 @@ public function preparePayloadForJsonResponse(MockObject $stream, callable $asse Assert::assertIsString($body); $data = json_decode($body, true); $assertion($data); - return $body; + return true; })); } @@ -110,7 +110,7 @@ public function preparePayloadForXmlResponse(MockObject $stream, callable $asser Assert::assertIsString($body); $data = $this->deserializeXmlPayload($body); $assertion($data); - return $body; + return true; })); } diff --git a/test/ProblemDetailsResponseFactoryTest.php b/test/ProblemDetailsResponseFactoryTest.php index 7fbf55c..aed939f 100644 --- a/test/ProblemDetailsResponseFactoryTest.php +++ b/test/ProblemDetailsResponseFactoryTest.php @@ -250,7 +250,7 @@ public function testCreateResponseRemovesResourcesFromInputData(string $header, ->method('write') ->with($this->callback(function ($body) { Assert::assertNotEmpty($body); - return $body; + return true; })); $this->response->method('getBody')->willReturn($stream); @@ -353,7 +353,7 @@ public function testFragileDataInExceptionMessageShouldBeHiddenInResponseBodyInN ->with($this->callback(function ($body) use ($fragileMessage) { Assert::assertNotContains($fragileMessage, $body); Assert::assertContains(ProblemDetailsResponseFactory::DEFAULT_DETAIL_MESSAGE, $body); - return $body; + return true; })); $this->response->method('getBody')->willReturn($stream); @@ -529,7 +529,7 @@ public function testTypeIsInferredFromDefaultTypesMap(array $map, int $status, s ->with($this->callback(function (string $body) use ($expectedType) { $payload = json_decode($body, true); Assert::assertEquals($expectedType, $payload['type']); - return $body; + return true; })); $this->response->method('getBody')->willReturn($stream); From ddb79f8d26a9d1482b255cb6eb86899a59fa690c Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 23 Jan 2021 19:12:45 +0100 Subject: [PATCH 08/11] Fixed tests broken with the PHPUnit's update Signed-off-by: Alejandro Celaya Signed-off-by: Alejandro Celaya --- src/ProblemDetailsResponseFactory.php | 2 +- test/ProblemDetailsResponseFactoryTest.php | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ProblemDetailsResponseFactory.php b/src/ProblemDetailsResponseFactory.php index e98a4e4..c8a164f 100644 --- a/src/ProblemDetailsResponseFactory.php +++ b/src/ProblemDetailsResponseFactory.php @@ -334,7 +334,7 @@ private function cleanKeysForXml(array $input): array { $return = []; foreach ($input as $key => $value) { - $key = str_replace("\n", '_', $key); + $key = str_replace("\n", '_', (string) $key); $startCharacterPattern = '[A-Z]|_|[a-z]|[\xC0-\xD6]|[\xD8-\xF6]|[\xF8-\x{2FF}]|[\x{370}-\x{37D}]|[\x{37F}-\x{1FFF}]|' . '[\x{200C}-\x{200D}]|[\x{2070}-\x{218F}]|[\x{2C00}-\x{2FEF}]|[\x{3001}-\x{D7FF}]|[\x{F900}-\x{FDCF}]' diff --git a/test/ProblemDetailsResponseFactoryTest.php b/test/ProblemDetailsResponseFactoryTest.php index aed939f..74e397f 100644 --- a/test/ProblemDetailsResponseFactoryTest.php +++ b/test/ProblemDetailsResponseFactoryTest.php @@ -219,7 +219,7 @@ function (array $payload) { ); $this->response->method('getBody')->willReturn($stream); - $this->response->method('withStatus')->with(500)->willReturn($this->response); + $this->response->method('withStatus')->with(400)->willReturn($this->response); $this->response ->method('withHeader') ->with('Content-Type', 'application/problem+json') @@ -351,8 +351,8 @@ public function testFragileDataInExceptionMessageShouldBeHiddenInResponseBodyInN ->expects($this->atLeastOnce()) ->method('write') ->with($this->callback(function ($body) use ($fragileMessage) { - Assert::assertNotContains($fragileMessage, $body); - Assert::assertContains(ProblemDetailsResponseFactory::DEFAULT_DETAIL_MESSAGE, $body); + Assert::assertStringNotContainsString($fragileMessage, $body); + Assert::assertStringContainsString(ProblemDetailsResponseFactory::DEFAULT_DETAIL_MESSAGE, $body); return true; })); @@ -539,7 +539,7 @@ public function testTypeIsInferredFromDefaultTypesMap(array $map, int $status, s ->with($status) ->willReturn($this->response); $this->response - ->method('withStatus') + ->method('withHeader') ->with('Content-Type', 'application/problem+json') ->willReturn($this->response); From fcf1bc16901b7d3b3c92ca56e915f5af6773a380 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 24 Jan 2021 22:27:33 +0100 Subject: [PATCH 09/11] Fixed phpunit xsd reference to use the local one Signed-off-by: Alejandro Celaya --- phpunit.xml.dist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index c996de4..3639332 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,5 +1,5 @@ - + ./src From 9d7f6899bddb3acf45065f2b3b4982ee200a0e67 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 24 Jan 2021 22:28:01 +0100 Subject: [PATCH 10/11] Removed unnecessary declaration of jsonSerialize Signed-off-by: Alejandro Celaya --- src/Exception/ProblemDetailsExceptionInterface.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Exception/ProblemDetailsExceptionInterface.php b/src/Exception/ProblemDetailsExceptionInterface.php index 2b2f698..622a1e8 100644 --- a/src/Exception/ProblemDetailsExceptionInterface.php +++ b/src/Exception/ProblemDetailsExceptionInterface.php @@ -35,6 +35,4 @@ public function getAdditionalData(): array; * for cases where the XML variant is desired. */ public function toArray(): array; - - public function jsonSerialize(); } From d5338dbe4da68e100e60e41eec9296f0efe8a10c Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 24 Jan 2021 22:42:18 +0100 Subject: [PATCH 11/11] Fixed error while moking interface extending another interface with PHPUnit Signed-off-by: Alejandro Celaya --- test/ProblemDetailsResponseFactoryTest.php | 38 ++++++++++++++-------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/test/ProblemDetailsResponseFactoryTest.php b/test/ProblemDetailsResponseFactoryTest.php index 74e397f..3f8654e 100644 --- a/test/ProblemDetailsResponseFactoryTest.php +++ b/test/ProblemDetailsResponseFactoryTest.php @@ -11,6 +11,7 @@ namespace MezzioTest\ProblemDetails; use Exception; +use Mezzio\ProblemDetails\Exception\CommonProblemDetailsExceptionTrait; use Mezzio\ProblemDetails\Exception\ProblemDetailsExceptionInterface; use Mezzio\ProblemDetails\ProblemDetailsResponseFactory; use PHPUnit\Framework\Assert; @@ -197,15 +198,31 @@ function (array $payload) use ($expectedKeyNames) { $this->assertSame($this->response, $response); } - public function testCreateResponseFromThrowableWillPullDetailsFromProblemDetailsExceptionInterface(): void + private function createProblemDetailsExceptionWithAdditional(array $additional): ProblemDetailsExceptionInterface { - $e = $this->createMock(ProblemDetailsExceptionInterface::class); - $e->method('getStatus')->willReturn(400); - $e->method('getDetail')->willReturn('Exception details'); - $e->method('getTitle')->willReturn('Invalid client request'); - $e->method('getType')->willReturn('https://example.com/api/doc/invalid-client-request'); - $e->method('getAdditionalData')->willReturn(['foo' => 'bar']); + return new class ( + 400, + 'Exception details', + 'Invalid client request', + 'https://example.com/api/doc/invalid-client-request', + $additional + ) extends Exception implements ProblemDetailsExceptionInterface { + use CommonProblemDetailsExceptionTrait; + + public function __construct(int $status, string $detail, string $title, string $type, array $additional) + { + $this->status = $status; + $this->detail = $detail; + $this->title = $title; + $this->type = $type; + $this->additional = $additional; + } + }; + } + public function testCreateResponseFromThrowableWillPullDetailsFromProblemDetailsExceptionInterface(): void + { + $e = $this->createProblemDetailsExceptionWithAdditional(['foo' => 'bar']); $stream = $this->createMock(StreamInterface::class); $this->preparePayloadForJsonResponse( $stream, @@ -462,12 +479,7 @@ function () { public function testRenderWithMalformedUtf8Sequences(): void { - $e = $this->createMock(ProblemDetailsExceptionInterface::class); - $e->method('getStatus')->willReturn(400); - $e->method('getDetail')->willReturn('Exception details'); - $e->method('getTitle')->willReturn('Invalid client request'); - $e->method('getType')->willReturn('https://example.com/api/doc/invalid-client-request'); - $e->method('getAdditionalData')->willReturn([ + $e = $this->createProblemDetailsExceptionWithAdditional([ 'malformed-utf8' => self::UTF_8_INVALID_2_OCTET_SEQUENCE, ]);