From 797dbcd401abc33efd5b55c009c0b030d3e16a9c Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Wed, 1 Dec 2021 12:18:47 -0600 Subject: [PATCH] feat: PHP 8.1 support This patch provides PHP 8.1 support, via the following changes: - Adds `~8.1.0` to the list of allowed PHP versions. - Marks a conflict with symfony/console versions prior to 5.3.11 (first version supporting PHP 8.1) - Replaces usage of `gmstrftime()` with a combination of casting timestamps to `DateTimeImmutable` objects with a GMT timezone, and using `IntlDateFormatter::formatObject()` with an equivalent date/time format to what was used with `gmstrftime()`. - Adds `#[\ReturnTimeWillChange]` attributes to classes implementing `JsonSerialize` to ensure they remain both backwards and forwards compatible. Signed-off-by: Matthew Weier O'Phinney --- composer.json | 9 +- composer.lock | 204 +++++++++--------- phpunit.xml.dist | 28 ++- psalm-baseline.xml | 2 +- .../LastModifiedMiddleware.php | 10 +- src/Task/ServiceBasedTask.php | 3 + src/Task/Task.php | 3 + test/FormatTimestampTrait.php | 21 ++ .../IntegrationMappedTest.php | 21 +- .../StaticResourceHandler/IntegrationTest.php | 21 +- .../LastModifiedMiddlewareTest.php | 6 +- 11 files changed, 188 insertions(+), 140 deletions(-) create mode 100644 test/FormatTimestampTrait.php diff --git a/composer.json b/composer.json index 95eeeef8..957a493d 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ "extra": { }, "require": { - "php": "^7.4 || ~8.0.0", + "php": "^7.4 || ~8.0.0 || ~8.1.0", "composer/package-versions-deprecated": "^1.11", "dflydev/fig-cookies": "^2.0.1 || ^3.0", "laminas/laminas-cli": "^0.1.5 || ^1.0", @@ -48,7 +48,7 @@ "filp/whoops": "^2.1", "laminas/laminas-coding-standard": "~2.2.0", "laminas/laminas-servicemanager": "^3.3", - "phpunit/phpunit": "^9.3.10", + "phpunit/phpunit": "^9.5.5", "psalm/plugin-phpunit": "^0.13.0", "swoole/ide-helper": "^4.5.5", "vimeo/psalm": "^4.0" @@ -79,7 +79,8 @@ "test": "phpunit --colors=always", "test-coverage": "phpunit --colors=always --coverage-clover clover.xml" }, - "replace": { - "zendframework/zend-expressive-swoole": "^2.5.0" + "conflict": { + "symfony/console": "<5.3.11", + "zendframework/zend-expressive-swoole": "*" } } diff --git a/composer.lock b/composer.lock index 53e28ffa..bbb4fd8e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8305ee6c3855a1356cf738e1c7635690", + "content-hash": "7bd9ff9a01c55400c1f4acbf92b7e7b7", "packages": [ { "name": "composer/package-versions-deprecated", @@ -199,29 +199,29 @@ }, { "name": "laminas/laminas-cli", - "version": "1.2.0", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-cli.git", - "reference": "725d5dbc791dd8c60865309fb35b5030a4a07a77" + "reference": "f108b8db3315ed17039922baa1c0674d1f1d64f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-cli/zipball/725d5dbc791dd8c60865309fb35b5030a4a07a77", - "reference": "725d5dbc791dd8c60865309fb35b5030a4a07a77", + "url": "https://api.github.com/repos/laminas/laminas-cli/zipball/f108b8db3315ed17039922baa1c0674d1f1d64f3", + "reference": "f108b8db3315ed17039922baa1c0674d1f1d64f3", "shasum": "" }, "require": { "composer/package-versions-deprecated": "^1.11.99.4", - "php": "^7.3 || ~8.0.0 || ~8.1.0", + "php": "^7.4 || ~8.0.0 || ~8.1.0", "psr/container": "^1.0 || ^2.0", - "symfony/console": "^5.3.7", - "symfony/event-dispatcher": "^5.0", + "symfony/console": "^5.3.7 || ^6.0", + "symfony/event-dispatcher": "^5.0 || ^6.0", "symfony/polyfill-php80": "^1.17", - "webmozart/assert": "^1.9" + "webmozart/assert": "^1.10" }, "require-dev": { - "laminas/laminas-coding-standard": "~2.2.0", + "laminas/laminas-coding-standard": "~2.2.1", "laminas/laminas-mvc": "^3.1.1", "laminas/laminas-servicemanager": "^3.4", "mikey179/vfsstream": "2.0.x-dev", @@ -263,7 +263,7 @@ "type": "community_bridge" } ], - "time": "2021-09-20T14:36:45+00:00" + "time": "2021-11-30T15:20:10+00:00" }, { "name": "laminas/laminas-diactoros", @@ -1270,26 +1270,26 @@ }, { "name": "symfony/console", - "version": "v5.3.10", + "version": "v5.4.0", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "d4e409d9fbcfbf71af0e5a940abb7b0b4bad0bd3" + "reference": "ec3661faca1d110d6c307e124b44f99ac54179e3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/d4e409d9fbcfbf71af0e5a940abb7b0b4bad0bd3", - "reference": "d4e409d9fbcfbf71af0e5a940abb7b0b4bad0bd3", + "url": "https://api.github.com/repos/symfony/console/zipball/ec3661faca1d110d6c307e124b44f99ac54179e3", + "reference": "ec3661faca1d110d6c307e124b44f99ac54179e3", "shasum": "" }, "require": { "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", + "symfony/deprecation-contracts": "^2.1|^3", "symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-php73": "^1.8", "symfony/polyfill-php80": "^1.16", - "symfony/service-contracts": "^1.1|^2", - "symfony/string": "^5.1" + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/string": "^5.1|^6.0" }, "conflict": { "psr/log": ">=3", @@ -1304,12 +1304,12 @@ }, "require-dev": { "psr/log": "^1|^2", - "symfony/config": "^4.4|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/event-dispatcher": "^4.4|^5.0", - "symfony/lock": "^4.4|^5.0", - "symfony/process": "^4.4|^5.0", - "symfony/var-dumper": "^4.4|^5.0" + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/event-dispatcher": "^4.4|^5.0|^6.0", + "symfony/lock": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/var-dumper": "^4.4|^5.0|^6.0" }, "suggest": { "psr/log": "For using the console logger", @@ -1349,7 +1349,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.3.10" + "source": "https://github.com/symfony/console/tree/v5.4.0" }, "funding": [ { @@ -1365,20 +1365,20 @@ "type": "tidelift" } ], - "time": "2021-10-26T09:30:15+00:00" + "time": "2021-11-29T15:30:56+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v2.4.0", + "version": "v2.5.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627" + "reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5f38c8804a9e97d23e0c8d63341088cd8a22d627", - "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/6f981ee24cf69ee7ce9736146d1c57c2780598a8", + "reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8", "shasum": "" }, "require": { @@ -1387,7 +1387,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.4-dev" + "dev-main": "2.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -1416,7 +1416,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v2.4.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.0" }, "funding": [ { @@ -1432,26 +1432,26 @@ "type": "tidelift" } ], - "time": "2021-03-23T23:28:01+00:00" + "time": "2021-07-12T14:48:14+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v5.3.7", + "version": "v5.4.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "ce7b20d69c66a20939d8952b617506a44d102130" + "reference": "27d39ae126352b9fa3be5e196ccf4617897be3eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/ce7b20d69c66a20939d8952b617506a44d102130", - "reference": "ce7b20d69c66a20939d8952b617506a44d102130", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/27d39ae126352b9fa3be5e196ccf4617897be3eb", + "reference": "27d39ae126352b9fa3be5e196ccf4617897be3eb", "shasum": "" }, "require": { "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", - "symfony/event-dispatcher-contracts": "^2", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/event-dispatcher-contracts": "^2|^3", "symfony/polyfill-php80": "^1.16" }, "conflict": { @@ -1463,13 +1463,13 @@ }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^4.4|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/error-handler": "^4.4|^5.0", - "symfony/expression-language": "^4.4|^5.0", - "symfony/http-foundation": "^4.4|^5.0", - "symfony/service-contracts": "^1.1|^2", - "symfony/stopwatch": "^4.4|^5.0" + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/http-foundation": "^4.4|^5.0|^6.0", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/stopwatch": "^4.4|^5.0|^6.0" }, "suggest": { "symfony/dependency-injection": "", @@ -1501,7 +1501,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v5.3.7" + "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.0" }, "funding": [ { @@ -1517,20 +1517,20 @@ "type": "tidelift" } ], - "time": "2021-08-04T21:20:46+00:00" + "time": "2021-11-23T10:19:22+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v2.4.0", + "version": "v2.5.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "69fee1ad2332a7cbab3aca13591953da9cdb7a11" + "reference": "66bea3b09be61613cd3b4043a65a8ec48cfa6d2a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/69fee1ad2332a7cbab3aca13591953da9cdb7a11", - "reference": "69fee1ad2332a7cbab3aca13591953da9cdb7a11", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/66bea3b09be61613cd3b4043a65a8ec48cfa6d2a", + "reference": "66bea3b09be61613cd3b4043a65a8ec48cfa6d2a", "shasum": "" }, "require": { @@ -1543,7 +1543,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.4-dev" + "dev-main": "2.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -1580,7 +1580,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.4.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.5.0" }, "funding": [ { @@ -1596,7 +1596,7 @@ "type": "tidelift" } ], - "time": "2021-03-23T23:28:01+00:00" + "time": "2021-07-12T14:48:14+00:00" }, { "name": "symfony/polyfill-ctype", @@ -2086,21 +2086,25 @@ }, { "name": "symfony/service-contracts", - "version": "v2.4.0", + "version": "v2.5.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb" + "reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb", - "reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc", + "reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc", "shasum": "" }, "require": { "php": ">=7.2.5", - "psr/container": "^1.1" + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1" + }, + "conflict": { + "ext-psr": "<1.1|>=2" }, "suggest": { "symfony/service-implementation": "" @@ -2108,7 +2112,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.4-dev" + "dev-main": "2.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -2145,7 +2149,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v2.4.0" + "source": "https://github.com/symfony/service-contracts/tree/v2.5.0" }, "funding": [ { @@ -2161,20 +2165,20 @@ "type": "tidelift" } ], - "time": "2021-04-01T10:43:52+00:00" + "time": "2021-11-04T16:48:04+00:00" }, { "name": "symfony/string", - "version": "v5.3.10", + "version": "v5.4.0", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "d70c35bb20bbca71fc4ab7921e3c6bda1a82a60c" + "reference": "9ffaaba53c61ba75a3c7a3a779051d1e9ec4fd2d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/d70c35bb20bbca71fc4ab7921e3c6bda1a82a60c", - "reference": "d70c35bb20bbca71fc4ab7921e3c6bda1a82a60c", + "url": "https://api.github.com/repos/symfony/string/zipball/9ffaaba53c61ba75a3c7a3a779051d1e9ec4fd2d", + "reference": "9ffaaba53c61ba75a3c7a3a779051d1e9ec4fd2d", "shasum": "" }, "require": { @@ -2185,11 +2189,14 @@ "symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-php80": "~1.15" }, + "conflict": { + "symfony/translation-contracts": ">=3.0" + }, "require-dev": { - "symfony/error-handler": "^4.4|^5.0", - "symfony/http-client": "^4.4|^5.0", + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/http-client": "^4.4|^5.0|^6.0", "symfony/translation-contracts": "^1.1|^2", - "symfony/var-exporter": "^4.4|^5.0" + "symfony/var-exporter": "^4.4|^5.0|^6.0" }, "type": "library", "autoload": { @@ -2228,7 +2235,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.3.10" + "source": "https://github.com/symfony/string/tree/v5.4.0" }, "funding": [ { @@ -2244,7 +2251,7 @@ "type": "tidelift" } ], - "time": "2021-10-27T18:21:46+00:00" + "time": "2021-11-24T10:02:00+00:00" }, { "name": "webmozart/assert", @@ -3310,16 +3317,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.13.1", + "version": "v4.13.2", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "63a79e8daa781cac14e5195e63ed8ae231dd10fd" + "reference": "210577fe3cf7badcc5814d99455df46564f3c077" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/63a79e8daa781cac14e5195e63ed8ae231dd10fd", - "reference": "63a79e8daa781cac14e5195e63ed8ae231dd10fd", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/210577fe3cf7badcc5814d99455df46564f3c077", + "reference": "210577fe3cf7badcc5814d99455df46564f3c077", "shasum": "" }, "require": { @@ -3360,9 +3367,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.13.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.13.2" }, - "time": "2021-11-03T20:52:16+00:00" + "time": "2021-11-30T19:35:32+00:00" }, { "name": "openlss/lib-array2xml", @@ -3810,16 +3817,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "9.2.8", + "version": "9.2.9", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "cf04e88a2e3c56fc1a65488afd493325b4c1bc3e" + "reference": "f301eb1453c9e7a1bc912ee8b0ea9db22c60223b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/cf04e88a2e3c56fc1a65488afd493325b4c1bc3e", - "reference": "cf04e88a2e3c56fc1a65488afd493325b4c1bc3e", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f301eb1453c9e7a1bc912ee8b0ea9db22c60223b", + "reference": "f301eb1453c9e7a1bc912ee8b0ea9db22c60223b", "shasum": "" }, "require": { @@ -3875,7 +3882,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.8" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.9" }, "funding": [ { @@ -3883,7 +3890,7 @@ "type": "github" } ], - "time": "2021-10-30T08:01:38+00:00" + "time": "2021-11-19T15:21:02+00:00" }, { "name": "phpunit/php-file-iterator", @@ -5368,16 +5375,16 @@ }, { "name": "swoole/ide-helper", - "version": "4.8.1", + "version": "4.8.3", "source": { "type": "git", "url": "https://github.com/swoole/ide-helper.git", - "reference": "0dcd9558133a1742a4a1754e5e2971152bd2267d" + "reference": "3ac4971814273889933b871e03b2a6b340e58f79" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/swoole/ide-helper/zipball/0dcd9558133a1742a4a1754e5e2971152bd2267d", - "reference": "0dcd9558133a1742a4a1754e5e2971152bd2267d", + "url": "https://api.github.com/repos/swoole/ide-helper/zipball/3ac4971814273889933b871e03b2a6b340e58f79", + "reference": "3ac4971814273889933b871e03b2a6b340e58f79", "shasum": "" }, "type": "library", @@ -5394,7 +5401,7 @@ "description": "IDE help files for Swoole.", "support": { "issues": "https://github.com/swoole/ide-helper/issues", - "source": "https://github.com/swoole/ide-helper/tree/4.8.1" + "source": "https://github.com/swoole/ide-helper/tree/4.8.3" }, "funding": [ { @@ -5406,7 +5413,7 @@ "type": "github" } ], - "time": "2021-10-29T16:44:09+00:00" + "time": "2021-12-01T08:11:40+00:00" }, { "name": "theseer/tokenizer", @@ -5460,16 +5467,16 @@ }, { "name": "vimeo/psalm", - "version": "4.12.0", + "version": "4.13.1", "source": { "type": "git", "url": "https://github.com/vimeo/psalm.git", - "reference": "e42bc4a23f67acba28a23bb09c348e2ff38a1d87" + "reference": "5cf660f63b548ccd4a56f62d916ee4d6028e01a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vimeo/psalm/zipball/e42bc4a23f67acba28a23bb09c348e2ff38a1d87", - "reference": "e42bc4a23f67acba28a23bb09c348e2ff38a1d87", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/5cf660f63b548ccd4a56f62d916ee4d6028e01a3", + "reference": "5cf660f63b548ccd4a56f62d916ee4d6028e01a3", "shasum": "" }, "require": { @@ -5515,7 +5522,8 @@ "weirdan/prophecy-shim": "^1.0 || ^2.0" }, "suggest": { - "ext-igbinary": "^2.0.5" + "ext-curl": "In order to send data to shepherd", + "ext-igbinary": "^2.0.5 is required, used to serialize caching data" }, "bin": [ "psalm", @@ -5559,9 +5567,9 @@ ], "support": { "issues": "https://github.com/vimeo/psalm/issues", - "source": "https://github.com/vimeo/psalm/tree/4.12.0" + "source": "https://github.com/vimeo/psalm/tree/4.13.1" }, - "time": "2021-11-06T10:31:17+00:00" + "time": "2021-11-23T23:52:49+00:00" }, { "name": "webimpress/coding-standard", @@ -5676,7 +5684,7 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "^7.4 || ~8.0.0" + "php": "^7.4 || ~8.0.0 || ~8.1.0" }, "platform-dev": [], "plugin-api-version": "2.1.0" diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 9f3800c0..e26904aa 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,13 +1,19 @@ - - - - ./src - - - - - ./test - - + + + + ./src + + + + + + ./test + + diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 17ab478a..7b645848 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,5 +1,5 @@ - + $compressionLevel diff --git a/src/StaticResourceHandler/LastModifiedMiddleware.php b/src/StaticResourceHandler/LastModifiedMiddleware.php index 6fd6b536..46d09095 100644 --- a/src/StaticResourceHandler/LastModifiedMiddleware.php +++ b/src/StaticResourceHandler/LastModifiedMiddleware.php @@ -9,12 +9,12 @@ namespace Mezzio\Swoole\StaticResourceHandler; use DateTimeImmutable; +use DateTimeZone; +use IntlDateFormatter; use Swoole\Http\Request; use function filemtime; -use function gmstrftime; use function preg_match; -use function trim; class LastModifiedMiddleware implements MiddlewareInterface { @@ -42,7 +42,11 @@ public function __invoke(Request $request, string $filename, callable $next): St } $lastModified = filemtime($filename) ?: 0; - $formattedLastModified = trim(gmstrftime('%A %d-%b-%y %T %Z', $lastModified)); + $lastModified = new DateTimeImmutable('@' . $lastModified, new DateTimeZone('GMT')); + $formattedLastModified = IntlDateFormatter::formatObject( + $lastModified, + 'EEEE dd-MMM-yy HH:mm:ss z' + ); $response->addHeader('Last-Modified', $formattedLastModified); diff --git a/src/Task/ServiceBasedTask.php b/src/Task/ServiceBasedTask.php index 7deec7e1..a4c56f77 100644 --- a/src/Task/ServiceBasedTask.php +++ b/src/Task/ServiceBasedTask.php @@ -9,6 +9,8 @@ namespace Mezzio\Swoole\Task; use Psr\Container\ContainerInterface; +// phpcs:ignore SlevomatCodingStandard.Namespaces.UnusedUses.UnusedUse +use ReturnTypeWillChange; use Webmozart\Assert\Assert; /** @@ -59,6 +61,7 @@ public function __invoke(ContainerInterface $container) * * @return array */ + #[ReturnTypeWillChange] public function jsonSerialize() { return [ diff --git a/src/Task/Task.php b/src/Task/Task.php index be553295..f860ac6c 100644 --- a/src/Task/Task.php +++ b/src/Task/Task.php @@ -9,6 +9,8 @@ namespace Mezzio\Swoole\Task; use Psr\Container\ContainerInterface; +// phpcs:ignore SlevomatCodingStandard.Namespaces.UnusedUses.UnusedUse +use ReturnTypeWillChange; use Webmozart\Assert\Assert; use function array_shift; @@ -63,6 +65,7 @@ public function __invoke(ContainerInterface $container) * * @return array */ + #[ReturnTypeWillChange] public function jsonSerialize() { return [ diff --git a/test/FormatTimestampTrait.php b/test/FormatTimestampTrait.php new file mode 100644 index 00000000..33daf09a --- /dev/null +++ b/test/FormatTimestampTrait.php @@ -0,0 +1,21 @@ +mockFileLocRepo->method('findFile')->with('/content.txt')->willReturn($file); $lastModified = filemtime($file); - $lastModifiedFormatted = trim(gmstrftime('%A %d-%b-%y %T %Z', $lastModified)); + $lastModifiedFormatted = $this->formatTimestamp($lastModified); $etag = sprintf('W/"%x-%x"', $lastModified, filesize($file)); $request = $this->createMock(SwooleHttpRequest::class); @@ -200,7 +201,7 @@ public function testSendStaticResourceEmitsHeadersOnlyWhenMatchingDirectivesForH $this->mockFileLocRepo->method('findFile')->with('/content.txt')->willReturn($file); $lastModified = filemtime($file); - $lastModifiedFormatted = trim(gmstrftime('%A %d-%b-%y %T %Z', $lastModified)); + $lastModifiedFormatted = $this->formatTimestamp($lastModified); $etag = sprintf('W/"%x-%x"', $lastModified, filesize($file)); $request = $this->createMock(SwooleHttpRequest::class); @@ -254,7 +255,7 @@ public function testSendStaticResourceEmitsAllowHeaderWithHeadersAndNoBodyWhenMa $this->mockFileLocRepo->method('findFile')->with('/content.txt')->willReturn($file); $lastModified = filemtime($file); - $lastModifiedFormatted = trim(gmstrftime('%A %d-%b-%y %T %Z', $lastModified)); + $lastModifiedFormatted = $this->formatTimestamp($lastModified); $etag = sprintf('W/"%x-%x"', $lastModified, filesize($file)); $request = $this->createMock(SwooleHttpRequest::class); @@ -309,7 +310,7 @@ public function testSendStaticResourceViaGetSkipsClientSideCacheMatchingIfNoETag $this->mockFileLocRepo->method('findFile')->with('/content.txt')->willReturn($file); $lastModified = filemtime($file); - $lastModifiedFormatted = trim(gmstrftime('%A %d-%b-%y %T %Z', $lastModified)); + $lastModifiedFormatted = $this->formatTimestamp($lastModified); $etag = sprintf('W/"%x-%x"', $lastModified, filesize($file)); $request = $this->createMock(SwooleHttpRequest::class); @@ -362,7 +363,7 @@ public function testSendStaticResourceViaHeadSkipsClientSideCacheMatchingIfNoETa $this->mockFileLocRepo->method('findFile')->with('/content.txt')->willReturn($file); $lastModified = filemtime($file); - $lastModifiedFormatted = trim(gmstrftime('%A %d-%b-%y %T %Z', $lastModified)); + $lastModifiedFormatted = $this->formatTimestamp($lastModified); $etag = sprintf('W/"%x-%x"', $lastModified, filesize($file)); $request = $this->createMock(SwooleHttpRequest::class); @@ -565,7 +566,7 @@ public function testSendStaticResourceViaGetHitsClientSideCacheMatchingIfLastMod $this->mockFileLocRepo->method('findFile')->with('/content.txt')->willReturn($file); $lastModified = filemtime($file); - $lastModifiedFormatted = trim(gmstrftime('%A %d-%b-%y %T %Z', $lastModified)); + $lastModifiedFormatted = $this->formatTimestamp($lastModified); $request = $this->createMock(SwooleHttpRequest::class); $request->header = [ @@ -613,9 +614,9 @@ public function testGetDoesNotHitClientSideCacheMatchingIfLastModifiedDoesNotMat $this->mockFileLocRepo->method('findFile')->with('/content.txt')->willReturn($file); $lastModified = filemtime($file); - $lastModifiedFormatted = trim(gmstrftime('%A %d-%b-%y %T %Z', $lastModified)); + $lastModifiedFormatted = $this->formatTimestamp($lastModified); $ifModifiedSince = $lastModified - 3600; - $ifModifiedSinceFormatted = trim(gmstrftime('%A %d-%b-%y %T %Z', $ifModifiedSince)); + $ifModifiedSinceFormatted = $this->formatTimestamp($ifModifiedSince); $request = $this->createMock(SwooleHttpRequest::class); $request->header = [ diff --git a/test/StaticResourceHandler/IntegrationTest.php b/test/StaticResourceHandler/IntegrationTest.php index 663efdc7..340eee06 100644 --- a/test/StaticResourceHandler/IntegrationTest.php +++ b/test/StaticResourceHandler/IntegrationTest.php @@ -10,19 +10,20 @@ use Mezzio\Swoole\StaticResourceHandler; use Mezzio\Swoole\StaticResourceHandler\StaticResourceResponse; +use MezzioTest\Swoole\FormatTimestampTrait; use PHPUnit\Framework\TestCase; use Swoole\Http\Request as SwooleHttpRequest; use Swoole\Http\Response as SwooleHttpResponse; use function filemtime; use function filesize; -use function gmstrftime; use function md5_file; use function sprintf; -use function trim; class IntegrationTest extends TestCase { + use FormatTimestampTrait; + /** * @var string * @psalm-var non-empty-string @@ -117,7 +118,7 @@ public function testSendStaticResourceEmitsContentAndHeadersMatchingDirectivesFo { $file = $this->docRoot . '/content.txt'; $lastModified = filemtime($file); - $lastModifiedFormatted = trim(gmstrftime('%A %d-%b-%y %T %Z', $lastModified)); + $lastModifiedFormatted = $this->formatTimestamp($lastModified); $etag = sprintf('W/"%x-%x"', $lastModified, filesize($file)); $request = $this->createMock(SwooleHttpRequest::class); @@ -164,7 +165,7 @@ public function testSendStaticResourceEmitsHeadersOnlyWhenMatchingDirectivesForH { $file = $this->docRoot . '/content.txt'; $lastModified = filemtime($file); - $lastModifiedFormatted = trim(gmstrftime('%A %d-%b-%y %T %Z', $lastModified)); + $lastModifiedFormatted = $this->formatTimestamp($lastModified); $etag = sprintf('W/"%x-%x"', $lastModified, filesize($file)); $request = $this->createMock(SwooleHttpRequest::class); @@ -213,7 +214,7 @@ public function testSendStaticResourceEmitsAllowHeaderWithHeadersAndNoBodyWhenMa { $file = $this->docRoot . '/content.txt'; $lastModified = filemtime($file); - $lastModifiedFormatted = trim(gmstrftime('%A %d-%b-%y %T %Z', $lastModified)); + $lastModifiedFormatted = $this->formatTimestamp($lastModified); $etag = sprintf('W/"%x-%x"', $lastModified, filesize($file)); $request = $this->createMock(SwooleHttpRequest::class); @@ -263,7 +264,7 @@ public function testSendStaticResourceViaGetSkipsClientSideCacheMatchingIfNoETag { $file = $this->docRoot . '/content.txt'; $lastModified = filemtime($file); - $lastModifiedFormatted = trim(gmstrftime('%A %d-%b-%y %T %Z', $lastModified)); + $lastModifiedFormatted = $this->formatTimestamp($lastModified); $etag = sprintf('W/"%x-%x"', $lastModified, filesize($file)); $request = $this->createMock(SwooleHttpRequest::class); @@ -311,7 +312,7 @@ public function testSendStaticResourceViaHeadSkipsClientSideCacheMatchingIfNoETa { $file = $this->docRoot . '/content.txt'; $lastModified = filemtime($file); - $lastModifiedFormatted = trim(gmstrftime('%A %d-%b-%y %T %Z', $lastModified)); + $lastModifiedFormatted = $this->formatTimestamp($lastModified); $etag = sprintf('W/"%x-%x"', $lastModified, filesize($file)); $request = $this->createMock(SwooleHttpRequest::class); @@ -494,7 +495,7 @@ public function testSendStaticResourceViaGetHitsClientSideCacheMatchingIfLastMod { $file = $this->docRoot . '/content.txt'; $lastModified = filemtime($file); - $lastModifiedFormatted = trim(gmstrftime('%A %d-%b-%y %T %Z', $lastModified)); + $lastModifiedFormatted = $this->formatTimestamp($lastModified); $request = $this->createMock(SwooleHttpRequest::class); $request->header = [ @@ -537,9 +538,9 @@ public function testGetDoesNotHitClientSideCacheMatchingIfLastModifiedDoesNotMat { $file = $this->docRoot . '/content.txt'; $lastModified = filemtime($file); - $lastModifiedFormatted = trim(gmstrftime('%A %d-%b-%y %T %Z', $lastModified)); + $lastModifiedFormatted = $this->formatTimestamp($lastModified); $ifModifiedSince = $lastModified - 3600; - $ifModifiedSinceFormatted = trim(gmstrftime('%A %d-%b-%y %T %Z', $ifModifiedSince)); + $ifModifiedSinceFormatted = $this->formatTimestamp($ifModifiedSince); $request = $this->createMock(SwooleHttpRequest::class); $request->header = [ diff --git a/test/StaticResourceHandler/LastModifiedMiddlewareTest.php b/test/StaticResourceHandler/LastModifiedMiddlewareTest.php index 0f7c617f..12832728 100644 --- a/test/StaticResourceHandler/LastModifiedMiddlewareTest.php +++ b/test/StaticResourceHandler/LastModifiedMiddlewareTest.php @@ -12,17 +12,17 @@ use Mezzio\Swoole\StaticResourceHandler\LastModifiedMiddleware; use Mezzio\Swoole\StaticResourceHandler\StaticResourceResponse; use MezzioTest\Swoole\AssertResponseTrait; +use MezzioTest\Swoole\FormatTimestampTrait; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Swoole\Http\Request; -use function gmstrftime; use function time; -use function trim; class LastModifiedMiddlewareTest extends TestCase { use AssertResponseTrait; + use FormatTimestampTrait; /** @var callable */ private $next; @@ -82,7 +82,7 @@ public function testMiddlewareCreatesLastModifiedHeaderWhenPathMatchesARegex(): public function testMiddlewareDisablesContentWhenLastModifiedIsGreaterThanClientExpectation(): void { $ifModifiedSince = time() + 3600; - $ifModifiedSince = trim(gmstrftime('%A %d-%b-%y %T %Z', $ifModifiedSince)); + $ifModifiedSince = $this->formatTimestamp($ifModifiedSince); $this->request->server = [ 'request_uri' => '/images/image.png',