From 44731f805980601413db7f10d2544b20a9371bb9 Mon Sep 17 00:00:00 2001 From: Bart Vanderstukken Date: Thu, 5 Jan 2023 12:44:58 +0100 Subject: [PATCH 1/3] Drop support for PHP < 8.x Adjust code to new standards --- composer.json | 8 ++-- src/Cache/FileCache.php | 16 +------- src/Cache/Router.php | 19 +-------- src/ContainerAwareTrait.php | 5 +-- src/Http/Exception.php | 27 ++----------- .../Decorator/DefaultHeaderDecorator.php | 5 +-- src/Middleware/MiddlewareAwareTrait.php | 5 +-- src/Route.php | 26 ++----------- src/RouteConditionHandlerTrait.php | 26 ++++--------- src/RouteGroup.php | 13 +------ src/Router.php | 39 ++++++------------- src/Strategy/AbstractStrategy.php | 5 +-- src/Strategy/ApplicationStrategy.php | 5 +-- src/Strategy/JsonStrategy.php | 29 ++------------ src/Strategy/StrategyAwareTrait.php | 5 +-- tests/DispatchIntegrationTest.php | 36 ++++++----------- tests/Http/ExceptionTest.php | 2 +- tests/RouterTest.php | 4 +- tests/Strategy/JsonStrategyTest.php | 2 - 19 files changed, 60 insertions(+), 217 deletions(-) diff --git a/composer.json b/composer.json index f6bd888..55c1c20 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ } ], "require": { - "php": "^7.2 || ^8.0", + "php": "^8.0", "nikic/fast-route": "^1.3", "psr/container": "^1.0|^2.0", "psr/http-factory": "^1.0", @@ -29,12 +29,12 @@ "psr/http-server-handler": "^1.0.1", "psr/http-server-middleware": "^1.0.1", "opis/closure": "^3.5.5", - "psr/simple-cache": "^1.0" + "psr/simple-cache": "^2.0|^3.0" }, "require-dev": { "laminas/laminas-diactoros": "^2.3", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", + "phpstan/phpstan": "^1.9", + "phpstan/phpstan-phpunit": "^1.3", "phpunit/phpunit": "^8.5", "roave/security-advisories": "dev-master", "scrutinizer/ocular": "^1.8", diff --git a/src/Cache/FileCache.php b/src/Cache/FileCache.php index 1ce0b22..879c0a9 100644 --- a/src/Cache/FileCache.php +++ b/src/Cache/FileCache.php @@ -8,23 +8,11 @@ class FileCache implements CacheInterface { - /** - * @var string - */ - protected $cacheFilePath; - - /** - * @var integer - */ - protected $ttl; - - public function __construct(string $cacheFilePath, int $ttl) + public function __construct(protected string $cacheFilePath, protected int $ttl) { - $this->cacheFilePath = $cacheFilePath; - $this->ttl = $ttl; } - public function get($key, $default = null) + public function get(string $key, mixed $default = null): mixed { return ($this->has($key)) ? file_get_contents($this->cacheFilePath) : $default; } diff --git a/src/Cache/Router.php b/src/Cache/Router.php index 8130127..f96414e 100644 --- a/src/Cache/Router.php +++ b/src/Cache/Router.php @@ -26,26 +26,11 @@ class Router */ protected $builder; - /** - * @var CacheInterface - */ - protected $cache; - - /** - * @var integer - */ - protected $ttl; - - /** - * @var bool - */ - protected $cacheEnabled; + protected int $ttl; - public function __construct(callable $builder, CacheInterface $cache, bool $cacheEnabled = true) + public function __construct(callable $builder, protected CacheInterface $cache, protected bool $cacheEnabled = true) { $this->builder = $builder; - $this->cache = $cache; - $this->cacheEnabled = $cacheEnabled; } public function dispatch(ServerRequestInterface $request): ResponseInterface diff --git a/src/ContainerAwareTrait.php b/src/ContainerAwareTrait.php index 33c5234..51b7b5d 100644 --- a/src/ContainerAwareTrait.php +++ b/src/ContainerAwareTrait.php @@ -9,10 +9,7 @@ trait ContainerAwareTrait { - /** - * @var ?ContainerInterface - */ - protected $container; + protected ?ContainerInterface $container = null; public function getContainer(): ?ContainerInterface { diff --git a/src/Http/Exception.php b/src/Http/Exception.php index 0fc8926..9e3925b 100644 --- a/src/Http/Exception.php +++ b/src/Http/Exception.php @@ -9,32 +9,13 @@ class Exception extends \Exception implements HttpExceptionInterface { - /** - * @var array - */ - protected $headers = []; - - /** - * @var string - */ - protected $message; - - /** - * @var integer - */ - protected $status; - public function __construct( - int $status, - string $message = null, + protected int $status, + protected $message = null, \Exception $previous = null, - array $headers = [], + protected array $headers = [], int $code = 0 ) { - $this->headers = $headers; - $this->message = $message; - $this->status = $status; - parent::__construct($message, $code, $previous); } @@ -61,7 +42,7 @@ public function buildJsonResponse(ResponseInterface $response): ResponseInterfac $response->getBody()->write(json_encode([ 'status_code' => $this->status, 'reason_phrase' => $this->message - ])); + ], JSON_THROW_ON_ERROR)); } return $response->withStatus($this->status, $this->message); diff --git a/src/Http/Response/Decorator/DefaultHeaderDecorator.php b/src/Http/Response/Decorator/DefaultHeaderDecorator.php index 2a57ec0..07b2ce7 100644 --- a/src/Http/Response/Decorator/DefaultHeaderDecorator.php +++ b/src/Http/Response/Decorator/DefaultHeaderDecorator.php @@ -8,10 +8,7 @@ class DefaultHeaderDecorator { - /** - * @var array - */ - protected $headers = []; + protected array $headers = []; public function __construct(array $headers = []) { diff --git a/src/Middleware/MiddlewareAwareTrait.php b/src/Middleware/MiddlewareAwareTrait.php index c02146d..a8afd03 100644 --- a/src/Middleware/MiddlewareAwareTrait.php +++ b/src/Middleware/MiddlewareAwareTrait.php @@ -11,10 +11,7 @@ trait MiddlewareAwareTrait { - /** - * @var array - */ - protected $middleware = []; + protected array $middleware = []; public function getMiddlewareStack(): iterable { diff --git a/src/Route.php b/src/Route.php index 1cb0ee1..482ed2d 100644 --- a/src/Route.php +++ b/src/Route.php @@ -26,30 +26,12 @@ class Route implements */ protected $handler; - /** - * @var RouteGroup - */ - protected $group; - - /** - * @var string - */ - protected $method; + protected RouteGroup $group; - /** - * @var string - */ - protected $path; - - /** - * @var array - */ - protected $vars = []; + protected array $vars = []; - public function __construct(string $method, string $path, $handler) + public function __construct(protected string $method, protected string $path, $handler) { - $this->method = $method; - $this->path = $path; $this->handler = $handler; } @@ -57,7 +39,7 @@ public function getCallable(?ContainerInterface $container = null): callable { $callable = $this->handler; - if (is_string($callable) && strpos($callable, '::') !== false) { + if (is_string($callable) && str_contains($callable, '::')) { $callable = explode('::', $callable); } diff --git a/src/RouteConditionHandlerTrait.php b/src/RouteConditionHandlerTrait.php index b12635b..d4e76ef 100644 --- a/src/RouteConditionHandlerTrait.php +++ b/src/RouteConditionHandlerTrait.php @@ -9,25 +9,13 @@ trait RouteConditionHandlerTrait { - /** - * @var ?string - */ - protected $host; - - /** - * @var ?string - */ - protected $name; - - /** - * @var ?int - */ - protected $port; - - /** - * @var ?string - */ - protected $scheme; + protected ?string $host = null; + + protected ?string $name = null; + + protected ?int $port = null; + + protected ?string $scheme = null; public function getHost(): ?string { diff --git a/src/RouteGroup.php b/src/RouteGroup.php index 47fd56b..a763a48 100644 --- a/src/RouteGroup.php +++ b/src/RouteGroup.php @@ -23,20 +23,11 @@ class RouteGroup implements */ protected $callback; - /** - * @var RouteCollectionInterface - */ - protected $collection; - - /** - * @var string - */ - protected $prefix; + protected string $prefix; - public function __construct(string $prefix, callable $callback, RouteCollectionInterface $collection) + public function __construct(string $prefix, callable $callback, protected RouteCollectionInterface $collection) { $this->callback = $callback; - $this->collection = $collection; $this->prefix = sprintf('/%s', ltrim($prefix, '/')); } diff --git a/src/Router.php b/src/Router.php index 0cabd8e..c1b832e 100644 --- a/src/Router.php +++ b/src/Router.php @@ -29,17 +29,14 @@ class Router implements /** * @var RouteGroup[] */ - protected $groups = []; + protected array $groups = []; /** * @var Route[] */ - protected $namedRoutes = []; + protected array $namedRoutes = []; - /** - * @var array - */ - protected $patternMatchers = [ + protected array $patternMatchers = [ '/{(.+?):number}/' => '{$1:[0-9]+}', '/{(.+?):word}/' => '{$1:[a-zA-Z]+}', '/{(.+?):alphanum_dash}/' => '{$1:[a-zA-Z0-9-_]+}', @@ -47,32 +44,20 @@ class Router implements '/{(.+?):uuid}/' => '{$1:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}+}' ]; - /** - * @var RouteCollector - */ - protected $routeCollector; - /** * @var Route[] */ - protected $routes = []; + protected array $routes = []; - /** - * @var bool - */ - protected $routesPrepared = false; + protected bool $routesPrepared = false; - /** - * @var array - */ - protected $routesData = []; + protected array $routesData = []; - public function __construct(?RouteCollector $routeCollector = null) + public function __construct(protected RouteCollector $routeCollector = new RouteCollector( + new RouteParser\Std(), + new DataGenerator\GroupCountBased() + )) { - $this->routeCollector = $routeCollector ?? new RouteCollector( - new RouteParser\Std(), - new DataGenerator\GroupCountBased() - ); } public function addPatternMatcher(string $alias, string $regex): self @@ -150,7 +135,7 @@ public function prepareRoutes(ServerRequestInterface $request): void $this->processGroups($request); $this->buildNameIndex(); - $routes = array_merge(array_values($this->routes), array_values($this->namedRoutes)); + $routes = [...array_values($this->routes), ...array_values($this->namedRoutes)]; $options = []; /** @var Route $route */ @@ -249,7 +234,7 @@ protected function processGroups(ServerRequestInterface $request): void // route is not matched so exceptions are handled correctly if ( $group->getStrategy() !== null - && strncmp($activePath, $group->getPrefix(), strlen($group->getPrefix())) === 0 + && str_starts_with($activePath, $group->getPrefix()) ) { $this->setStrategy($group->getStrategy()); } diff --git a/src/Strategy/AbstractStrategy.php b/src/Strategy/AbstractStrategy.php index d866907..3c2107b 100644 --- a/src/Strategy/AbstractStrategy.php +++ b/src/Strategy/AbstractStrategy.php @@ -8,10 +8,7 @@ abstract class AbstractStrategy implements StrategyInterface { - /** - * @var array - */ - protected $responseDecorators = []; + protected array $responseDecorators = []; public function addResponseDecorator(callable $decorator): StrategyInterface { diff --git a/src/Strategy/ApplicationStrategy.php b/src/Strategy/ApplicationStrategy.php index a3159f0..a31404a 100644 --- a/src/Strategy/ApplicationStrategy.php +++ b/src/Strategy/ApplicationStrategy.php @@ -53,11 +53,8 @@ protected function throwThrowableMiddleware(Throwable $error): MiddlewareInterfa { return new class ($error) implements MiddlewareInterface { - protected $error; - - public function __construct(Throwable $error) + public function __construct(protected Throwable $error) { - $this->error = $error; } public function process( diff --git a/src/Strategy/JsonStrategy.php b/src/Strategy/JsonStrategy.php index bbfd389..19a8bbd 100644 --- a/src/Strategy/JsonStrategy.php +++ b/src/Strategy/JsonStrategy.php @@ -17,21 +17,8 @@ class JsonStrategy extends AbstractStrategy implements ContainerAwareInterface, { use ContainerAwareTrait; - /** - * @var ResponseFactoryInterface - */ - protected $responseFactory; - - /** - * @var int - */ - protected $jsonFlags; - - public function __construct(ResponseFactoryInterface $responseFactory, int $jsonFlags = 0) + public function __construct(protected ResponseFactoryInterface $responseFactory, protected int $jsonFlags = 0) { - $this->responseFactory = $responseFactory; - $this->jsonFlags = $jsonFlags; - $this->addResponseDecorator(static function (ResponseInterface $response): ResponseInterface { if (false === $response->hasHeader('content-type')) { $response = $response->withHeader('content-type', 'application/json'); @@ -65,11 +52,8 @@ public function getThrowableHandler(): MiddlewareInterface { return new class ($this->responseFactory->createResponse()) implements MiddlewareInterface { - protected $response; - - public function __construct(ResponseInterface $response) + public function __construct(protected ResponseInterface $response) { - $this->response = $response; } public function process( @@ -88,7 +72,7 @@ public function process( $response->getBody()->write(json_encode([ 'status_code' => 500, 'reason_phrase' => $exception->getMessage() - ])); + ], JSON_THROW_ON_ERROR)); $response = $response->withAddedHeader('content-type', 'application/json'); return $response->withStatus(500, strtok($exception->getMessage(), "\n")); @@ -115,13 +99,8 @@ protected function buildJsonResponseMiddleware(Http\Exception $exception): Middl { return new class ($this->responseFactory->createResponse(), $exception) implements MiddlewareInterface { - protected $response; - protected $exception; - - public function __construct(ResponseInterface $response, Http\Exception $exception) + public function __construct(protected ResponseInterface $response, protected Http\Exception $exception) { - $this->response = $response; - $this->exception = $exception; } public function process( diff --git a/src/Strategy/StrategyAwareTrait.php b/src/Strategy/StrategyAwareTrait.php index 1b7f3cb..34eb9bb 100644 --- a/src/Strategy/StrategyAwareTrait.php +++ b/src/Strategy/StrategyAwareTrait.php @@ -6,10 +6,7 @@ trait StrategyAwareTrait { - /** - * @var ?StrategyInterface - */ - protected $strategy; + protected ?StrategyInterface $strategy = null; public function setStrategy(StrategyInterface $strategy): StrategyAwareInterface { diff --git a/tests/DispatchIntegrationTest.php b/tests/DispatchIntegrationTest.php index e5eee56..0b47bb8 100644 --- a/tests/DispatchIntegrationTest.php +++ b/tests/DispatchIntegrationTest.php @@ -124,7 +124,7 @@ public function testDispatchesExceptionRoute(): void $router = new Router(); - $router->map('GET', '/example/route', static function () { + $router->map('GET', '/example/route', static function (): never { throw new Exception(); }); @@ -227,7 +227,7 @@ public function testDispatchesExceptionWithJsonStrategyRoute(): void /** @var Router $router */ $router = (new Router())->setStrategy(new JsonStrategy($factory)); - $router->map('GET', '/example/route', function () { + $router->map('GET', '/example/route', function (): never { throw new Exception('Blah'); }); @@ -305,7 +305,7 @@ public function testDispatchesHttpExceptionWithJsonStrategyRoute(): void $router = (new Router())->setStrategy(new JsonStrategy($factory)); - $router->map('GET', '/example/route', static function () { + $router->map('GET', '/example/route', static function (): never { throw new BadRequestException(); }); @@ -730,9 +730,7 @@ public function testRouteStrategyOverridesGlobalStrategy(): void /** @var Router $router */ $router = (new Router())->setStrategy(new Strategy\ApplicationStrategy()); - $router->map('GET', '/', function (): array { - return []; - })->setStrategy(new JsonStrategy($factory)); + $router->map('GET', '/', static fn(): array => [])->setStrategy(new JsonStrategy($factory)); $router->dispatch($request); } @@ -777,9 +775,7 @@ public function testRouteStrategyOverridesGroupStrategy(): void $router = new Router(); $router->group('/group', function ($r) use ($factory) { - $r->get('/id', function (): array { - return []; - })->setStrategy(new JsonStrategy($factory)); + $r->get('/id', static fn(): array => [])->setStrategy(new JsonStrategy($factory)); })->setStrategy(new Strategy\ApplicationStrategy()); $router->dispatch($request); @@ -789,7 +785,7 @@ public function testMiddlewareIsOrderedCorrectly(): void { $counter = new class () { - private $counter = 0; + private int $counter = 0; public function getCounter(): int { @@ -907,9 +903,7 @@ public function process( ; $router->group('/group', static function ($r) use ($response, $middlewareThree, $middlewareFour): void { - $r->get('/route', static function (ServerRequestInterface $request) use ($response): ResponseInterface { - return $response; - })->middleware($middlewareThree)->lazyMiddlewares([$middlewareFour]); + $r->get('/route', static fn(ServerRequestInterface $request): ResponseInterface => $response)->middleware($middlewareThree)->lazyMiddlewares([$middlewareFour]); })->middleware($middlewareTwo); $router->dispatch($request); @@ -926,30 +920,22 @@ public function testCanMapSameRoutePathOnDifferentConditions(): void $responseTwo->expects(self::once())->method('withHeader')->willReturnSelf(); $routerOne - ->get('/', static function (ServerRequestInterface $request) use ($responseOne): ResponseInterface { - return $responseOne->withHeader('test', 'test'); - }) + ->get('/', static fn(ServerRequestInterface $request): ResponseInterface => $responseOne->withHeader('test', 'test')) ->setHost('test1.com') ; $routerOne - ->get('/', static function (ServerRequestInterface $request) use ($responseOne): ResponseInterface { - return $responseOne->withHeader('test', 'test'); - }) + ->get('/', static fn(ServerRequestInterface $request): ResponseInterface => $responseOne->withHeader('test', 'test')) ->setHost('test2.com') ; $routerTwo - ->get('/', static function (ServerRequestInterface $request) use ($responseTwo): ResponseInterface { - return $responseTwo->withHeader('test', 'test'); - }) + ->get('/', static fn(ServerRequestInterface $request): ResponseInterface => $responseTwo->withHeader('test', 'test')) ->setHost('test1.com') ; $routerTwo - ->get('/', static function (ServerRequestInterface $request) use ($responseTwo): ResponseInterface { - return $responseTwo->withHeader('test', 'test'); - }) + ->get('/', static fn(ServerRequestInterface $request): ResponseInterface => $responseTwo->withHeader('test', 'test')) ->setHost('test2.com') ; diff --git a/tests/Http/ExceptionTest.php b/tests/Http/ExceptionTest.php index d5e8a5e..41c795f 100644 --- a/tests/Http/ExceptionTest.php +++ b/tests/Http/ExceptionTest.php @@ -14,7 +14,7 @@ protected function responseTester(Exception $e): void $json = json_encode([ 'status_code' => $e->getStatusCode(), 'reason_phrase' => $e->getMessage() - ]); + ], JSON_THROW_ON_ERROR); $body = $this->createMock(StreamInterface::class); diff --git a/tests/RouterTest.php b/tests/RouterTest.php index ab49f1b..66627d1 100644 --- a/tests/RouterTest.php +++ b/tests/RouterTest.php @@ -60,14 +60,12 @@ public function testCollectionThrowsExceptionWhenAttemptingToGetNamedRouteThatDo /** * Asserts that appropriately configured regex strings are added to patternMatchers. - * - * @return void */ public function testNewPatternMatchesCanBeAddedAtRuntime(): void { $router = new class () extends Router { - public $patternMatchers = []; + public array $patternMatchers = []; }; $router->addPatternMatcher('mockMatcher', '[a-zA-Z]'); diff --git a/tests/Strategy/JsonStrategyTest.php b/tests/Strategy/JsonStrategyTest.php index 5c4135d..23e0cc4 100644 --- a/tests/Strategy/JsonStrategyTest.php +++ b/tests/Strategy/JsonStrategyTest.php @@ -139,8 +139,6 @@ public function testStrategyInvokesRouteCallableWithArrayReturn(): void /** * Asserts that the strategy returns the correct middleware to decorate NotFoundException. - * - * @return void */ public function testStrategyReturnsCorrectNotFoundDecorator(): void { From 795ad8a005d2bf3951692557b20bbeed5caed013 Mon Sep 17 00:00:00 2001 From: Bart Vanderstukken Date: Thu, 5 Jan 2023 12:46:38 +0100 Subject: [PATCH 2/3] Fix type hint Was wrongly annotated before --- src/Route.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Route.php b/src/Route.php index 482ed2d..2516e15 100644 --- a/src/Route.php +++ b/src/Route.php @@ -26,7 +26,7 @@ class Route implements */ protected $handler; - protected RouteGroup $group; + protected ?RouteGroup $group = null; protected array $vars = []; From 500f918ab8c8177dea0953629e01775f76e25a65 Mon Sep 17 00:00:00 2001 From: Bart Vanderstukken Date: Thu, 5 Jan 2023 13:19:37 +0100 Subject: [PATCH 3/3] Adapt CI build to PHP8 --- .github/workflows/test.yml | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e02fa66..ab6ff33 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,7 +14,7 @@ jobs: - uses: shivammathur/setup-php@v2 with: - php-version: 7.2 + php-version: 8.0 extensions: curl, mbstring coverage: none tools: composer:v2, cs2pr @@ -27,16 +27,13 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: ['7.2', '7.3', '7.4'] + php: ['8.0', '8.1', '8.2'] coverage: [true] composer-flags: [''] include: - php: '8.0' coverage: false composer-flags: '--ignore-platform-req=php' - - php: '7.2' - coverage: false - composer-flags: '--prefer-lowest' steps: - uses: actions/checkout@v2 @@ -52,10 +49,6 @@ jobs: - run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" - - name: "Use PHPUnit 9.3+ on PHP 8" - run: composer require --no-update --dev phpunit/phpunit:^9.3 - if: "matrix.php == '8.0'" - - run: composer update --no-progress ${{ matrix.composer-flags }} - run: vendor/bin/phpunit --no-coverage @@ -76,7 +69,7 @@ jobs: - uses: shivammathur/setup-php@v2 with: - php-version: 7.2 + php-version: 8.0 extensions: curl, mbstring coverage: none tools: composer:v2