From 671174534c9fcc96fec01e76e26a8245a212f7e2 Mon Sep 17 00:00:00 2001 From: Jurj-Bogdan Date: Tue, 18 Jul 2023 15:48:14 +0300 Subject: [PATCH 1/4] issue#13, code quality --- .gitignore | 5 +- LICENSE | 4 +- README.md | 1 + composer.json | 41 +++- config/error-handling.global.php.dist | 2 +- phpcs.xml | 21 ++ phpunit.xml | 14 ++ psalm.xml | 17 ++ src/ConfigProvider.php | 8 +- src/ErrorHandler.php | 42 ++-- src/ErrorHandlerFactory.php | 18 +- src/ErrorHandlerInterface.php | 12 +- src/LogErrorHandler.php | 59 +++--- src/LogErrorHandlerFactory.php | 39 ++-- tests/ConfigProviderTest.php | 38 ++++ tests/ErrorHandlerFactoryTest.php | 73 +++++++ tests/ErrorHandlerTest.php | 167 ++++++++++++++++ tests/LogErrorHandlerFactoryTest.php | 145 ++++++++++++++ tests/LogErrorHandlerTest.php | 264 ++++++++++++++++++++++++++ 19 files changed, 864 insertions(+), 106 deletions(-) create mode 100644 phpcs.xml create mode 100644 phpunit.xml create mode 100644 psalm.xml create mode 100644 tests/ConfigProviderTest.php create mode 100644 tests/ErrorHandlerFactoryTest.php create mode 100644 tests/ErrorHandlerTest.php create mode 100644 tests/LogErrorHandlerFactoryTest.php create mode 100644 tests/LogErrorHandlerTest.php diff --git a/.gitignore b/.gitignore index b72c48e..d3096f4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ clover.xml coveralls-upload.json -phpunit.xml +.phpunit.result.cache +.phpcs-cache # Created by .ignore support plugin (hsz.mobi) ### JetBrains template @@ -36,4 +37,4 @@ composer.phar # Commit your application's lock file http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file # You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file -composer.lock \ No newline at end of file +composer.lock diff --git a/LICENSE b/LICENSE index cfec16e..85fe647 100644 --- a/LICENSE +++ b/LICENSE @@ -1,9 +1,9 @@ MIT License -Copyright (c) 2020 Apidemia +Copyright (c) 2023 Apidemia Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 9d150b9..0d66e09 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Logging Error Handler for DotKernel [![GitHub stars](https://img.shields.io/github/stars/dotkernel/dot-errorhandler)](https://github.com/dotkernel/dot-errorhandler/stargazers) [![GitHub license](https://img.shields.io/github/license/dotkernel/dot-errorhandler)](https://github.com/dotkernel/dot-errorhandler/blob/3.0/LICENSE) +[![SymfonyInsight](https://insight.symfony.com/projects/cf1f8d89-f230-4157-bc8b-7cce20c75454/big.svg)](https://insight.symfony.com/projects/cf1f8d89-f230-4157-bc8b-7cce20c75454) ## Adding the error handler * Add the composer package: diff --git a/composer.json b/composer.json index 0780cef..b90bcb4 100644 --- a/composer.json +++ b/composer.json @@ -19,17 +19,48 @@ "mezzio", "service-manager" ], + "config": { + "sort-packages": true, + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, "require": { - "php": "~7.4.0 || ~8.0.0 || ~8.1.0", - "dotkernel/dot-log": "^3.2.0", - "laminas/laminas-diactoros": "^2.8.0", - "psr/http-message": "^1.0.1", + "php": "~8.1.0 || ~8.2.0", + "dotkernel/dot-log": "^3.3.0", + "laminas/laminas-diactoros": "^2.25.2", + "laminas/laminas-stratigility": "^3.10.0", + "mezzio/mezzio": "^3.17.0", + "psr/http-message": "^1.1", + "psr/http-server-middleware": "^1.0.2", "psr/container": "^1.1.2" }, + "require-dev": { + "laminas/laminas-coding-standard": "^2.5", + "mikey179/vfsstream": "^1.6", + "phpunit/phpunit": "^10.2.3", + "vimeo/psalm": "^5.13" + }, "autoload": { "psr-4": { "Dot\\ErrorHandler\\": "src/" } }, - "minimum-stability": "stable" + "autoload-dev": { + "psr-4": { + "Dot\\Tests\\": "tests/" + } + }, + "minimum-stability": "stable", + "scripts": { + "check": [ + "@cs-check", + "@test" + ], + "cs-check": "phpcs", + "cs-fix": "phpcbf", + "test": "phpunit --colors=always", + "test-coverage": "phpunit --colors=always --coverage-clover clover.xml", + "static-analysis": "psalm --shepherd --stats" + } } diff --git a/config/error-handling.global.php.dist b/config/error-handling.global.php.dist index 6f82bed..eb07019 100644 --- a/config/error-handling.global.php.dist +++ b/config/error-handling.global.php.dist @@ -13,4 +13,4 @@ return [ 'loggerEnabled' => true, 'logger' => 'dot-log.default_logger' ] -]; \ No newline at end of file +]; diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 0000000..80e0ee4 --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + config + src + tests + + + + \ No newline at end of file diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..8b3ad2e --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,14 @@ + + + + + ./tests + + + + + + ./src + + + \ No newline at end of file diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 0000000..b4b6bb5 --- /dev/null +++ b/psalm.xml @@ -0,0 +1,17 @@ + + + + + + + + + diff --git a/src/ConfigProvider.php b/src/ConfigProvider.php index 3f63cfa..a0e68de 100644 --- a/src/ConfigProvider.php +++ b/src/ConfigProvider.php @@ -6,17 +6,17 @@ class ConfigProvider { - public function __invoke() + public function __invoke(): array { return [ 'dependencies' => [ - 'aliases' => [ + 'aliases' => [ ErrorHandlerInterface::class => ErrorHandler::class, ], 'factories' => [ LogErrorHandler::class => LogErrorHandlerFactory::class, - ErrorHandler::class => ErrorHandlerFactory::class, - ] + ErrorHandler::class => ErrorHandlerFactory::class, + ], ], ]; } diff --git a/src/ErrorHandler.php b/src/ErrorHandler.php index d499309..9b4b9ae 100644 --- a/src/ErrorHandler.php +++ b/src/ErrorHandler.php @@ -5,12 +5,12 @@ namespace Dot\ErrorHandler; use ErrorException; +use Laminas\Stratigility\Middleware\ErrorResponseGenerator; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use Throwable; -use Laminas\Stratigility\Exception\MissingResponseException; use function error_reporting; use function in_array; @@ -65,19 +65,11 @@ */ class ErrorHandler implements MiddlewareInterface, ErrorHandlerInterface { - /** - * @var callable[] - */ - private $listeners = []; - - /** - * @var callable Routine that will generate the error response. - */ + /** @var callable[] */ + private array $listeners = []; + /** @var callable Routine that will generate the error response. */ private $responseGenerator; - - /** - * @var callable - */ + /** @var callable */ private $responseFactory; /** @@ -87,9 +79,9 @@ class ErrorHandler implements MiddlewareInterface, ErrorHandlerInterface * @param null|callable $responseGenerator Callback that will generate the final * error response; if none is provided, ErrorResponseGenerator is used. */ - public function __construct(callable $responseFactory, callable $responseGenerator = null) + public function __construct(callable $responseFactory, ?callable $responseGenerator = null) { - $this->responseFactory = function () use ($responseFactory) : ResponseInterface { + $this->responseFactory = function () use ($responseFactory): ResponseInterface { return $responseFactory(); }; $this->responseGenerator = $responseGenerator ?: new ErrorResponseGenerator(); @@ -108,7 +100,7 @@ public function __construct(callable $responseFactory, callable $responseGenerat * 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; @@ -130,16 +122,12 @@ public function attachListener(callable $listener) : void * and returned instead; otherwise, the response returned by $next is * used. */ - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { set_error_handler($this->createErrorHandler()); try { $response = $handler->handle($request); - - if (! $response instanceof ResponseInterface) { - throw new MissingResponseException('Application did not return a response'); - } } catch (Throwable $e) { $response = $this->handleThrowable($e, $request); } @@ -156,10 +144,10 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface * triggers all listeners with the same arguments (but using the response * returned from createErrorResponse()), and then returns the response. */ - private function handleThrowable(Throwable $e, ServerRequestInterface $request) : ResponseInterface + public function handleThrowable(Throwable $e, ServerRequestInterface $request): ResponseInterface { $generator = $this->responseGenerator; - $response = $generator($e, $request, ($this->responseFactory)()); + $response = $generator($e, $request, ($this->responseFactory)()); $this->triggerListeners($e, $request, $response); return $response; } @@ -169,12 +157,12 @@ private function handleThrowable(Throwable $e, ServerRequestInterface $request) * * Only raises exceptions for errors that are within the error_reporting mask. */ - private function createErrorHandler() : callable + public function createErrorHandler(): callable { /** * @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; @@ -187,11 +175,11 @@ private function createErrorHandler() : callable /** * Trigger all error listeners. */ - private function triggerListeners( + public function triggerListeners( Throwable $error, ServerRequestInterface $request, ResponseInterface $response - ) : void { + ): void { foreach ($this->listeners as $listener) { $listener($error, $request, $response); } diff --git a/src/ErrorHandlerFactory.php b/src/ErrorHandlerFactory.php index 681c1d0..5f25f70 100644 --- a/src/ErrorHandlerFactory.php +++ b/src/ErrorHandlerFactory.php @@ -1,23 +1,25 @@ has(\Mezzio\Middleware\ErrorResponseGenerator::class) - ? $container->get(\Mezzio\Middleware\ErrorResponseGenerator::class) + $generator = $container->has(ErrorResponseGenerator::class) + ? $container->get(ErrorResponseGenerator::class) : null; return new ErrorHandler($container->get(ResponseInterface::class), $generator); diff --git a/src/ErrorHandlerInterface.php b/src/ErrorHandlerInterface.php index 5fbf69a..9e5cbb7 100644 --- a/src/ErrorHandlerInterface.php +++ b/src/ErrorHandlerInterface.php @@ -4,18 +4,10 @@ namespace Dot\ErrorHandler; -use ErrorException; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -use Throwable; -use Laminas\Stratigility\Exception\MissingResponseException; - -use function error_reporting; -use function in_array; -use function restore_error_handler; -use function set_error_handler; /** * Error handler middleware. @@ -78,7 +70,7 @@ interface ErrorHandlerInterface extends MiddlewareInterface * listeners are ignored; use listeners for reporting purposes * only. */ - public function attachListener(callable $listener) : void; + public function attachListener(callable $listener): void; /** * Middleware to handle errors and exceptions in layers it wraps. @@ -93,5 +85,5 @@ public function attachListener(callable $listener) : void; * and returned instead; otherwise, the response returned by $next is * used. */ - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface; + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface; } diff --git a/src/LogErrorHandler.php b/src/LogErrorHandler.php index d2588e3..a30a3cf 100644 --- a/src/LogErrorHandler.php +++ b/src/LogErrorHandler.php @@ -5,14 +5,13 @@ namespace Dot\ErrorHandler; use ErrorException; +use Laminas\Log\LoggerInterface; +use Laminas\Stratigility\Middleware\ErrorResponseGenerator; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use Throwable; -use Laminas\Log\Logger; -use Laminas\Log\LoggerInterface; -use Laminas\Stratigility\Exception\MissingResponseException; use function error_reporting; use function in_array; @@ -67,25 +66,13 @@ */ class LogErrorHandler implements MiddlewareInterface, ErrorHandlerInterface { - /** - * @var callable[] - */ + /** @var callable[] */ private $listeners = []; - - /** - * @var callable Routine that will generate the error response. - */ + /** @var callable|null Routine that will generate the error response. */ private $responseGenerator; - - /** - * @var callable - */ + /** @var callable */ private $responseFactory; - - /** - * @var LoggerInterface - */ - private $logger; + private LoggerInterface|null $logger; /** * @param callable $responseFactory A factory capable of returning an @@ -94,13 +81,16 @@ class LogErrorHandler implements MiddlewareInterface, ErrorHandlerInterface * @param null|callable $responseGenerator Callback that will generate the final * error response; if none is provided, ErrorResponseGenerator is used. */ - public function __construct(callable $responseFactory, callable $responseGenerator = null, LoggerInterface $logger = null) - { - $this->responseFactory = function () use ($responseFactory) : ResponseInterface { + public function __construct( + callable $responseFactory, + ?callable $responseGenerator = null, + ?LoggerInterface $logger = null + ) { + $this->responseFactory = function () use ($responseFactory): ResponseInterface { return $responseFactory(); }; $this->responseGenerator = $responseGenerator ?: new ErrorResponseGenerator(); - $this->logger = $logger; + $this->logger = $logger; } /** @@ -116,7 +106,7 @@ public function __construct(callable $responseFactory, callable $responseGenerat * 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; @@ -138,16 +128,12 @@ public function attachListener(callable $listener) : void * and returned instead; otherwise, the response returned by $next is * used. */ - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { set_error_handler($this->createErrorHandler()); try { $response = $handler->handle($request); - - if (! $response instanceof ResponseInterface) { - throw new MissingResponseException('Application did not return a response'); - } } catch (Throwable $e) { $response = $this->handleThrowable($e, $request); } @@ -163,12 +149,15 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface * Passes the error, request, and response prototype to createErrorResponse(), * triggers all listeners with the same arguments (but using the response * returned from createErrorResponse()), and then returns the response. + * + * If a valid Logger is available, the error and it's message are logged in the + * configured format. */ - private function handleThrowable(Throwable $e, ServerRequestInterface $request) : ResponseInterface + public function handleThrowable(Throwable $e, ServerRequestInterface $request): ResponseInterface { $generator = $this->responseGenerator; if ($this->logger instanceof LoggerInterface) { - $this->logger->err($e->getMessage(), (array)$e); + $this->logger->err($e->getMessage(), (array) $e); } $response = $generator($e, $request, ($this->responseFactory)()); @@ -182,12 +171,12 @@ private function handleThrowable(Throwable $e, ServerRequestInterface $request) * * Only raises exceptions for errors that are within the error_reporting mask. */ - private function createErrorHandler() : callable + public function createErrorHandler(): callable { /** * @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; @@ -200,11 +189,11 @@ private function createErrorHandler() : callable /** * Trigger all error listeners. */ - private function triggerListeners( + public function triggerListeners( Throwable $error, ServerRequestInterface $request, ResponseInterface $response - ) : void { + ): void { foreach ($this->listeners as $listener) { $listener($error, $request, $response); } diff --git a/src/LogErrorHandlerFactory.php b/src/LogErrorHandlerFactory.php index f76d680..29332f3 100644 --- a/src/LogErrorHandlerFactory.php +++ b/src/LogErrorHandlerFactory.php @@ -5,35 +5,50 @@ namespace Dot\ErrorHandler; use InvalidArgumentException; +use Laminas\Log\LoggerInterface; +use Mezzio\Middleware\ErrorResponseGenerator; +use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface; +use Psr\Container\NotFoundExceptionInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Server\MiddlewareInterface; -use Laminas\Log\LoggerInterface; + +use function is_array; +use function sprintf; class LogErrorHandlerFactory { - const ERROR_HANDLER_KEY = 'dot-errorhandler'; - const ERROR_HANDLER_LOGGER_KEY = 'logger'; - - public function __invoke(ContainerInterface $container) : MiddlewareInterface + public const ERROR_HANDLER_KEY = 'dot-errorhandler'; + public const ERROR_HANDLER_LOGGER_KEY = 'logger'; + + /** + * @throws NotFoundExceptionInterface + * @throws ContainerExceptionInterface + */ + public function __invoke(ContainerInterface $container): MiddlewareInterface { $errorHandlerConfig = $container->get('config')[self::ERROR_HANDLER_KEY] ?? null; - if (!is_array($errorHandlerConfig)) { + if (! is_array($errorHandlerConfig)) { throw new InvalidArgumentException(sprintf('\'[%s\'] not found in config', self::ERROR_HANDLER_KEY)); } - if ($errorHandlerConfig['loggerEnabled'] && !isset($errorHandlerConfig[self::ERROR_HANDLER_LOGGER_KEY])) { - throw new InvalidArgumentException(sprintf('Logger: \'[%s\'] is enabled, but not found in config', self::ERROR_HANDLER_LOGGER_KEY)); + if ($errorHandlerConfig['loggerEnabled'] && ! isset($errorHandlerConfig[self::ERROR_HANDLER_LOGGER_KEY])) { + throw new InvalidArgumentException( + sprintf( + 'Logger: \'[%s\'] is enabled, but not found in config', + self::ERROR_HANDLER_LOGGER_KEY + ) + ); } - + $logger = null; if ($errorHandlerConfig['loggerEnabled']) { /** @var LoggerInterface $logger */ - $logger = $container->get($errorHandlerConfig['logger']); + $logger = $container->get($errorHandlerConfig[self::ERROR_HANDLER_LOGGER_KEY]); } - $generator = $container->has(\Mezzio\Middleware\ErrorResponseGenerator::class) - ? $container->get(\Mezzio\Middleware\ErrorResponseGenerator::class) + $generator = $container->has(ErrorResponseGenerator::class) + ? $container->get(ErrorResponseGenerator::class) : null; return new LogErrorHandler($container->get(ResponseInterface::class), $generator, $logger); diff --git a/tests/ConfigProviderTest.php b/tests/ConfigProviderTest.php new file mode 100644 index 0000000..84db919 --- /dev/null +++ b/tests/ConfigProviderTest.php @@ -0,0 +1,38 @@ +config = (new ConfigProvider())(); + } + + public function testHasDependencies(): void + { + $this->assertArrayHasKey('dependencies', $this->config); + } + + public function testDependenciesHaveAliases(): void + { + $this->assertArrayHasKey('aliases', $this->config['dependencies']); + $this->assertArrayHasKey(ErrorHandlerInterface::class, $this->config['dependencies']['aliases']); + + $this->assertArrayHasKey('factories', $this->config['dependencies']); + $this->assertArrayHasKey(LogErrorHandler::class, $this->config['dependencies']['factories']); + $this->assertArrayHasKey(ErrorHandler::class, $this->config['dependencies']['factories']); + } +} diff --git a/tests/ErrorHandlerFactoryTest.php b/tests/ErrorHandlerFactoryTest.php new file mode 100644 index 0000000..73e8f72 --- /dev/null +++ b/tests/ErrorHandlerFactoryTest.php @@ -0,0 +1,73 @@ +container = $this->createMock(ContainerInterface::class); + $this->responseFactory = fn(): ResponseInterface => $this->createMock(ResponseInterface::class); + } + + /** + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + public function testWillCreateWithDefaultOption(): void + { + $this->container->method('has') + ->with(ErrorResponseGenerator::class) + ->willReturn(false); + + $this->container->method('get') + ->with(ResponseInterface::class) + ->willReturn($this->responseFactory); + + $result = (new ErrorHandlerFactory())($this->container); + $this->assertInstanceOf(ErrorHandler::class, $result); + } + + /** + * @throws ContainerExceptionInterface + * @throws Exception + * @throws NotFoundExceptionInterface + */ + public function testWillCreateWithErrorResponseGenerator(): void + { + $this->container->method('has') + ->with(ErrorResponseGenerator::class) + ->willReturn($this->createMock(ErrorResponseGenerator::class)); + + $this->container->method('get') + ->willReturnMap([ + [ErrorResponseGenerator::class, $this->createMock(ErrorResponseGenerator::class)], + [ResponseInterface::class, $this->responseFactory], + ]); + + $result = (new ErrorHandlerFactory())($this->container); + $this->assertInstanceOf(ErrorHandler::class, $result); + } +} diff --git a/tests/ErrorHandlerTest.php b/tests/ErrorHandlerTest.php new file mode 100644 index 0000000..4864112 --- /dev/null +++ b/tests/ErrorHandlerTest.php @@ -0,0 +1,167 @@ +response = $this->createMock(ResponseInterface::class); + $this->serverRequest = $this->createMock(ServerRequestInterface::class); + $this->body = $this->createMock(StreamInterface::class); + $this->handler = $this->createMock(RequestHandlerInterface::class); + + $this->responseFactory = fn(): ResponseInterface => $this->response; + $this->errorResponseGenerator = new ErrorResponseGenerator(); + $this->subject = new ErrorHandler($this->responseFactory, $this->errorResponseGenerator); + $this->exception = new RuntimeException('Not Implemented', 501); + } + + public function testWillCreateWithDefaultParameters(): void + { + $this->assertInstanceOf(Subject::class, $this->subject); + } + + public function testCreateErrorHandlerReturnsCallable(): void + { + $this->assertIsCallable($this->subject->createErrorHandler()); + } + + public function testCreateErrorHandlerRaisesErrorException(): void + { + $callableErrorHandler = $this->subject->createErrorHandler(); + $this->expectException(ErrorException::class); + + $callableErrorHandler(error_reporting(), ErrorException::class, 'testErrFile', 0); + } + + public function testCreateErrorHandlerSkipsErrorsOutsideErrorReportingMask(): void + { + $callableErrorHandler = $this->subject->createErrorHandler(); + $this->assertNull($callableErrorHandler(-(error_reporting() + 1), ErrorException::class, 'testErrfile', 0)); + } + + public function testAttachListenerDoesNotAttachDuplicates(): void + { + $listener = static function (): void { + }; + + $this->subject->attachListener($listener); + $this->subject->attachListener($listener); + + $ref = new ReflectionObject($this->subject); + $listeners = $ref->getProperty('listeners'); + + $this->assertContains($listener, $listeners->getValue($this->subject)); + $this->assertCount(1, $listeners->getValue($this->subject)); + } + + public function testHandleThrowable(): void + { + $this->body + ->expects(self::once()) + ->method('write') + ->with('Not Implemented') + ->willReturn(0); + + $this->response + ->method('getStatusCode') + ->willReturn(501); + $this->response + ->method('withStatus') + ->with(501) + ->willReturnSelf(); + $this->response + ->method('getBody') + ->willReturn($this->body); + $this->response + ->method('getReasonPhrase') + ->willReturn('Not Implemented'); + + $response = ($this->errorResponseGenerator)($this->exception, $this->serverRequest, ($this->responseFactory)()); + + $this->assertInstanceOf(ResponseInterface::class, $response); + } + + public function testErrorHandlingTriggersListeners(): void + { + $this->handler + ->method('handle') + ->with($this->serverRequest) + ->willThrowException($this->exception); + + $this->body + ->expects(self::once()) + ->method('write') + ->with('Not Implemented') + ->willReturn(0); + + $this->response + ->method('getStatusCode') + ->willReturn(501); + $this->response + ->method('withStatus') + ->with(501) + ->willReturnSelf(); + $this->response + ->method('getBody') + ->willReturn($this->body); + $this->response + ->method('getReasonPhrase') + ->willReturn('Not Implemented'); + + $listener = function ( + Throwable $error, + ServerRequestInterface $request, + ResponseInterface $response + ): void { + $this->assertSame($this->exception, $error); + $this->assertSame($this->serverRequest, $request); + $this->assertSame($this->response, $response); + }; + $listener2 = clone $listener; + + $this->subject->attachListener($listener); + $this->subject->attachListener($listener2); + + $result = $this->subject->process($this->serverRequest, $this->handler); + + $this->assertSame($this->response, $result); + } +} diff --git a/tests/LogErrorHandlerFactoryTest.php b/tests/LogErrorHandlerFactoryTest.php new file mode 100644 index 0000000..a51c021 --- /dev/null +++ b/tests/LogErrorHandlerFactoryTest.php @@ -0,0 +1,145 @@ +container = $this->createMock(ContainerInterface::class); + $this->responseFactory = fn(): ResponseInterface => $this->createMock(ResponseInterface::class); + } + + public function testWillNotCreateWithoutConfig(): void + { + $this->container->method('get') + ->with('config') + ->willReturn(false); + + $this->expectException('InvalidArgumentException'); + $this->expectExceptionMessage( + sprintf('\'[%s\'] not found in config', LogErrorHandlerFactory::ERROR_HANDLER_KEY) + ); + + (new LogErrorHandlerFactory())($this->container); + } + + /** + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + public function testWillNotCreateWithMissingLoggerKey(): void + { + $this->container->method('get') + ->with('config') + ->willReturn([ + LogErrorHandlerFactory::ERROR_HANDLER_KEY => [ + 'loggerEnabled' => true, + 'test', + ], + ]); + + $this->expectException('InvalidArgumentException'); + $this->expectExceptionMessage( + sprintf( + 'Logger: \'[%s\'] is enabled, but not found in config', + LogErrorHandlerFactory::ERROR_HANDLER_LOGGER_KEY + ) + ); + + (new LogErrorHandlerFactory())($this->container); + } + + /** + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + * @throws Exception + */ + public function testWillCreateWithValidConfigAndMissingLogger(): void + { + $this->container->method('get') + ->willReturnMap([ + [ + 'config', + [ + LogErrorHandlerFactory::ERROR_HANDLER_KEY => [ + 'loggerEnabled' => true, + LogErrorHandlerFactory::ERROR_HANDLER_LOGGER_KEY => 'test', + ], + ], + ], + [ + 'config[' . LogErrorHandlerFactory::ERROR_HANDLER_KEY + . '][' . LogErrorHandlerFactory::ERROR_HANDLER_LOGGER_KEY . ']', + null, + ], + [ErrorResponseGenerator::class, $this->createMock(ErrorResponseGenerator::class)], + [ResponseInterface::class, $this->responseFactory], + ]); + + $result = (new LogErrorHandlerFactory())($this->container); + $this->assertInstanceOf(LogErrorHandler::class, $result); + } + + /** + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + * @throws Exception + */ + public function testWillCreateWithValidConfig(): void + { + $logger = $this->createMock(LoggerInterface::class); + + $this->container->method('has') + ->with(ErrorResponseGenerator::class) + ->willReturn(true); + + $this->container->method('get') + ->willReturnMap([ + [ + 'config', + [ + LogErrorHandlerFactory::ERROR_HANDLER_KEY => [ + 'loggerEnabled' => true, + LogErrorHandlerFactory::ERROR_HANDLER_LOGGER_KEY => 'test', + ], + ], + ], + [ + 'config[' . LogErrorHandlerFactory::ERROR_HANDLER_KEY . '][' + . LogErrorHandlerFactory::ERROR_HANDLER_LOGGER_KEY . ']', + $logger, + ], + [ErrorResponseGenerator::class, $this->createMock(ErrorResponseGenerator::class)], + [ResponseInterface::class, $this->responseFactory], + ]); + + $result = (new LogErrorHandlerFactory())($this->container); + $this->assertInstanceOf(LogErrorHandler::class, $result); + } +} diff --git a/tests/LogErrorHandlerTest.php b/tests/LogErrorHandlerTest.php new file mode 100644 index 0000000..94537d5 --- /dev/null +++ b/tests/LogErrorHandlerTest.php @@ -0,0 +1,264 @@ +response = $this->createMock(ResponseInterface::class); + $this->serverRequest = $this->createMock(ServerRequestInterface::class); + $this->body = $this->createMock(StreamInterface::class); + $this->handler = $this->createMock(RequestHandlerInterface::class); + + $this->responseFactory = fn(): ResponseInterface => $this->response; + $this->errorResponseGenerator = new ErrorResponseGenerator(); + $this->subject = new LogErrorHandler( + $this->responseFactory, + $this->errorResponseGenerator, + $this->createMock(LoggerInterface::class) + ); + $this->exception = new RuntimeException('Not Implemented', 501); + $this->fileSystem = vfsStream::setup('root', 0644, ['log']); + } + + public function testWillCreateWithDefaultParameters(): void + { + $this->assertInstanceOf(Subject::class, $this->subject); + } + + public function testCreateErrorHandlerReturnsCallable(): void + { + $this->assertIsCallable($this->subject->createErrorHandler()); + } + + public function testCreateErrorHandlerRaisesErrorException(): void + { + $callableErrorHandler = $this->subject->createErrorHandler(); + $this->expectException(ErrorException::class); + + $callableErrorHandler(error_reporting(), ErrorException::class, 'testErrfile', 0); + } + + public function testCreateErrorHandlerSkipsErrorsOutsideErrorReportingMask(): void + { + $callableErrorHandler = $this->subject->createErrorHandler(); + $this->assertNull($callableErrorHandler(-(error_reporting() + 1), ErrorException::class, 'testErrfile', 0)); + } + + public function testAttachListenerDoesNotAttachDuplicates(): void + { + $listener = static function (): void { + }; + + $this->subject->attachListener($listener); + $this->subject->attachListener($listener); + + $ref = new ReflectionObject($this->subject); + $listeners = $ref->getProperty('listeners'); + + $this->assertContains($listener, $listeners->getValue($this->subject)); + $this->assertCount(1, $listeners->getValue($this->subject)); + } + + public function testTriggerListenersWithTwoListeners(): void + { + $listener1 = function ( + Throwable $error, + ServerRequestInterface $request, + ResponseInterface $response + ): void { + $this->assertSame($this->exception, $error); + $this->assertSame($this->serverRequest, $request); + $this->assertSame($this->response, $response); + }; + + $listener2 = clone $listener1; + $this->subject->attachListener($listener1); + $this->subject->attachListener($listener2); + + $this->subject->triggerListeners($this->exception, $this->serverRequest, $this->response); + } + + public function testHandleThrowable(): void + { + $responseGenerator = new ErrorResponseGenerator(); + + $this->body + ->expects(self::once()) + ->method('write') + ->with('Not Implemented') + ->willReturn(0); + + $this->response + ->method('getStatusCode') + ->willReturn(501); + $this->response + ->method('withStatus') + ->with(501) + ->willReturnSelf(); + $this->response + ->method('getBody') + ->willReturn($this->body); + $this->response + ->method('getReasonPhrase') + ->willReturn('Not Implemented'); + + $response = $responseGenerator($this->exception, $this->serverRequest, ($this->responseFactory)()); + + $this->assertInstanceOf(ResponseInterface::class, $response); + } + + public function testErrorHandlingTriggersListeners(): void + { + $this->handler + ->method('handle') + ->with($this->serverRequest) + ->willThrowException($this->exception); + + $this->body + ->expects(self::once()) + ->method('write') + ->with('Not Implemented') + ->willReturn(0); + + $this->response + ->method('getStatusCode') + ->willReturn(501); + $this->response + ->method('withStatus') + ->with(501) + ->willReturnSelf(); + $this->response + ->method('getBody') + ->willReturn($this->body); + $this->response + ->method('getReasonPhrase') + ->willReturn('Not Implemented'); + + $listener = function ( + Throwable $error, + ServerRequestInterface $request, + ResponseInterface $response + ): void { + $this->assertSame($this->exception, $error, 'Listener did not receive same exception as was raised'); + $this->assertSame($this->serverRequest, $request, 'Listener did not receive same request'); + $this->assertSame($this->response, $response, 'Listener did not receive same response'); + }; + + $listener2 = clone $listener; + $this->subject->attachListener($listener); + $this->subject->attachListener($listener2); + + $result = $this->subject->process($this->serverRequest, $this->handler); + + $this->assertSame($this->response, $result); + } + + public function testHandleThrowableLogsError(): void + { + $config = $this->getConfig(); + + $logErrorHandler = new LogErrorHandler( + $this->responseFactory, + $this->errorResponseGenerator, + new Logger($config) + ); + + $this->handler + ->method('handle') + ->with($this->serverRequest) + ->willThrowException($this->exception); + + $this->body + ->expects(self::once()) + ->method('write') + ->with('Not Implemented') + ->willReturn(0); + + $this->response + ->method('getStatusCode') + ->willReturn(501); + $this->response + ->method('withStatus') + ->with(501) + ->willReturnSelf(); + $this->response + ->method('getBody') + ->willReturn($this->body); + $this->response + ->method('getReasonPhrase') + ->willReturn('Not Implemented'); + + $logErrorHandler->process($this->serverRequest, $this->handler); + + $this->assertTrue($this->fileSystem->hasChild('test-error-log.log')); + $this->assertNotEmpty(file_get_contents($this->fileSystem->url() . '/test-error-log.log')); + } + + private function getConfig(): array + { + return [ + 'writers' => [ + 'FileWriter' => [ + 'name' => 'stream', + 'priority' => Logger::ALERT, + 'options' => [ + 'stream' => $this->fileSystem->url() . '/test-error-log.log', + 'filters' => [ + 'allMessages' => [ + 'name' => 'priority', + 'options' => [ + 'operator' => '>=', + 'priority' => Logger::EMERG, + ], + ], + ], + 'formatter' => [ + 'name' => Json::class, + ], + ], + ], + ], + ]; + } +} From df75d4f7a8dda47555f57d07e39a51d5f830d9d3 Mon Sep 17 00:00:00 2001 From: Jurj-Bogdan Date: Tue, 18 Jul 2023 15:54:48 +0300 Subject: [PATCH 2/4] missing newlines --- phpcs.xml | 2 +- phpunit.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/phpcs.xml b/phpcs.xml index 80e0ee4..60157b0 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -18,4 +18,4 @@ - \ No newline at end of file + diff --git a/phpunit.xml b/phpunit.xml index 8b3ad2e..2aa816e 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -11,4 +11,4 @@ ./src - \ No newline at end of file + From 99ef2d034dfbb75be413626082db74e7d5631bc2 Mon Sep 17 00:00:00 2001 From: Jurj-Bogdan Date: Tue, 18 Jul 2023 22:16:28 +0300 Subject: [PATCH 3/4] requested changes --- README.md | 4 +- composer.json | 2 +- phpcs.xml | 3 +- phpunit.xml | 2 +- src/ErrorHandler.php | 72 ------------------- src/LogErrorHandler.php | 72 ------------------- {tests => test}/ConfigProviderTest.php | 4 +- {tests => test}/ErrorHandlerFactoryTest.php | 4 +- {tests => test}/ErrorHandlerTest.php | 7 +- .../LogErrorHandlerFactoryTest.php | 8 ++- {tests => test}/LogErrorHandlerTest.php | 4 +- 11 files changed, 17 insertions(+), 165 deletions(-) rename {tests => test}/ConfigProviderTest.php (95%) rename {tests => test}/ErrorHandlerFactoryTest.php (97%) rename {tests => test}/ErrorHandlerTest.php (98%) rename {tests => test}/LogErrorHandlerFactoryTest.php (97%) rename {tests => test}/LogErrorHandlerTest.php (99%) diff --git a/README.md b/README.md index 0d66e09..afe1f47 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Logging Error Handler for DotKernel ![OSS Lifecycle](https://img.shields.io/osslifecycle/dotkernel/dot-errorhandler) -![PHP from Packagist (specify version)](https://img.shields.io/packagist/php-v/dotkernel/dot-errorhandler/3.2.0) +![PHP from Packagist (specify version)](https://img.shields.io/packagist/php-v/dotkernel/dot-errorhandler/3.3.0) [![GitHub issues](https://img.shields.io/github/issues/dotkernel/dot-errorhandler)](https://github.com/dotkernel/dot-errorhandler/issues) [![GitHub forks](https://img.shields.io/github/forks/dotkernel/dot-errorhandler)](https://github.com/dotkernel/dot-errorhandler/network) @@ -11,6 +11,8 @@ Logging Error Handler for DotKernel [![GitHub license](https://img.shields.io/github/license/dotkernel/dot-errorhandler)](https://github.com/dotkernel/dot-errorhandler/blob/3.0/LICENSE) [![SymfonyInsight](https://insight.symfony.com/projects/cf1f8d89-f230-4157-bc8b-7cce20c75454/big.svg)](https://insight.symfony.com/projects/cf1f8d89-f230-4157-bc8b-7cce20c75454) + + ## Adding the error handler * Add the composer package: diff --git a/composer.json b/composer.json index b90bcb4..3f3a0ad 100644 --- a/composer.json +++ b/composer.json @@ -48,7 +48,7 @@ }, "autoload-dev": { "psr-4": { - "Dot\\Tests\\": "tests/" + "DotTest\\ErrorHandler\\": "test/" } }, "minimum-stability": "stable", diff --git a/phpcs.xml b/phpcs.xml index 60157b0..1efe663 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -12,9 +12,8 @@ - config src - tests + test diff --git a/phpunit.xml b/phpunit.xml index 2aa816e..ef7dc50 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -2,7 +2,7 @@ - ./tests + ./test diff --git a/src/ErrorHandler.php b/src/ErrorHandler.php index 9b4b9ae..2f21f75 100644 --- a/src/ErrorHandler.php +++ b/src/ErrorHandler.php @@ -17,52 +17,6 @@ use function restore_error_handler; use function set_error_handler; -/** - * Error handler middleware. - * - * Use this middleware as the outermost (or close to outermost) middleware - * layer, and use it to intercept PHP errors and exceptions. - * - * The class offers two extension points: - * - * - Error response generators. - * - Listeners. - * - * Error response generators are callables with the following signature: - * - * - * function ( - * Throwable $e, - * ServerRequestInterface $request, - * ResponseInterface $response - * ) : ResponseInterface - * - * - * These are provided the error, and the request responsible; the response - * provided is the response prototype provided to the ErrorHandler instance - * itself, and can be used as the basis for returning an error response. - * - * An error response generator must be provided as a constructor argument; - * if not provided, an instance of Laminas\Stratigility\Middleware\ErrorResponseGenerator - * will be used. - * - * Listeners use the following signature: - * - * - * function ( - * Throwable $e, - * ServerRequestInterface $request, - * ResponseInterface $response - * ) : void - * - * - * Listeners are given the error, the request responsible, and the generated - * error response, and can then react to them. They are best suited for - * logging and monitoring purposes. - * - * Listeners are attached using the attachListener() method, and triggered - * in the order attached. - */ class ErrorHandler implements MiddlewareInterface, ErrorHandlerInterface { /** @var callable[] */ @@ -87,19 +41,6 @@ public function __construct(callable $responseFactory, ?callable $responseGenera $this->responseGenerator = $responseGenerator ?: new ErrorResponseGenerator(); } - /** - * Attach an error listener. - * - * Each listener receives the following three arguments: - * - * - Throwable $error - * - ServerRequestInterface $request - * - ResponseInterface $response - * - * These instances are all immutable, and the return values of - * listeners are ignored; use listeners for reporting purposes - * only. - */ public function attachListener(callable $listener): void { if (in_array($listener, $this->listeners, true)) { @@ -109,19 +50,6 @@ public function attachListener(callable $listener): void $this->listeners[] = $listener; } - /** - * Middleware to handle errors and exceptions in layers it wraps. - * - * Adds an error handler that will convert PHP errors to ErrorException - * instances. - * - * Internally, wraps the call to $next() in a try/catch block, catching - * all PHP Throwables. - * - * When an exception is caught, an appropriate error response is created - * and returned instead; otherwise, the response returned by $next is - * used. - */ public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { set_error_handler($this->createErrorHandler()); diff --git a/src/LogErrorHandler.php b/src/LogErrorHandler.php index a30a3cf..fa99659 100644 --- a/src/LogErrorHandler.php +++ b/src/LogErrorHandler.php @@ -18,52 +18,6 @@ use function restore_error_handler; use function set_error_handler; -/** - * Error handler middleware. - * - * Use this middleware as the outermost (or close to outermost) middleware - * layer, and use it to intercept PHP errors and exceptions. - * - * The class offers two extension points: - * - * - Error response generators. - * - Listeners. - * - * Error response generators are callables with the following signature: - * - * - * function ( - * Throwable $e, - * ServerRequestInterface $request, - * ResponseInterface $response - * ) : ResponseInterface - * - * - * These are provided the error, and the request responsible; the response - * provided is the response prototype provided to the ErrorHandler instance - * itself, and can be used as the basis for returning an error response. - * - * An error response generator must be provided as a constructor argument; - * if not provided, an instance of Laminas\Stratigility\Middleware\ErrorResponseGenerator - * will be used. - * - * Listeners use the following signature: - * - * - * function ( - * Throwable $e, - * ServerRequestInterface $request, - * ResponseInterface $response - * ) : void - * - * - * Listeners are given the error, the request responsible, and the generated - * error response, and can then react to them. They are best suited for - * logging and monitoring purposes. - * - * Listeners are attached using the attachListener() method, and triggered - * in the order attached. - */ class LogErrorHandler implements MiddlewareInterface, ErrorHandlerInterface { /** @var callable[] */ @@ -93,19 +47,6 @@ public function __construct( $this->logger = $logger; } - /** - * Attach an error listener. - * - * Each listener receives the following three arguments: - * - * - Throwable $error - * - ServerRequestInterface $request - * - ResponseInterface $response - * - * These instances are all immutable, and the return values of - * listeners are ignored; use listeners for reporting purposes - * only. - */ public function attachListener(callable $listener): void { if (in_array($listener, $this->listeners, true)) { @@ -115,19 +56,6 @@ public function attachListener(callable $listener): void $this->listeners[] = $listener; } - /** - * Middleware to handle errors and exceptions in layers it wraps. - * - * Adds an error handler that will convert PHP errors to ErrorException - * instances. - * - * Internally, wraps the call to $next() in a try/catch block, catching - * all PHP Throwables. - * - * When an exception is caught, an appropriate error response is created - * and returned instead; otherwise, the response returned by $next is - * used. - */ public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { set_error_handler($this->createErrorHandler()); diff --git a/tests/ConfigProviderTest.php b/test/ConfigProviderTest.php similarity index 95% rename from tests/ConfigProviderTest.php rename to test/ConfigProviderTest.php index 84db919..4b3ec7a 100644 --- a/tests/ConfigProviderTest.php +++ b/test/ConfigProviderTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Dot\Tests; +namespace DotTest\ErrorHandler; use Dot\ErrorHandler\ConfigProvider; use Dot\ErrorHandler\ErrorHandler; @@ -16,8 +16,6 @@ class ConfigProviderTest extends TestCase protected function setUp(): void { - parent::setUp(); - $this->config = (new ConfigProvider())(); } diff --git a/tests/ErrorHandlerFactoryTest.php b/test/ErrorHandlerFactoryTest.php similarity index 97% rename from tests/ErrorHandlerFactoryTest.php rename to test/ErrorHandlerFactoryTest.php index 73e8f72..85676fd 100644 --- a/tests/ErrorHandlerFactoryTest.php +++ b/test/ErrorHandlerFactoryTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Dot\Tests; +namespace DotTest\ErrorHandler; use Dot\ErrorHandler\ErrorHandler; use Dot\ErrorHandler\ErrorHandlerFactory; @@ -26,8 +26,6 @@ class ErrorHandlerFactoryTest extends TestCase */ public function setUp(): void { - parent::setUp(); - $this->container = $this->createMock(ContainerInterface::class); $this->responseFactory = fn(): ResponseInterface => $this->createMock(ResponseInterface::class); } diff --git a/tests/ErrorHandlerTest.php b/test/ErrorHandlerTest.php similarity index 98% rename from tests/ErrorHandlerTest.php rename to test/ErrorHandlerTest.php index 4864112..8b9536b 100644 --- a/tests/ErrorHandlerTest.php +++ b/test/ErrorHandlerTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Dot\Tests; +namespace DotTest\ErrorHandler; use Dot\ErrorHandler\ErrorHandler; use Dot\ErrorHandler\ErrorHandler as Subject; @@ -40,8 +40,6 @@ class ErrorHandlerTest extends TestCase */ public function setUp(): void { - parent::setUp(); - $this->response = $this->createMock(ResponseInterface::class); $this->serverRequest = $this->createMock(ServerRequestInterface::class); $this->body = $this->createMock(StreamInterface::class); @@ -146,7 +144,7 @@ public function testErrorHandlingTriggersListeners(): void ->method('getReasonPhrase') ->willReturn('Not Implemented'); - $listener = function ( + $listener = function ( Throwable $error, ServerRequestInterface $request, ResponseInterface $response @@ -155,6 +153,7 @@ public function testErrorHandlingTriggersListeners(): void $this->assertSame($this->serverRequest, $request); $this->assertSame($this->response, $response); }; + $listener2 = clone $listener; $this->subject->attachListener($listener); diff --git a/tests/LogErrorHandlerFactoryTest.php b/test/LogErrorHandlerFactoryTest.php similarity index 97% rename from tests/LogErrorHandlerFactoryTest.php rename to test/LogErrorHandlerFactoryTest.php index a51c021..418dc7a 100644 --- a/tests/LogErrorHandlerFactoryTest.php +++ b/test/LogErrorHandlerFactoryTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Dot\Tests; +namespace DotTest\ErrorHandler; use Dot\ErrorHandler\LogErrorHandler; use Dot\ErrorHandler\LogErrorHandlerFactory; @@ -29,12 +29,14 @@ class LogErrorHandlerFactoryTest extends TestCase */ public function setUp(): void { - parent::setUp(); - $this->container = $this->createMock(ContainerInterface::class); $this->responseFactory = fn(): ResponseInterface => $this->createMock(ResponseInterface::class); } + /** + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ public function testWillNotCreateWithoutConfig(): void { $this->container->method('get') diff --git a/tests/LogErrorHandlerTest.php b/test/LogErrorHandlerTest.php similarity index 99% rename from tests/LogErrorHandlerTest.php rename to test/LogErrorHandlerTest.php index 94537d5..73fa3a0 100644 --- a/tests/LogErrorHandlerTest.php +++ b/test/LogErrorHandlerTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Dot\Tests; +namespace DotTest\ErrorHandler; use Dot\ErrorHandler\LogErrorHandler; use Dot\ErrorHandler\LogErrorHandler as Subject; @@ -43,8 +43,6 @@ class LogErrorHandlerTest extends TestCase */ public function setUp(): void { - parent::setUp(); - $this->response = $this->createMock(ResponseInterface::class); $this->serverRequest = $this->createMock(ServerRequestInterface::class); $this->body = $this->createMock(StreamInterface::class); From 34f72a6a82ef1b6000c3f8e3de4ead785edfb18f Mon Sep 17 00:00:00 2001 From: Jurj-Bogdan Date: Wed, 19 Jul 2023 07:41:45 +0300 Subject: [PATCH 4/4] added github actions --- .github/workflows/cs-tests.yml | 46 ++++++++++++++++++++++++++ .github/workflows/static-analysis.yml | 46 ++++++++++++++++++++++++++ .github/workflows/unit-tests.yml | 47 +++++++++++++++++++++++++++ OSSMETADATA | 2 +- 4 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/cs-tests.yml create mode 100644 .github/workflows/static-analysis.yml create mode 100644 .github/workflows/unit-tests.yml diff --git a/.github/workflows/cs-tests.yml b/.github/workflows/cs-tests.yml new file mode 100644 index 0000000..3da9965 --- /dev/null +++ b/.github/workflows/cs-tests.yml @@ -0,0 +1,46 @@ +on: + - push + +name: Run phpcs checks + +jobs: + mutation: + name: PHP ${{ matrix.php }}-${{ matrix.os }} + + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: + - ubuntu-latest + + php: + - "8.1" + - "8.2" + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "${{ matrix.php }}" + tools: composer:v2, cs2pr + coverage: none + + - name: Determine composer cache directory + run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV + + - name: Cache dependencies installed with composer + uses: actions/cache@v3 + with: + path: ${{ env.COMPOSER_CACHE_DIR }} + key: php${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: | + php${{ matrix.php }}-composer- + - name: Install dependencies with composer + run: composer update --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi + + - name: Run phpcs checks + run: vendor/bin/phpcs diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml new file mode 100644 index 0000000..74550fc --- /dev/null +++ b/.github/workflows/static-analysis.yml @@ -0,0 +1,46 @@ +on: + - push + +name: Run static analysis + +jobs: + mutation: + name: PHP ${{ matrix.php }}-${{ matrix.os }} + + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: + - ubuntu-latest + + php: + - "8.1" + - "8.2" + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "${{ matrix.php }}" + tools: composer:v2, cs2pr + coverage: none + + - name: Determine composer cache directory + run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV + + - name: Cache dependencies installed with composer + uses: actions/cache@v3 + with: + path: ${{ env.COMPOSER_CACHE_DIR }} + key: php${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: | + php${{ matrix.php }}-composer- + - name: Install dependencies with composer + run: composer update --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi + + - name: Run static analysis + run: vendor/bin/psalm --no-cache --output-format=github --show-info=false --threads=4 diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml new file mode 100644 index 0000000..d2ab8e7 --- /dev/null +++ b/.github/workflows/unit-tests.yml @@ -0,0 +1,47 @@ +on: + - push + +name: Run PHPUnit tests + +jobs: + mutation: + name: PHP ${{ matrix.php }}-${{ matrix.os }} + + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: + - ubuntu-latest + + php: + - "8.1" + - "8.2" + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "${{ matrix.php }}" + tools: composer:v2, cs2pr + coverage: none + + - name: Determine composer cache directory + run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV + + - name: Cache dependencies installed with composer + uses: actions/cache@v3 + with: + path: ${{ env.COMPOSER_CACHE_DIR }} + key: php${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: | + php${{ matrix.php }}-composer- + + - name: Install dependencies with composer + run: composer install --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi + + - name: Run PHPUnit tests + run: vendor/bin/phpunit --colors=always diff --git a/OSSMETADATA b/OSSMETADATA index 6c7e106..b96d4a4 100644 --- a/OSSMETADATA +++ b/OSSMETADATA @@ -1 +1 @@ -osslifecycle=active \ No newline at end of file +osslifecycle=active