From fcfe8adc5d05d4ffa091ad179df659ab5a5852f6 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Sun, 18 Feb 2024 17:04:49 -0400 Subject: [PATCH 01/14] Upgraded dev dependencies --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index b45d101..ba3dcc0 100644 --- a/composer.json +++ b/composer.json @@ -41,11 +41,11 @@ }, "require-dev": { "ext-pdo_sqlite": "*", - "phpunit/phpunit": "^9.5", - "phpstan/phpstan": "^1.10", "phpstan/extension-installer": "^1.3", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5", "rregeer/phpunit-coverage-check": "^0.3.1", - "squizlabs/php_codesniffer": "^3.8" + "squizlabs/php_codesniffer": "^3.9" }, "config": { "allow-plugins": { From 3fba60ca7fc943d55b905b7cf21fe7bf6e3ec7a0 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Sun, 18 Feb 2024 20:34:18 -0400 Subject: [PATCH 02/14] Apply some fixes of phpcs --- flight/Engine.php | 10 ++++++++-- flight/Flight.php | 6 ++---- index.php | 6 ++++-- phpcs.xml | 8 ++++++-- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/flight/Engine.php b/flight/Engine.php index ffd51e4..89b6b72 100644 --- a/flight/Engine.php +++ b/flight/Engine.php @@ -27,7 +27,8 @@ use flight\net\Route; * # Core methods * @method void start() Starts engine * @method void stop() Stops framework and outputs current response - * @method void halt(int $code = 200, string $message = '', bool $actuallyExit = true) Stops processing and returns a given response. + * @method void halt(int $code = 200, string $message = '', bool $actuallyExit = true) + * Stops processing and returns a given response. * * # Routing * @method Route route(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') @@ -464,7 +465,12 @@ class Engine // Run any before middlewares if (count($route->middleware) > 0) { - $at_least_one_middleware_failed = $this->processMiddleware($route->middleware, $route->params, 'before'); + $at_least_one_middleware_failed = $this->processMiddleware( + $route->middleware, + $route->params, + 'before' + ); + if ($at_least_one_middleware_failed === true) { $failed_middleware_check = true; break; diff --git a/flight/Flight.php b/flight/Flight.php index 6e29781..4609c60 100644 --- a/flight/Flight.php +++ b/flight/Flight.php @@ -10,8 +10,6 @@ use flight\net\Router; use flight\template\View; use flight\net\Route; -require_once __DIR__ . '/autoload.php'; - /** * The Flight class is a static representation of the framework. * @@ -140,6 +138,8 @@ class Flight */ public static function __callStatic(string $name, array $params) { + require_once __DIR__ . '/autoload.php'; + return Dispatcher::invokeMethod([self::app(), $name], $params); } @@ -147,8 +147,6 @@ class Flight public static function app(): Engine { if (!self::$initialized) { - require_once __DIR__ . '/autoload.php'; - self::setEngine(new Engine()); self::$initialized = true; } diff --git a/index.php b/index.php index 5a21ed6..65ea154 100644 --- a/index.php +++ b/index.php @@ -1,7 +1,9 @@ - + - @@ -39,6 +38,11 @@ + + + + + flight/ tests/ tests/views/* From 253c86482eca09fbce4e76559c823bd0cbad9c06 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Sun, 18 Feb 2024 21:49:59 -0400 Subject: [PATCH 03/14] Fixed yoda comparisons --- flight/Engine.php | 58 ++++++++++++++-------------- flight/net/Request.php | 57 +++++++++++++-------------- flight/net/Response.php | 11 ++++-- flight/net/Route.php | 27 ++++++------- flight/net/Router.php | 4 +- flight/template/View.php | 4 +- flight/util/Collection.php | 6 +-- flight/util/ReturnTypeWillChange.php | 1 - phpcs.xml | 27 +++++++------ tests/RedirectTest.php | 8 +--- 10 files changed, 100 insertions(+), 103 deletions(-) diff --git a/flight/Engine.php b/flight/Engine.php index 89b6b72..e3af4a1 100644 --- a/flight/Engine.php +++ b/flight/Engine.php @@ -67,9 +67,7 @@ use flight\net\Route; */ class Engine { - /** - * @var array List of methods that can be extended in the Engine class. - */ + /** @var array List of methods that can be extended in the Engine class. */ private const MAPPABLE_METHODS = [ 'start', 'stop', 'route', 'halt', 'error', 'notFound', 'render', 'redirect', 'etag', 'lastModified', 'json', 'jsonp', @@ -298,7 +296,7 @@ class Engine */ public function get(?string $key = null) { - if (null === $key) { + if ($key === null) { return $this->vars; } @@ -344,8 +342,9 @@ class Engine */ public function clear(?string $key = null): void { - if (null === $key) { + if ($key === null) { $this->vars = []; + return; } @@ -367,7 +366,7 @@ class Engine * * @param array $middleware Middleware attached to the route. * @param array $params `$route->params`. - * @param string $event_name If this is the before or after method. + * @param 'before'|'after' $event_name If this is the before or after method. */ protected function processMiddleware(array $middleware, array $params, string $event_name): bool { @@ -376,23 +375,23 @@ class Engine foreach ($middleware as $middleware) { $middleware_object = false; - if ($event_name === 'before') { + if ($event_name === $this->dispatcher::FILTER_BEFORE) { // can be a callable or a class $middleware_object = (is_callable($middleware) === true ? $middleware - : (method_exists($middleware, 'before') === true - ? [$middleware, 'before'] + : (method_exists($middleware, $this->dispatcher::FILTER_BEFORE) === true + ? [$middleware, $this->dispatcher::FILTER_BEFORE] : false ) ); - } elseif ($event_name === 'after') { + } elseif ($event_name === $this->dispatcher::FILTER_AFTER) { // must be an object. No functions allowed here if ( is_object($middleware) === true && !($middleware instanceof Closure) - && method_exists($middleware, 'after') === true + && method_exists($middleware, $this->dispatcher::FILTER_AFTER) === true ) { - $middleware_object = [$middleware, 'after']; + $middleware_object = [$middleware, $this->dispatcher::FILTER_AFTER]; } } @@ -532,9 +531,7 @@ class Engine public function _error(Throwable $e): void { $msg = sprintf( - '

500 Internal Server Error

' . - '

%s (%s)

' . - '
%s
', + '

500 Internal Server Error

%s (%s)

%s
', $e->getMessage(), $e->getCode(), $e->getTraceAsString() @@ -565,7 +562,7 @@ class Engine $response = $this->response(); if (!$response->sent()) { - if (null !== $code) { + if ($code !== null) { $response->status($code); } @@ -633,8 +630,12 @@ class Engine * @param callable $callback Callback function * @param bool $pass_route Pass the matching route object to the callback */ - public function _patch(string $pattern, callable $callback, bool $pass_route = false, string $route_alias = ''): void - { + public function _patch( + string $pattern, + callable $callback, + bool $pass_route = false, + string $route_alias = '' + ): void { $this->router()->map('PATCH ' . $pattern, $callback, $pass_route, $route_alias); } @@ -645,8 +646,12 @@ class Engine * @param callable $callback Callback function * @param bool $pass_route Pass the matching route object to the callback */ - public function _delete(string $pattern, callable $callback, bool $pass_route = false, string $route_alias = ''): void - { + public function _delete( + string $pattern, + callable $callback, + bool $pass_route = false, + string $route_alias = '' + ): void { $this->router()->map('DELETE ' . $pattern, $callback, $pass_route, $route_alias); } @@ -688,14 +693,10 @@ class Engine */ public function _redirect(string $url, int $code = 303): void { - $base = $this->get('flight.base_url'); - - if (null === $base) { - $base = $this->request()->base; - } + $base = $this->get('flight.base_url') ?? $this->request()->base; // Append base url to redirect url - if ('/' !== $base && false === strpos($url, '://')) { + if ($base !== '/' && strpos($url, '://') === false) { $url = $base . preg_replace('#/+#', '/', '/' . $url); } @@ -717,8 +718,9 @@ class Engine */ public function _render(string $file, ?array $data = null, ?string $key = null): void { - if (null !== $key) { + if ($key !== null) { $this->view()->set($key, $this->view()->fetch($file, $data)); + return; } @@ -790,7 +792,7 @@ class Engine */ public function _etag(string $id, string $type = 'strong'): void { - $id = (('weak' === $type) ? 'W/' : '') . $id; + $id = (($type === 'weak') ? 'W/' : '') . $id; $this->response()->header('ETag', '"' . str_replace('"', '\"', $id) . '"'); diff --git a/flight/net/Request.php b/flight/net/Request.php index eb932c0..b322ae3 100644 --- a/flight/net/Request.php +++ b/flight/net/Request.php @@ -151,7 +151,7 @@ class Request 'method' => self::getMethod(), 'referrer' => self::getVar('HTTP_REFERER'), 'ip' => self::getVar('REMOTE_ADDR'), - 'ajax' => 'XMLHttpRequest' === self::getVar('HTTP_X_REQUESTED_WITH'), + 'ajax' => self::getVar('HTTP_X_REQUESTED_WITH') === 'XMLHttpRequest', 'scheme' => self::getScheme(), 'user_agent' => self::getVar('HTTP_USER_AGENT'), 'type' => self::getVar('CONTENT_TYPE'), @@ -160,7 +160,7 @@ class Request 'data' => new Collection($_POST), 'cookies' => new Collection($_COOKIE), 'files' => new Collection($_FILES), - 'secure' => 'https' === self::getScheme(), + 'secure' => self::getScheme() === 'https', 'accept' => self::getVar('HTTP_ACCEPT'), 'proxy_ip' => self::getProxyIpAddress(), 'host' => self::getVar('HTTP_HOST'), @@ -188,7 +188,7 @@ class Request // This rewrites the url in case the public url and base directories match // (such as installing on a subdirectory in a web server) // @see testInitUrlSameAsBaseDirectory - if ('/' !== $this->base && '' !== $this->base && 0 === strpos($this->url, $this->base)) { + if ($this->base !== '/' && $this->base !== '' && strpos($this->url, $this->base) === 0) { $this->url = substr($this->url, \strlen($this->base)); } @@ -203,9 +203,10 @@ class Request } // Check for JSON input - if (0 === strpos($this->type, 'application/json')) { + if (strpos($this->type, 'application/json') === 0) { $body = $this->getBody(); - if ('' !== $body) { + + if ($body !== '') { $data = json_decode($body, true); if (is_array($data)) { $this->data->setData($data); @@ -225,14 +226,17 @@ class Request { $body = $this->body; - if ('' !== $body) { + if ($body !== '') { return $body; } - $method = self::getMethod(); - - if ('POST' === $method || 'PUT' === $method || 'DELETE' === $method || 'PATCH' === $method) { - $body = file_get_contents($this->stream_path); + switch (self::getMethod()) { + case 'POST': + case 'PUT': + case 'DELETE': + case 'PATCH': + $body = file_get_contents($this->stream_path); + break; } $this->body = $body; @@ -277,7 +281,8 @@ class Request foreach ($forwarded as $key) { if (\array_key_exists($key, $_SERVER)) { sscanf($_SERVER[$key], '%[^,]', $ip); - if (false !== filter_var($ip, \FILTER_VALIDATE_IP, $flags)) { + + if (filter_var($ip, \FILTER_VALIDATE_IP, $flags) !== false) { return $ip; } } @@ -321,13 +326,15 @@ class Request public static function getHeaders(): array { $headers = []; + foreach ($_SERVER as $key => $value) { - if (0 === strpos($key, 'HTTP_')) { + if (strpos($key, 'HTTP_') === 0) { // converts headers like HTTP_CUSTOM_HEADER to Custom-Header $key = str_replace(' ', '-', ucwords(str_replace('_', ' ', strtolower(substr($key, 5))))); $headers[$key] = $value; } } + return $headers; } @@ -336,10 +343,8 @@ class Request * * @param string $header Header name. Can be caps, lowercase, or mixed. * @param string $default Default value if the header does not exist - * - * @return string */ - public static function header(string $header, $default = '') + public static function header(string $header, $default = ''): string { return self::getHeader($header, $default); } @@ -354,21 +359,13 @@ class Request return self::getHeaders(); } - /** - * Gets the full request URL. - * - * @return string URL - */ + /** Gets the full request URL. */ public function getFullUrl(): string { return $this->scheme . '://' . $this->host . $this->url; } - /** - * Grabs the scheme and host. Does not end with a / - * - * @return string - */ + /** Grabs the scheme and host. Does not end with a / */ public function getBaseUrl(): string { return $this->scheme . '://' . $this->host; @@ -396,18 +393,18 @@ class Request /** * Gets the URL Scheme * - * @return string 'http'|'https' + * @return 'http'|'https' */ public static function getScheme(): string { if ( - (isset($_SERVER['HTTPS']) && 'on' === strtolower($_SERVER['HTTPS'])) + (isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) === 'on') || - (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && 'https' === $_SERVER['HTTP_X_FORWARDED_PROTO']) + (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') || - (isset($_SERVER['HTTP_FRONT_END_HTTPS']) && 'on' === $_SERVER['HTTP_FRONT_END_HTTPS']) + (isset($_SERVER['HTTP_FRONT_END_HTTPS']) && $_SERVER['HTTP_FRONT_END_HTTPS'] === 'on') || - (isset($_SERVER['REQUEST_SCHEME']) && 'https' === $_SERVER['REQUEST_SCHEME']) + (isset($_SERVER['REQUEST_SCHEME']) && $_SERVER['REQUEST_SCHEME'] === 'https') ) { return 'https'; } diff --git a/flight/net/Response.php b/flight/net/Response.php index 761d1a6..291e84e 100644 --- a/flight/net/Response.php +++ b/flight/net/Response.php @@ -139,7 +139,7 @@ class Response */ public function status(?int $code = null) { - if (null === $code) { + if ($code === null) { return $this->status; } @@ -263,19 +263,22 @@ class Response */ public function cache($expires): self { - if (false === $expires) { + if ($expires === false) { $this->headers['Expires'] = 'Mon, 26 Jul 1997 05:00:00 GMT'; + $this->headers['Cache-Control'] = [ 'no-store, no-cache, must-revalidate', 'post-check=0, pre-check=0', 'max-age=0', ]; + $this->headers['Pragma'] = 'no-cache'; } else { $expires = \is_int($expires) ? $expires : strtotime($expires); $this->headers['Expires'] = gmdate('D, d M Y H:i:s', $expires) . ' GMT'; $this->headers['Cache-Control'] = 'max-age=' . ($expires - time()); - if (isset($this->headers['Pragma']) && 'no-cache' == $this->headers['Pragma']) { + + if (isset($this->headers['Pragma']) && $this->headers['Pragma'] === 'no-cache') { unset($this->headers['Pragma']); } } @@ -291,7 +294,7 @@ class Response public function sendHeaders(): self { // Send status code header - if (false !== strpos(\PHP_SAPI, 'cgi')) { + if (strpos(\PHP_SAPI, 'cgi') !== false) { // @codeCoverageIgnoreStart $this->setRealHeader( sprintf( diff --git a/flight/net/Route.php b/flight/net/Route.php index 2070cc6..e73906b 100644 --- a/flight/net/Route.php +++ b/flight/net/Route.php @@ -95,7 +95,7 @@ class Route public function matchUrl(string $url, bool $case_sensitive = false): bool { // Wildcard or exact match - if ('*' === $this->pattern || $this->pattern === $url) { + if ($this->pattern === '*' || $this->pattern === $url) { return true; } @@ -110,8 +110,9 @@ class Route for ($i = 0; $i < $len; $i++) { if ($url[$i] === '/') { - $n++; + ++$n; } + if ($n === $count) { break; } @@ -136,24 +137,20 @@ class Route $regex ); - if ('/' === $last_char) { // Fix trailing slash - $regex .= '?'; - } else { // Allow trailing slash - $regex .= '/?'; - } + $regex .= $last_char === '/' ? '?' : '/?'; // Attempt to match route and named parameters - if (preg_match('#^' . $regex . '(?:\?[\s\S]*)?$#' . (($case_sensitive) ? '' : 'i'), $url, $matches)) { - foreach ($ids as $k => $v) { - $this->params[$k] = (\array_key_exists($k, $matches)) ? urldecode($matches[$k]) : null; - } - - $this->regex = $regex; + if (!preg_match('#^' . $regex . '(?:\?[\s\S]*)?$#' . (($case_sensitive) ? '' : 'i'), $url, $matches)) { + return false; + } - return true; + foreach (array_keys($ids) as $k) { + $this->params[$k] = (\array_key_exists($k, $matches)) ? urldecode($matches[$k]) : null; } - return false; + $this->regex = $regex; + + return true; } /** diff --git a/flight/net/Router.php b/flight/net/Router.php index 895b337..2387baa 100644 --- a/flight/net/Router.php +++ b/flight/net/Router.php @@ -96,7 +96,7 @@ class Router $methods = ['*']; - if (false !== strpos($url, ' ')) { + if (strpos($url, ' ') !== false) { [$method, $url] = explode(' ', $url, 2); $url = trim($url); $methods = explode('|', $method); @@ -211,10 +211,12 @@ class Router public function route(Request $request) { $url_decoded = urldecode($request->url); + while ($route = $this->current()) { if ($route->matchMethod($request->method) && $route->matchUrl($url_decoded, $this->case_sensitive)) { return $route; } + $this->next(); } diff --git a/flight/template/View.php b/flight/template/View.php index d1bc07f..c43a35f 100644 --- a/flight/template/View.php +++ b/flight/template/View.php @@ -88,7 +88,7 @@ class View */ public function clear(?string $key = null): self { - if (null === $key) { + if ($key === null) { $this->vars = []; } else { unset($this->vars[$key]); @@ -169,7 +169,7 @@ class View $is_windows = \strtoupper(\substr(PHP_OS, 0, 3)) === 'WIN'; - if (('/' == \substr($file, 0, 1)) || ($is_windows === true && ':' == \substr($file, 1, 1))) { + if ((\substr($file, 0, 1) === '/') || ($is_windows && \substr($file, 1, 1) === ':')) { return $file; } diff --git a/flight/util/Collection.php b/flight/util/Collection.php index 6ffe0b5..e17ed37 100644 --- a/flight/util/Collection.php +++ b/flight/util/Collection.php @@ -95,7 +95,7 @@ class Collection implements ArrayAccess, Iterator, Countable, JsonSerializable #[\ReturnTypeWillChange] public function offsetSet($offset, $value): void { - if (null === $offset) { + if ($offset === null) { $this->data[] = $value; } else { $this->data[$offset] = $value; @@ -166,9 +166,7 @@ class Collection implements ArrayAccess, Iterator, Countable, JsonSerializable */ public function valid(): bool { - $key = key($this->data); - - return null !== $key; + return key($this->data) !== null; } /** diff --git a/flight/util/ReturnTypeWillChange.php b/flight/util/ReturnTypeWillChange.php index 31a929b..1eba39e 100644 --- a/flight/util/ReturnTypeWillChange.php +++ b/flight/util/ReturnTypeWillChange.php @@ -3,7 +3,6 @@ declare(strict_types=1); // This file is only here so that the PHP8 attribute for doesn't throw an error in files -// phpcs:ignoreFile PSR1.Classes.ClassDeclaration.MissingNamespace class ReturnTypeWillChange { } diff --git a/phpcs.xml b/phpcs.xml index 8f8acb6..bafe06d 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -18,31 +18,34 @@ + - + + + + - - - - - - - - - - -
+ + + + + + + + + + flight/ tests/ tests/views/* diff --git a/tests/RedirectTest.php b/tests/RedirectTest.php index e44186e..07698eb 100644 --- a/tests/RedirectTest.php +++ b/tests/RedirectTest.php @@ -21,7 +21,7 @@ class RedirectTest extends TestCase public function getBaseUrl($base, $url) { - if ('/' !== $base && false === strpos($url, '://')) { + if ($base !== '/' && strpos($url, '://') === false) { $url = preg_replace('#/+#', '/', $base . '/' . $url); } @@ -67,11 +67,7 @@ class RedirectTest extends TestCase public function testBaseOverride() { $url = 'login'; - if (null !== $this->app->get('flight.base_url')) { - $base = $this->app->get('flight.base_url'); - } else { - $base = $this->app->request()->base; - } + $base = $this->app->get('flight.base_url') ?? $this->app->request()->base; self::assertEquals('/testdir/login', $this->getBaseUrl($base, $url)); } From 86df1cb2bd20075357092a69a6f7e9186fcda611 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Sun, 18 Feb 2024 22:11:29 -0400 Subject: [PATCH 04/14] Resolved phpcs Generic standard rules --- tests/RouterTest.php | 10 ++++++---- tests/server/index.php | 18 +++++++++++++----- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/tests/RouterTest.php b/tests/RouterTest.php index 1b52ae1..84322c2 100644 --- a/tests/RouterTest.php +++ b/tests/RouterTest.php @@ -262,12 +262,14 @@ class RouterTest extends TestCase { $this->router->map('GET /api/intune/hey', [$this, 'ok']); + $error_description = 'error_description=AADSTS65004%3a+User+declined+to+consent+to+access+the'; + $error_description .= '+app.%0d%0aTrace+ID%3a+747c0cc1-ccbd-4e53-8e2f-48812eb24100%0d%0a'; + $error_description .= 'Correlation+ID%3a+362e3cb3-20ef-400b-904e-9983bd989184%0d%0a'; + $error_description .= 'Timestamp%3a+2022-09-08+09%3a58%3a12Z'; + $query_params = [ 'error=access_denied', - 'error_description=AADSTS65004%3a+User+declined+to+consent+to+access+the' - . '+app.%0d%0aTrace+ID%3a+747c0cc1-ccbd-4e53-8e2f-48812eb24100%0d%0a' - . 'Correlation+ID%3a+362e3cb3-20ef-400b-904e-9983bd989184%0d%0a' - . 'Timestamp%3a+2022-09-08+09%3a58%3a12Z', + $error_description, 'error_uri=https%3a%2f%2flogin.microsoftonline.com%2ferror%3fcode%3d65004', 'admin_consent=True', 'state=x2EUE0fcSj#' diff --git a/tests/server/index.php b/tests/server/index.php index b3d5230..c13e359 100644 --- a/tests/server/index.php +++ b/tests/server/index.php @@ -9,7 +9,9 @@ declare(strict_types=1); * @author Kristaps Muižnieks https://github.com/krmu */ - require file_exists(__DIR__ . '/../../vendor/autoload.php') ? __DIR__ . '/../../vendor/autoload.php' : __DIR__ . '/../../flight/autoload.php'; +require_once file_exists(__DIR__ . '/../../vendor/autoload.php') + ? __DIR__ . '/../../vendor/autoload.php' + : __DIR__ . '/../../flight/autoload.php'; Flight::set('flight.content_length', false); Flight::set('flight.views.path', './'); @@ -95,17 +97,23 @@ Flight::group('', function () { Flight::route('/error', function () { trigger_error('This is a successful error'); }); -}, [ new LayoutMiddleware() ]); +}, [new LayoutMiddleware()]); Flight::map('error', function (Throwable $e) { + $styles = join(';', [ + 'border: 2px solid red', + 'padding: 21px', + 'background: lightgray', + 'font-weight: bold' + ]); + echo sprintf( - '

500 Internal Server Error

' . - '

%s (%s)

' . - '
%s
', + "

500 Internal Server Error

%s (%s)

%s
", $e->getMessage(), $e->getCode(), str_replace(getenv('PWD'), '***CONFIDENTIAL***', $e->getTraceAsString()) ); + echo "
Go back"; }); Flight::map('notFound', function () { From 26de40b25556d2a070d7dd4caeb83a3351daa0fe Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Mon, 19 Feb 2024 03:05:57 -0400 Subject: [PATCH 05/14] Created phpcs.xml.dist with automatic local config creation script --- .gitignore | 1 + composer.json | 3 ++- phpcs.xml => phpcs.xml.dist | 0 3 files changed, 3 insertions(+), 1 deletion(-) rename phpcs.xml => phpcs.xml.dist (100%) diff --git a/.gitignore b/.gitignore index fd379ad..6ca7489 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ coverage/ *.sublime* .vscode/ clover.xml +phpcs.xml diff --git a/composer.json b/composer.json index ba3dcc0..6c61fde 100644 --- a/composer.json +++ b/composer.json @@ -62,7 +62,8 @@ "test-coverage:win": "del clover.xml && phpunit --coverage-html=coverage --coverage-clover=clover.xml && coverage-check clover.xml 100", "lint": "phpstan --no-progress -cphpstan.neon", "beautify": "phpcbf --standard=phpcs.xml", - "phpcs": "phpcs --standard=phpcs.xml -n" + "phpcs": "phpcs --standard=phpcs.xml -n", + "post-install-cmd": ["php -r \"if (!file_exists('phpcs.xml')) copy('phpcs.xml.dist', 'phpcs.xml');\""] }, "suggest": { "latte/latte": "Latte template engine", diff --git a/phpcs.xml b/phpcs.xml.dist similarity index 100% rename from phpcs.xml rename to phpcs.xml.dist From e322135d9c8d0947e55bb3f73f98673fefa637a9 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Mon, 1 Apr 2024 18:45:37 -0400 Subject: [PATCH 06/14] Add symfony/polyfill-80 --- composer.json | 2 ++ flight/core/Dispatcher.php | 31 ++++++++++++++----------------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/composer.json b/composer.json index 2be3e4b..e30b937 100644 --- a/composer.json +++ b/composer.json @@ -25,6 +25,8 @@ "require": { "php": "^7.4|^8.0|^8.1|^8.2|^8.3", "ext-json": "*" + "ext-json": "*", + "symfony/polyfill-php80": "^1.29" }, "autoload": { "files": [ diff --git a/flight/core/Dispatcher.php b/flight/core/Dispatcher.php index 6a167a8..dd202be 100644 --- a/flight/core/Dispatcher.php +++ b/flight/core/Dispatcher.php @@ -251,7 +251,10 @@ class Dispatcher */ public function execute($callback, array &$params = []) { - if (is_string($callback) === true && (strpos($callback, '->') !== false || strpos($callback, '::') !== false)) { + if ( + is_string($callback) + && (str_contains($callback, '->') || str_contains($callback, '::')) + ) { $callback = $this->parseStringClassAndMethod($callback); } @@ -327,27 +330,21 @@ class Dispatcher } [$class, $method] = $func; - $resolvedClass = null; - // Only execute the container handler if it's not a Flight class - if ( - $this->containerHandler !== null && - ( - ( - is_object($class) === true && - strpos(get_class($class), 'flight\\') === false - ) || - is_string($class) === true - ) - ) { - $containerHandler = $this->containerHandler; - $resolvedClass = $this->resolveContainerClass($containerHandler, $class, $params); - if ($resolvedClass !== null) { + $mustUseTheContainer = $this->containerHandler && ( + (is_object($class) && !str_starts_with(get_class($class), 'flight\\')) + || is_string($class) + ); + + if ($mustUseTheContainer) { + $resolvedClass = $this->resolveContainerClass($class, $params); + + if ($resolvedClass) { $class = $resolvedClass; } } - $this->verifyValidClassCallable($class, $method, $resolvedClass); + $this->verifyValidClassCallable($class, $method, $resolvedClass ?? null); // Class is a string, and method exists, create the object by hand and inject only the Engine if (is_string($class) === true) { From d931b41b464d3d1551e9acdeb6fe815ae2e04043 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Mon, 1 Apr 2024 18:51:54 -0400 Subject: [PATCH 07/14] Improved docblocks --- flight/core/Dispatcher.php | 103 +++++++++++++++++-------------------- 1 file changed, 47 insertions(+), 56 deletions(-) diff --git a/flight/core/Dispatcher.php b/flight/core/Dispatcher.php index dd202be..f9c1b68 100644 --- a/flight/core/Dispatcher.php +++ b/flight/core/Dispatcher.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace flight\core; -use Closure; use Exception; use flight\Engine; use InvalidArgumentException; @@ -31,13 +30,13 @@ class Dispatcher /** @var ?Engine $engine Engine instance */ protected ?Engine $engine = null; - /** @var array Mapped events. */ + /** @var array Mapped events. */ protected array $events = []; /** * Method filters. * - * @var array &$params, mixed &$output): (void|false)>>> + * @var array &$params, mixed &$output): (void|false)>>> */ protected array $filters = []; @@ -68,11 +67,11 @@ class Dispatcher /** * Dispatches an event. * - * @param string $name Event name + * @param string $name Event name. * @param array $params Callback parameters. * * @return mixed Output of callback - * @throws Exception If event name isn't found or if event throws an `Exception` + * @throws Exception If event name isn't found or if event throws an `Exception`. */ public function run(string $name, array $params = []) { @@ -110,7 +109,7 @@ class Dispatcher $requestedMethod = $this->get($eventName); if ($requestedMethod === null) { - throw new Exception("Event '{$eventName}' isn't found."); + throw new Exception("Event '$eventName' isn't found."); } return $this->execute($requestedMethod, $params); @@ -138,8 +137,8 @@ class Dispatcher /** * Assigns a callback to an event. * - * @param string $name Event name - * @param Closure(): (void|mixed) $callback Callback function + * @param string $name Event name. + * @param callable(): (void|mixed) $callback Callback function. * * @return $this */ @@ -153,9 +152,9 @@ class Dispatcher /** * Gets an assigned callback. * - * @param string $name Event name + * @param string $name Event name. * - * @return null|(Closure(): (void|mixed)) $callback Callback function + * @return null|(callable(): (void|mixed)) $callback Callback function. */ public function get(string $name): ?callable { @@ -165,9 +164,9 @@ class Dispatcher /** * Checks if an event has been set. * - * @param string $name Event name + * @param string $name Event name. * - * @return bool Event status + * @return bool If event exists or doesn't exists. */ public function has(string $name): bool { @@ -177,7 +176,7 @@ class Dispatcher /** * Clears an event. If no name is given, all events will be removed. * - * @param ?string $name Event name + * @param ?string $name Event name. */ public function clear(?string $name = null): void { @@ -196,8 +195,8 @@ class Dispatcher * Hooks a callback to an event. * * @param string $name Event name - * @param 'before'|'after' $type Filter type - * @param Closure(array &$params, string &$output): (void|false) $callback + * @param 'before'|'after' $type Filter type. + * @param callable(array &$params, string &$output): (void|false) $callback * * @return $this */ @@ -217,10 +216,10 @@ class Dispatcher /** * Executes a chain of method filters. * - * @param array &$params, mixed &$output): (void|false)> $filters - * Chain of filters- - * @param array $params Method parameters - * @param mixed $output Method output + * @param array &$params, mixed &$output): (void|false)> $filters + * Chain of filters. + * @param array $params Method parameters. + * @param mixed $output Method output. * * @throws Exception If an event throws an `Exception` or if `$filters` contains an invalid filter. */ @@ -242,11 +241,11 @@ class Dispatcher /** * Executes a callback function. * - * @param callable-string|(Closure(): mixed)|array{class-string|object, string} $callback - * Callback function - * @param array $params Function parameters + * @param callable-string|(callable(): mixed)|array{class-string|object, string} $callback + * Callback function. + * @param array $params Function parameters. * - * @return mixed Function results + * @return mixed Function results. * @throws Exception If `$callback` also throws an `Exception`. */ public function execute($callback, array &$params = []) @@ -266,28 +265,26 @@ class Dispatcher * * @param string $classAndMethod Class and method * - * @return array{class-string|object, string} Class and method + * @return array{0: class-string|object, 1: string} Class and method */ public function parseStringClassAndMethod(string $classAndMethod): array { - $class_parts = explode('->', $classAndMethod); - if (count($class_parts) === 1) { - $class_parts = explode('::', $class_parts[0]); - } + $classParts = explode('->', $classAndMethod); - $class = $class_parts[0]; - $method = $class_parts[1]; + if (count($classParts) === 1) { + $classParts = explode('::', $classParts[0]); + } - return [ $class, $method ]; + return $classParts; } /** * Calls a function. * - * @param callable $func Name of function to call - * @param array &$params Function parameters + * @param callable $func Name of function to call. + * @param array &$params Function parameters. * - * @return mixed Function results + * @return mixed Function results. * @deprecated 3.7.0 Use invokeCallable instead */ public function callFunction(callable $func, array &$params = []) @@ -298,12 +295,12 @@ class Dispatcher /** * Invokes a method. * - * @param array{class-string|object, string} $func Class method - * @param array &$params Class method parameters + * @param array{0: class-string|object, 1: string} $func Class method. + * @param array &$params Class method parameters. * - * @return mixed Function results + * @return mixed Function results. * @throws TypeError For nonexistent class name. - * @deprecated 3.7.0 Use invokeCallable instead + * @deprecated 3.7.0 Use invokeCallable instead. */ public function invokeMethod(array $func, array &$params = []) { @@ -313,12 +310,12 @@ class Dispatcher /** * Invokes a callable (anonymous function or Class->method). * - * @param array{class-string|object, string}|Callable $func Class method - * @param array &$params Class method parameters + * @param array{0: class-string|object, 1: string}|callable $func Class method. + * @param array &$params Class method parameters. * - * @return mixed Function results + * @return mixed Function results. * @throws TypeError For nonexistent class name. - * @throws InvalidArgumentException If the constructor requires parameters + * @throws InvalidArgumentException If the constructor requires parameters. * @version 3.7.0 */ public function invokeCallable($func, array &$params = []) @@ -357,10 +354,10 @@ class Dispatcher /** * Handles invalid callback types. * - * @param callable-string|(Closure(): mixed)|array{class-string|object, string} $callback - * Callback function + * @param callable-string|(callable(): mixed)|array{0: class-string|object, 1: string} $callback + * Callback function. * - * @throws InvalidArgumentException If `$callback` is an invalid type + * @throws InvalidArgumentException If `$callback` is an invalid type. */ protected function verifyValidFunction($callback): void { @@ -378,13 +375,11 @@ class Dispatcher /** * Verifies if the provided class and method are valid callable. * - * @param string|object $class The class name. + * @param class-string|object $class The class name. * @param string $method The method name. * @param object|null $resolvedClass The resolved class. * * @throws Exception If the class or method is not found. - * - * @return void */ protected function verifyValidClassCallable($class, $method, $resolvedClass): void { @@ -413,10 +408,10 @@ class Dispatcher * Resolves the container class. * * @param callable|object $container_handler Dependency injection container - * @param class-string $class Class name - * @param array &$params Class constructor parameters + * @param class-string $class Class name. + * @param array &$params Class constructor parameters. * - * @return object Class object + * @return ?object Class object. */ protected function resolveContainerClass($container_handler, $class, array &$params) { @@ -451,11 +446,7 @@ class Dispatcher return $class_object; } - /** - * Because this could throw an exception in the middle of an output buffer, - * - * @return void - */ + /** Because this could throw an exception in the middle of an output buffer, */ protected function fixOutputBuffering(): void { // Cause PHPUnit has 1 level of output buffering by default From 4616f521cdb612841366cafc56cd876051206f74 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Mon, 1 Apr 2024 18:55:26 -0400 Subject: [PATCH 08/14] Dispatcher refactor --- flight/core/Dispatcher.php | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/flight/core/Dispatcher.php b/flight/core/Dispatcher.php index f9c1b68..8f1610d 100644 --- a/flight/core/Dispatcher.php +++ b/flight/core/Dispatcher.php @@ -22,12 +22,11 @@ class Dispatcher { public const FILTER_BEFORE = 'before'; public const FILTER_AFTER = 'after'; - private const FILTER_TYPES = [self::FILTER_BEFORE, self::FILTER_AFTER]; - /** @var mixed $containerException Exception message if thrown by setting the container as a callable method */ - protected $containerException = null; + /** Exception message if thrown by setting the container as a callable method. */ + protected ?Exception $containerException = null; - /** @var ?Engine $engine Engine instance */ + /** @var ?Engine $engine Engine instance. */ protected ?Engine $engine = null; /** @var array Mapped events. */ @@ -187,8 +186,7 @@ class Dispatcher return; } - $this->events = []; - $this->filters = []; + $this->reset(); } /** @@ -202,8 +200,10 @@ class Dispatcher */ public function hook(string $name, string $type, callable $callback): self { - if (!in_array($type, self::FILTER_TYPES, true)) { - $noticeMessage = "Invalid filter type '$type', use " . join('|', self::FILTER_TYPES); + static $filterTypes = [self::FILTER_BEFORE, self::FILTER_AFTER]; + + if (!in_array($type, $filterTypes, true)) { + $noticeMessage = "Invalid filter type '$type', use " . join('|', $filterTypes); trigger_error($noticeMessage, E_USER_NOTICE); } @@ -321,8 +321,9 @@ class Dispatcher public function invokeCallable($func, array &$params = []) { // If this is a directly callable function, call it - if (is_array($func) === false) { + if (!is_array($func)) { $this->verifyValidFunction($func); + return call_user_func_array($func, $params); } @@ -344,11 +345,11 @@ class Dispatcher $this->verifyValidClassCallable($class, $method, $resolvedClass ?? null); // Class is a string, and method exists, create the object by hand and inject only the Engine - if (is_string($class) === true) { + if (is_string($class)) { $class = new $class($this->engine); } - return call_user_func_array([ $class, $method ], $params); + return call_user_func_array([$class, $method], $params); } /** @@ -361,12 +362,7 @@ class Dispatcher */ protected function verifyValidFunction($callback): void { - $isInvalidFunctionName = ( - is_string($callback) - && !function_exists($callback) - ); - - if ($isInvalidFunctionName) { + if (is_string($callback) && !function_exists($callback)) { throw new InvalidArgumentException('Invalid callback specified.'); } } From 43b689fa6cb1c4ea7e585b8d25aebf7e340d77bb Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Mon, 1 Apr 2024 18:57:29 -0400 Subject: [PATCH 09/14] Add Short After Filter Syntax with one parameter --- flight/core/Dispatcher.php | 11 ++++++++++ tests/DispatcherTest.php | 41 ++++++++++++++++++++++++++++---------- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/flight/core/Dispatcher.php b/flight/core/Dispatcher.php index 8f1610d..f08d1f0 100644 --- a/flight/core/Dispatcher.php +++ b/flight/core/Dispatcher.php @@ -7,6 +7,7 @@ namespace flight\core; use Exception; use flight\Engine; use InvalidArgumentException; +use ReflectionFunction; use TypeError; /** @@ -208,6 +209,16 @@ class Dispatcher trigger_error($noticeMessage, E_USER_NOTICE); } + if ($type === self::FILTER_AFTER) { + $callbackInfo = new ReflectionFunction($callback); + $parametersNumber = $callbackInfo->getNumberOfParameters(); + + if ($parametersNumber === 1) { + /** @disregard &$params in after filters are deprecated. */ + $callback = fn (array &$params, &$output) => $callback($output); + } + } + $this->filters[$name][$type][] = $callback; return $this; diff --git a/tests/DispatcherTest.php b/tests/DispatcherTest.php index a755666..418897d 100644 --- a/tests/DispatcherTest.php +++ b/tests/DispatcherTest.php @@ -37,9 +37,7 @@ class DispatcherTest extends TestCase public function testFunctionMapping(): void { - $this->dispatcher->set('map2', function (): string { - return 'hello'; - }); + $this->dispatcher->set('map2', fn (): string => 'hello'); $this->assertSame('hello', $this->dispatcher->run('map2')); } @@ -61,6 +59,9 @@ class DispatcherTest extends TestCase ->set('map-event', $customFunction) ->set('map-event-2', $anotherFunction); + $this->assertTrue($this->dispatcher->has('map-event')); + $this->assertTrue($this->dispatcher->has('map-event-2')); + $this->dispatcher->clear(); $this->assertFalse($this->dispatcher->has('map-event')); @@ -76,6 +77,9 @@ class DispatcherTest extends TestCase ->set('map-event', $customFunction) ->set('map-event-2', $anotherFunction); + $this->assertTrue($this->dispatcher->has('map-event')); + $this->assertTrue($this->dispatcher->has('map-event-2')); + $this->dispatcher->clear('map-event'); $this->assertFalse($this->dispatcher->has('map-event')); @@ -105,9 +109,7 @@ class DispatcherTest extends TestCase public function testBeforeAndAfter(): void { - $this->dispatcher->set('hello', function (string $name): string { - return "Hello, $name!"; - }); + $this->dispatcher->set('hello', fn (string $name): string => "Hello, $name!"); $this->dispatcher ->hook('hello', Dispatcher::FILTER_BEFORE, function (array &$params): void { @@ -124,6 +126,25 @@ class DispatcherTest extends TestCase $this->assertSame('Hello, Fred! Have a nice day!', $result); } + public function testBeforeAndAfterWithShortAfterFilterSyntax(): void + { + $this->dispatcher->set('hello', fn (string $name): string => "Hello, $name!"); + + $this->dispatcher + ->hook('hello', Dispatcher::FILTER_BEFORE, function (array &$params): void { + // Manipulate the parameter + $params[0] = 'Fred'; + }) + ->hook('hello', Dispatcher::FILTER_AFTER, function (string &$output): void { + // Manipulate the output + $output .= ' Have a nice day!'; + }); + + $result = $this->dispatcher->run('hello', ['Bob']); + + $this->assertSame('Hello, Fred! Have a nice day!', $result); + } + public function testInvalidCallback(): void { $this->expectException(Exception::class); @@ -245,7 +266,7 @@ class DispatcherTest extends TestCase public function testInvokeMethod(): void { $class = new TesterClass('param1', 'param2', 'param3', 'param4', 'param5', 'param6'); - $result = $this->dispatcher->invokeMethod([ $class, 'instanceMethod' ]); + $result = $this->dispatcher->invokeMethod([$class, 'instanceMethod']); $this->assertSame('param1', $class->param2); } @@ -271,7 +292,7 @@ class DispatcherTest extends TestCase public function testExecuteStringClassNoConstructArraySyntax(): void { - $result = $this->dispatcher->execute([ Hello::class, 'sayHi' ]); + $result = $this->dispatcher->execute([Hello::class, 'sayHi']); $this->assertSame('hello', $result); } @@ -298,7 +319,7 @@ class DispatcherTest extends TestCase $engine = new Engine(); $engine->set('test_me_out', 'You got it boss!'); $this->dispatcher->setEngine($engine); - $result = $this->dispatcher->execute([ ContainerDefault::class, 'testTheContainer' ]); + $result = $this->dispatcher->execute([ContainerDefault::class, 'testTheContainer']); $this->assertSame('You got it boss!', $result); } @@ -306,6 +327,6 @@ class DispatcherTest extends TestCase { $this->expectException(TypeError::class); $this->expectExceptionMessageMatches('#tests\\\\classes\\\\ContainerDefault::__construct\(\).+flight\\\\Engine, null given#'); - $result = $this->dispatcher->execute([ ContainerDefault::class, 'testTheContainer' ]); + $result = $this->dispatcher->execute([ContainerDefault::class, 'testTheContainer']); } } From 389ebfd32978c9556367c76f2d121bf52dd507ba Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Mon, 1 Apr 2024 18:59:23 -0400 Subject: [PATCH 10/14] Add PSR/Container dependency for type hint and expose the API --- composer.json | 2 +- flight/core/Dispatcher.php | 70 ++++++++++++++++++++------------------ 2 files changed, 38 insertions(+), 34 deletions(-) diff --git a/composer.json b/composer.json index e30b937..df9bfb0 100644 --- a/composer.json +++ b/composer.json @@ -24,8 +24,8 @@ ], "require": { "php": "^7.4|^8.0|^8.1|^8.2|^8.3", - "ext-json": "*" "ext-json": "*", + "psr/container": "^2.0", "symfony/polyfill-php80": "^1.29" }, "autoload": { diff --git a/flight/core/Dispatcher.php b/flight/core/Dispatcher.php index f08d1f0..77184e7 100644 --- a/flight/core/Dispatcher.php +++ b/flight/core/Dispatcher.php @@ -7,6 +7,7 @@ namespace flight\core; use Exception; use flight\Engine; use InvalidArgumentException; +use Psr\Container\ContainerInterface; use ReflectionFunction; use TypeError; @@ -43,20 +44,24 @@ class Dispatcher /** * This is a container for the dependency injection. * - * @var callable|object|null + * @var null|ContainerInterface|(callable(string $classString, array $params): (null|object)) */ protected $containerHandler = null; /** * Sets the dependency injection container handler. * - * @param callable|object $containerHandler Dependency injection container - * - * @return void + * @param ContainerInterface|(callable(string $classString, array $params): (null|object)) $containerHandler + * Dependency injection container. */ public function setContainerHandler($containerHandler): void { - $this->containerHandler = $containerHandler; + if ( + $containerHandler instanceof ContainerInterface + || is_callable($containerHandler) + ) { + $this->containerHandler = $containerHandler; + } } public function setEngine(Engine $engine): void @@ -390,67 +395,66 @@ class Dispatcher */ protected function verifyValidClassCallable($class, $method, $resolvedClass): void { - $final_exception = null; + $exception = null; // Final check to make sure it's actually a class and a method, or throw an error - if (is_object($class) === false && class_exists($class) === false) { - $final_exception = new Exception("Class '$class' not found. Is it being correctly autoloaded with Flight::path()?"); + if (!is_object($class) && !class_exists($class)) { + $exception = new Exception("Class '$class' not found. Is it being correctly autoloaded with Flight::path()?"); - // If this tried to resolve a class in a container and failed somehow, throw the exception - } elseif (isset($resolvedClass) === false && $this->containerException !== null) { - $final_exception = $this->containerException; + // If this tried to resolve a class in a container and failed somehow, throw the exception + } elseif (!$resolvedClass && $this->containerException) { + $exception = $this->containerException; - // Class is there, but no method - } elseif (is_object($class) === true && method_exists($class, $method) === false) { - $final_exception = new Exception("Class found, but method '" . get_class($class) . "::$method' not found."); + // Class is there, but no method + } elseif (is_object($class) && !method_exists($class, $method)) { + $classNamespace = get_class($class); + $exception = new Exception("Class found, but method '$classNamespace::$method' not found."); } - if ($final_exception !== null) { + if ($exception) { $this->fixOutputBuffering(); - throw $final_exception; + + throw $exception; } } /** * Resolves the container class. * - * @param callable|object $container_handler Dependency injection container * @param class-string $class Class name. * @param array &$params Class constructor parameters. * * @return ?object Class object. */ - protected function resolveContainerClass($container_handler, $class, array &$params) + protected function resolveContainerClass(string $class, array &$params) { - $class_object = null; - // PSR-11 if ( - is_object($container_handler) === true && - method_exists($container_handler, 'has') === true && - $container_handler->has($class) + $this->containerHandler instanceof ContainerInterface + && $this->containerHandler->has($class) ) { - $class_object = call_user_func([$container_handler, 'get'], $class); + return $this->containerHandler->get($class); + } // Just a callable where you configure the behavior (Dice, PHP-DI, etc.) - } elseif (is_callable($container_handler) === true) { - // This is to catch all the error that could be thrown by whatever container you are using + if (is_callable($this->containerHandler)) { + /* This is to catch all the error that could be thrown by whatever + container you are using */ try { - $class_object = call_user_func($container_handler, $class, $params); - } catch (Exception $e) { - // could not resolve a class for some reason - $class_object = null; + return ($this->containerHandler)($class, $params); + // could not resolve a class for some reason + } catch (Exception $exception) { // If the container throws an exception, we need to catch it // and store it somewhere. If we just let it throw itself, it // doesn't properly close the output buffers and can cause other // issues. - // This is thrown in the verifyValidClassCallable method - $this->containerException = $e; + // This is thrown in the verifyValidClassCallable method. + $this->containerException = $exception; } } - return $class_object; + return null; } /** Because this could throw an exception in the middle of an output buffer, */ From 598c71d01c2c0b74838d0397973f6579fbdac8cd Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Mon, 1 Apr 2024 19:18:50 -0400 Subject: [PATCH 11/14] Restored phpcs.dist.xml > phpcs.xml script --- composer.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index df9bfb0..8fdf8cc 100644 --- a/composer.json +++ b/composer.json @@ -66,7 +66,10 @@ "test-coverage:win": "del clover.xml && phpunit --coverage-html=coverage --coverage-clover=clover.xml && coverage-check clover.xml 100", "lint": "phpstan --no-progress -cphpstan.neon", "beautify": "phpcbf --standard=phpcs.xml", - "phpcs": "phpcs --standard=phpcs.xml -n" + "phpcs": "phpcs --standard=phpcs.xml -n", + "post-install-cmd": [ + "php -r \"if (!file_exists('phpcs.xml')) copy('phpcs.xml.dist', 'phpcs.xml');\"" + ] }, "suggest": { "latte/latte": "Latte template engine", From 99a7d75440b4cc3f80e8d9b98630091b2595a16d Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 3 Apr 2024 17:39:55 -0400 Subject: [PATCH 12/14] Removed PSR/Container --- composer.json | 1 - flight/core/Dispatcher.php | 20 +++++++++++++++----- phpstan-baseline.neon | 6 ------ 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/composer.json b/composer.json index 8fdf8cc..9d8f6e8 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,6 @@ "require": { "php": "^7.4|^8.0|^8.1|^8.2|^8.3", "ext-json": "*", - "psr/container": "^2.0", "symfony/polyfill-php80": "^1.29" }, "autoload": { diff --git a/flight/core/Dispatcher.php b/flight/core/Dispatcher.php index 77184e7..5604d69 100644 --- a/flight/core/Dispatcher.php +++ b/flight/core/Dispatcher.php @@ -44,24 +44,34 @@ class Dispatcher /** * This is a container for the dependency injection. * - * @var null|ContainerInterface|(callable(string $classString, array $params): (null|object)) + * @var null|ContainerInterface|(callable(string $classString, array $params): (null|object)) */ protected $containerHandler = null; /** * Sets the dependency injection container handler. * - * @param ContainerInterface|(callable(string $classString, array $params): (null|object)) $containerHandler + * @param ContainerInterface|(callable(string $classString, array $params): (null|object)) $containerHandler * Dependency injection container. + * + * @throws InvalidArgumentException If $containerHandler is not a `callable` or instance of `Psr\Container\ContainerInterface`. */ public function setContainerHandler($containerHandler): void { + $containerInterfaceNS = '\Psr\Container\ContainerInterface'; + if ( - $containerHandler instanceof ContainerInterface + is_a($containerHandler, $containerInterfaceNS) || is_callable($containerHandler) ) { $this->containerHandler = $containerHandler; + + return; } + + throw new InvalidArgumentException( + "\$containerHandler must be of type callable or instance $containerInterfaceNS" + ); } public function setEngine(Engine $engine): void @@ -200,7 +210,7 @@ class Dispatcher * * @param string $name Event name * @param 'before'|'after' $type Filter type. - * @param callable(array &$params, string &$output): (void|false) $callback + * @param callable(array &$params, mixed &$output): (void|false)|callable(mixed &$output): (void|false) $callback * * @return $this */ @@ -430,7 +440,7 @@ class Dispatcher { // PSR-11 if ( - $this->containerHandler instanceof ContainerInterface + is_a($this->containerHandler, '\Psr\Container\ContainerInterface') && $this->containerHandler->has($class) ) { return $this->containerHandler->get($class); diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 9fa597c..e69de29 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,6 +0,0 @@ -parameters: - ignoreErrors: - - - message: "#^Parameter \\#2 \\$callback of method flight\\\\core\\\\Dispatcher\\:\\:set\\(\\) expects Closure\\(\\)\\: mixed, array\\{\\$this\\(flight\\\\Engine\\), literal\\-string&non\\-falsy\\-string\\} given\\.$#" - count: 1 - path: flight/Engine.php From 8f3f6b5c74efa1dd873bdb13bd4c6c93bc3abb70 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 3 Apr 2024 17:51:16 -0400 Subject: [PATCH 13/14] Make explicit comparisons --- composer.json | 3 +-- flight/core/Dispatcher.php | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/composer.json b/composer.json index 9d8f6e8..9384c12 100644 --- a/composer.json +++ b/composer.json @@ -24,8 +24,7 @@ ], "require": { "php": "^7.4|^8.0|^8.1|^8.2|^8.3", - "ext-json": "*", - "symfony/polyfill-php80": "^1.29" + "ext-json": "*" }, "autoload": { "files": [ diff --git a/flight/core/Dispatcher.php b/flight/core/Dispatcher.php index 5604d69..fd3eac7 100644 --- a/flight/core/Dispatcher.php +++ b/flight/core/Dispatcher.php @@ -277,8 +277,8 @@ class Dispatcher public function execute($callback, array &$params = []) { if ( - is_string($callback) - && (str_contains($callback, '->') || str_contains($callback, '::')) + is_string($callback) === true + && (strpos($callback, '->') !== false || strpos($callback, '::') !== false) ) { $callback = $this->parseStringClassAndMethod($callback); } @@ -347,7 +347,7 @@ class Dispatcher public function invokeCallable($func, array &$params = []) { // If this is a directly callable function, call it - if (!is_array($func)) { + if (is_array($func) === false) { $this->verifyValidFunction($func); return call_user_func_array($func, $params); @@ -355,12 +355,12 @@ class Dispatcher [$class, $method] = $func; - $mustUseTheContainer = $this->containerHandler && ( - (is_object($class) && !str_starts_with(get_class($class), 'flight\\')) + $mustUseTheContainer = $this->containerHandler !== null && ( + (is_object($class) === true && strpos(get_class($class), 'flight\\') === false) || is_string($class) ); - if ($mustUseTheContainer) { + if ($mustUseTheContainer === true) { $resolvedClass = $this->resolveContainerClass($class, $params); if ($resolvedClass) { @@ -408,20 +408,20 @@ class Dispatcher $exception = null; // Final check to make sure it's actually a class and a method, or throw an error - if (!is_object($class) && !class_exists($class)) { + if (is_object($class) === false && class_exists($class) === false) { $exception = new Exception("Class '$class' not found. Is it being correctly autoloaded with Flight::path()?"); // If this tried to resolve a class in a container and failed somehow, throw the exception - } elseif (!$resolvedClass && $this->containerException) { + } elseif (!$resolvedClass && $this->containerException !== null) { $exception = $this->containerException; // Class is there, but no method - } elseif (is_object($class) && !method_exists($class, $method)) { + } elseif (is_object($class) === true && method_exists($class, $method) === false) { $classNamespace = get_class($class); $exception = new Exception("Class found, but method '$classNamespace::$method' not found."); } - if ($exception) { + if ($exception !== null) { $this->fixOutputBuffering(); throw $exception; From 414f605f67c0c36f93eddd9bf0d718ff0bee17dc Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 3 Apr 2024 18:53:15 -0400 Subject: [PATCH 14/14] More abstraction to Dispatcher::containerException typehint --- flight/core/Dispatcher.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flight/core/Dispatcher.php b/flight/core/Dispatcher.php index fd3eac7..1d40f95 100644 --- a/flight/core/Dispatcher.php +++ b/flight/core/Dispatcher.php @@ -9,6 +9,7 @@ use flight\Engine; use InvalidArgumentException; use Psr\Container\ContainerInterface; use ReflectionFunction; +use Throwable; use TypeError; /** @@ -26,7 +27,7 @@ class Dispatcher public const FILTER_AFTER = 'after'; /** Exception message if thrown by setting the container as a callable method. */ - protected ?Exception $containerException = null; + protected ?Throwable $containerException = null; /** @var ?Engine $engine Engine instance. */ protected ?Engine $engine = null;