From e5fd6f661652b86b3a9b3d659c3c2aab6aa27bd4 Mon Sep 17 00:00:00 2001 From: alexmerlin Date: Wed, 29 Oct 2025 19:09:24 +0200 Subject: [PATCH 1/4] Issue #55: Remove PHP 8.1 and add PHP 8.4 & 8.5 support Signed-off-by: alexmerlin --- .github/workflows/codecov.yml | 2 + .github/workflows/docs-build.yml | 16 --- .github/workflows/static-analysis.yml | 50 +++++++++ README.md | 68 ++++++------ composer.json | 29 ++--- config/error-handling.global.php.dist | 6 +- docs/book/index.md | 1 - docs/book/v3/configuration.md | 76 -------------- docs/book/v3/installation.md | 5 - docs/book/v3/overview.md | 6 -- docs/book/v4/configuration.md | 76 -------------- docs/book/v4/installation.md | 5 - docs/book/v4/overview.md | 6 -- mkdocs.yml | 2 +- phpstan.neon | 8 ++ psalm.xml | 17 --- src/ErrorHandlerFactory.php | 5 +- src/ErrorResponseGenerator.php | 64 +++++++++++ src/LogErrorHandlerFactory.php | 5 +- test/ErrorHandlerFactoryTest.php | 45 ++++---- test/ErrorHandlerTest.php | 4 +- test/LogErrorHandlerFactoryTest.php | 146 +++++++++++++------------- test/LogErrorHandlerTest.php | 4 +- 23 files changed, 281 insertions(+), 365 deletions(-) delete mode 100644 .github/workflows/docs-build.yml create mode 100644 .github/workflows/static-analysis.yml delete mode 100644 docs/book/index.md delete mode 100644 docs/book/v3/configuration.md delete mode 100644 docs/book/v3/installation.md delete mode 100644 docs/book/v3/overview.md delete mode 100644 docs/book/v4/configuration.md delete mode 100644 docs/book/v4/installation.md delete mode 100644 docs/book/v4/overview.md create mode 100644 phpstan.neon delete mode 100644 psalm.xml create mode 100644 src/ErrorResponseGenerator.php diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 184fa62..c0fbc53 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -17,6 +17,8 @@ jobs: php: - "8.2" - "8.3" + - "8.4" + - "8.5" steps: - name: Checkout diff --git a/.github/workflows/docs-build.yml b/.github/workflows/docs-build.yml deleted file mode 100644 index 1a7aa24..0000000 --- a/.github/workflows/docs-build.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: docs-build - -on: - release: - types: [published] - workflow_dispatch: - -jobs: - build-deploy: - runs-on: ubuntu-latest - steps: - - name: Build Docs - uses: dotkernel/documentation-theme/github-actions/docs@main - env: - DEPLOY_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml new file mode 100644 index 0000000..9976515 --- /dev/null +++ b/.github/workflows/static-analysis.yml @@ -0,0 +1,50 @@ +on: + - push + +name: Run PHPStan checks + +jobs: + mutation: + name: PHPStan ${{ matrix.php }}-${{ matrix.os }} + + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: + - ubuntu-latest + + php: + - "8.2" + - "8.3" + - "8.4" + - "8.5" + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "${{ matrix.php }}" + coverage: pcov + ini-values: assert.exception=1, zend.assertions=1, error_reporting=-1, log_errors_max_len=0, display_errors=On + tools: composer:v2, cs2pr + + - 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@v4 + 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 static analysis with PHPStan + run: vendor/bin/phpstan analyse diff --git a/README.md b/README.md index 9b1a63e..99b55b6 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,22 @@ # dot-errorhandler -Error Logging Handler for DotKernel +Error Logging Handler for Dotkernel + +## Version History + +| Branch | Release | Service Manager | Log style implementation | PHP Version | +|--------|----------|-------------------|--------------------------|------------------------------------------------------------------------------------------------------------------| +| 4.0 | < 4.1.0 | Service Manager 3 | Laminas Log style | ![PHP from Packagist (specify version)](https://img.shields.io/packagist/php-v/dotkernel/dot-errorhandler/4.0.1) | +| 3.0 | < 4.0.0 | Service Manager 3 | Laminas Log | ![PHP from Packagist (specify version)](https://img.shields.io/packagist/php-v/dotkernel/dot-errorhandler/3.4.1) | + +## Documentation + +Documentation is available at: https://docs.dotkernel.org/dot-errorhandler/ + +## Badges ![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/4.0.0) +![PHP from Packagist (specify version)](https://img.shields.io/packagist/php-v/dotkernel/dot-errorhandler/4.0.1) [![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) @@ -12,24 +25,27 @@ Error Logging Handler for DotKernel [![Build Static](https://github.com/dotkernel/dot-errorhandler/actions/workflows/continuous-integration.yml/badge.svg?branch=4.0)](https://github.com/dotkernel/dot-errorhandler/actions/workflows/continuous-integration.yml) [![codecov](https://codecov.io/gh/dotkernel/dot-errorhandler/branch/4.0/graph/badge.svg?token=0KIJARS5RS)](https://codecov.io/gh/dotkernel/dot-errorhandler) - -[![SymfonyInsight](https://insight.symfony.com/projects/cf1f8d89-f230-4157-bc8b-7cce20c75454/big.svg)](https://insight.symfony.com/projects/cf1f8d89-f230-4157-bc8b-7cce20c75454) +[![PHPStan](https://github.com/dotkernel/dot-errorhandler/actions/workflows/static-analysis.yml/badge.svg?branch=4.0)](https://github.com/dotkernel/dot-errorhandler/actions/workflows/static-analysis.yml) ## Adding the error handler -- Add the composer package: +- Add the Composer package. -`composer require dotkernel/dot-errorhandler` +```shell +composer require dotkernel/dot-errorhandler +``` - Add the config provider - in `config/config.php` add `\Dot\ErrorHandler\ConfigProvider` - in `config/pipeline.php` add `\Dot\ErrorHandler\ErrorHandlerInterface::class` - the interface is used as an alias to keep all error handling related configurations in one file - - **IMPORTANT NOTE** there should be no other error handlers after this one (only before) because the other error handler will catch the error causing dot-errorhandler not to catch any error, we recommend using just one error handler unless you have an error-specific handler -- Configure the error handler as shown below +> If you need other error handlers, you should place them before DotErrorhandler in the pipeline; else it will not be able to catch errors. +> We recommend using just one error handler unless you have an error-specific handler. -config/autoload/error-handling.global.php +- Configure the error handler as shown below. + +In `config/autoload/error-handling.global.php`: ```php Both `LogErrorHandler` and `ErrorHandler` have factories declared in the package's `ConfigProvider`. +> If you need a custom ErrorHandler, it must have a factory declared in the config, as in the below example: Example: ```php [ + 'dependencies' => [ 'factories' => [ - MyErrorHandler::class => MyCustomHandlerFactory::class, + \App\CustomErrorHandler::class => \App\CustomHandlerFactory::class, ], - 'aliases' => [ - ErrorHandlerInterface::class => MyErrorHandler::class, - ] + \Dot\ErrorHandler\ErrorHandlerInterface::class => \App\CustomErrorHandler::class, + ], ], 'dot-errorhandler' => [ 'loggerEnabled' => true, - 'logger' => 'dot-log.default_logger' - ] + 'logger' => 'dot-log.default_logger', + ], ]; ``` diff --git a/composer.json b/composer.json index 854cc32..eee5c11 100644 --- a/composer.json +++ b/composer.json @@ -6,7 +6,7 @@ "homepage": "https://github.com/dotkernel/dot-errorhandler", "authors": [ { - "name": "DotKernel Team", + "name": "Dotkernel Team", "email": "team@dotkernel.com" } ], @@ -26,19 +26,20 @@ } }, "require": { - "php": "~8.2.0 || ~8.3.0", - "dotkernel/dot-log": "^4.0.2", - "laminas/laminas-diactoros": "^3.3", - "laminas/laminas-stratigility": "^3.11", - "mezzio/mezzio": "^3.19", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "dotkernel/dot-log": "^4.0.5", + "laminas/laminas-diactoros": "^3.8.0", + "laminas/laminas-stratigility": "^4.3.0", "psr/http-message": "^1.0 || ^2.0", - "psr/http-server-middleware": "^1.0" + "psr/http-server-middleware": "^1.0", + "psr/log": "^3.0" }, "require-dev": { - "laminas/laminas-coding-standard": "^2.5", + "laminas/laminas-coding-standard": "^3.0", "mikey179/vfsstream": "^1.6.7", - "phpunit/phpunit": "^10.5", - "vimeo/psalm": "^5.22" + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5" }, "autoload": { "psr-4": { @@ -54,12 +55,12 @@ "scripts": { "check": [ "@cs-check", - "@test" + "@test", + "@static-analysis" ], "cs-check": "phpcs", "cs-fix": "phpcbf", - "test": "phpunit --colors=always", - "test-coverage": "phpunit --colors=always --coverage-clover clover.xml", - "static-analysis": "psalm --shepherd --stats" + "static-analysis": "phpstan analyse --memory-limit 1G", + "test": "phpunit --colors=always" } } diff --git a/config/error-handling.global.php.dist b/config/error-handling.global.php.dist index eb07019..335f7dd 100644 --- a/config/error-handling.global.php.dist +++ b/config/error-handling.global.php.dist @@ -7,10 +7,10 @@ return [ 'dependencies' => [ 'aliases' => [ ErrorHandlerInterface::class => LogErrorHandler::class, - ] + ], ], 'dot-errorhandler' => [ 'loggerEnabled' => true, - 'logger' => 'dot-log.default_logger' - ] + 'logger' => 'dot-log.default_logger', + ], ]; diff --git a/docs/book/index.md b/docs/book/index.md deleted file mode 100644 index fe84005..0000000 --- a/docs/book/index.md +++ /dev/null @@ -1 +0,0 @@ -../../README.md \ No newline at end of file diff --git a/docs/book/v3/configuration.md b/docs/book/v3/configuration.md deleted file mode 100644 index ae1f365..0000000 --- a/docs/book/v3/configuration.md +++ /dev/null @@ -1,76 +0,0 @@ -# Configuration - -Register `dot-errorhandler` in you project by adding `Dot\ErrorHandler\ConfigProvider::class` to your configuration aggregator (to `config/config.php` for example), -and add `\Dot\ErrorHandler\ErrorHandlerInterface::class` (to `config/pipeline.php` for example) **as the outermost layer of the middleware** to catch all Exceptions - -- Configure the error handler as shown below - -config/autoload/error-handling.global.php - -```php - [ - 'aliases' => [ - ErrorHandlerInterface::class => LogErrorHandler::class, - ] - - ], - 'dot-errorhandler' => [ - 'loggerEnabled' => true, - 'logger' => 'dot-log.default_logger' - ] -]; -``` - -A configuration example for the default logger can be found in `config/log.global.php.dist`. - -When declaring the `ErrorHandlerInterface` alias you can choose whether to log or not: - -- for the simple Zend Expressive handler user `ErrorHandler` -- for logging use `LogErrorHandler` - -The class `Dot\ErrorHandler\ErrorHandler` is the same as the Zend Expressive error handling class -the only difference being the removal of the `final` statement for making extension possible. - -The class `Dot\ErrorHandler\LogErrorHandler` is `Dot\ErrorHandler\ErrorHandler` with -added logging support. - -As a note: both `LogErrorHandler` and `ErrorHandler` have factories declared in the -package's `ConfigProvider`. If you need a custom ErrorHandler it must have a factory -declared in the config, as in the example. - -Example: - -```php - [ - 'factories' => [ - MyErrorHandler::class => MyCustomHandlerFactory::class, - ], - - 'aliases' => [ - ErrorHandlerInterface::class => MyErrorHandler::class, - ] - - ], - 'dot-errorhandler' => [ - 'loggerEnabled' => true, - 'logger' => 'dot-log.default_logger' - ] -]; -``` - -Config examples can be found in this project's `config` directory. diff --git a/docs/book/v3/installation.md b/docs/book/v3/installation.md deleted file mode 100644 index 723581c..0000000 --- a/docs/book/v3/installation.md +++ /dev/null @@ -1,5 +0,0 @@ -# Installation - -Install `dotkernel/dot-errorhandler` by executing the following Composer command: - - composer require dotkernel/dot-errorhandler diff --git a/docs/book/v3/overview.md b/docs/book/v3/overview.md deleted file mode 100644 index c124631..0000000 --- a/docs/book/v3/overview.md +++ /dev/null @@ -1,6 +0,0 @@ -# Overview - -`dot-errorhandler` is DotKernel's logging error handler, providing two options: - -- `Dot\ErrorHandler\ErrorHandler`, same as the Zend Expressive error handling class with the only difference being the removal of the `final` statement for making extension possible -- `Dot\ErrorHandler\LogErrorHandler` adds logging support to the default `ErrorHandler` class diff --git a/docs/book/v4/configuration.md b/docs/book/v4/configuration.md deleted file mode 100644 index ae1f365..0000000 --- a/docs/book/v4/configuration.md +++ /dev/null @@ -1,76 +0,0 @@ -# Configuration - -Register `dot-errorhandler` in you project by adding `Dot\ErrorHandler\ConfigProvider::class` to your configuration aggregator (to `config/config.php` for example), -and add `\Dot\ErrorHandler\ErrorHandlerInterface::class` (to `config/pipeline.php` for example) **as the outermost layer of the middleware** to catch all Exceptions - -- Configure the error handler as shown below - -config/autoload/error-handling.global.php - -```php - [ - 'aliases' => [ - ErrorHandlerInterface::class => LogErrorHandler::class, - ] - - ], - 'dot-errorhandler' => [ - 'loggerEnabled' => true, - 'logger' => 'dot-log.default_logger' - ] -]; -``` - -A configuration example for the default logger can be found in `config/log.global.php.dist`. - -When declaring the `ErrorHandlerInterface` alias you can choose whether to log or not: - -- for the simple Zend Expressive handler user `ErrorHandler` -- for logging use `LogErrorHandler` - -The class `Dot\ErrorHandler\ErrorHandler` is the same as the Zend Expressive error handling class -the only difference being the removal of the `final` statement for making extension possible. - -The class `Dot\ErrorHandler\LogErrorHandler` is `Dot\ErrorHandler\ErrorHandler` with -added logging support. - -As a note: both `LogErrorHandler` and `ErrorHandler` have factories declared in the -package's `ConfigProvider`. If you need a custom ErrorHandler it must have a factory -declared in the config, as in the example. - -Example: - -```php - [ - 'factories' => [ - MyErrorHandler::class => MyCustomHandlerFactory::class, - ], - - 'aliases' => [ - ErrorHandlerInterface::class => MyErrorHandler::class, - ] - - ], - 'dot-errorhandler' => [ - 'loggerEnabled' => true, - 'logger' => 'dot-log.default_logger' - ] -]; -``` - -Config examples can be found in this project's `config` directory. diff --git a/docs/book/v4/installation.md b/docs/book/v4/installation.md deleted file mode 100644 index 723581c..0000000 --- a/docs/book/v4/installation.md +++ /dev/null @@ -1,5 +0,0 @@ -# Installation - -Install `dotkernel/dot-errorhandler` by executing the following Composer command: - - composer require dotkernel/dot-errorhandler diff --git a/docs/book/v4/overview.md b/docs/book/v4/overview.md deleted file mode 100644 index c124631..0000000 --- a/docs/book/v4/overview.md +++ /dev/null @@ -1,6 +0,0 @@ -# Overview - -`dot-errorhandler` is DotKernel's logging error handler, providing two options: - -- `Dot\ErrorHandler\ErrorHandler`, same as the Zend Expressive error handling class with the only difference being the removal of the `final` statement for making extension possible -- `Dot\ErrorHandler\LogErrorHandler` adds logging support to the default `ErrorHandler` class diff --git a/mkdocs.yml b/mkdocs.yml index b95a4df..c7b60cd 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -17,7 +17,7 @@ nav: - Installation: v3/installation.md - Configuration: v3/configuration.md site_name: dot-errorhandler -site_description: "DotKernel's error logging handler" +site_description: "Dotkernel's error logging handler" repo_url: "https://github.com/dotkernel/dot-errorhandler" plugins: - search diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..349be25 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,8 @@ +includes: + - vendor/phpstan/phpstan-phpunit/extension.neon +parameters: + level: 5 + paths: + - src + - test + treatPhpDocTypesAsCertain: false diff --git a/psalm.xml b/psalm.xml deleted file mode 100644 index b4b6bb5..0000000 --- a/psalm.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - diff --git a/src/ErrorHandlerFactory.php b/src/ErrorHandlerFactory.php index 5f25f70..718e81c 100644 --- a/src/ErrorHandlerFactory.php +++ b/src/ErrorHandlerFactory.php @@ -4,7 +4,6 @@ namespace Dot\ErrorHandler; -use Mezzio\Middleware\ErrorResponseGenerator; use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface; use Psr\Container\NotFoundExceptionInterface; @@ -18,8 +17,8 @@ class ErrorHandlerFactory */ public function __invoke(ContainerInterface $container): ErrorHandler { - $generator = $container->has(ErrorResponseGenerator::class) - ? $container->get(ErrorResponseGenerator::class) + $generator = $container->has('Mezzio\Middleware\ErrorResponseGenerator') + ? $container->get('Mezzio\Middleware\ErrorResponseGenerator') : null; return new ErrorHandler($container->get(ResponseInterface::class), $generator); diff --git a/src/ErrorResponseGenerator.php b/src/ErrorResponseGenerator.php new file mode 100644 index 0000000..bdb21cd --- /dev/null +++ b/src/ErrorResponseGenerator.php @@ -0,0 +1,64 @@ +withStatus(self::getStatusCode($e, $response)); + $body = $response->getBody(); + + if ($this->isDevelopmentMode) { + $escaper = new Escaper(); + $body->write($escaper->escapeHtml((string) $e)); + return $response; + } + + $body->write($response->getReasonPhrase() ?: 'Unknown Error'); + return $response; + } + + /** + * Determine status code from an error and/or response. + * + * If the error is an exception with a code between 400 and 599, returns + * the exception code. + * + * Otherwise, retrieves the code from the response; if not present, or + * less than 400 or greater than 599, returns 500; otherwise, returns it. + */ + public static function getStatusCode(Throwable $error, ResponseInterface $response): int + { + $errorCode = $error->getCode(); + if (is_int($errorCode) && $errorCode >= 400 && $errorCode < 600) { + return $errorCode; + } + + $status = $response->getStatusCode(); + if ($status < 400 || $status >= 600) { + $status = 500; + } + return $status; + } +} diff --git a/src/LogErrorHandlerFactory.php b/src/LogErrorHandlerFactory.php index 4220e4a..8edcefd 100644 --- a/src/LogErrorHandlerFactory.php +++ b/src/LogErrorHandlerFactory.php @@ -6,7 +6,6 @@ use Dot\Log\LoggerInterface; use InvalidArgumentException; -use Mezzio\Middleware\ErrorResponseGenerator; use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface; use Psr\Container\NotFoundExceptionInterface; @@ -47,8 +46,8 @@ public function __invoke(ContainerInterface $container): MiddlewareInterface $logger = $container->get($errorHandlerConfig[self::ERROR_HANDLER_LOGGER_KEY]); } - $generator = $container->has(ErrorResponseGenerator::class) - ? $container->get(ErrorResponseGenerator::class) + $generator = $container->has('Mezzio\Middleware\ErrorResponseGenerator') + ? $container->get('Mezzio\Middleware\ErrorResponseGenerator') : null; return new LogErrorHandler($container->get(ResponseInterface::class), $generator, $logger); diff --git a/test/ErrorHandlerFactoryTest.php b/test/ErrorHandlerFactoryTest.php index 85676fd..d31feea 100644 --- a/test/ErrorHandlerFactoryTest.php +++ b/test/ErrorHandlerFactoryTest.php @@ -6,7 +6,6 @@ use Dot\ErrorHandler\ErrorHandler; use Dot\ErrorHandler\ErrorHandlerFactory; -use Mezzio\Middleware\ErrorResponseGenerator; use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -37,7 +36,7 @@ public function setUp(): void public function testWillCreateWithDefaultOption(): void { $this->container->method('has') - ->with(ErrorResponseGenerator::class) + ->with('Mezzio\Middleware\ErrorResponseGenerator') ->willReturn(false); $this->container->method('get') @@ -45,27 +44,27 @@ public function testWillCreateWithDefaultOption(): void ->willReturn($this->responseFactory); $result = (new ErrorHandlerFactory())($this->container); - $this->assertInstanceOf(ErrorHandler::class, $result); + $this->assertContainsOnlyInstancesOf(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); - } +// /** +// * @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/test/ErrorHandlerTest.php b/test/ErrorHandlerTest.php index 8b9536b..c1e514e 100644 --- a/test/ErrorHandlerTest.php +++ b/test/ErrorHandlerTest.php @@ -53,7 +53,7 @@ public function setUp(): void public function testWillCreateWithDefaultParameters(): void { - $this->assertInstanceOf(Subject::class, $this->subject); + $this->assertContainsOnlyInstancesOf(Subject::class, [$this->subject]); } public function testCreateErrorHandlerReturnsCallable(): void @@ -114,7 +114,7 @@ public function testHandleThrowable(): void $response = ($this->errorResponseGenerator)($this->exception, $this->serverRequest, ($this->responseFactory)()); - $this->assertInstanceOf(ResponseInterface::class, $response); + $this->assertContainsOnlyInstancesOf(ResponseInterface::class, [$response]); } public function testErrorHandlingTriggersListeners(): void diff --git a/test/LogErrorHandlerFactoryTest.php b/test/LogErrorHandlerFactoryTest.php index 418dc7a..ea0c3ff 100644 --- a/test/LogErrorHandlerFactoryTest.php +++ b/test/LogErrorHandlerFactoryTest.php @@ -4,33 +4,29 @@ namespace DotTest\ErrorHandler; -use Dot\ErrorHandler\LogErrorHandler; use Dot\ErrorHandler\LogErrorHandlerFactory; -use Mezzio\Middleware\ErrorResponseGenerator; use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface; use Psr\Container\NotFoundExceptionInterface; -use Psr\Http\Message\ResponseInterface; -use Psr\Log\LoggerInterface; use function sprintf; class LogErrorHandlerFactoryTest extends TestCase { private ContainerInterface|MockObject $container; - /** @var callable $responseFactory */ - private $responseFactory; +// /** @var callable $responseFactory */ +// private $responseFactory; /** * @throws Exception */ public function setUp(): void { - $this->container = $this->createMock(ContainerInterface::class); - $this->responseFactory = fn(): ResponseInterface => $this->createMock(ResponseInterface::class); + $this->container = $this->createMock(ContainerInterface::class); +// $this->responseFactory = fn(): ResponseInterface => $this->createMock(ResponseInterface::class); } /** @@ -77,71 +73,71 @@ public function testWillNotCreateWithMissingLoggerKey(): void (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); - } +// /** +// * @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/test/LogErrorHandlerTest.php b/test/LogErrorHandlerTest.php index 9afbb6d..317902f 100644 --- a/test/LogErrorHandlerTest.php +++ b/test/LogErrorHandlerTest.php @@ -63,7 +63,7 @@ public function setUp(): void public function testWillCreateWithDefaultParameters(): void { - $this->assertInstanceOf(Subject::class, $this->subject); + $this->assertContainsOnlyInstancesOf(Subject::class, [$this->subject]); } public function testCreateErrorHandlerReturnsCallable(): void @@ -145,7 +145,7 @@ public function testHandleThrowable(): void $response = $responseGenerator($this->exception, $this->serverRequest, ($this->responseFactory)()); - $this->assertInstanceOf(ResponseInterface::class, $response); + $this->assertContainsOnlyInstancesOf(ResponseInterface::class, [$response]); } public function testErrorHandlingTriggersListeners(): void From 658760adb368cd1d5ad5a264f3f73cd0a66ec6be Mon Sep 17 00:00:00 2001 From: alexmerlin Date: Wed, 29 Oct 2025 19:14:43 +0200 Subject: [PATCH 2/4] Issue #55: Remove PHP 8.1 and add PHP 8.4 & 8.5 support Signed-off-by: alexmerlin --- .laminas-ci.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .laminas-ci.json diff --git a/.laminas-ci.json b/.laminas-ci.json new file mode 100644 index 0000000..29216fa --- /dev/null +++ b/.laminas-ci.json @@ -0,0 +1,3 @@ +{ + "backwardCompatibilityCheck": true +} From 19b763033f13e0f4f458158f7439e43e29d85148 Mon Sep 17 00:00:00 2001 From: alexmerlin Date: Thu, 30 Oct 2025 12:22:24 +0200 Subject: [PATCH 3/4] Issue #55: Remove PHP 8.1 and add PHP 8.4 & 8.5 support Signed-off-by: alexmerlin --- composer.json | 1 + src/ErrorHandlerFactory.php | 5 +- src/LogErrorHandlerFactory.php | 5 +- test/ErrorHandlerFactoryTest.php | 43 ++++---- test/LogErrorHandlerFactoryTest.php | 146 ++++++++++++++-------------- 5 files changed, 104 insertions(+), 96 deletions(-) diff --git a/composer.json b/composer.json index eee5c11..5b16213 100644 --- a/composer.json +++ b/composer.json @@ -30,6 +30,7 @@ "dotkernel/dot-log": "^4.0.5", "laminas/laminas-diactoros": "^3.8.0", "laminas/laminas-stratigility": "^4.3.0", + "mezzio/mezzio": "^3.24", "psr/http-message": "^1.0 || ^2.0", "psr/http-server-middleware": "^1.0", "psr/log": "^3.0" diff --git a/src/ErrorHandlerFactory.php b/src/ErrorHandlerFactory.php index 718e81c..5f25f70 100644 --- a/src/ErrorHandlerFactory.php +++ b/src/ErrorHandlerFactory.php @@ -4,6 +4,7 @@ namespace Dot\ErrorHandler; +use Mezzio\Middleware\ErrorResponseGenerator; use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface; use Psr\Container\NotFoundExceptionInterface; @@ -17,8 +18,8 @@ class ErrorHandlerFactory */ public function __invoke(ContainerInterface $container): ErrorHandler { - $generator = $container->has('Mezzio\Middleware\ErrorResponseGenerator') - ? $container->get('Mezzio\Middleware\ErrorResponseGenerator') + $generator = $container->has(ErrorResponseGenerator::class) + ? $container->get(ErrorResponseGenerator::class) : null; return new ErrorHandler($container->get(ResponseInterface::class), $generator); diff --git a/src/LogErrorHandlerFactory.php b/src/LogErrorHandlerFactory.php index 8edcefd..4220e4a 100644 --- a/src/LogErrorHandlerFactory.php +++ b/src/LogErrorHandlerFactory.php @@ -6,6 +6,7 @@ use Dot\Log\LoggerInterface; use InvalidArgumentException; +use Mezzio\Middleware\ErrorResponseGenerator; use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface; use Psr\Container\NotFoundExceptionInterface; @@ -46,8 +47,8 @@ public function __invoke(ContainerInterface $container): MiddlewareInterface $logger = $container->get($errorHandlerConfig[self::ERROR_HANDLER_LOGGER_KEY]); } - $generator = $container->has('Mezzio\Middleware\ErrorResponseGenerator') - ? $container->get('Mezzio\Middleware\ErrorResponseGenerator') + $generator = $container->has(ErrorResponseGenerator::class) + ? $container->get(ErrorResponseGenerator::class) : null; return new LogErrorHandler($container->get(ResponseInterface::class), $generator, $logger); diff --git a/test/ErrorHandlerFactoryTest.php b/test/ErrorHandlerFactoryTest.php index d31feea..01de087 100644 --- a/test/ErrorHandlerFactoryTest.php +++ b/test/ErrorHandlerFactoryTest.php @@ -6,6 +6,7 @@ use Dot\ErrorHandler\ErrorHandler; use Dot\ErrorHandler\ErrorHandlerFactory; +use Mezzio\Middleware\ErrorResponseGenerator; use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -36,7 +37,7 @@ public function setUp(): void public function testWillCreateWithDefaultOption(): void { $this->container->method('has') - ->with('Mezzio\Middleware\ErrorResponseGenerator') + ->with(ErrorResponseGenerator::class) ->willReturn(false); $this->container->method('get') @@ -47,24 +48,24 @@ public function testWillCreateWithDefaultOption(): void $this->assertContainsOnlyInstancesOf(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); -// } + /** + * @throws ContainerExceptionInterface + * @throws Exception + * @throws NotFoundExceptionInterface + */ + public function testWillCreateWithErrorResponseGenerator(): void + { + $this->container->method('has') + ->with(ErrorResponseGenerator::class) + ->willReturn(true); + + $this->container->method('get') + ->willReturnMap([ + [ErrorResponseGenerator::class, $this->createMock(ErrorResponseGenerator::class)], + [ResponseInterface::class, $this->responseFactory], + ]); + + $result = (new ErrorHandlerFactory())($this->container); + $this->assertContainsOnlyInstancesOf(ErrorHandler::class, [$result]); + } } diff --git a/test/LogErrorHandlerFactoryTest.php b/test/LogErrorHandlerFactoryTest.php index ea0c3ff..418dc7a 100644 --- a/test/LogErrorHandlerFactoryTest.php +++ b/test/LogErrorHandlerFactoryTest.php @@ -4,29 +4,33 @@ namespace DotTest\ErrorHandler; +use Dot\ErrorHandler\LogErrorHandler; use Dot\ErrorHandler\LogErrorHandlerFactory; +use Mezzio\Middleware\ErrorResponseGenerator; use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface; use Psr\Container\NotFoundExceptionInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Log\LoggerInterface; use function sprintf; class LogErrorHandlerFactoryTest extends TestCase { private ContainerInterface|MockObject $container; -// /** @var callable $responseFactory */ -// private $responseFactory; + /** @var callable $responseFactory */ + private $responseFactory; /** * @throws Exception */ public function setUp(): void { - $this->container = $this->createMock(ContainerInterface::class); -// $this->responseFactory = fn(): ResponseInterface => $this->createMock(ResponseInterface::class); + $this->container = $this->createMock(ContainerInterface::class); + $this->responseFactory = fn(): ResponseInterface => $this->createMock(ResponseInterface::class); } /** @@ -73,71 +77,71 @@ public function testWillNotCreateWithMissingLoggerKey(): void (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); -// } + /** + * @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); + } } From 735ff1dff4bb694b9782043af559870ee1aa0307 Mon Sep 17 00:00:00 2001 From: alexmerlin Date: Thu, 30 Oct 2025 12:23:17 +0200 Subject: [PATCH 4/4] Issue #55: Remove PHP 8.1 and add PHP 8.4 & 8.5 support Signed-off-by: alexmerlin --- src/ErrorResponseGenerator.php | 64 ---------------------------------- 1 file changed, 64 deletions(-) delete mode 100644 src/ErrorResponseGenerator.php diff --git a/src/ErrorResponseGenerator.php b/src/ErrorResponseGenerator.php deleted file mode 100644 index bdb21cd..0000000 --- a/src/ErrorResponseGenerator.php +++ /dev/null @@ -1,64 +0,0 @@ -withStatus(self::getStatusCode($e, $response)); - $body = $response->getBody(); - - if ($this->isDevelopmentMode) { - $escaper = new Escaper(); - $body->write($escaper->escapeHtml((string) $e)); - return $response; - } - - $body->write($response->getReasonPhrase() ?: 'Unknown Error'); - return $response; - } - - /** - * Determine status code from an error and/or response. - * - * If the error is an exception with a code between 400 and 599, returns - * the exception code. - * - * Otherwise, retrieves the code from the response; if not present, or - * less than 400 or greater than 599, returns 500; otherwise, returns it. - */ - public static function getStatusCode(Throwable $error, ResponseInterface $response): int - { - $errorCode = $error->getCode(); - if (is_int($errorCode) && $errorCode >= 400 && $errorCode < 600) { - return $errorCode; - } - - $status = $response->getStatusCode(); - if ($status < 400 || $status >= 600) { - $status = 500; - } - return $status; - } -}