From c929626fc6de968902aeada12534e398669fc045 Mon Sep 17 00:00:00 2001 From: Sascha Nos Date: Tue, 5 Sep 2023 12:17:27 +0200 Subject: [PATCH 01/29] fix wrong type return --- flight/Engine.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flight/Engine.php b/flight/Engine.php index f8c996bf..bd6921da 100644 --- a/flight/Engine.php +++ b/flight/Engine.php @@ -427,7 +427,8 @@ public function _stop(?int $code = null): void $response->status($code); } - $response->write(ob_get_clean()); + $content = ob_get_clean(); + $response->write($content ?: ''); $response->send(); } From cce78f18a3e70399852909bc735a63fcc7cf56d7 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Mon, 4 Dec 2023 16:59:19 -0400 Subject: [PATCH 02/29] PHPStan installation --- README.md | 3 +++ composer.json | 26 +++++++++++++++++++++++--- phpstan.neon | 10 ++++++++++ 3 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 phpstan.neon diff --git a/README.md b/README.md index 61083e9b..e078d003 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # What is Flight? +![](https://user-images.githubusercontent.com/104888/50957476-9c4acb80-14be-11e9-88ce-6447364dc1bb.png) +![](https://img.shields.io/badge/PHPStan-level%206-brightgreen.svg?style=flat) + Flight is a fast, simple, extensible framework for PHP. Flight enables you to quickly and easily build RESTful web applications. diff --git a/composer.json b/composer.json index 8e2611b1..325ba7f2 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "mikecao/flight", + "name": "faslatam/flight", "description": "Flight is a fast, simple, extensible framework for PHP. Flight enables you to quickly and easily build RESTful web applications.", "homepage": "http://flightphp.com", "license": "MIT", @@ -9,16 +9,36 @@ "email": "mike@mikecao.com", "homepage": "http://www.mikecao.com/", "role": "Original Developer" + }, + { + "name": "Franyer Sánchez", + "email": "franyeradriansanchez@gmail.com", + "homepage": "https://faslatam.000webhostapp.com", + "role": "Maintainer" } ], "require": { - "php": "^7.4|^8.0|^8.1", + "php": "^7.4|^8.0|^8.1|^8.2", "ext-json": "*" }, "autoload": { "files": [ "flight/autoload.php", "flight/Flight.php" ] }, "require-dev": { - "phpunit/phpunit": "^9.5" + "phpunit/phpunit": "^9.5", + "phpstan/phpstan": "^1.10", + "phpstan/extension-installer": "^1.3" + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true + } + }, + "scripts": { + "test": "phpunit tests", + "lint": "phpstan --no-progress -cphpstan.neon" + }, + "suggest": { + "phpstan/phpstan": "PHP Static Analysis Tool" } } diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 00000000..56c06c39 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,10 @@ +includes: + - vendor/phpstan/phpstan/conf/bleedingEdge.neon + +parameters: + level: 6 + excludePaths: + - vendor + paths: + - flight + - index.php From fbcc9108c202970ae2484dffdf354b39a07fdf1d Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Mon, 4 Dec 2023 16:59:49 -0400 Subject: [PATCH 03/29] DocBlocks improved --- flight/Engine.php | 39 ++++++++++++--------- flight/Flight.php | 40 +++++++++++---------- flight/core/Dispatcher.php | 26 +++++++------- flight/core/Loader.php | 26 ++++++++------ flight/net/Request.php | 48 ++++++++++++++------------ flight/net/Response.php | 15 ++++---- flight/net/Route.php | 8 ++--- flight/net/Router.php | 7 ++-- flight/template/View.php | 18 +++++++--- flight/util/Collection.php | 32 ++++++++--------- flight/util/LegacyJsonSerializable.php | 4 +++ 11 files changed, 146 insertions(+), 117 deletions(-) diff --git a/flight/Engine.php b/flight/Engine.php index f8c996bf..34060813 100644 --- a/flight/Engine.php +++ b/flight/Engine.php @@ -40,7 +40,7 @@ * Request-response * @method Request request() Gets current request * @method Response response() Gets current response - * @method void error(Exception $e) Sends an HTTP 500 response for any errors. + * @method void error(Throwable $e) Sends an HTTP 500 response for any errors. * @method void notFound() Sends an HTTP 404 response when a URL is not found. * @method void redirect(string $url, int $code = 303) Redirects the current request to another URL. * @method void json(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf-8', int $option = 0) Sends a JSON response. @@ -54,6 +54,7 @@ class Engine { /** * Stored variables. + * @var array */ protected array $vars; @@ -84,7 +85,7 @@ public function __construct() * Handles calls to class methods. * * @param string $name Method name - * @param array $params Method parameters + * @param array $params Method parameters * * @throws Exception * @@ -155,8 +156,8 @@ public function init(): void $this->before('start', function () use ($self) { // Enable error handling if ($self->get('flight.handle_errors')) { - set_error_handler([$self, 'handleError']); - set_exception_handler([$self, 'handleException']); + set_error_handler(array($self, 'handleError')); + set_exception_handler(array($self, 'handleException')); } // Set case-sensitivity @@ -177,18 +178,21 @@ public function init(): void * @param int $errline Error file line number * * @throws ErrorException + * @return bool */ public function handleError(int $errno, string $errstr, string $errfile, int $errline) { if ($errno & error_reporting()) { throw new ErrorException($errstr, $errno, 0, $errfile, $errline); } + + return false; } /** * Custom exception handler. Logs exceptions. * - * @param Exception $e Thrown exception + * @param Throwable $e Thrown exception */ public function handleException($e): void { @@ -203,7 +207,7 @@ public function handleException($e): void * Maps a callback to a framework method. * * @param string $name Method name - * @param callback $callback Callback function + * @param callable $callback Callback function * * @throws Exception If trying to map over a framework method */ @@ -218,11 +222,12 @@ public function map(string $name, callable $callback): void /** * Registers a class to a framework method. + * @template T of object * * @param string $name Method name - * @param string $class Class name - * @param array $params Class initialization parameters - * @param callable|null $callback $callback Function to call after object instantiation + * @param class-string $class Class name + * @param array $params Class initialization parameters + * @param ?callable(T $instance): void $callback Function to call after object instantiation * * @throws Exception If trying to map over a framework method */ @@ -239,7 +244,7 @@ public function register(string $name, string $class, array $params = [], ?calla * Adds a pre-filter to a method. * * @param string $name Method name - * @param callback $callback Callback function + * @param callable $callback Callback function */ public function before(string $name, callable $callback): void { @@ -250,7 +255,7 @@ public function before(string $name, callable $callback): void * Adds a post-filter to a method. * * @param string $name Method name - * @param callback $callback Callback function + * @param callable $callback Callback function */ public function after(string $name, callable $callback): void { @@ -437,7 +442,7 @@ public function _stop(?int $code = null): void * Routes a URL to a callback function. * * @param string $pattern URL pattern to match - * @param callback $callback Callback function + * @param callable $callback Callback function * @param bool $pass_route Pass the matching route object to the callback */ public function _route(string $pattern, callable $callback, bool $pass_route = false): void @@ -449,7 +454,7 @@ public function _route(string $pattern, callable $callback, bool $pass_route = f * Routes a URL to a callback function. * * @param string $pattern URL pattern to match - * @param callback $callback Callback function + * @param callable $callback Callback function * @param bool $pass_route Pass the matching route object to the callback */ public function _post(string $pattern, callable $callback, bool $pass_route = false): void @@ -461,7 +466,7 @@ public function _post(string $pattern, callable $callback, bool $pass_route = fa * Routes a URL to a callback function. * * @param string $pattern URL pattern to match - * @param callback $callback Callback function + * @param callable $callback Callback function * @param bool $pass_route Pass the matching route object to the callback */ public function _put(string $pattern, callable $callback, bool $pass_route = false): void @@ -473,7 +478,7 @@ public function _put(string $pattern, callable $callback, bool $pass_route = fal * Routes a URL to a callback function. * * @param string $pattern URL pattern to match - * @param callback $callback Callback function + * @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): void @@ -485,7 +490,7 @@ public function _patch(string $pattern, callable $callback, bool $pass_route = f * Routes a URL to a callback function. * * @param string $pattern URL pattern to match - * @param callback $callback Callback function + * @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): void @@ -555,7 +560,7 @@ public function _redirect(string $url, int $code = 303): void * Renders a template. * * @param string $file Template file - * @param array|null $data Template data + * @param ?array $data Template data * @param string|null $key View variable name * * @throws Exception diff --git a/flight/Flight.php b/flight/Flight.php index 101c455c..2bca787d 100644 --- a/flight/Flight.php +++ b/flight/Flight.php @@ -18,36 +18,27 @@ /** * The Flight class is a static representation of the framework. * - * Core. - * * @method static void start() Starts the framework. - * @method static void path($path) Adds a path for autoloading classes. + * @method static void path(string $path) Adds a path for autoloading classes. * @method static void stop() Stops the framework and sends a response. - * @method static void halt($code = 200, $message = '') Stop the framework with an optional status code and message. + * @method static void halt(int $code = 200, string $message = '') Stop the framework with an optional status code and message. * - * Routing. - * @method static void route($pattern, $callback) Maps a URL pattern to a callback. + * @method static void route(string $pattern, callable $callback) Maps a URL pattern to a callback. * @method static Router router() Returns Router instance. * - * Extending & Overriding. - * @method static void map($name, $callback) Creates a custom framework method. - * @method static void register($name, $class, array $params = array(), $callback = null) Registers a class to a framework method. + * @method static void map(string $name, callable $callback) Creates a custom framework method. * - * Filtering. * @method static void before($name, $callback) Adds a filter before a framework method. * @method static void after($name, $callback) Adds a filter after a framework method. * - * Variables. * @method static void set($key, $value) Sets a variable. * @method static mixed get($key) Gets a variable. * @method static bool has($key) Checks if a variable is set. * @method static void clear($key = null) Clears a variable. * - * Views. * @method static void render($file, array $data = null, $key = null) Renders a template file. * @method static View view() Returns View instance. * - * Request & Response. * @method static Request request() Returns Request instance. * @method static Response response() Returns Response instance. * @method static void redirect($url, $code = 303) Redirects to another URL. @@ -56,7 +47,6 @@ * @method static void error($exception) Sends an HTTP 500 response. * @method static void notFound() Sends an HTTP 404 response. * - * HTTP Caching. * @method static void etag($id, $type = 'strong') Performs ETag HTTP caching. * @method static void lastModified($time) Performs last modified HTTP caching. */ @@ -72,19 +62,33 @@ private function __construct() { } - private function __destruct() + private function __clone() { } - private function __clone() - { + /** + * Registers a class to a framework method. + * @template T of object + * @param string $name Static method name + * ``` + * Flight::register('user', User::class); + * + * Flight::user(); # <- Return a User instance + * ``` + * @param class-string $class Fully Qualified Class Name + * @param array $params Class constructor params + * @param ?Closure(T $instance): void $callback Perform actions with the instance + * @return void + */ + static function register($name, $class, $params = array(), $callback = null) { + static::__callStatic('register', func_get_args()); } /** * Handles calls to static methods. * * @param string $name Method name - * @param array $params Method parameters + * @param array $params Method parameters * * @throws Exception * diff --git a/flight/core/Dispatcher.php b/flight/core/Dispatcher.php index 50b73f00..0f810bdb 100644 --- a/flight/core/Dispatcher.php +++ b/flight/core/Dispatcher.php @@ -23,11 +23,13 @@ class Dispatcher { /** * Mapped events. + * @var array */ protected array $events = []; /** * Method filters. + * @var array>> */ protected array $filters = []; @@ -35,9 +37,9 @@ class Dispatcher * Dispatches an event. * * @param string $name Event name - * @param array $params Callback parameters + * @param array $params Callback parameters * - *@throws Exception + * @throws Exception * * @return mixed|null Output of callback */ @@ -65,7 +67,7 @@ final public function run(string $name, array $params = []) * Assigns a callback to an event. * * @param string $name Event name - * @param callback $callback Callback function + * @param callable $callback Callback function */ final public function set(string $name, callable $callback): void { @@ -77,7 +79,7 @@ final public function set(string $name, callable $callback): void * * @param string $name Event name * - * @return callback $callback Callback function + * @return callable $callback Callback function */ final public function get(string $name): ?callable { @@ -118,7 +120,7 @@ final public function clear(?string $name = null): void * * @param string $name Event name * @param string $type Filter type - * @param callback $callback Callback function + * @param callable $callback Callback function */ final public function hook(string $name, string $type, callable $callback): void { @@ -128,8 +130,8 @@ final public function hook(string $name, string $type, callable $callback): void /** * Executes a chain of method filters. * - * @param array $filters Chain of filters - * @param array $params Method parameters + * @param array $filters Chain of filters + * @param array $params Method parameters * @param mixed $output Method output * * @throws Exception @@ -148,10 +150,10 @@ final public function filter(array $filters, array &$params, &$output): void /** * Executes a callback function. * - * @param array|callback $callback Callback function - * @param array $params Function parameters + * @param callable|array $callback Callback function + * @param array $params Function parameters * - *@throws Exception + * @throws Exception * * @return mixed Function results */ @@ -170,7 +172,7 @@ public static function execute($callback, array &$params = []) * Calls a function. * * @param callable|string $func Name of function to call - * @param array $params Function parameters + * @param array $params Function parameters * * @return mixed Function results */ @@ -203,7 +205,7 @@ public static function callFunction($func, array &$params = []) * Invokes a method. * * @param mixed $func Class method - * @param array $params Class method parameters + * @param array $params Class method parameters * * @return mixed Function results */ diff --git a/flight/core/Loader.php b/flight/core/Loader.php index 95874a6d..5ff66fe3 100644 --- a/flight/core/Loader.php +++ b/flight/core/Loader.php @@ -10,6 +10,7 @@ namespace flight\core; +use Closure; use Exception; use ReflectionClass; use ReflectionException; @@ -24,26 +25,30 @@ class Loader { /** * Registered classes. + * @var array, ?callable}> $classes */ protected array $classes = []; /** * Class instances. + * @var array */ protected array $instances = []; /** * Autoload directories. + * @var array */ protected static array $dirs = []; /** * Registers a class. + * @template T of object * * @param string $name Registry name - * @param callable|string $class Class name or function to instantiate class - * @param array $params Class initialization parameters - * @param callable|null $callback $callback Function to call after object instantiation + * @param class-string $class Class name or function to instantiate class + * @param array $params Class initialization parameters + * @param ?callable(T $instance): void $callback $callback Function to call after object instantiation */ public function register(string $name, $class, array $params = [], ?callable $callback = null): void { @@ -77,7 +82,7 @@ public function load(string $name, bool $shared = true): ?object $obj = null; if (isset($this->classes[$name])) { - [$class, $params, $callback] = $this->classes[$name]; + [0 => $class, 1 => $params, 2 => $callback] = $this->classes[$name]; $exists = isset($this->instances[$name]); @@ -116,15 +121,16 @@ public function getInstance(string $name): ?object /** * Gets a new instance of a class. + * @template T of object * - * @param callable|string $class Class name or callback function to instantiate class - * @param array $params Class initialization parameters + * @param class-string|Closure(): class-string $class Class name or callback function to instantiate class + * @param array $params Class initialization parameters * * @throws Exception * - * @return object Class instance + * @return T Class instance */ - public function newInstance($class, array $params = []): object + public function newInstance($class, array $params = []) { if (\is_callable($class)) { return \call_user_func_array($class, $params); @@ -179,7 +185,7 @@ public function reset(): void * Starts/stops autoloader. * * @param bool $enabled Enable/disable autoloading - * @param mixed $dirs Autoload directories + * @param string|iterable $dirs Autoload directories */ public static function autoload(bool $enabled = true, $dirs = []): void { @@ -216,7 +222,7 @@ public static function loadClass(string $class): void /** * Adds a directory for autoloading classes. * - * @param mixed $dir Directory path + * @param string|iterable $dir Directory path */ public static function addDirectory($dir): void { diff --git a/flight/net/Request.php b/flight/net/Request.php index 75d4665f..8f8ecf8f 100644 --- a/flight/net/Request.php +++ b/flight/net/Request.php @@ -18,23 +18,24 @@ * are stored and accessible via the Request object. * * The default request properties are: - * url - The URL being requested - * base - The parent subdirectory of the URL - * method - The request method (GET, POST, PUT, DELETE) - * referrer - The referrer URL - * ip - IP address of the client - * ajax - Whether the request is an AJAX request - * scheme - The server protocol (http, https) - * user_agent - Browser information - * type - The content type - * length - The content length - * query - Query string parameters - * data - Post parameters - * cookies - Cookie parameters - * files - Uploaded files - * secure - Connection is secure - * accept - HTTP accept parameters - * proxy_ip - Proxy IP address of the client + * + * - **url** - The URL being requested + * - **base** - The parent subdirectory of the URL + * - **method** - The request method (GET, POST, PUT, DELETE) + * - **referrer** - The referrer URL + * - **ip** - IP address of the client + * - **ajax** - Whether the request is an AJAX request + * - **scheme** - The server protocol (http, https) + * - **user_agent** - Browser information + * - **type** - The content type + * - **length** - The content length + * - **query** - Query string parameters + * - **data** - Post parameters + * - **cookies** - Cookie parameters + * - **files** - Uploaded files + * - **secure** - Connection is secure + * - **accept** - HTTP accept parameters + * - **proxy_ip** - Proxy IP address of the client */ final class Request { @@ -131,9 +132,9 @@ final class Request /** * Constructor. * - * @param array $config Request configuration + * @param array $config Request configuration */ - public function __construct(array $config = []) + public function __construct($config = array()) { // Default properties if (empty($config)) { @@ -165,7 +166,8 @@ public function __construct(array $config = []) /** * Initialize request properties. * - * @param array $properties Array of request properties + * @param array $properties Array of request properties + * @return static */ public function init(array $properties = []) { @@ -199,6 +201,8 @@ public function init(array $properties = []) } } } + + return $this; } /** @@ -287,11 +291,11 @@ public static function getVar(string $var, $default = '') * * @param string $url URL string * - * @return array Query parameters + * @return array> */ public static function parseQuery(string $url): array { - $params = []; + $params = array(); $args = parse_url($url); if (isset($args['query'])) { diff --git a/flight/net/Response.php b/flight/net/Response.php index e21fc9a2..5873ec7c 100644 --- a/flight/net/Response.php +++ b/flight/net/Response.php @@ -25,7 +25,7 @@ class Response public bool $content_length = true; /** - * @var array HTTP status codes + * @var array HTTP status codes */ public static array $codes = [ 100 => 'Continue', @@ -103,7 +103,7 @@ class Response protected int $status = 200; /** - * @var array HTTP headers + * @var array> HTTP headers */ protected array $headers = []; @@ -124,7 +124,7 @@ class Response * * @throws Exception If invalid status code * - * @return int|object Self reference + * @return int|static Self reference */ public function status(?int $code = null) { @@ -144,10 +144,10 @@ public function status(?int $code = null) /** * Adds a header to the response. * - * @param array|string $name Header name or array of names and values + * @param array|string $name Header name or array of names and values * @param string|null $value Header value * - * @return object Self reference + * @return static Self reference */ public function header($name, ?string $value = null) { @@ -164,8 +164,7 @@ public function header($name, ?string $value = null) /** * Returns the headers from the response. - * - * @return array + * @return array> */ public function headers() { @@ -203,7 +202,7 @@ public function clear(): self /** * Sets caching headers for the response. * - * @param int|string $expires Expiration time + * @param int|string|false $expires Expiration time * * @return Response Self reference */ diff --git a/flight/net/Route.php b/flight/net/Route.php index 91a6aff9..fbfc20b2 100644 --- a/flight/net/Route.php +++ b/flight/net/Route.php @@ -28,12 +28,12 @@ final class Route public $callback; /** - * @var array HTTP methods + * @var array HTTP methods */ public array $methods = []; /** - * @var array Route parameters + * @var array Route parameters */ public array $params = []; @@ -56,8 +56,8 @@ final class Route * Constructor. * * @param string $pattern URL pattern - * @param mixed $callback Callback function - * @param array $methods HTTP methods + * @param callable $callback Callback function + * @param array $methods HTTP methods * @param bool $pass Pass self in callback parameters */ public function __construct(string $pattern, $callback, array $methods, bool $pass) diff --git a/flight/net/Router.php b/flight/net/Router.php index dcf15918..f1f8842d 100644 --- a/flight/net/Router.php +++ b/flight/net/Router.php @@ -23,6 +23,7 @@ class Router public bool $case_sensitive = false; /** * Mapped routes. + * @var array */ protected array $routes = []; @@ -34,7 +35,7 @@ class Router /** * Gets mapped routes. * - * @return array Array of routes + * @return array Array of routes */ public function getRoutes(): array { @@ -53,7 +54,7 @@ public function clear(): void * Maps a URL pattern to a callback function. * * @param string $pattern URL pattern to match - * @param callback $callback Callback function + * @param callable $callback Callback function * @param bool $pass_route Pass the matching route object to the callback */ public function map(string $pattern, callable $callback, bool $pass_route = false): void @@ -81,7 +82,7 @@ public function route(Request $request) { $url_decoded = urldecode($request->url); while ($route = $this->current()) { - if (false !== $route && $route->matchMethod($request->method) && $route->matchUrl($url_decoded, $this->case_sensitive)) { + 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 90dbb819..f4d866cc 100644 --- a/flight/template/View.php +++ b/flight/template/View.php @@ -34,7 +34,7 @@ class View /** * View variables. * - * @var array + * @var array */ protected $vars = []; @@ -70,8 +70,9 @@ public function get($key) /** * Sets a template variable. * - * @param mixed $key Key - * @param string $value Value + * @param string|iterable $key Key + * @param mixed $value Value + * @return static */ public function set($key, $value = null) { @@ -82,6 +83,8 @@ public function set($key, $value = null) } else { $this->vars[$key] = $value; } + + return $this; } /** @@ -100,6 +103,7 @@ public function has($key) * Unsets a template variable. If no key is passed in, clear all variables. * * @param string $key Key + * @return static */ public function clear($key = null) { @@ -108,15 +112,18 @@ public function clear($key = null) } else { unset($this->vars[$key]); } + + return $this; } /** * Renders a template. * * @param string $file Template file - * @param array $data Template data + * @param array $data Template data * * @throws \Exception If template not found + * @return void */ public function render($file, $data = null) { @@ -139,7 +146,7 @@ public function render($file, $data = null) * Gets the output of a template. * * @param string $file Template file - * @param array $data Template data + * @param array $data Template data * * @return string Output of template */ @@ -196,5 +203,6 @@ public function getTemplate($file) public function e($str) { echo htmlentities($str); + return htmlentities($str); } } diff --git a/flight/util/Collection.php b/flight/util/Collection.php index 97658ec6..5998b252 100644 --- a/flight/util/Collection.php +++ b/flight/util/Collection.php @@ -11,7 +11,6 @@ namespace flight\util; use ArrayAccess; -use function count; use Countable; use Iterator; use JsonSerializable; @@ -23,18 +22,21 @@ /** * The Collection class allows you to access a set of data * using both array and object notation. + * @implements ArrayAccess + * @implements Iterator */ final class Collection implements ArrayAccess, Iterator, Countable, JsonSerializable { /** * Collection data. + * @var array */ private array $data; /** * Constructor. * - * @param array $data Initial data + * @param array $data Initial data */ public function __construct(array $data = []) { @@ -102,11 +104,11 @@ public function offsetGet($offset) /** * Sets an item at the offset. * - * @param string $offset Offset + * @param ?string $offset Offset * @param mixed $value Value */ #[\ReturnTypeWillChange] - public function offsetSet($offset, $value) + public function offsetSet($offset, $value): void { if (null === $offset) { $this->data[] = $value; @@ -169,13 +171,11 @@ public function key() /** * Gets the next collection value. - * - * @return mixed Value */ #[\ReturnTypeWillChange] - public function next() + public function next(): void { - return next($this->data); + next($this->data); } /** @@ -187,7 +187,7 @@ public function valid(): bool { $key = key($this->data); - return null !== $key && false !== $key; + return null !== $key; } /** @@ -203,7 +203,7 @@ public function count(): int /** * Gets the item keys. * - * @return array Collection keys + * @return array Collection keys */ public function keys(): array { @@ -213,7 +213,7 @@ public function keys(): array /** * Gets the collection data. * - * @return array Collection data + * @return array Collection data */ public function getData(): array { @@ -223,19 +223,15 @@ public function getData(): array /** * Sets the collection data. * - * @param array $data New collection data + * @param array $data New collection data */ public function setData(array $data): void { $this->data = $data; } - /** - * Gets the collection data which can be serialized to JSON. - * - * @return array Collection data which can be serialized by json_encode - */ - public function jsonSerialize(): array + #[\ReturnTypeWillChange] + public function jsonSerialize() { return $this->data; } diff --git a/flight/util/LegacyJsonSerializable.php b/flight/util/LegacyJsonSerializable.php index 0c973aa2..39e1721a 100644 --- a/flight/util/LegacyJsonSerializable.php +++ b/flight/util/LegacyJsonSerializable.php @@ -9,5 +9,9 @@ */ interface LegacyJsonSerializable { + /** + * Gets the collection data which can be serialized to JSON. + * @return mixed Collection data which can be serialized by json_encode + */ public function jsonSerialize(); } From ba0c6bda3310436139b9fd6fd5137b0c6489c2ca Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Mon, 4 Dec 2023 17:28:50 -0400 Subject: [PATCH 04/29] Improved documentation --- README.md | 366 +++++++++++++++++++++++++++--------------------------- 1 file changed, 186 insertions(+), 180 deletions(-) diff --git a/README.md b/README.md index e078d003..469c5f70 100644 --- a/README.md +++ b/README.md @@ -3,14 +3,14 @@ ![](https://user-images.githubusercontent.com/104888/50957476-9c4acb80-14be-11e9-88ce-6447364dc1bb.png) ![](https://img.shields.io/badge/PHPStan-level%206-brightgreen.svg?style=flat) -Flight is a fast, simple, extensible framework for PHP. Flight enables you to +Flight is a fast, simple, extensible framework for PHP. Flight enables you to quickly and easily build RESTful web applications. ```php require 'flight/Flight.php'; -Flight::route('/', function(){ - echo 'hello world!'; +Flight::route('/', function() { + echo 'hello world!'; }); Flight::start(); @@ -30,14 +30,15 @@ Flight is released under the [MIT](http://flightphp.com/license) license. 1\. Download the files. -If you're using [Composer](https://getcomposer.org/), you can run the following command: +If you're using [Composer](https://getcomposer.org/), you can run the following +command: -``` +```bash composer require mikecao/flight ``` -OR you can [download](https://github.com/mikecao/flight/archive/master.zip) them directly -and extract them to your web directory. +OR you can [download](https://github.com/mikecao/flight/archive/master.zip) +them directly and extract them to your web directory. 2\. Configure your webserver. @@ -50,15 +51,16 @@ RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ index.php [QSA,L] ``` -**Note**: If you need to use flight in a subdirectory add the line `RewriteBase /subdir/` just after `RewriteEngine On`. +**Note**: If you need to use flight in a subdirectory add the line +`RewriteBase /subdir/` just after `RewriteEngine On`. For *Nginx*, add the following to your server declaration: ``` server { - location / { - try_files $uri $uri/ /index.php; - } + location / { + try_files $uri $uri/ /index.php; + } } ``` 3\. Create your `index.php` file. @@ -78,8 +80,8 @@ require 'vendor/autoload.php'; Then define a route and assign a function to handle the request. ```php -Flight::route('/', function(){ - echo 'hello world!'; +Flight::route('/', function () { + echo 'hello world!'; }); ``` @@ -94,16 +96,16 @@ Flight::start(); Routing in Flight is done by matching a URL pattern with a callback function. ```php -Flight::route('/', function(){ - echo 'hello world!'; +Flight::route('/', function () { + echo 'hello world!'; }); ``` The callback can be any object that is callable. So you can use a regular function: ```php -function hello(){ - echo 'hello world!'; +function hello() { + echo 'hello world!'; } Flight::route('/', 'hello'); @@ -113,9 +115,9 @@ Or a class method: ```php class Greeting { - public static function hello() { - echo 'hello world!'; - } + public static function hello() { + echo 'hello world!'; + } } Flight::route('/', array('Greeting', 'hello')); @@ -126,18 +128,18 @@ Or an object method: ```php class Greeting { - public function __construct() { - $this->name = 'John Doe'; - } + public function __construct() { + $this->name = 'John Doe'; + } - public function hello() { - echo "Hello, {$this->name}!"; - } + public function hello() { + echo "Hello, {$this->name}!"; + } } $greeting = new Greeting(); -Flight::route('/', array($greeting, 'hello')); +Flight::route('/', array($greeting, 'hello')); ``` Routes are matched in the order they are defined. The first route to match a @@ -149,20 +151,20 @@ By default, route patterns are matched against all request methods. You can resp to specific methods by placing an identifier before the URL. ```php -Flight::route('GET /', function(){ - echo 'I received a GET request.'; +Flight::route('GET /', function () { + echo 'I received a GET request.'; }); -Flight::route('POST /', function(){ - echo 'I received a POST request.'; +Flight::route('POST /', function () { + echo 'I received a POST request.'; }); ``` You can also map multiple methods to a single callback by using a `|` delimiter: ```php -Flight::route('GET|POST /', function(){ - echo 'I received either a GET or a POST request.'; +Flight::route('GET|POST /', function () { + echo 'I received either a GET or a POST request.'; }); ``` @@ -171,8 +173,8 @@ Flight::route('GET|POST /', function(){ You can use regular expressions in your routes: ```php -Flight::route('/user/[0-9]+', function(){ - // This will match /user/1234 +Flight::route('/user/[0-9]+', function () { + // This will match /user/1234 }); ``` @@ -182,8 +184,8 @@ You can specify named parameters in your routes which will be passed along to your callback function. ```php -Flight::route('/@name/@id', function($name, $id){ - echo "hello, $name ($id)!"; +Flight::route('/@name/@id', function(string $name, string $id) { + echo "hello, $name ($id)!"; }); ``` @@ -191,9 +193,9 @@ You can also include regular expressions with your named parameters by using the `:` delimiter: ```php -Flight::route('/@name/@id:[0-9]{3}', function($name, $id){ - // This will match /bob/123 - // But will not match /bob/12345 +Flight::route('/@name/@id:[0-9]{3}', function(string $name, string $id) { + // This will match /bob/123 + // But will not match /bob/12345 }); ``` @@ -205,13 +207,16 @@ You can specify named parameters that are optional for matching by wrapping segments in parentheses. ```php -Flight::route('/blog(/@year(/@month(/@day)))', function($year, $month, $day){ +Flight::route( + '/blog(/@year(/@month(/@day)))', + function(?string $year, ?string $month, ?string $day) { // This will match the following URLS: // /blog/2012/12/10 // /blog/2012/12 // /blog/2012 // /blog -}); + } +); ``` Any optional parameters that are not matched will be passed in as NULL. @@ -222,16 +227,16 @@ Matching is only done on individual URL segments. If you want to match multiple segments you can use the `*` wildcard. ```php -Flight::route('/blog/*', function(){ - // This will match /blog/2000/02/01 +Flight::route('/blog/*', function () { + // This will match /blog/2000/02/01 }); ``` To route all requests to a single callback, you can do: ```php -Flight::route('*', function(){ - // Do something +Flight::route('*', function () { + // Do something }); ``` @@ -241,16 +246,16 @@ You can pass execution on to the next matching route by returning `true` from your callback function. ```php -Flight::route('/user/@name', function($name){ - // Check some condition - if ($name != "Bob") { - // Continue to next route - return true; - } +Flight::route('/user/@name', function (string $name) { + // Check some condition + if ($name != "Bob") { + // Continue to next route + return true; + } }); -Flight::route('/user/*', function(){ - // This will get called +Flight::route('/user/*', function () { + // This will get called }); ``` @@ -262,18 +267,18 @@ the route method. The route object will always be the last parameter passed to y callback function. ```php -Flight::route('/', function($route){ - // Array of HTTP methods matched against - $route->methods; +Flight::route('/', function(\flight\net\Route $route) { + // Array of HTTP methods matched against + $route->methods; - // Array of named parameters - $route->params; + // Array of named parameters + $route->params; - // Matching regular expression - $route->regex; + // Matching regular expression + $route->regex; - // Contains the contents of any '*' used in the URL pattern - $route->splat; + // Contains the contents of any '*' used in the URL pattern + $route->splat; }, true); ``` @@ -289,8 +294,8 @@ To map your own custom method, you use the `map` function: ```php // Map your method -Flight::map('hello', function($name){ - echo "hello $name!"; +Flight::map('hello', function ($name) { + echo "hello $name!"; }); // Call your custom method @@ -321,7 +326,7 @@ Flight::register('db', 'PDO', array('mysql:host=localhost;dbname=test','user','p // Get an instance of your class // This will create an object with the defined parameters // -// new PDO('mysql:host=localhost;dbname=test','user','pass'); +// new PDO('mysql:host=localhost;dbname=test','user','pass'); // $db = Flight::db(); ``` @@ -332,8 +337,8 @@ new object. The callback function takes one parameter, an instance of the new ob ```php // The callback will be passed the object that was constructed -Flight::register('db', 'PDO', array('mysql:host=localhost;dbname=test','user','pass'), function($db){ - $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); +Flight::register('db', 'PDO', array('mysql:host=localhost;dbname=test','user','pass'), function (PDO $db): void { + $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); }); ``` @@ -362,8 +367,8 @@ by using the `map` method: ```php Flight::map('notFound', function(){ - // Display custom 404 page - include 'errors/404.html'; + // Display custom 404 page + include 'errors/404.html'; }); ``` @@ -390,8 +395,8 @@ methods as well as any custom methods that you've mapped. A filter function looks like this: ```php -function(&$params, &$output) { - // Filter code +function (array &$params, string &$output) { + // Filter code } ``` @@ -400,16 +405,16 @@ Using the passed in variables you can manipulate the input parameters and/or the You can have a filter run before a method by doing: ```php -Flight::before('start', function(&$params, &$output){ - // Do something +Flight::before('start', function (array &$params, string &$output) { + // Do something }); ``` You can have a filter run after a method by doing: ```php -Flight::after('start', function(&$params, &$output){ - // Do something +Flight::after('start', function (array &$params, string &$output) { + // Do something }); ``` @@ -420,20 +425,20 @@ Here's an example of the filtering process: ```php // Map a custom method -Flight::map('hello', function($name){ - return "Hello, $name!"; +Flight::map('hello', function ($name) { + return "Hello, $name!"; }); // Add a before filter -Flight::before('hello', function(&$params, &$output){ - // Manipulate the parameter - $params[0] = 'Fred'; +Flight::before('hello', function (array &$params, string &$output) { + // Manipulate the parameter + $params[0] = 'Fred'; }); // Add an after filter -Flight::after('hello', function(&$params, &$output){ - // Manipulate the output - $output .= " Have a nice day!"; +Flight::after('hello', function (array &$params, string &$output) { + // Manipulate the output + $output .= " Have a nice day!"; }); // Invoke the custom method @@ -442,26 +447,28 @@ echo Flight::hello('Bob'); This should display: - Hello Fred! Have a nice day! +``` +Hello Fred! Have a nice day! +``` If you have defined multiple filters, you can break the chain by returning `false` in any of your filter functions: ```php -Flight::before('start', function(&$params, &$output){ - echo 'one'; +Flight::before('start', function (array &$params, string &$output){ + echo 'one'; }); -Flight::before('start', function(&$params, &$output){ - echo 'two'; +Flight::before('start', function (array &$params, string &$output): bool { + echo 'two'; - // This will end the chain - return false; + // This will end the chain + return false; }); // This will not get called -Flight::before('start', function(&$params, &$output){ - echo 'three'; +Flight::before('start', function (array &$params, string &$output){ + echo 'three'; }); ``` @@ -483,7 +490,7 @@ To see if a variable has been set you can do: ```php if (Flight::has('id')) { - // Do something + // Do something } ``` @@ -518,12 +525,14 @@ be reference like a local variable. Template files are simply PHP files. If the content of the `hello.php` template file is: ```php -Hello, ''! +Hello, ! ``` The output would be: - Hello, Bob! +``` +Hello, Bob! +``` You can also manually set view variables by using the set method: @@ -583,26 +592,26 @@ If the template files looks like this: ```php - -<?php echo $title; ?> - - - - - + + <?php echo $title; ?> + + + + + ``` The output would be: ```html - -Home Page - - -

Hello

-
World
- + + Home Page + + +

Hello

+
World
+ ``` @@ -618,11 +627,11 @@ require './Smarty/libs/Smarty.class.php'; // Register Smarty as the view class // Also pass a callback function to configure Smarty on load -Flight::register('view', 'Smarty', array(), function($smarty){ - $smarty->template_dir = './templates/'; - $smarty->compile_dir = './templates_c/'; - $smarty->config_dir = './config/'; - $smarty->cache_dir = './cache/'; +Flight::register('view', 'Smarty', array(), function (Smarty $smarty) { + $smarty->setTemplateDir() = './templates/'; + $smarty->setCompileDir() = './templates_c/'; + $smarty->setConfigDir() = './config/'; + $smarty->setCacheDir() = './cache/'; }); // Assign template data @@ -636,8 +645,8 @@ For completeness, you should also override Flight's default render method: ```php Flight::map('render', function($template, $data){ - Flight::view()->assign($data); - Flight::view()->display($template); + Flight::view()->assign($data); + Flight::view()->display($template); }); ``` # Error Handling @@ -651,9 +660,9 @@ response with some error information. You can override this behavior for your own needs: ```php -Flight::map('error', function(Exception $ex){ - // Handle error - echo $ex->getTraceAsString(); +Flight::map('error', function(Throwable $ex){ + // Handle error + echo $ex->getTraceAsString(); }); ``` @@ -672,8 +681,8 @@ behavior is to send an `HTTP 404 Not Found` response with a simple message. You can override this behavior for your own needs: ```php -Flight::map('notFound', function(){ - // Handle not found +Flight::map('notFound', function () { + // Handle not found }); ``` @@ -704,26 +713,24 @@ $request = Flight::request(); The request object provides the following properties: -``` -url - The URL being requested -base - The parent subdirectory of the URL -method - The request method (GET, POST, PUT, DELETE) -referrer - The referrer URL -ip - IP address of the client -ajax - Whether the request is an AJAX request -scheme - The server protocol (http, https) -user_agent - Browser information -type - The content type -length - The content length -query - Query string parameters -data - Post data or JSON data -cookies - Cookie data -files - Uploaded files -secure - Whether the connection is secure -accept - HTTP accept parameters -proxy_ip - Proxy IP address of the client -host - The request host name -``` +- **url** - The URL being requested +- **base** - The parent subdirectory of the URL +- **method** - The request method (GET, POST, PUT, DELETE) +- **referrer** - The referrer URL +- **ip** - IP address of the client +- **ajax** - Whether the request is an AJAX request +- **scheme** - The server protocol (http, https) +- **user_agent** - Browser information +- **type** - The content type +- **length** - The content length +- **query** - Query string parameters +- **data** - Post data or JSON data +- **cookies** - Cookie data +- **files** - Uploaded files +- **secure** - Whether the connection is secure +- **accept** - HTTP accept parameters +- **proxy_ip** - Proxy IP address of the client +- **host** - The request host name You can access the `query`, `data`, `cookies`, and `files` properties as arrays or objects. @@ -742,7 +749,8 @@ $id = Flight::request()->query->id; ## RAW Request Body -To get the raw HTTP request body, for example when dealing with PUT requests, you can do: +To get the raw HTTP request body, for example when dealing with PUT requests, +you can do: ```php $body = Flight::request()->getBody(); @@ -750,8 +758,8 @@ $body = Flight::request()->getBody(); ## JSON Input -If you send a request with the type `application/json` and the data `{"id": 123}` it will be available -from the `data` property: +If you send a request with the type `application/json` and the data `{"id": 123}` +it will be available from the `data` property: ```php $id = Flight::request()->data->id; @@ -771,9 +779,9 @@ and time a page was last modified. The client will continue to use their cache u the last modified value is changed. ```php -Flight::route('/news', function(){ - Flight::lastModified(1234567890); - echo 'This content will be cached.'; +Flight::route('/news', function () { + Flight::lastModified(1234567890); + echo 'This content will be cached.'; }); ``` @@ -783,9 +791,9 @@ Flight::route('/news', function(){ want for the resource: ```php -Flight::route('/news', function(){ - Flight::etag('my-unique-id'); - echo 'This content will be cached.'; +Flight::route('/news', function () { + Flight::etag('my-unique-id'); + echo 'This content will be cached.'; }); ``` @@ -850,12 +858,12 @@ Flight::set('flight.log_errors', true); The following is a list of all the available configuration settings: - flight.base_url - Override the base url of the request. (default: null) - flight.case_sensitive - Case sensitive matching for URLs. (default: false) - flight.handle_errors - Allow Flight to handle all errors internally. (default: true) - flight.log_errors - Log errors to the web server's error log file. (default: false) - flight.views.path - Directory containing view template files. (default: ./views) - flight.views.extension - View template file extension. (default: .php) +- **flight.base_url** - Override the base url of the request. (default: null) +- **flight.case_sensitive** - Case sensitive matching for URLs. (default: false) +- **flight.handle_errors** - Allow Flight to handle all errors internally. (default: true) +- **flight.log_errors** - Log errors to the web server's error log file. (default: false) +- **flight.views.path** - Directory containing view template files. (default: ./views) +- **flight.views.extension** - View template file extension. (default: .php) # Framework Methods @@ -867,15 +875,15 @@ or overridden. ## Core Methods ```php -Flight::map($name, $callback) // Creates a custom framework method. -Flight::register($name, $class, [$params], [$callback]) // Registers a class to a framework method. -Flight::before($name, $callback) // Adds a filter before a framework method. -Flight::after($name, $callback) // Adds a filter after a framework method. -Flight::path($path) // Adds a path for autoloading classes. -Flight::get($key) // Gets a variable. -Flight::set($key, $value) // Sets a variable. -Flight::has($key) // Checks if a variable is set. -Flight::clear([$key]) // Clears a variable. +Flight::map(string $name, callable $callback, bool $pass_route = false) // Creates a custom framework method. +Flight::register(string $name, string $class, array $params = [], ?callable $callback = null) // Registers a class to a framework method. +Flight::before(string $name, callable $callback) // Adds a filter before a framework method. +Flight::after(string $name, callable $callback) // Adds a filter after a framework method. +Flight::path(string $path) // Adds a path for autoloading classes. +Flight::get(string $key) // Gets a variable. +Flight::set(string $key, mixed $value) // Sets a variable. +Flight::has(string $key) // Checks if a variable is set. +Flight::clear(array|string $key = []) // Clears a variable. Flight::init() // Initializes the framework to its default settings. Flight::app() // Gets the application object instance ``` @@ -885,16 +893,16 @@ Flight::app() // Gets the application object instance ```php Flight::start() // Starts the framework. Flight::stop() // Stops the framework and sends a response. -Flight::halt([$code], [$message]) // Stop the framework with an optional status code and message. -Flight::route($pattern, $callback) // Maps a URL pattern to a callback. -Flight::redirect($url, [$code]) // Redirects to another URL. -Flight::render($file, [$data], [$key]) // Renders a template file. -Flight::error($exception) // Sends an HTTP 500 response. +Flight::halt(int $code = 200, string $message = '') // Stop the framework with an optional status code and message. +Flight::route(string $pattern, callable $callback, bool $pass_route = false) // Maps a URL pattern to a callback. +Flight::redirect(string $url, int $code) // Redirects to another URL. +Flight::render(string $file, array $data, ?string $key = null) // Renders a template file. +Flight::error(Throwable $exception) // Sends an HTTP 500 response. Flight::notFound() // Sends an HTTP 404 response. -Flight::etag($id, [$type]) // Performs ETag HTTP caching. -Flight::lastModified($time) // Performs last modified HTTP caching. -Flight::json($data, [$code], [$encode], [$charset], [$option]) // Sends a JSON response. -Flight::jsonp($data, [$param], [$code], [$encode], [$charset], [$option]) // Sends a JSONP response. +Flight::etag(string $id, string $type = 'string') // Performs ETag HTTP caching. +Flight::lastModified(int $time) // Performs last modified HTTP caching. +Flight::json(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // Sends a JSON response. +Flight::jsonp(mixed $data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // Sends a JSONP response. ``` Any custom methods added with `map` and `register` can also be filtered. @@ -908,12 +916,10 @@ as an object instance. ```php require 'flight/autoload.php'; -use flight\Engine; +$app = new flight\Engine(); -$app = new Engine(); - -$app->route('/', function(){ - echo 'hello world!'; +$app->route('/', function () { + echo 'hello world!'; }); $app->start(); From 42ec161d8a1f3da5dc26767e24d74677d8d898cd Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Mon, 4 Dec 2023 17:38:10 -0400 Subject: [PATCH 05/29] Added code format --- .editorconfig | 12 ++++++++++++ .gitignore | 1 + composer.json | 5 ++++- flight.sublime-project | 21 +++++++++++++++++++++ flight/Engine.php | 23 ++++++++++++++--------- flight/Flight.php | 7 ++++--- flight/net/Response.php | 3 ++- flight/util/ReturnTypeWillChange.php | 3 +++ phpstan.neon | 14 +++++++------- 9 files changed, 68 insertions(+), 21 deletions(-) create mode 100644 .editorconfig create mode 100644 flight.sublime-project create mode 100644 flight/util/ReturnTypeWillChange.php diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..e8d15f4f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +indent_size = 2 diff --git a/.gitignore b/.gitignore index a5ec20f9..c92307cf 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ vendor/ composer.phar composer.lock +*.sublime-workspace diff --git a/composer.json b/composer.json index 325ba7f2..2becdbcb 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,10 @@ "ext-json": "*" }, "autoload": { - "files": [ "flight/autoload.php", "flight/Flight.php" ] + "files": [ + "flight/autoload.php", + "flight/Flight.php" + ] }, "require-dev": { "phpunit/phpunit": "^9.5", diff --git a/flight.sublime-project b/flight.sublime-project new file mode 100644 index 00000000..26552b2a --- /dev/null +++ b/flight.sublime-project @@ -0,0 +1,21 @@ +{ + "folders": [ + { + "path": ".", + } + ], + "settings": { + "LSP": { + "LSP-intelephense": { + "settings": { + "intelephense.environment.phpVersion": "7.4.0", + "intelephense.format.braces": "psr12", + }, + }, + "formatters": + { + "embedding.php": "LSP-intelephense" + }, + }, + }, +} diff --git a/flight/Engine.php b/flight/Engine.php index 34060813..66442fc8 100644 --- a/flight/Engine.php +++ b/flight/Engine.php @@ -397,9 +397,10 @@ public function _start(): void */ public function _error($e): void { - $msg = sprintf('

500 Internal Server Error

' . - '

%s (%s)

' . - '
%s
', + $msg = sprintf( + '

500 Internal Server Error

' . + '

%s (%s)

' . + '
%s
', $e->getMessage(), $e->getCode(), $e->getTraceAsString() @@ -524,8 +525,8 @@ public function _notFound(): void ->status(404) ->write( '

404 Not Found

' . - '

The page you have requested could not be found.

' . - str_repeat(' ', 512) + '

The page you have requested could not be found.

' . + str_repeat(' ', 512) ) ->send(); } @@ -644,8 +645,10 @@ public function _etag(string $id, string $type = 'strong'): void $this->response()->header('ETag', $id); - if (isset($_SERVER['HTTP_IF_NONE_MATCH']) && - $_SERVER['HTTP_IF_NONE_MATCH'] === $id) { + if ( + isset($_SERVER['HTTP_IF_NONE_MATCH']) && + $_SERVER['HTTP_IF_NONE_MATCH'] === $id + ) { $this->halt(304); } } @@ -659,8 +662,10 @@ public function _lastModified(int $time): void { $this->response()->header('Last-Modified', gmdate('D, d M Y H:i:s \G\M\T', $time)); - if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && - strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) === $time) { + if ( + isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && + strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) === $time + ) { $this->halt(304); } } diff --git a/flight/Flight.php b/flight/Flight.php index 2bca787d..e6cdcac1 100644 --- a/flight/Flight.php +++ b/flight/Flight.php @@ -23,7 +23,7 @@ * @method static void stop() Stops the framework and sends a response. * @method static void halt(int $code = 200, string $message = '') Stop the framework with an optional status code and message. * - * @method static void route(string $pattern, callable $callback) Maps a URL pattern to a callback. + * @method static void route(string $pattern, callable $callback, bool $pass_route = false) Maps a URL pattern to a callback. * @method static Router router() Returns Router instance. * * @method static void map(string $name, callable $callback) Creates a custom framework method. @@ -80,8 +80,9 @@ private function __clone() * @param ?Closure(T $instance): void $callback Perform actions with the instance * @return void */ - static function register($name, $class, $params = array(), $callback = null) { - static::__callStatic('register', func_get_args()); + static function register($name, $class, $params = array(), $callback = null) + { + static::__callStatic('register', func_get_args()); } /** diff --git a/flight/net/Response.php b/flight/net/Response.php index 5873ec7c..6b69c670 100644 --- a/flight/net/Response.php +++ b/flight/net/Response.php @@ -251,7 +251,8 @@ public function sendHeaders(): self '%s %d %s', $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.1', $this->status, - self::$codes[$this->status]), + self::$codes[$this->status] + ), true, $this->status ); diff --git a/flight/util/ReturnTypeWillChange.php b/flight/util/ReturnTypeWillChange.php new file mode 100644 index 00000000..3ed841b0 --- /dev/null +++ b/flight/util/ReturnTypeWillChange.php @@ -0,0 +1,3 @@ + Date: Mon, 4 Dec 2023 17:38:40 -0400 Subject: [PATCH 06/29] Bump version v2.0.2 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 38f77a65..e9307ca5 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.0.1 +2.0.2 From 3d58a5bee4efc3453f68d1a613975dfaf6100d81 Mon Sep 17 00:00:00 2001 From: Austin Collier Date: Wed, 27 Dec 2023 16:56:02 -0700 Subject: [PATCH 07/29] added phpunit config for testing coverage --- .gitignore | 3 +++ phpunit.xml | 22 ++++++++++++++++++++++ tests/FlightTest.php | 7 +++++++ tests/RequestTest.php | 7 +++++++ tests/RouterTest.php | 7 +++++++ 5 files changed, 46 insertions(+) create mode 100644 phpunit.xml diff --git a/.gitignore b/.gitignore index a5ec20f9..2161d402 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ vendor/ composer.phar composer.lock +.phpunit.result.cache +coverage/ +.vscode/settings.json diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 00000000..76d14634 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,22 @@ + + + + + flight/ + + + + + tests/ + + + + diff --git a/tests/FlightTest.php b/tests/FlightTest.php index 19dfdd33..d002eb6b 100644 --- a/tests/FlightTest.php +++ b/tests/FlightTest.php @@ -18,9 +18,16 @@ class FlightTest extends PHPUnit\Framework\TestCase { protected function setUp(): void { + $_SERVER = []; + $_REQUEST = []; Flight::init(); } + protected function tearDown(): void { + unset($_REQUEST); + unset($_SERVER); + } + // Checks that default components are loaded public function testDefaultComponents() { diff --git a/tests/RequestTest.php b/tests/RequestTest.php index 842c1bc8..05c8c493 100644 --- a/tests/RequestTest.php +++ b/tests/RequestTest.php @@ -17,6 +17,8 @@ class RequestTest extends PHPUnit\Framework\TestCase protected function setUp(): void { + $_SERVER = []; + $_REQUEST = []; $_SERVER['REQUEST_URI'] = '/'; $_SERVER['SCRIPT_NAME'] = '/index.php'; $_SERVER['REQUEST_METHOD'] = 'GET'; @@ -34,6 +36,11 @@ protected function setUp(): void $this->request = new Request(); } + protected function tearDown(): void { + unset($_REQUEST); + unset($_SERVER); + } + public function testDefaults() { self::assertEquals('/', $this->request->url); diff --git a/tests/RouterTest.php b/tests/RouterTest.php index 686d91f3..5b4069f0 100644 --- a/tests/RouterTest.php +++ b/tests/RouterTest.php @@ -23,11 +23,18 @@ class RouterTest extends PHPUnit\Framework\TestCase protected function setUp(): void { + $_SERVER = []; + $_REQUEST = []; $this->router = new Router(); $this->request = new Request(); $this->dispatcher = new Dispatcher(); } + protected function tearDown(): void { + unset($_REQUEST); + unset($_SERVER); + } + // Simple output public function ok() { From 57d4d20bba400537f39c850350633cf7eefccffd Mon Sep 17 00:00:00 2001 From: n0nag0n Date: Sat, 30 Dec 2023 13:05:46 -0700 Subject: [PATCH 08/29] Update composer.json --- composer.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 2becdbcb..760e656e 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "faslatam/flight", + "name": "n0nag0n/flight", "description": "Flight is a fast, simple, extensible framework for PHP. Flight enables you to quickly and easily build RESTful web applications.", "homepage": "http://flightphp.com", "license": "MIT", @@ -15,6 +15,11 @@ "email": "franyeradriansanchez@gmail.com", "homepage": "https://faslatam.000webhostapp.com", "role": "Maintainer" + }, + { + "name": "n0nag0n", + "email": "n0nag0n@sky-9.com", + "role": "Maintainer" } ], "require": { From 8b60a8a72f2fd70cf435919c13910bff87ebfb2a Mon Sep 17 00:00:00 2001 From: n0nag0n Date: Sat, 30 Dec 2023 13:17:01 -0700 Subject: [PATCH 09/29] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 469c5f70..26c444d8 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +# What the fork? +This is a fork of the original project [https://github.com/mikecao/flight](https://github.com/mikecao/flight). That project hasn't seen updates in quite some time, so this fork is to help maintain the project going forward. + # What is Flight? ![](https://user-images.githubusercontent.com/104888/50957476-9c4acb80-14be-11e9-88ce-6447364dc1bb.png) From 7bc2f738c4f53b3415b826b94ee3a2f33faa3c8f Mon Sep 17 00:00:00 2001 From: n0nag0n Date: Sat, 30 Dec 2023 13:30:05 -0700 Subject: [PATCH 10/29] Versioned to 2.0.3 --- .gitignore | 1 + VERSION | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index efa50c0d..6014b459 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ composer.lock coverage/ .vscode/settings.json *.sublime-workspace +.vscode/ \ No newline at end of file diff --git a/VERSION b/VERSION index e9307ca5..50ffc5aa 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.0.2 +2.0.3 From 3676602454bb42d227c0704925e8527137b5b520 Mon Sep 17 00:00:00 2001 From: n0nag0n Date: Sat, 30 Dec 2023 13:45:56 -0700 Subject: [PATCH 11/29] fixed references to old repo --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 26c444d8..24c1d073 100644 --- a/README.md +++ b/README.md @@ -37,10 +37,10 @@ If you're using [Composer](https://getcomposer.org/), you can run the following command: ```bash -composer require mikecao/flight +composer require n0nag0n/flight ``` -OR you can [download](https://github.com/mikecao/flight/archive/master.zip) +OR you can [download](https://github.com/n0nag0n/flight/archive/master.zip) them directly and extract them to your web directory. 2\. Configure your webserver. From 991a1705b91b05925e5503db12b9b6817ef46338 Mon Sep 17 00:00:00 2001 From: n0nag0n Date: Sat, 30 Dec 2023 22:40:52 -0700 Subject: [PATCH 12/29] added matrix link and readme updates --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 24c1d073..5376e704 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,15 @@ This is a fork of the original project [https://github.com/mikecao/flight](https Flight is a fast, simple, extensible framework for PHP. Flight enables you to quickly and easily build RESTful web applications. +Chat with us on Matrix IRC [#flight-php-framework:matrix.org](https://matrix.to/#/#flight-php-framework:matrix.org) + +# Basic Usage ```php -require 'flight/Flight.php'; + +// if installed with composer +require 'vendor/autoload.php'; +// or if installed manually by zip file +//require 'flight/Flight.php'; Flight::route('/', function() { echo 'hello world!'; From ccdca7e180d731e9013996209a4d38b8d1e8a8c1 Mon Sep 17 00:00:00 2001 From: n0nag0n Date: Sat, 30 Dec 2023 22:51:09 -0700 Subject: [PATCH 13/29] added chat badge --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5376e704..21c116d5 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ +![](https://user-images.githubusercontent.com/104888/50957476-9c4acb80-14be-11e9-88ce-6447364dc1bb.png) +![](https://img.shields.io/badge/PHPStan-level%206-brightgreen.svg?style=flat) +![](https://img.shields.io/matrix/flight-php-framework%3Amatrix.org?server_fqdn=matrix.org&style=social&logo=matrix) + # What the fork? This is a fork of the original project [https://github.com/mikecao/flight](https://github.com/mikecao/flight). That project hasn't seen updates in quite some time, so this fork is to help maintain the project going forward. # What is Flight? -![](https://user-images.githubusercontent.com/104888/50957476-9c4acb80-14be-11e9-88ce-6447364dc1bb.png) -![](https://img.shields.io/badge/PHPStan-level%206-brightgreen.svg?style=flat) - Flight is a fast, simple, extensible framework for PHP. Flight enables you to quickly and easily build RESTful web applications. From e395c7826274e6e9f970a7961cf0ead3a9dcdba9 Mon Sep 17 00:00:00 2001 From: n0nag0n Date: Sun, 31 Dec 2023 15:12:50 -0700 Subject: [PATCH 14/29] Update composer.json --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 760e656e..6dfce678 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { - "name": "n0nag0n/flight", - "description": "Flight is a fast, simple, extensible framework for PHP. Flight enables you to quickly and easily build RESTful web applications.", + "name": "flightphp/core", + "description": "Flight is a fast, simple, extensible framework for PHP. Flight enables you to quickly and easily build RESTful web applications. This is the maintained fork of mikecao/flight", "homepage": "http://flightphp.com", "license": "MIT", "authors": [ From 9faf27262c707368f3ec94af9778b05ed388716c Mon Sep 17 00:00:00 2001 From: n0nag0n Date: Sun, 31 Dec 2023 16:09:16 -0700 Subject: [PATCH 15/29] cleaned up some unit test requires --- composer.json | 14 +++++++++++--- phpunit.xml | 1 + tests/AutoloadTest.php | 3 --- tests/DispatcherTest.php | 3 --- tests/FilterTest.php | 3 --- tests/FlightTest.php | 3 --- tests/LoaderTest.php | 1 - tests/MapTest.php | 2 -- tests/RedirectTest.php | 3 --- tests/RegisterTest.php | 2 -- tests/RenderTest.php | 3 --- tests/RequestTest.php | 3 --- tests/RouterTest.php | 3 --- tests/VariableTest.php | 3 --- tests/ViewTest.php | 3 --- tests/phpunit_autoload.php | 6 ++++++ 16 files changed, 18 insertions(+), 38 deletions(-) create mode 100644 tests/phpunit_autoload.php diff --git a/composer.json b/composer.json index 760e656e..f5a61d23 100644 --- a/composer.json +++ b/composer.json @@ -43,10 +43,18 @@ } }, "scripts": { - "test": "phpunit tests", + "test": "phpunit", + "test-coverage": "XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-html=coverage", "lint": "phpstan --no-progress -cphpstan.neon" }, "suggest": { - "phpstan/phpstan": "PHP Static Analysis Tool" - } + "phpstan/phpstan": "PHP Static Analysis Tool", + "latte/latte": "Latte template engine" + }, + "suggest-dev": { + "tracy/tracy": "Tracy debugger" + }, + "replace": { + "mikecao/flight": "2.0.2" + } } diff --git a/phpunit.xml b/phpunit.xml index 76d14634..1577ac23 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,5 +1,6 @@ * @license MIT, http://flightphp.com/license */ -require_once 'vendor/autoload.php'; -require_once __DIR__ . '/../flight/Flight.php'; - class FlightTest extends PHPUnit\Framework\TestCase { protected function setUp(): void diff --git a/tests/LoaderTest.php b/tests/LoaderTest.php index 2e2de8f6..0fa96839 100644 --- a/tests/LoaderTest.php +++ b/tests/LoaderTest.php @@ -8,7 +8,6 @@ use flight\core\Loader; -require_once 'vendor/autoload.php'; require_once __DIR__ . '/classes/User.php'; require_once __DIR__ . '/classes/Factory.php'; diff --git a/tests/MapTest.php b/tests/MapTest.php index 5b0d3df7..4f4a0eb3 100644 --- a/tests/MapTest.php +++ b/tests/MapTest.php @@ -8,8 +8,6 @@ use flight\Engine; -require_once 'vendor/autoload.php'; -require_once __DIR__ . '/../flight/autoload.php'; require_once __DIR__ . '/classes/Hello.php'; class MapTest extends PHPUnit\Framework\TestCase diff --git a/tests/RedirectTest.php b/tests/RedirectTest.php index 0f8079ac..9099fe42 100644 --- a/tests/RedirectTest.php +++ b/tests/RedirectTest.php @@ -8,9 +8,6 @@ use flight\Engine; -require_once 'vendor/autoload.php'; -require_once __DIR__ . '/../flight/autoload.php'; - class RedirectTest extends PHPUnit\Framework\TestCase { private Engine $app; diff --git a/tests/RegisterTest.php b/tests/RegisterTest.php index dc383cd7..bd671065 100644 --- a/tests/RegisterTest.php +++ b/tests/RegisterTest.php @@ -8,8 +8,6 @@ use flight\Engine; -require_once 'vendor/autoload.php'; -require_once __DIR__ . '/../flight/autoload.php'; require_once __DIR__ . '/classes/User.php'; class RegisterTest extends PHPUnit\Framework\TestCase diff --git a/tests/RenderTest.php b/tests/RenderTest.php index 260caa6a..dccdc690 100644 --- a/tests/RenderTest.php +++ b/tests/RenderTest.php @@ -8,9 +8,6 @@ use flight\Engine; -require_once 'vendor/autoload.php'; -require_once __DIR__ . '/../flight/Flight.php'; - class RenderTest extends PHPUnit\Framework\TestCase { private Engine $app; diff --git a/tests/RequestTest.php b/tests/RequestTest.php index 05c8c493..dc7c5cf2 100644 --- a/tests/RequestTest.php +++ b/tests/RequestTest.php @@ -8,9 +8,6 @@ use flight\net\Request; -require_once 'vendor/autoload.php'; -require_once __DIR__ . '/../flight/autoload.php'; - class RequestTest extends PHPUnit\Framework\TestCase { private Request $request; diff --git a/tests/RouterTest.php b/tests/RouterTest.php index 5b4069f0..db02757c 100644 --- a/tests/RouterTest.php +++ b/tests/RouterTest.php @@ -10,9 +10,6 @@ use flight\net\Request; use flight\net\Router; -require_once 'vendor/autoload.php'; -require_once __DIR__ . '/../flight/autoload.php'; - class RouterTest extends PHPUnit\Framework\TestCase { private Router $router; diff --git a/tests/VariableTest.php b/tests/VariableTest.php index e7a610d0..03f29008 100644 --- a/tests/VariableTest.php +++ b/tests/VariableTest.php @@ -5,9 +5,6 @@ * @copyright Copyright (c) 2012, Mike Cao * @license MIT, http://flightphp.com/license */ -require_once 'vendor/autoload.php'; -require_once __DIR__ . '/../flight/autoload.php'; - class VariableTest extends PHPUnit\Framework\TestCase { /** diff --git a/tests/ViewTest.php b/tests/ViewTest.php index bdfcfebc..485a5bd1 100644 --- a/tests/ViewTest.php +++ b/tests/ViewTest.php @@ -5,9 +5,6 @@ * @copyright Copyright (c) 2012, Mike Cao * @license MIT, http://flightphp.com/license */ -require_once 'vendor/autoload.php'; -require_once __DIR__ . '/../flight/autoload.php'; - class ViewTest extends PHPUnit\Framework\TestCase { /** diff --git a/tests/phpunit_autoload.php b/tests/phpunit_autoload.php new file mode 100644 index 00000000..7b9b7620 --- /dev/null +++ b/tests/phpunit_autoload.php @@ -0,0 +1,6 @@ + Date: Sun, 31 Dec 2023 18:21:42 -0700 Subject: [PATCH 16/29] added test for unique subdir setup in request --- flight/net/Request.php | 3 +++ tests/RequestTest.php | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/flight/net/Request.php b/flight/net/Request.php index 8f8ecf8f..aceda0b4 100644 --- a/flight/net/Request.php +++ b/flight/net/Request.php @@ -177,6 +177,9 @@ public function init(array $properties = []) } // Get the requested URL without the base directory + // 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)) { $this->url = substr($this->url, \strlen($this->base)); } diff --git a/tests/RequestTest.php b/tests/RequestTest.php index dc7c5cf2..a2e4eec0 100644 --- a/tests/RequestTest.php +++ b/tests/RequestTest.php @@ -7,6 +7,7 @@ */ use flight\net\Request; +use flight\util\Collection; class RequestTest extends PHPUnit\Framework\TestCase { @@ -154,4 +155,14 @@ public function testHttps() $request = new Request(); self::assertEquals('http', $request->scheme); } + + public function testInitUrlSameAsBaseDirectory() { + $request = new Request([ + 'url' => '/vagrant/public/flightphp', + 'base' => '/vagrant/public', + 'query' => new Collection(), + 'type' => '' + ]); + $this->assertEquals('/flightphp', $request->url); + } } From a416bfe19b3b903f5036dbbd299f1b9b59b46b5f Mon Sep 17 00:00:00 2001 From: n0nag0n Date: Mon, 1 Jan 2024 10:22:54 -0700 Subject: [PATCH 17/29] 100% coverage for Request and Route --- flight/net/Request.php | 25 ++++++++++++++++++++----- tests/RequestTest.php | 28 ++++++++++++++++++++++++++++ tests/RouterTest.php | 10 ++++++++++ 3 files changed, 58 insertions(+), 5 deletions(-) diff --git a/flight/net/Request.php b/flight/net/Request.php index aceda0b4..8a40a504 100644 --- a/flight/net/Request.php +++ b/flight/net/Request.php @@ -129,10 +129,23 @@ final class Request */ public string $host; + /** + * Stream path for where to pull the request body from + * + * @var string + */ + private string $stream_path = 'php://input'; + + /** + * @var string Raw HTTP request body + */ + public string $body = ''; + /** * Constructor. * * @param array $config Request configuration + * @param string */ public function __construct($config = array()) { @@ -196,7 +209,7 @@ public function init(array $properties = []) // Check for JSON input if (0 === strpos($this->type, 'application/json')) { - $body = self::getBody(); + $body = $this->getBody(); if ('' !== $body && null !== $body) { $data = json_decode($body, true); if (is_array($data)) { @@ -213,20 +226,22 @@ public function init(array $properties = []) * * @return string Raw HTTP request body */ - public static function getBody(): ?string + public function getBody(): ?string { - static $body; + $body = $this->body; - if (null !== $body) { + if ('' !== $body) { return $body; } $method = self::getMethod(); if ('POST' === $method || 'PUT' === $method || 'DELETE' === $method || 'PATCH' === $method) { - $body = file_get_contents('php://input'); + $body = file_get_contents($this->stream_path); } + $this->body = $body; + return $body; } diff --git a/tests/RequestTest.php b/tests/RequestTest.php index a2e4eec0..aea65eed 100644 --- a/tests/RequestTest.php +++ b/tests/RequestTest.php @@ -165,4 +165,32 @@ public function testInitUrlSameAsBaseDirectory() { ]); $this->assertEquals('/flightphp', $request->url); } + + public function testInitNoUrl() { + $request = new Request([ + 'url' => '', + 'base' => '/vagrant/public', + 'type' => '' + ]); + $this->assertEquals('/', $request->url); + } + + public function testInitWithJsonBody() { + // create dummy file to pull request body from + $tmpfile = tmpfile(); + $stream_path = stream_get_meta_data($tmpfile)['uri']; + file_put_contents($stream_path, '{"foo":"bar"}'); + $_SERVER['REQUEST_METHOD'] = 'POST'; + $request = new Request([ + 'url' => '/something/fancy', + 'base' => '/vagrant/public', + 'type' => 'application/json', + 'length' => 13, + 'data' => new Collection(), + 'query' => new Collection(), + 'stream_path' => $stream_path + ]); + $this->assertEquals([ 'foo' => 'bar' ], $request->data->getData()); + $this->assertEquals('{"foo":"bar"}', $request->getBody()); + } } diff --git a/tests/RouterTest.php b/tests/RouterTest.php index db02757c..b6b8a6a4 100644 --- a/tests/RouterTest.php +++ b/tests/RouterTest.php @@ -105,6 +105,16 @@ public function testPathRoute() $this->check('OK'); } + // Simple path with trailing slash + // Simple path + public function testPathRouteTrailingSlash() + { + $this->router->map('/path/', [$this, 'ok']); + $this->request->url = '/path'; + + $this->check('OK'); + } + // POST route public function testPostRoute() { From 39f61b3d7474b8fffcfc71a8be5c128b9bcab513 Mon Sep 17 00:00:00 2001 From: n0nag0n Date: Mon, 1 Jan 2024 10:29:11 -0700 Subject: [PATCH 18/29] Router with 100% coverage --- tests/RouterTest.php | 45 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/RouterTest.php b/tests/RouterTest.php index b6b8a6a4..9adc7fc5 100644 --- a/tests/RouterTest.php +++ b/tests/RouterTest.php @@ -307,4 +307,49 @@ public function testRegExParametersCyrillic() $this->check('цветя'); } + + public function testGetAndClearRoutes() { + $this->router->map('/path1', [$this, 'ok']); + $this->router->map('/path2', [$this, 'ok']); + $this->router->map('/path3', [$this, 'ok']); + $this->router->map('/path4', [$this, 'ok']); + $this->router->map('/path5', [$this, 'ok']); + $this->router->map('/path6', [$this, 'ok']); + $this->router->map('/path7', [$this, 'ok']); + $this->router->map('/path8', [$this, 'ok']); + $this->router->map('/path9', [$this, 'ok']); + + $routes = $this->router->getRoutes(); + $this->assertEquals(9, count($routes)); + + $this->router->clear(); + + $this->assertEquals(0, count($this->router->getRoutes())); + } + + public function testResetRoutes() { + $router = new class extends Router { + public function getIndex() { + return $this->index; + } + }; + + $router->map('/path1', [$this, 'ok']); + $router->map('/path2', [$this, 'ok']); + $router->map('/path3', [$this, 'ok']); + $router->map('/path4', [$this, 'ok']); + $router->map('/path5', [$this, 'ok']); + $router->map('/path6', [$this, 'ok']); + $router->map('/path7', [$this, 'ok']); + $router->map('/path8', [$this, 'ok']); + $router->map('/path9', [$this, 'ok']); + + $router->next(); + $router->next(); + $router->next(); + + $this->assertEquals(3, $router->getIndex()); + $router->reset(); + $this->assertEquals(0, $router->getIndex()); + } } From 7b15d2cfca65c0e35b8f2387b5169031285791fe Mon Sep 17 00:00:00 2001 From: n0nag0n Date: Mon, 1 Jan 2024 16:52:23 -0700 Subject: [PATCH 19/29] 100% coverage for net classes --- flight/net/Response.php | 34 ++++-- tests/ResponseTest.php | 238 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 263 insertions(+), 9 deletions(-) create mode 100644 tests/ResponseTest.php diff --git a/flight/net/Response.php b/flight/net/Response.php index 6b69c670..38d06eca 100644 --- a/flight/net/Response.php +++ b/flight/net/Response.php @@ -202,7 +202,7 @@ public function clear(): self /** * Sets caching headers for the response. * - * @param int|string|false $expires Expiration time + * @param int|string|false $expires Expiration time as time() or as strtotime() string value * * @return Response Self reference */ @@ -237,7 +237,8 @@ public function sendHeaders(): self { // Send status code header if (false !== strpos(\PHP_SAPI, 'cgi')) { - header( + // @codeCoverageIgnoreStart + $this->setRealHeader( sprintf( 'Status: %d %s', $this->status, @@ -245,8 +246,9 @@ public function sendHeaders(): self ), true ); + // @codeCoverageIgnoreEnd } else { - header( + $this->setRealHeader( sprintf( '%s %d %s', $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.1', @@ -262,10 +264,10 @@ public function sendHeaders(): self foreach ($this->headers as $field => $value) { if (\is_array($value)) { foreach ($value as $v) { - header($field . ': ' . $v, false); + $this->setRealHeader($field . ': ' . $v, false); } } else { - header($field . ': ' . $value); + $this->setRealHeader($field . ': ' . $value); } } @@ -274,13 +276,27 @@ public function sendHeaders(): self $length = $this->getContentLength(); if ($length > 0) { - header('Content-Length: ' . $length); + $this->setRealHeader('Content-Length: ' . $length); } } return $this; } + /** + * Sets a real header. Mostly used for test mocking. + * + * @param string $header_string The header string you would pass to header() + * @param bool $replace The optional replace parameter indicates whether the header should replace a previous similar header, or add a second header of the same type. By default it will replace, but if you pass in false as the second argument you can force multiple headers of the same type. + * @param int $response_code The response code to send + * @return self + * @codeCoverageIgnore + */ + public function setRealHeader(string $header_string, bool $replace = true, int $response_code = 0): self { + header($header_string, $replace, $response_code); + return $this; + } + /** * Gets the content length. * @@ -294,7 +310,7 @@ public function getContentLength(): int } /** - * Gets whether response was sent. + * Gets whether response body was sent. */ public function sent(): bool { @@ -307,11 +323,11 @@ public function sent(): bool public function send(): void { if (ob_get_length() > 0) { - ob_end_clean(); + ob_end_clean(); // @codeCoverageIgnore } if (!headers_sent()) { - $this->sendHeaders(); + $this->sendHeaders(); // @codeCoverageIgnore } echo $this->body; diff --git a/tests/ResponseTest.php b/tests/ResponseTest.php new file mode 100644 index 00000000..cd3e582f --- /dev/null +++ b/tests/ResponseTest.php @@ -0,0 +1,238 @@ + + * @license MIT, http://flightphp.com/license + */ + +use flight\net\Request; +use flight\net\Response; +use flight\util\Collection; + +class ResponseTest extends PHPUnit\Framework\TestCase +{ + + protected function setUp(): void + { + $_SERVER = []; + $_REQUEST = []; + // $_SERVER['REQUEST_URI'] = '/'; + // $_SERVER['SCRIPT_NAME'] = '/index.php'; + // $_SERVER['REQUEST_METHOD'] = 'GET'; + // $_SERVER['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'; + // $_SERVER['REMOTE_ADDR'] = '8.8.8.8'; + // $_SERVER['HTTP_X_FORWARDED_FOR'] = '32.32.32.32'; + // $_SERVER['HTTP_HOST'] = 'example.com'; + // $_SERVER['CONTENT_TYPE'] = ''; + + $_GET = []; + $_POST = []; + $_COOKIE = []; + $_FILES = []; + + // $this->request = new Request(); + } + + protected function tearDown(): void { + unset($_REQUEST); + unset($_SERVER); + } + + public function testStatusDefault() { + $response = new Response(); + $this->assertSame(200, $response->status()); + } + + public function testStatusValidCode() { + $response = new Response(); + $response->status(200); + $this->assertEquals(200, $response->status()); + } + + public function testStatusInvalidCode() { + $response = new Response(); + $this->expectException(Exception::class); + $this->expectExceptionMessage('Invalid status code.'); + $response->status(999); + } + + public function testStatusReturnObject() { + $response = new Response(); + $this->assertEquals($response, $response->status(200)); + } + + public function testHeaderSingle() { + $response = new Response(); + $response->header('Content-Type', 'text/html'); + $this->assertEquals(['Content-Type' => 'text/html'], $response->headers()); + } + + public function testHeaderSingleKeepCaseSensitive() { + $response = new Response(); + $response->header('content-type', 'text/html'); + $response->header('x-test', 'test'); + $this->assertEquals(['content-type' => 'text/html', 'x-test' => 'test'], $response->headers()); + } + + public function testHeaderArray() { + $response = new Response(); + $response->header(['Content-Type' => 'text/html', 'X-Test' => 'test']); + $this->assertEquals(['Content-Type' => 'text/html', 'X-Test' => 'test'], $response->headers()); + } + + public function testHeaderReturnObject() { + $response = new Response(); + $this->assertEquals($response, $response->header('Content-Type', 'text/html')); + } + + public function testWrite() { + $response = new class extends Response { + public function getBody() { + return $this->body; + } + }; + $response->write('test'); + $this->assertEquals('test', $response->getBody()); + } + + public function testWriteEmptyString() { + $response = new class extends Response { + public function getBody() { + return $this->body; + } + }; + $response->write(''); + $this->assertEquals('', $response->getBody()); + } + + public function testWriteReturnObject() { + $response = new Response(); + $this->assertEquals($response, $response->write('test')); + } + + public function testClear() { + $response = new class extends Response { + public function getBody() { + return $this->body; + } + }; + $response->write('test'); + $response->status(404); + $response->header('Content-Type', 'text/html'); + $response->clear(); + $this->assertEquals('', $response->getBody()); + $this->assertEquals(200, $response->status()); + $this->assertEquals([], $response->headers()); + } + + public function testCacheSimple() { + $response = new Response(); + $cache_time = time() + 60; + $response->cache($cache_time); + $this->assertEquals([ + 'Expires' => gmdate('D, d M Y H:i:s', $cache_time) . ' GMT', + 'Cache-Control' => 'max-age=60' + ], $response->headers()); + } + + public function testCacheSimpleWithString() { + $response = new Response(); + $cache_time = time() + 60; + $response->cache('now +60 seconds'); + $this->assertEquals([ + 'Expires' => gmdate('D, d M Y H:i:s', $cache_time) . ' GMT', + 'Cache-Control' => 'max-age=60' + ], $response->headers()); + } + + public function testCacheSimpleWithPragma() { + $response = new Response(); + $cache_time = time() + 60; + $response->header('Pragma', 'no-cache'); + $response->cache($cache_time); + $this->assertEquals([ + 'Expires' => gmdate('D, d M Y H:i:s', $cache_time) . ' GMT', + 'Cache-Control' => 'max-age=60' + ], $response->headers()); + } + + public function testCacheFalseExpiresValue() { + $response = new Response(); + $response->cache(false); + $this->assertEquals([ + 'Expires' => 'Mon, 26 Jul 1997 05:00:00 GMT', + 'Cache-Control' => [ + 'no-store, no-cache, must-revalidate', + 'post-check=0, pre-check=0', + 'max-age=0', + ], + 'Pragma' => 'no-cache' + ], $response->headers()); + } + + public function testSendHeadersRegular() { + $response = new class extends Response { + protected $test_sent_headers = []; + + protected array $headers = [ + 'Cache-Control' => [ + 'no-store, no-cache, must-revalidate', + 'post-check=0, pre-check=0', + 'max-age=0', + ] + ]; + public function setRealHeader(string $header_string, bool $replace = true, int $response_code = 0): Response + { + $this->test_sent_headers[] = $header_string; + return $this; + } + + public function getSentHeaders(): array + { + return $this->test_sent_headers; + } + }; + $response->header('Content-Type', 'text/html'); + $response->header('X-Test', 'test'); + $response->write('Something'); + + $response->sendHeaders(); + $sent_headers = $response->getSentHeaders(); + $this->assertEquals([ + 'HTTP/1.1 200 OK', + 'Cache-Control: no-store, no-cache, must-revalidate', + 'Cache-Control: post-check=0, pre-check=0', + 'Cache-Control: max-age=0', + 'Content-Type: text/html', + 'X-Test: test', + 'Content-Length: 9' + ], $sent_headers); + } + + public function testSentDefault() { + $response = new Response(); + $this->assertFalse($response->sent()); + } + + public function testSentTrue() { + $response = new class extends Response { + protected $test_sent_headers = []; + + public function setRealHeader(string $header_string, bool $replace = true, int $response_code = 0): Response + { + $this->test_sent_headers[] = $header_string; + return $this; + } + }; + $response->header('Content-Type', 'text/html'); + $response->header('X-Test', 'test'); + $response->write('Something'); + + $this->expectOutputString('Something'); + $response->send(); + $this->assertTrue($response->sent()); + } + + +} From 978a05d765bce0d27052ccfb58c22fdc7d7b7c57 Mon Sep 17 00:00:00 2001 From: n0nag0n Date: Mon, 1 Jan 2024 16:54:20 -0700 Subject: [PATCH 20/29] Flight class 100% coverage (too easy!) --- flight/Flight.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/flight/Flight.php b/flight/Flight.php index e6cdcac1..69a8b652 100644 --- a/flight/Flight.php +++ b/flight/Flight.php @@ -57,11 +57,22 @@ class Flight */ private static Engine $engine; - // Don't allow object instantiation + /** + * Don't allow object instantiation + * + * @codeCoverageIgnore + * @return void + */ private function __construct() { } + /** + * Forbid cloning the class + * + * @codeCoverageIgnore + * @return void + */ private function __clone() { } From e36e9024c69b07bda9e927f5756aaa30eaadb442 Mon Sep 17 00:00:00 2001 From: n0nag0n Date: Mon, 1 Jan 2024 17:08:32 -0700 Subject: [PATCH 21/29] 100% Coverage Dispatcher Class --- flight/core/Dispatcher.php | 3 ++ tests/DispatcherTest.php | 82 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/flight/core/Dispatcher.php b/flight/core/Dispatcher.php index 0f810bdb..4ce68540 100644 --- a/flight/core/Dispatcher.php +++ b/flight/core/Dispatcher.php @@ -232,6 +232,8 @@ public static function invokeMethod($func, array &$params = []) return ($instance) ? $class->$method($params[0], $params[1], $params[2]) : $class::$method($params[0], $params[1], $params[2]); + // This will be refactored soon enough + // @codeCoverageIgnoreStart case 4: return ($instance) ? $class->$method($params[0], $params[1], $params[2], $params[3]) : @@ -242,6 +244,7 @@ public static function invokeMethod($func, array &$params = []) $class::$method($params[0], $params[1], $params[2], $params[3], $params[4]); default: return \call_user_func_array($func, $params); + // @codeCoverageIgnoreEnd } } diff --git a/tests/DispatcherTest.php b/tests/DispatcherTest.php index 97fb3918..0fc5d470 100644 --- a/tests/DispatcherTest.php +++ b/tests/DispatcherTest.php @@ -44,6 +44,61 @@ public function testFunctionMapping() self::assertEquals('hello', $result); } + public function testHasEvent() + { + $this->dispatcher->set('map-event', function () { + return 'hello'; + }); + + $result = $this->dispatcher->has('map-event'); + + $this->assertTrue($result); + } + + public function testClearAllRegisteredEvents() { + $this->dispatcher->set('map-event', function () { + return 'hello'; + }); + + $this->dispatcher->set('map-event-2', function () { + return 'there'; + }); + + $this->dispatcher->clear(); + + $result = $this->dispatcher->has('map-event'); + $this->assertFalse($result); + $result = $this->dispatcher->has('map-event-2'); + $this->assertFalse($result); + } + + public function testClearDeclaredRegisteredEvent() { + $this->dispatcher->set('map-event', function () { + return 'hello'; + }); + + $this->dispatcher->set('map-event-2', function () { + return 'there'; + }); + + $this->dispatcher->clear('map-event'); + + $result = $this->dispatcher->has('map-event'); + $this->assertFalse($result); + $result = $this->dispatcher->has('map-event-2'); + $this->assertTrue($result); + } + + // Map a static function + public function testStaticFunctionMapping() + { + $this->dispatcher->set('map2', 'Hello::sayHi'); + + $result = $this->dispatcher->run('map2'); + + self::assertEquals('hello', $result); + } + // Map a class method public function testClassMethodMapping() { @@ -95,4 +150,31 @@ public function testInvalidCallback() $this->dispatcher->execute(['NonExistentClass', 'nonExistentMethod']); } + + public function testCallFunction4Params() { + $closure = function($param1, $params2, $params3, $param4) { + return 'hello'.$param1.$params2.$params3.$param4; + }; + $params = ['param1', 'param2', 'param3', 'param4']; + $result = $this->dispatcher->callFunction($closure, $params); + $this->assertEquals('helloparam1param2param3param4', $result); + } + + public function testCallFunction5Params() { + $closure = function($param1, $params2, $params3, $param4, $param5) { + return 'hello'.$param1.$params2.$params3.$param4.$param5; + }; + $params = ['param1', 'param2', 'param3', 'param4', 'param5']; + $result = $this->dispatcher->callFunction($closure, $params); + $this->assertEquals('helloparam1param2param3param4param5', $result); + } + + public function testCallFunction6Params() { + $closure = function($param1, $params2, $params3, $param4, $param5, $param6) { + return 'hello'.$param1.$params2.$params3.$param4.$param5.$param6; + }; + $params = ['param1', 'param2', 'param3', 'param4', 'param5', 'param6']; + $result = $this->dispatcher->callFunction($closure, $params); + $this->assertEquals('helloparam1param2param3param4param5param6', $result); + } } From 9a007c521618387ed5203c0aac04f368ac6b774a Mon Sep 17 00:00:00 2001 From: n0nag0n Date: Mon, 1 Jan 2024 17:54:47 -0700 Subject: [PATCH 22/29] 100% Coverage for core Classes --- flight/core/Loader.php | 4 +++- tests/LoaderTest.php | 39 +++++++++++++++++++++++++++++++++++ tests/classes/TesterClass.php | 17 +++++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 tests/classes/TesterClass.php diff --git a/flight/core/Loader.php b/flight/core/Loader.php index 5ff66fe3..c4384453 100644 --- a/flight/core/Loader.php +++ b/flight/core/Loader.php @@ -141,6 +141,7 @@ public function newInstance($class, array $params = []) return new $class(); case 1: return new $class($params[0]); + // @codeCoverageIgnoreStart case 2: return new $class($params[0], $params[1]); case 3: @@ -149,6 +150,7 @@ public function newInstance($class, array $params = []) return new $class($params[0], $params[1], $params[2], $params[3]); case 5: return new $class($params[0], $params[1], $params[2], $params[3], $params[4]); + // @codeCoverageIgnoreEnd default: try { $refClass = new ReflectionClass($class); @@ -192,7 +194,7 @@ public static function autoload(bool $enabled = true, $dirs = []): void if ($enabled) { spl_autoload_register([__CLASS__, 'loadClass']); } else { - spl_autoload_unregister([__CLASS__, 'loadClass']); + spl_autoload_unregister([__CLASS__, 'loadClass']); // @codeCoverageIgnore } if (!empty($dirs)) { diff --git a/tests/LoaderTest.php b/tests/LoaderTest.php index 0fa96839..ecadd0a5 100644 --- a/tests/LoaderTest.php +++ b/tests/LoaderTest.php @@ -10,6 +10,7 @@ require_once __DIR__ . '/classes/User.php'; require_once __DIR__ . '/classes/Factory.php'; +require_once __DIR__ . '/classes/TesterClass.php'; class LoaderTest extends PHPUnit\Framework\TestCase { @@ -117,4 +118,42 @@ public function testRegisterUsingCallback() self::assertIsObject($obj); self::assertInstanceOf(Factory::class, $obj); } + + public function testUnregisterClass() { + $this->loader->register('g', 'User'); + $current_class = $this->loader->get('g'); + $this->assertEquals([ 'User', [], null ], $current_class); + $this->loader->unregister('g'); + $unregistered_class_result = $this->loader->get('g'); + $this->assertNull($unregistered_class_result); + } + + public function testNewInstance6Params() { + $TesterClass = $this->loader->newInstance('TesterClass', ['Bob','Fred', 'Joe', 'Jane', 'Sally', 'Suzie']); + $this->assertEquals('Bob', $TesterClass->param1); + $this->assertEquals('Fred', $TesterClass->param2); + $this->assertEquals('Joe', $TesterClass->param3); + $this->assertEquals('Jane', $TesterClass->param4); + $this->assertEquals('Sally', $TesterClass->param5); + $this->assertEquals('Suzie', $TesterClass->param6); + } + + public function testNewInstance6ParamsBadClass() { + $this->expectException(Exception::class); + $this->expectExceptionMessage('Cannot instantiate BadClass'); + $TesterClass = $this->loader->newInstance('BadClass', ['Bob','Fred', 'Joe', 'Jane', 'Sally', 'Suzie']); + } + + public function testAddDirectoryAsArray() { + $loader = new class extends Loader { + public function getDirectories() { + return self::$dirs; + } + }; + $loader->addDirectory([__DIR__ . '/classes']); + self::assertEquals([ + dirname(__DIR__), + __DIR__ . '/classes' + ], $loader->getDirectories()); + } } diff --git a/tests/classes/TesterClass.php b/tests/classes/TesterClass.php new file mode 100644 index 00000000..1b063fda --- /dev/null +++ b/tests/classes/TesterClass.php @@ -0,0 +1,17 @@ +param1 = $param1; + $this->param2 = $param2; + $this->param3 = $param3; + $this->param4 = $param4; + $this->param5 = $param5; + $this->param6 = $param6; + } +}; \ No newline at end of file From d075fc8be661373f118830e2172c764c307c01b7 Mon Sep 17 00:00:00 2001 From: n0nag0n Date: Mon, 1 Jan 2024 18:05:34 -0700 Subject: [PATCH 23/29] 100% View class coverage --- flight/template/View.php | 5 +++-- tests/ViewTest.php | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/flight/template/View.php b/flight/template/View.php index f4d866cc..6796577b 100644 --- a/flight/template/View.php +++ b/flight/template/View.php @@ -202,7 +202,8 @@ public function getTemplate($file) */ public function e($str) { - echo htmlentities($str); - return htmlentities($str); + $value = htmlentities($str); + echo $value; + return $value; } } diff --git a/tests/ViewTest.php b/tests/ViewTest.php index 485a5bd1..c952b506 100644 --- a/tests/ViewTest.php +++ b/tests/ViewTest.php @@ -33,6 +33,21 @@ public function testVariables() $this->assertNull($this->view->get('test')); } + public function testMultipleVariables() { + $this->view->set([ + 'test' => 123, + 'foo' => 'bar' + ]); + + $this->assertEquals(123, $this->view->get('test')); + $this->assertEquals('bar', $this->view->get('foo')); + + $this->view->clear(); + + $this->assertNull($this->view->get('test')); + $this->assertNull($this->view->get('foo')); + } + // Check if template files exist public function testTemplateExists() { @@ -48,6 +63,13 @@ public function testRender() $this->expectOutputString('Hello, Bob!'); } + public function testRenderBadFilePath() { + $this->expectException(Exception::class); + $this->expectExceptionMessage('Template file not found: '.__DIR__ . '/views/badfile.php'); + + $this->view->render('badfile'); + } + // Fetch template output public function testFetch() { @@ -76,4 +98,22 @@ public function testTemplateWithCustomExtension() $this->expectOutputString('Hello world, Bob!'); } + + public function testGetTemplateAbsolutePath() { + $tmpfile = tmpfile(); + $file_path = stream_get_meta_data($tmpfile)['uri'].'.php'; + $this->assertEquals($file_path, $this->view->getTemplate($file_path)); + } + + public function testE() { + $this->expectOutputString('<script>'); + $result = $this->view->e('