From 04e471bf462018a5f3d65c08209f08c5b55478e7 Mon Sep 17 00:00:00 2001 From: Masroor Ehsan Date: Sat, 8 May 2021 14:26:42 +0200 Subject: [PATCH 01/13] PHP ^7.4|^8.0 compatibility --- composer.json | 3 +- flight/Engine.php | 336 ++++++++++++++----------- flight/Flight.php | 63 +++-- flight/autoload.php | 8 +- flight/core/Dispatcher.php | 135 +++++----- flight/core/Loader.php | 138 +++++----- flight/net/Request.php | 133 +++++----- flight/net/Response.php | 153 +++++------ flight/net/Route.php | 84 ++++--- flight/net/Router.php | 75 +++--- flight/template/View.php | 80 +++--- flight/util/Collection.php | 119 +++++---- flight/util/LegacyJsonSerializable.php | 8 +- 13 files changed, 761 insertions(+), 574 deletions(-) diff --git a/composer.json b/composer.json index 89456e7..6999b21 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,8 @@ } ], "require": { - "php": ">=5.3.0" + "php": "^7.4|^8.0", + "ext-json": "*" }, "autoload": { "files": [ "flight/autoload.php", "flight/Flight.php" ] diff --git a/flight/Engine.php b/flight/Engine.php index 784d431..5ad9238 100644 --- a/flight/Engine.php +++ b/flight/Engine.php @@ -1,4 +1,6 @@ vars = array(); + public function __construct() + { + $this->vars = []; $this->loader = new Loader(); $this->dispatcher = new Dispatcher(); @@ -80,38 +83,42 @@ class Engine { /** * Handles calls to class methods. * - * @param string $name Method name - * @param array $params Method parameters + * @param string $name Method name + * @param array $params Method parameters + * + *@throws Exception + * * @return mixed Callback results - * @throws \Exception */ - public function __call($name, $params) { + public function __call(string $name, array $params) + { $callback = $this->dispatcher->get($name); - if (is_callable($callback)) { + if (\is_callable($callback)) { return $this->dispatcher->run($name, $params); } if (!$this->loader->get($name)) { - throw new \Exception("{$name} must be a mapped method."); + throw new Exception("{$name} must be a mapped method."); } - $shared = (!empty($params)) ? (bool)$params[0] : true; + $shared = (!empty($params)) ? (bool) $params[0] : true; return $this->loader->load($name, $shared); } - /*** Core Methods ***/ + // Core Methods /** * Initializes the framework. */ - public function init() { + public function init(): void + { static $initialized = false; $self = $this; if ($initialized) { - $this->vars = array(); + $this->vars = []; $this->loader->reset(); $this->dispatcher->reset(); } @@ -120,18 +127,18 @@ class Engine { $this->loader->register('request', '\flight\net\Request'); $this->loader->register('response', '\flight\net\Response'); $this->loader->register('router', '\flight\net\Router'); - $this->loader->register('view', '\flight\template\View', array(), function($view) use ($self) { + $this->loader->register('view', '\flight\template\View', [], function ($view) use ($self) { $view->path = $self->get('flight.views.path'); $view->extension = $self->get('flight.views.extension'); }); // Register framework methods - $methods = array( - 'start','stop','route','halt','error','notFound', - 'render','redirect','etag','lastModified','json','jsonp' - ); + $methods = [ + 'start', 'stop', 'route', 'halt', 'error', 'notFound', + 'render', 'redirect', 'etag', 'lastModified', 'json', 'jsonp', + ]; foreach ($methods as $name) { - $this->dispatcher->set($name, array($this, '_'.$name)); + $this->dispatcher->set($name, [$this, '_' . $name]); } // Default configuration settings @@ -144,11 +151,11 @@ class Engine { $this->set('flight.content_length', true); // Startup configuration - $this->before('start', function() use ($self) { + $this->before('start', function () use ($self) { // Enable error handling if ($self->get('flight.handle_errors')) { - set_error_handler(array($self, 'handleError')); - set_exception_handler(array($self, 'handleException')); + set_error_handler([$self, 'handleError']); + set_exception_handler([$self, 'handleException']); } // Set case-sensitivity @@ -163,24 +170,27 @@ class Engine { /** * Custom error handler. Converts errors into exceptions. * - * @param int $errno Error number - * @param int $errstr Error string + * @param int $errno Error number + * @param int $errstr Error string * @param int $errfile Error file name * @param int $errline Error file line number - * @throws \ErrorException + * + * @throws ErrorException */ - public function handleError($errno, $errstr, $errfile, $errline) { + public function handleError(int $errno, int $errstr, int $errfile, int $errline) + { if ($errno & error_reporting()) { - throw new \ErrorException($errstr, $errno, 0, $errfile, $errline); + throw new ErrorException($errstr, $errno, 0, $errfile, $errline); } } /** * Custom exception handler. Logs exceptions. * - * @param \Exception $e Thrown exception + * @param Exception $e Thrown exception */ - public function handleException($e) { + public function handleException($e): void + { if ($this->get('flight.log_errors')) { error_log($e->getMessage()); } @@ -191,13 +201,15 @@ class Engine { /** * Maps a callback to a framework method. * - * @param string $name Method name + * @param string $name Method name * @param callback $callback Callback function - * @throws \Exception If trying to map over a framework method + * + * @throws Exception If trying to map over a framework method */ - public function map($name, $callback) { + public function map(string $name, callable $callback): void + { if (method_exists($this, $name)) { - throw new \Exception('Cannot override an existing framework method.'); + throw new Exception('Cannot override an existing framework method.'); } $this->dispatcher->set($name, $callback); @@ -206,15 +218,17 @@ class Engine { /** * Registers a class to a framework method. * - * @param string $name Method name - * @param string $class Class name - * @param array $params Class initialization parameters - * @param callback $callback Function to call after object instantiation - * @throws \Exception If trying to map over a framework method + * @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 + * + * @throws Exception If trying to map over a framework method */ - public function register($name, $class, array $params = array(), $callback = null) { + public function register(string $name, string $class, array $params = [], ?callable $callback = null): void + { if (method_exists($this, $name)) { - throw new \Exception('Cannot override an existing framework method.'); + throw new Exception('Cannot override an existing framework method.'); } $this->loader->register($name, $class, $params, $callback); @@ -223,48 +237,54 @@ class Engine { /** * Adds a pre-filter to a method. * - * @param string $name Method name + * @param string $name Method name * @param callback $callback Callback function */ - public function before($name, $callback) { + public function before(string $name, callable $callback): void + { $this->dispatcher->hook($name, 'before', $callback); } /** * Adds a post-filter to a method. * - * @param string $name Method name + * @param string $name Method name * @param callback $callback Callback function */ - public function after($name, $callback) { + public function after(string $name, callable $callback): void + { $this->dispatcher->hook($name, 'after', $callback); } /** * Gets a variable. * - * @param string $key Key - * @return mixed + * @param string|null $key Key + * + * @return array|mixed|null */ - public function get($key = null) { - if ($key === null) return $this->vars; + public function get(?string $key = null) + { + if (null === $key) { + return $this->vars; + } - return isset($this->vars[$key]) ? $this->vars[$key] : null; + return $this->vars[$key] ?? null; } /** * Sets a variable. * - * @param mixed $key Key - * @param string $value Value + * @param mixed $key Key + * @param mixed|null $value Value */ - public function set($key, $value = null) { - if (is_array($key) || is_object($key)) { + public function set($key, $value = null): void + { + if (\is_array($key) || \is_object($key)) { foreach ($key as $k => $v) { $this->vars[$k] = $v; } - } - else { + } else { $this->vars[$key] = $value; } } @@ -273,22 +293,24 @@ class Engine { * Checks if a variable has been set. * * @param string $key Key + * * @return bool Variable status */ - public function has($key) { + public function has(string $key): bool + { return isset($this->vars[$key]); } /** * Unsets a variable. If no key is passed in, clear all variables. * - * @param string $key Key + * @param string|null $key Key */ - public function clear($key = null) { - if (is_null($key)) { - $this->vars = array(); - } - else { + public function clear(?string $key = null): void + { + if (null === $key) { + $this->vars = []; + } else { unset($this->vars[$key]); } } @@ -298,17 +320,20 @@ class Engine { * * @param string $dir Directory path */ - public function path($dir) { + public function path(string $dir): void + { $this->loader->addDirectory($dir); } - /*** Extensible Methods ***/ + // Extensible Methods /** * Starts the framework. - * @throws \Exception + * + * @throws Exception */ - public function _start() { + public function _start(): void + { $dispatched = false; $self = $this; $request = $this->request(); @@ -316,7 +341,7 @@ class Engine { $router = $this->router(); // Allow filters to run - $this->after('start', function() use ($self) { + $this->after('start', function () use ($self) { $self->stop(); }); @@ -345,7 +370,9 @@ class Engine { $dispatched = true; - if (!$continue) break; + if (!$continue) { + break; + } $router->next(); @@ -360,14 +387,16 @@ class Engine { /** * Stops the framework and outputs the current response. * - * @param int $code HTTP status code - * @throws \Exception + * @param int|null $code HTTP status code + * + * @throws Exception */ - public function _stop($code = null) { + public function _stop(?int $code = null): void + { $response = $this->response(); if (!$response->sent()) { - if ($code !== null) { + if (null !== $code) { $response->status($code); } @@ -380,21 +409,23 @@ class Engine { /** * Routes a URL to a callback function. * - * @param string $pattern URL pattern to match - * @param callback $callback Callback function - * @param boolean $pass_route Pass the matching route object to the callback + * @param string $pattern URL pattern to match + * @param callback $callback Callback function + * @param bool $pass_route Pass the matching route object to the callback */ - public function _route($pattern, $callback, $pass_route = false) { + public function _route(string $pattern, callable $callback, bool $pass_route = false): void + { $this->router()->map($pattern, $callback, $pass_route); } /** * Stops processing and returns a given response. * - * @param int $code HTTP status code + * @param int $code HTTP status code * @param string $message Response message */ - public function _halt($code = 200, $message = '') { + public function _halt(int $code = 200, string $message = ''): void + { $this->response() ->clear() ->status($code) @@ -406,11 +437,12 @@ class Engine { /** * Sends an HTTP 500 response for any errors. * - * @param \Exception|\Throwable $e Thrown exception + * @param Exception|Throwable $e Thrown exception */ - public function _error($e) { - $msg = sprintf('

500 Internal Server Error

'. - '

%s (%s)

'. + public function _error($e): void + { + $msg = sprintf('

500 Internal Server Error

' . + '

%s (%s)

' . '
%s
', $e->getMessage(), $e->getCode(), @@ -423,10 +455,9 @@ class Engine { ->status(500) ->write($msg) ->send(); - } - catch (\Throwable $t) { // PHP 7.0+ + } catch (Throwable $t) { // PHP 7.0+ exit($msg); - } catch(\Exception $e) { // PHP < 7 + } catch (Exception $e) { // PHP < 7 exit($msg); } } @@ -434,13 +465,14 @@ class Engine { /** * Sends an HTTP 404 response when a URL is not found. */ - public function _notFound() { + public function _notFound(): void + { $this->response() ->clear() ->status(404) ->write( - '

404 Not Found

'. - '

The page you have requested could not be found.

'. + '

404 Not Found

' . + '

The page you have requested could not be found.

' . str_repeat(' ', 512) ) ->send(); @@ -449,18 +481,19 @@ class Engine { /** * Redirects the current request to another URL. * - * @param string $url URL - * @param int $code HTTP status code + * @param string $url URL + * @param int $code HTTP status code */ - public function _redirect($url, $code = 303) { + public function _redirect(string $url, int $code = 303): void + { $base = $this->get('flight.base_url'); - if ($base === null) { + if (null === $base) { $base = $this->request()->base; } // Append base url to redirect url - if ($base != '/' && strpos($url, '://') === false) { + if ('/' != $base && false === strpos($url, '://')) { $url = $base . preg_replace('#/+#', '/', '/' . $url); } @@ -474,16 +507,17 @@ class Engine { /** * Renders a template. * - * @param string $file Template file - * @param array $data Template data - * @param string $key View variable name - * @throws \Exception + * @param string $file Template file + * @param array|null $data Template data + * @param string|null $key View variable name + * + * @throws Exception */ - public function _render($file, $data = null, $key = null) { - if ($key !== null) { + public function _render(string $file, ?array $data = null, ?string $key = null): void + { + if (null !== $key) { $this->view()->set($key, $this->view()->fetch($file, $data)); - } - else { + } else { $this->view()->render($file, $data); } } @@ -491,67 +525,70 @@ class Engine { /** * Sends a JSON response. * - * @param mixed $data JSON data - * @param int $code HTTP status code - * @param bool $encode Whether to perform JSON encoding + * @param mixed $data JSON data + * @param int $code HTTP status code + * @param bool $encode Whether to perform JSON encoding * @param string $charset Charset - * @param int $option Bitmask Json constant such as JSON_HEX_QUOT - * @throws \Exception + * @param int $option Bitmask Json constant such as JSON_HEX_QUOT + * + * @throws Exception */ public function _json( $data, - $code = 200, - $encode = true, - $charset = 'utf-8', - $option = 0 - ) { + int $code = 200, + bool $encode = true, + string $charset = 'utf-8', + int $option = 0 + ): void { $json = ($encode) ? json_encode($data, $option) : $data; $this->response() ->status($code) - ->header('Content-Type', 'application/json; charset='.$charset) + ->header('Content-Type', 'application/json; charset=' . $charset) ->write($json) ->send(); } - + /** * Sends a JSONP response. * - * @param mixed $data JSON data - * @param string $param Query parameter that specifies the callback name. - * @param int $code HTTP status code - * @param bool $encode Whether to perform JSON encoding + * @param mixed $data JSON data + * @param string $param Query parameter that specifies the callback name. + * @param int $code HTTP status code + * @param bool $encode Whether to perform JSON encoding * @param string $charset Charset - * @param int $option Bitmask Json constant such as JSON_HEX_QUOT - * @throws \Exception + * @param int $option Bitmask Json constant such as JSON_HEX_QUOT + * + * @throws Exception */ public function _jsonp( $data, - $param = 'jsonp', - $code = 200, - $encode = true, - $charset = 'utf-8', - $option = 0 - ) { + string $param = 'jsonp', + int $code = 200, + bool $encode = true, + string $charset = 'utf-8', + int $option = 0 + ): void { $json = ($encode) ? json_encode($data, $option) : $data; $callback = $this->request()->query[$param]; $this->response() ->status($code) - ->header('Content-Type', 'application/javascript; charset='.$charset) - ->write($callback.'('.$json.');') + ->header('Content-Type', 'application/javascript; charset=' . $charset) + ->write($callback . '(' . $json . ');') ->send(); } /** * Handles ETag HTTP caching. * - * @param string $id ETag identifier + * @param string $id ETag identifier * @param string $type ETag type */ - public function _etag($id, $type = 'strong') { - $id = (($type === 'weak') ? 'W/' : '').$id; + public function _etag(string $id, string $type = 'strong'): void + { + $id = (('weak' === $type) ? 'W/' : '') . $id; $this->response()->header('ETag', $id); @@ -566,7 +603,8 @@ class Engine { * * @param int $time Unix timestamp */ - public function _lastModified($time) { + 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']) && diff --git a/flight/Flight.php b/flight/Flight.php index 1f75cde..101c455 100644 --- a/flight/Flight.php +++ b/flight/Flight.php @@ -1,4 +1,6 @@ events[$name] = $callback; } @@ -70,19 +75,23 @@ class Dispatcher { * Gets an assigned callback. * * @param string $name Event name + * * @return callback $callback Callback function */ - public function get($name) { - return isset($this->events[$name]) ? $this->events[$name] : null; + public function get(string $name): ?callable + { + return $this->events[$name] ?? null; } /** * Checks if an event has been set. * * @param string $name Event name + * * @return bool Event status */ - public function has($name) { + public function has(string $name): bool + { return isset($this->events[$name]); } @@ -90,27 +99,28 @@ class Dispatcher { * Clears an event. If no name is given, * all events are removed. * - * @param string $name Event name + * @param string|null $name Event name */ - public function clear($name = null) { - if ($name !== null) { + public function clear(?string $name = null): void + { + if (null !== $name) { unset($this->events[$name]); unset($this->filters[$name]); - } - else { - $this->events = array(); - $this->filters = array(); + } else { + $this->events = []; + $this->filters = []; } } /** * Hooks a callback to an event. * - * @param string $name Event name - * @param string $type Filter type + * @param string $name Event name + * @param string $type Filter type * @param callback $callback Callback function */ - public function hook($name, $type, $callback) { + public function hook(string $name, string $type, callable $callback) + { $this->filters[$name][$type][] = $callback; } @@ -118,15 +128,19 @@ class Dispatcher { * Executes a chain of method filters. * * @param array $filters Chain of filters - * @param array $params Method parameters - * @param mixed $output Method output - * @throws \Exception + * @param array $params Method parameters + * @param mixed $output Method output + * + * @throws Exception */ - public function filter($filters, &$params, &$output) { - $args = array(&$params, &$output); + public function filter(array $filters, array &$params, &$output): void + { + $args = [&$params, &$output]; foreach ($filters as $callback) { $continue = $this->execute($callback, $args); - if ($continue === false) break; + if (false === $continue) { + break; + } } } @@ -134,35 +148,39 @@ class Dispatcher { * Executes a callback function. * * @param callback $callback Callback function - * @param array $params Function parameters + * @param array $params Function parameters + * + *@throws Exception + * * @return mixed Function results - * @throws \Exception */ - public static function execute($callback, array &$params = array()) { - if (is_callable($callback)) { - return is_array($callback) ? + public static function execute(callable $callback, array &$params = []) + { + if (\is_callable($callback)) { + return \is_array($callback) ? self::invokeMethod($callback, $params) : self::callFunction($callback, $params); } - else { - throw new \Exception('Invalid callback specified.'); - } + + throw new Exception('Invalid callback specified.'); } /** * Calls a function. * - * @param string $func Name of function to call - * @param array $params Function parameters + * @param callable|string $func Name of function to call + * @param array $params Function parameters + * * @return mixed Function results */ - public static function callFunction($func, array &$params = array()) { + public static function callFunction($func, array &$params = []) + { // Call static method - if (is_string($func) && strpos($func, '::') !== false) { - return call_user_func_array($func, $params); + if (\is_string($func) && false !== strpos($func, '::')) { + return \call_user_func_array($func, $params); } - switch (count($params)) { + switch (\count($params)) { case 0: return $func(); case 1: @@ -176,23 +194,25 @@ class Dispatcher { case 5: return $func($params[0], $params[1], $params[2], $params[3], $params[4]); default: - return call_user_func_array($func, $params); + return \call_user_func_array($func, $params); } } /** * Invokes a method. * - * @param mixed $func Class method + * @param mixed $func Class method * @param array $params Class method parameters + * * @return mixed Function results */ - public static function invokeMethod($func, array &$params = array()) { - list($class, $method) = $func; + public static function invokeMethod($func, array &$params = []) + { + [$class, $method] = $func; + + $instance = \is_object($class); - $instance = is_object($class); - - switch (count($params)) { + switch (\count($params)) { case 0: return ($instance) ? $class->$method() : @@ -218,15 +238,16 @@ class Dispatcher { $class->$method($params[0], $params[1], $params[2], $params[3], $params[4]) : $class::$method($params[0], $params[1], $params[2], $params[3], $params[4]); default: - return call_user_func_array($func, $params); + return \call_user_func_array($func, $params); } } /** * Resets the object to the initial state. */ - public function reset() { - $this->events = array(); - $this->filters = array(); + public function reset(): void + { + $this->events = []; + $this->filters = []; } } diff --git a/flight/core/Loader.php b/flight/core/Loader.php index 76e3ea4..4520b32 100644 --- a/flight/core/Loader.php +++ b/flight/core/Loader.php @@ -1,4 +1,6 @@ instances[$name]); - $this->classes[$name] = array($class, $params, $callback); + $this->classes[$name] = [$class, $params, $callback]; } /** @@ -55,23 +57,27 @@ class Loader { * * @param string $name Registry name */ - public function unregister($name) { + public function unregister(string $name): void + { unset($this->classes[$name]); } /** * Loads a registered class. * - * @param string $name Method name - * @param bool $shared Shared instance + * @param string $name Method name + * @param bool $shared Shared instance + * + * @throws Exception + * * @return object Class instance - * @throws \Exception */ - public function load($name, $shared = true) { + public function load(string $name, bool $shared = true): ?object + { $obj = null; if (isset($this->classes[$name])) { - list($class, $params, $callback) = $this->classes[$name]; + [$class, $params, $callback] = $this->classes[$name]; $exists = isset($this->instances[$name]); @@ -79,18 +85,17 @@ class Loader { $obj = ($exists) ? $this->getInstance($name) : $this->newInstance($class, $params); - + if (!$exists) { $this->instances[$name] = $obj; } - } - else { + } else { $obj = $this->newInstance($class, $params); } if ($callback && (!$shared || !$exists)) { - $ref = array(&$obj); - call_user_func_array($callback, $ref); + $ref = [&$obj]; + \call_user_func_array($callback, $ref); } } @@ -101,26 +106,31 @@ class Loader { * Gets a single instance of a class. * * @param string $name Instance name + * * @return object Class instance */ - public function getInstance($name) { - return isset($this->instances[$name]) ? $this->instances[$name] : null; + public function getInstance(string $name): ?object + { + return $this->instances[$name] ?? null; } /** * Gets a new instance of a class. * - * @param string|callable $class Class name or callback function to instantiate class - * @param array $params Class initialization parameters + * @param callable|string $class Class name or callback function to instantiate class + * @param array $params Class initialization parameters + * + * @throws Exception + * * @return object Class instance - * @throws \Exception */ - public function newInstance($class, array $params = array()) { - if (is_callable($class)) { - return call_user_func_array($class, $params); + public function newInstance($class, array $params = []): object + { + if (\is_callable($class)) { + return \call_user_func_array($class, $params); } - switch (count($params)) { + switch (\count($params)) { case 0: return new $class(); case 1: @@ -135,44 +145,48 @@ class Loader { return new $class($params[0], $params[1], $params[2], $params[3], $params[4]); default: try { - $refClass = new \ReflectionClass($class); + $refClass = new ReflectionClass($class); + return $refClass->newInstanceArgs($params); - } catch (\ReflectionException $e) { - throw new \Exception("Cannot instantiate {$class}", 0, $e); + } catch (ReflectionException $e) { + throw new Exception("Cannot instantiate {$class}", 0, $e); } } } /** * @param string $name Registry name + * * @return mixed Class information or null if not registered */ - public function get($name) { - return isset($this->classes[$name]) ? $this->classes[$name] : null; + public function get(string $name) + { + return $this->classes[$name] ?? null; } /** * Resets the object to the initial state. */ - public function reset() { - $this->classes = array(); - $this->instances = array(); + public function reset(): void + { + $this->classes = []; + $this->instances = []; } - /*** Autoloading Functions ***/ + // Autoloading Functions /** * Starts/stops autoloader. * - * @param bool $enabled Enable/disable autoloading - * @param array $dirs Autoload directories + * @param bool $enabled Enable/disable autoloading + * @param array $dirs Autoload directories */ - public static function autoload($enabled = true, $dirs = array()) { + public static function autoload(bool $enabled = true, array $dirs = []): void + { if ($enabled) { - spl_autoload_register(array(__CLASS__, 'loadClass')); - } - else { - spl_autoload_unregister(array(__CLASS__, 'loadClass')); + spl_autoload_register([__CLASS__, 'loadClass']); + } else { + spl_autoload_unregister([__CLASS__, 'loadClass']); } if (!empty($dirs)) { @@ -185,13 +199,15 @@ class Loader { * * @param string $class Class name */ - public static function loadClass($class) { - $class_file = str_replace(array('\\', '_'), '/', $class).'.php'; + public static function loadClass(string $class): void + { + $class_file = str_replace(['\\', '_'], '/', $class) . '.php'; foreach (self::$dirs as $dir) { - $file = $dir.'/'.$class_file; + $file = $dir . '/' . $class_file; if (file_exists($file)) { require $file; + return; } } @@ -202,14 +218,16 @@ class Loader { * * @param mixed $dir Directory path */ - public static function addDirectory($dir) { - if (is_array($dir) || is_object($dir)) { + public static function addDirectory($dir): void + { + if (\is_array($dir) || \is_object($dir)) { foreach ($dir as $value) { self::addDirectory($value); } - } - else if (is_string($dir)) { - if (!in_array($dir, self::$dirs)) self::$dirs[] = $dir; + } elseif (\is_string($dir)) { + if (!\in_array($dir, self::$dirs, true)) { + self::$dirs[] = $dir; + } } } } diff --git a/flight/net/Request.php b/flight/net/Request.php index 19a8331..670ce79 100644 --- a/flight/net/Request.php +++ b/flight/net/Request.php @@ -1,4 +1,6 @@ str_replace('@', '%40', self::getVar('REQUEST_URI', '/')), - 'base' => str_replace(array('\\',' '), array('/','%20'), dirname(self::getVar('SCRIPT_NAME'))), + 'base' => str_replace(['\\', ' '], ['/', '%20'], \dirname(self::getVar('SCRIPT_NAME'))), 'method' => self::getMethod(), 'referrer' => self::getVar('HTTP_REFERER'), 'ip' => self::getVar('REMOTE_ADDR'), - 'ajax' => self::getVar('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest', + 'ajax' => 'XMLHttpRequest' == self::getVar('HTTP_X_REQUESTED_WITH'), 'scheme' => self::getScheme(), 'user_agent' => self::getVar('HTTP_USER_AGENT'), 'type' => self::getVar('CONTENT_TYPE'), @@ -148,11 +152,11 @@ class Request { 'data' => new Collection($_POST), 'cookies' => new Collection($_COOKIE), 'files' => new Collection($_FILES), - 'secure' => self::getScheme() == 'https', + 'secure' => 'https' == self::getScheme(), 'accept' => self::getVar('HTTP_ACCEPT'), 'proxy_ip' => self::getProxyIpAddress(), 'host' => self::getVar('HTTP_HOST'), - ); + ]; } $this->init($config); @@ -163,15 +167,16 @@ class Request { * * @param array $properties Array of request properties */ - public function init($properties = array()) { + public function init(array $properties = []) + { // Set all the defined properties foreach ($properties as $name => $value) { $this->$name = $value; } // Get the requested URL without the base directory - if ($this->base != '/' && strlen($this->base) > 0 && strpos($this->url, $this->base) === 0) { - $this->url = substr($this->url, strlen($this->base)); + if ('/' != $this->base && \strlen($this->base) > 0 && 0 === strpos($this->url, $this->base)) { + $this->url = substr($this->url, \strlen($this->base)); } // Default url @@ -186,11 +191,11 @@ class Request { } // Check for JSON input - if (strpos($this->type, 'application/json') === 0) { + if (0 === strpos($this->type, 'application/json')) { $body = $this->getBody(); - if ($body != '') { + if ('' != $body) { $data = json_decode($body, true); - if ($data != null) { + if (null != $data) { $this->data->setData($data); } } @@ -202,16 +207,17 @@ class Request { * * @return string Raw HTTP request body */ - public static function getBody() { + public static function getBody(): ?string + { static $body; - if (!is_null($body)) { + if (null !== $body) { return $body; } $method = self::getMethod(); - if ($method == 'POST' || $method == 'PUT' || $method == 'DELETE' || $method == 'PATCH') { + if ('POST' === $method || 'PUT' === $method || 'DELETE' === $method || 'PATCH' === $method) { $body = file_get_contents('php://input'); } @@ -220,16 +226,14 @@ class Request { /** * Gets the request method. - * - * @return string */ - public static function getMethod() { + public static function getMethod(): string + { $method = self::getVar('REQUEST_METHOD', 'GET'); if (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) { $method = $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']; - } - elseif (isset($_REQUEST['_method'])) { + } elseif (isset($_REQUEST['_method'])) { $method = $_REQUEST['_method']; } @@ -241,22 +245,23 @@ class Request { * * @return string IP address */ - public static function getProxyIpAddress() { - static $forwarded = array( + public static function getProxyIpAddress(): string + { + static $forwarded = [ 'HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', - 'HTTP_FORWARDED' - ); + 'HTTP_FORWARDED', + ]; $flags = \FILTER_FLAG_NO_PRIV_RANGE | \FILTER_FLAG_NO_RES_RANGE; foreach ($forwarded as $key) { - if (array_key_exists($key, $_SERVER)) { + if (\array_key_exists($key, $_SERVER)) { sscanf($_SERVER[$key], '%[^,]', $ip); - if (filter_var($ip, \FILTER_VALIDATE_IP, $flags) !== false) { + if (false !== filter_var($ip, \FILTER_VALIDATE_IP, $flags)) { return $ip; } } @@ -268,22 +273,26 @@ class Request { /** * Gets a variable from $_SERVER using $default if not provided. * - * @param string $var Variable name - * @param string $default Default value to substitute - * @return string Server variable value + * @param string $var Variable name + * @param mixed $default Default value to substitute + * + * @return mixed Server variable value */ - public static function getVar($var, $default = '') { - return isset($_SERVER[$var]) ? $_SERVER[$var] : $default; + public static function getVar(string $var, $default = '') + { + return $_SERVER[$var] ?? $default; } /** * Parse query parameters from a URL. * * @param string $url URL string + * * @return array Query parameters */ - public static function parseQuery($url) { - $params = array(); + public static function parseQuery(string $url): array + { + $params = []; $args = parse_url($url); if (isset($args['query'])) { @@ -293,18 +302,20 @@ class Request { return $params; } - public static function getScheme() { + public static function getScheme(): string + { if ( - (isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) === 'on') + (isset($_SERVER['HTTPS']) && 'on' === strtolower($_SERVER['HTTPS'])) || - (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') + (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && 'https' === $_SERVER['HTTP_X_FORWARDED_PROTO']) || - (isset($_SERVER['HTTP_FRONT_END_HTTPS']) && $_SERVER['HTTP_FRONT_END_HTTPS'] === 'on') + (isset($_SERVER['HTTP_FRONT_END_HTTPS']) && 'on' === $_SERVER['HTTP_FRONT_END_HTTPS']) || - (isset($_SERVER['REQUEST_SCHEME']) && $_SERVER['REQUEST_SCHEME'] === 'https') + (isset($_SERVER['REQUEST_SCHEME']) && 'https' === $_SERVER['REQUEST_SCHEME']) ) { return 'https'; } + return 'http'; } } diff --git a/flight/net/Response.php b/flight/net/Response.php index 9f4b925..38c4dcc 100644 --- a/flight/net/Response.php +++ b/flight/net/Response.php @@ -1,4 +1,6 @@ 'Continue', 101 => 'Switching Protocols', 102 => 'Processing', @@ -112,25 +95,46 @@ class Response { 508 => 'Loop Detected', 510 => 'Not Extended', - 511 => 'Network Authentication Required' - ); + 511 => 'Network Authentication Required', + ]; + /** + * @var int HTTP status + */ + protected $status = 200; + + /** + * @var array HTTP headers + */ + protected $headers = []; + + /** + * @var string HTTP response body + */ + protected $body; + + /** + * @var bool HTTP response sent + */ + protected $sent = false; /** * Sets the HTTP status of the response. * * @param int $code HTTP status code. - * @return object|int Self reference + * * @throws \Exception If invalid status code + * + * @return int|object Self reference */ - public function status($code = null) { - if ($code === null) { + public function status($code = null) + { + if (null === $code) { return $this->status; } - if (array_key_exists($code, self::$codes)) { + if (\array_key_exists($code, self::$codes)) { $this->status = $code; - } - else { + } else { throw new \Exception('Invalid status code.'); } @@ -140,17 +144,18 @@ class Response { /** * Adds a header to the response. * - * @param string|array $name Header name or array of names and values - * @param string $value Header value + * @param array|string $name Header name or array of names and values + * @param string $value Header value + * * @return object Self reference */ - public function header($name, $value = null) { - if (is_array($name)) { + public function header($name, $value = null) + { + if (\is_array($name)) { foreach ($name as $k => $v) { $this->headers[$k] = $v; } - } - else { + } else { $this->headers[$name] = $value; } @@ -158,10 +163,12 @@ class Response { } /** - * Returns the headers from the response + * Returns the headers from the response. + * * @return array */ - public function headers() { + public function headers() + { return $this->headers; } @@ -169,9 +176,11 @@ class Response { * Writes content to the response body. * * @param string $str Response content + * * @return object Self reference */ - public function write($str) { + public function write($str) + { $this->body .= $str; return $this; @@ -182,9 +191,10 @@ class Response { * * @return object Self reference */ - public function clear() { + public function clear() + { $this->status = 200; - $this->headers = array(); + $this->headers = []; $this->body = ''; return $this; @@ -194,26 +204,28 @@ class Response { * Sets caching headers for the response. * * @param int|string $expires Expiration time + * * @return object Self reference */ - public function cache($expires) { - if ($expires === false) { + public function cache($expires) + { + if (false === $expires) { $this->headers['Expires'] = 'Mon, 26 Jul 1997 05:00:00 GMT'; - $this->headers['Cache-Control'] = array( + $this->headers['Cache-Control'] = [ 'no-store, no-cache, must-revalidate', 'post-check=0, pre-check=0', - 'max-age=0' - ); + 'max-age=0', + ]; $this->headers['Pragma'] = 'no-cache'; - } - else { - $expires = is_int($expires) ? $expires : strtotime($expires); + } else { + $expires = \is_int($expires) ? $expires : strtotime($expires); $this->headers['Expires'] = gmdate('D, d M Y H:i:s', $expires) . ' GMT'; - $this->headers['Cache-Control'] = 'max-age='.($expires - time()); - if (isset($this->headers['Pragma']) && $this->headers['Pragma'] == 'no-cache'){ + $this->headers['Cache-Control'] = 'max-age=' . ($expires - time()); + if (isset($this->headers['Pragma']) && 'no-cache' == $this->headers['Pragma']) { unset($this->headers['Pragma']); } } + return $this; } @@ -222,9 +234,10 @@ class Response { * * @return object Self reference */ - public function sendHeaders() { + public function sendHeaders() + { // Send status code header - if (strpos(php_sapi_name(), 'cgi') !== false) { + if (false !== strpos(\PHP_SAPI, 'cgi')) { header( sprintf( 'Status: %d %s', @@ -233,12 +246,11 @@ class Response { ), true ); - } - else { + } else { header( sprintf( '%s %d %s', - (isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.1'), + ($_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.1'), $this->status, self::$codes[$this->status]), true, @@ -248,13 +260,12 @@ class Response { // Send other headers foreach ($this->headers as $field => $value) { - if (is_array($value)) { + if (\is_array($value)) { foreach ($value as $v) { - header($field.': '.$v, false); + header($field . ': ' . $v, false); } - } - else { - header($field.': '.$value); + } else { + header($field . ': ' . $value); } } @@ -263,7 +274,7 @@ class Response { $length = $this->getContentLength(); if ($length > 0) { - header('Content-Length: '.$length); + header('Content-Length: ' . $length); } } @@ -275,23 +286,26 @@ class Response { * * @return string Content length */ - public function getContentLength() { - return extension_loaded('mbstring') ? + public function getContentLength() + { + return \extension_loaded('mbstring') ? mb_strlen($this->body, 'latin1') : - strlen($this->body); + \strlen($this->body); } /** * Gets whether response was sent. */ - public function sent() { + public function sent() + { return $this->sent; } /** * Sends a HTTP response. */ - public function send() { + public function send() + { if (ob_get_length() > 0) { ob_end_clean(); } @@ -305,4 +319,3 @@ class Response { $this->sent = true; } } - diff --git a/flight/net/Route.php b/flight/net/Route.php index a3c4457..f82536a 100644 --- a/flight/net/Route.php +++ b/flight/net/Route.php @@ -1,4 +1,6 @@ pattern = $pattern; $this->callback = $callback; $this->methods = $methods; @@ -67,61 +71,67 @@ class Route { /** * Checks if a URL matches the route pattern. Also parses named parameters in the URL. * - * @param string $url Requested URL - * @param boolean $case_sensitive Case sensitive matching - * @return boolean Match status + * @param string $url Requested URL + * @param bool $case_sensitive Case sensitive matching + * + * @return bool Match status */ - public function matchUrl($url, $case_sensitive = false) { + public function matchUrl(string $url, bool $case_sensitive = false): bool + { // Wildcard or exact match - if ($this->pattern === '*' || $this->pattern === $url) { + if ('*' === $this->pattern || $this->pattern === $url) { return true; } - $ids = array(); + $ids = []; $last_char = substr($this->pattern, -1); // Get splat - if ($last_char === '*') { + if ('*' === $last_char) { $n = 0; - $len = strlen($url); + $len = \strlen($url); $count = substr_count($this->pattern, '/'); for ($i = 0; $i < $len; $i++) { - if ($url[$i] == '/') $n++; - if ($n == $count) break; + if ('/' === $url[$i]) { + $n++; + } + if ($n === $count) { + break; + } } - $this->splat = (string)substr($url, $i+1); + $this->splat = (string) substr($url, $i + 1); } // Build the regex for matching - $regex = str_replace(array(')','/*'), array(')?','(/?|/.*?)'), $this->pattern); + $regex = str_replace([')', '/*'], [')?', '(/?|/.*?)'], $this->pattern); $regex = preg_replace_callback( '#@([\w]+)(:([^/\(\)]*))?#', - function($matches) use (&$ids) { + static function ($matches) use (&$ids) { $ids[$matches[1]] = null; if (isset($matches[3])) { - return '(?P<'.$matches[1].'>'.$matches[3].')'; + return '(?P<' . $matches[1] . '>' . $matches[3] . ')'; } - return '(?P<'.$matches[1].'>[^/\?]+)'; + + return '(?P<' . $matches[1] . '>[^/\?]+)'; }, $regex ); // Fix trailing slash - if ($last_char === '/') { + if ('/' === $last_char) { $regex .= '?'; - } - // Allow trailing slash + } // Allow trailing slash else { $regex .= '/?'; } // Attempt to match route and named parameters - if (preg_match('#^'.$regex.'(?:\?.*)?$#'.(($case_sensitive) ? '' : 'i'), $url, $matches)) { + if (preg_match('#^' . $regex . '(?:\?.*)?$#' . (($case_sensitive) ? '' : 'i'), $url, $matches)) { foreach ($ids as $k => $v) { - $this->params[$k] = (array_key_exists($k, $matches)) ? urldecode($matches[$k]) : null; + $this->params[$k] = (\array_key_exists($k, $matches)) ? urldecode($matches[$k]) : null; } $this->regex = $regex; @@ -136,9 +146,11 @@ class Route { * Checks if an HTTP method matches the route methods. * * @param string $method HTTP method + * * @return bool Match status */ - public function matchMethod($method) { - return count(array_intersect(array($method, '*'), $this->methods)) > 0; + public function matchMethod(string $method): bool + { + return \count(array_intersect([$method, '*'], $this->methods)) > 0; } } diff --git a/flight/net/Router.php b/flight/net/Router.php index 173ba11..c85dba4 100644 --- a/flight/net/Router.php +++ b/flight/net/Router.php @@ -1,4 +1,6 @@ routes; } /** * Clears all routes in the router. */ - public function clear() { - $this->routes = array(); + public function clear(): void + { + $this->routes = []; } /** * Maps a URL pattern to a callback function. * - * @param string $pattern URL pattern to match - * @param callback $callback Callback function - * @param boolean $pass_route Pass the matching route object to the callback + * @param string $pattern URL pattern to match + * @param callback $callback Callback function + * @param bool $pass_route Pass the matching route object to the callback */ - public function map($pattern, $callback, $pass_route = false) { + public function map(string $pattern, callable $callback, bool $pass_route = false): void + { $url = $pattern; - $methods = array('*'); + $methods = ['*']; - if (strpos($pattern, ' ') !== false) { - list($method, $url) = explode(' ', trim($pattern), 2); + if (false !== strpos($pattern, ' ')) { + [$method, $url] = explode(' ', trim($pattern), 2); $url = trim($url); $methods = explode('|', $method); } @@ -75,12 +74,14 @@ class Router { * Routes the current request. * * @param Request $request Request object - * @return Route|bool Matching route or false if no match + * + * @return bool|Route Matching route or false if no match */ - public function route(Request $request) { - $url_decoded = urldecode( $request->url ); + public function route(Request $request) + { + $url_decoded = urldecode($request->url); while ($route = $this->current()) { - if ($route !== false && $route->matchMethod($request->method) && $route->matchUrl($url_decoded, $this->case_sensitive)) { + if (false !== $route && $route->matchMethod($request->method) && $route->matchUrl($url_decoded, $this->case_sensitive)) { return $route; } $this->next(); @@ -92,26 +93,26 @@ class Router { /** * Gets the current route. * - * @return Route + * @return bool|Route */ - public function current() { - return isset($this->routes[$this->index]) ? $this->routes[$this->index] : false; + public function current() + { + return $this->routes[$this->index] ?? false; } /** * Gets the next route. - * - * @return Route */ - public function next() { + public function next(): void + { $this->index++; } /** * Reset to the first route. */ - public function reset() { + public function reset(): void + { $this->index = 0; } } - diff --git a/flight/template/View.php b/flight/template/View.php index a0bc3c3..90dbb81 100644 --- a/flight/template/View.php +++ b/flight/template/View.php @@ -1,4 +1,6 @@ path = $path; } @@ -55,25 +59,27 @@ class View { * Gets a template variable. * * @param string $key Key + * * @return mixed Value */ - public function get($key) { - return isset($this->vars[$key]) ? $this->vars[$key] : null; + public function get($key) + { + return $this->vars[$key] ?? null; } /** * Sets a template variable. * - * @param mixed $key Key + * @param mixed $key Key * @param string $value Value */ - public function set($key, $value = null) { - if (is_array($key) || is_object($key)) { + public function set($key, $value = null) + { + if (\is_array($key) || \is_object($key)) { foreach ($key as $k => $v) { $this->vars[$k] = $v; } - } - else { + } else { $this->vars[$key] = $value; } } @@ -82,9 +88,11 @@ class View { * Checks if a template variable is set. * * @param string $key Key - * @return boolean If key exists + * + * @return bool If key exists */ - public function has($key) { + public function has($key) + { return isset($this->vars[$key]); } @@ -93,11 +101,11 @@ class View { * * @param string $key Key */ - public function clear($key = null) { - if (is_null($key)) { - $this->vars = array(); - } - else { + public function clear($key = null) + { + if (null === $key) { + $this->vars = []; + } else { unset($this->vars[$key]); } } @@ -106,17 +114,19 @@ class View { * Renders a template. * * @param string $file Template file - * @param array $data Template data + * @param array $data Template data + * * @throws \Exception If template not found */ - public function render($file, $data = null) { + public function render($file, $data = null) + { $this->template = $this->getTemplate($file); if (!file_exists($this->template)) { throw new \Exception("Template file not found: {$this->template}."); } - if (is_array($data)) { + if (\is_array($data)) { $this->vars = array_merge($this->vars, $data); } @@ -129,25 +139,28 @@ class View { * 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 */ - public function fetch($file, $data = null) { + public function fetch($file, $data = null) + { ob_start(); $this->render($file, $data); - $output = ob_get_clean(); - return $output; + return ob_get_clean(); } /** * Checks if a template file exists. * * @param string $file Template file + * * @return bool Template file exists */ - public function exists($file) { + public function exists($file) + { return file_exists($this->getTemplate($file)); } @@ -155,30 +168,33 @@ class View { * Gets the full path to a template file. * * @param string $file Template file + * * @return string Template file location */ - public function getTemplate($file) { + public function getTemplate($file) + { $ext = $this->extension; - if (!empty($ext) && (substr($file, -1 * strlen($ext)) != $ext)) { + if (!empty($ext) && (substr($file, -1 * \strlen($ext)) != $ext)) { $file .= $ext; } - if ((substr($file, 0, 1) == '/')) { + if (('/' == substr($file, 0, 1))) { return $file; } - - return $this->path.'/'.$file; + + return $this->path . '/' . $file; } /** * Displays escaped output. * * @param string $str String to escape + * * @return string Escaped string */ - public function e($str) { + public function e($str) + { echo htmlentities($str); } } - diff --git a/flight/util/Collection.php b/flight/util/Collection.php index 1062b11..883dccf 100644 --- a/flight/util/Collection.php +++ b/flight/util/Collection.php @@ -1,4 +1,6 @@ data = $data; } @@ -37,19 +45,22 @@ class Collection implements \ArrayAccess, \Iterator, \Countable, \JsonSerializab * Gets an item. * * @param string $key Key + * * @return mixed Value */ - public function __get($key) { - return isset($this->data[$key]) ? $this->data[$key] : null; + public function __get(string $key) + { + return $this->data[$key] ?? null; } /** * Set an item. * - * @param string $key Key - * @param mixed $value Value + * @param string $key Key + * @param mixed $value Value */ - public function __set($key, $value) { + public function __set(string $key, $value): void + { $this->data[$key] = $value; } @@ -57,9 +68,11 @@ class Collection implements \ArrayAccess, \Iterator, \Countable, \JsonSerializab * Checks if an item exists. * * @param string $key Key + * * @return bool Item status */ - public function __isset($key) { + public function __isset(string $key): bool + { return isset($this->data[$key]); } @@ -68,7 +81,8 @@ class Collection implements \ArrayAccess, \Iterator, \Countable, \JsonSerializab * * @param string $key Key */ - public function __unset($key) { + public function __unset(string $key): void + { unset($this->data[$key]); } @@ -76,23 +90,25 @@ class Collection implements \ArrayAccess, \Iterator, \Countable, \JsonSerializab * Gets an item at the offset. * * @param string $offset Offset + * * @return mixed Value */ - public function offsetGet($offset) { - return isset($this->data[$offset]) ? $this->data[$offset] : null; + public function offsetGet($offset) + { + return $this->data[$offset] ?? null; } /** * Sets an item at the offset. * * @param string $offset Offset - * @param mixed $value Value + * @param mixed $value Value */ - public function offsetSet($offset, $value) { - if (is_null($offset)) { + public function offsetSet($offset, $value) + { + if (null === $offset) { $this->data[] = $value; - } - else { + } else { $this->data[$offset] = $value; } } @@ -101,9 +117,11 @@ class Collection implements \ArrayAccess, \Iterator, \Countable, \JsonSerializab * Checks if an item exists at the offset. * * @param string $offset Offset + * * @return bool Item status */ - public function offsetExists($offset) { + public function offsetExists($offset): bool + { return isset($this->data[$offset]); } @@ -112,54 +130,59 @@ class Collection implements \ArrayAccess, \Iterator, \Countable, \JsonSerializab * * @param string $offset Offset */ - public function offsetUnset($offset) { + public function offsetUnset($offset): void + { unset($this->data[$offset]); } /** * Resets the collection. */ - public function rewind() { + public function rewind(): void + { reset($this->data); } - + /** * Gets current collection item. * * @return mixed Value - */ - public function current() { + */ + public function current() + { return current($this->data); } - + /** * Gets current collection key. * * @return mixed Value - */ - public function key() { + */ + public function key() + { return key($this->data); } - + /** * Gets the next collection value. * * @return mixed Value - */ - public function next() + */ + public function next() { return next($this->data); } - + /** * Checks if the current collection key is valid. * * @return bool Key status - */ - public function valid() + */ + public function valid(): bool { $key = key($this->data); - return ($key !== NULL && $key !== FALSE); + + return null !== $key && false !== $key; } /** @@ -167,8 +190,9 @@ class Collection implements \ArrayAccess, \Iterator, \Countable, \JsonSerializab * * @return int Collection size */ - public function count() { - return sizeof($this->data); + public function count(): int + { + return \count($this->data); } /** @@ -176,7 +200,8 @@ class Collection implements \ArrayAccess, \Iterator, \Countable, \JsonSerializab * * @return array Collection keys */ - public function keys() { + public function keys(): array + { return array_keys($this->data); } @@ -185,7 +210,8 @@ class Collection implements \ArrayAccess, \Iterator, \Countable, \JsonSerializab * * @return array Collection data */ - public function getData() { + public function getData(): array + { return $this->data; } @@ -194,23 +220,26 @@ class Collection implements \ArrayAccess, \Iterator, \Countable, \JsonSerializab * * @param array $data New collection data */ - public function setData(array $data) { + public function setData(array $data): void + { $this->data = $data; } /** - * Gets the collection data which can be serialized to JSON + * Gets the collection data which can be serialized to JSON. * * @return array Collection data which can be serialized by json_encode */ - public function jsonSerialize() { + public function jsonSerialize(): array + { return $this->data; } /** * Removes all items from the collection. */ - public function clear() { - $this->data = array(); + public function clear(): void + { + $this->data = []; } } diff --git a/flight/util/LegacyJsonSerializable.php b/flight/util/LegacyJsonSerializable.php index 4c907c7..0c973aa 100644 --- a/flight/util/LegacyJsonSerializable.php +++ b/flight/util/LegacyJsonSerializable.php @@ -1,11 +1,13 @@ * @license MIT, http://flightphp.com/license */ - -interface JsonSerializable { +interface LegacyJsonSerializable +{ public function jsonSerialize(); -} \ No newline at end of file +} From d3feb77ce9c644a250a186d98e6940e343a053f9 Mon Sep 17 00:00:00 2001 From: Masroor Ehsan Date: Sat, 8 May 2021 15:11:54 +0200 Subject: [PATCH 02/13] fixed parameter types --- flight/core/Dispatcher.php | 31 ++++++++++++++++--------------- flight/core/Loader.php | 4 ++-- flight/net/Route.php | 2 +- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/flight/core/Dispatcher.php b/flight/core/Dispatcher.php index 5652fcd..50b73f0 100644 --- a/flight/core/Dispatcher.php +++ b/flight/core/Dispatcher.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace flight\core; use Exception; +use InvalidArgumentException; /** * The Dispatcher class is responsible for dispatching events. Events @@ -38,9 +39,9 @@ class Dispatcher * *@throws Exception * - * @return string Output of callback + * @return mixed|null Output of callback */ - public function run(string $name, array $params = []): ?string + final public function run(string $name, array $params = []) { $output = ''; @@ -50,7 +51,7 @@ class Dispatcher } // Run requested method - $output = $this->execute($this->get($name), $params); + $output = self::execute($this->get($name), $params); // Run post-filters if (!empty($this->filters[$name]['after'])) { @@ -66,7 +67,7 @@ class Dispatcher * @param string $name Event name * @param callback $callback Callback function */ - public function set(string $name, callable $callback): void + final public function set(string $name, callable $callback): void { $this->events[$name] = $callback; } @@ -78,7 +79,7 @@ class Dispatcher * * @return callback $callback Callback function */ - public function get(string $name): ?callable + final public function get(string $name): ?callable { return $this->events[$name] ?? null; } @@ -90,7 +91,7 @@ class Dispatcher * * @return bool Event status */ - public function has(string $name): bool + final public function has(string $name): bool { return isset($this->events[$name]); } @@ -101,7 +102,7 @@ class Dispatcher * * @param string|null $name Event name */ - public function clear(?string $name = null): void + final public function clear(?string $name = null): void { if (null !== $name) { unset($this->events[$name]); @@ -119,7 +120,7 @@ class Dispatcher * @param string $type Filter type * @param callback $callback Callback function */ - public function hook(string $name, string $type, callable $callback) + final public function hook(string $name, string $type, callable $callback): void { $this->filters[$name][$type][] = $callback; } @@ -133,11 +134,11 @@ class Dispatcher * * @throws Exception */ - public function filter(array $filters, array &$params, &$output): void + final public function filter(array $filters, array &$params, &$output): void { $args = [&$params, &$output]; foreach ($filters as $callback) { - $continue = $this->execute($callback, $args); + $continue = self::execute($callback, $args); if (false === $continue) { break; } @@ -147,14 +148,14 @@ class Dispatcher /** * Executes a callback function. * - * @param callback $callback Callback function - * @param array $params Function parameters + * @param array|callback $callback Callback function + * @param array $params Function parameters * *@throws Exception * * @return mixed Function results */ - public static function execute(callable $callback, array &$params = []) + public static function execute($callback, array &$params = []) { if (\is_callable($callback)) { return \is_array($callback) ? @@ -162,7 +163,7 @@ class Dispatcher self::callFunction($callback, $params); } - throw new Exception('Invalid callback specified.'); + throw new InvalidArgumentException('Invalid callback specified.'); } /** @@ -245,7 +246,7 @@ class Dispatcher /** * Resets the object to the initial state. */ - public function reset(): void + final public function reset(): void { $this->events = []; $this->filters = []; diff --git a/flight/core/Loader.php b/flight/core/Loader.php index 4520b32..95874a6 100644 --- a/flight/core/Loader.php +++ b/flight/core/Loader.php @@ -179,9 +179,9 @@ class Loader * Starts/stops autoloader. * * @param bool $enabled Enable/disable autoloading - * @param array $dirs Autoload directories + * @param mixed $dirs Autoload directories */ - public static function autoload(bool $enabled = true, array $dirs = []): void + public static function autoload(bool $enabled = true, $dirs = []): void { if ($enabled) { spl_autoload_register([__CLASS__, 'loadClass']); diff --git a/flight/net/Route.php b/flight/net/Route.php index f82536a..91a6aff 100644 --- a/flight/net/Route.php +++ b/flight/net/Route.php @@ -40,7 +40,7 @@ final class Route /** * @var string|null Matching regular expression */ - public ?string $regex; + public ?string $regex = null; /** * @var string URL splat content From ff852c19e34e75a4a106e078575d1dfd91cd94fd Mon Sep 17 00:00:00 2001 From: Masroor Ehsan Date: Sat, 8 May 2021 15:12:31 +0200 Subject: [PATCH 03/13] updated phpunit and tests --- composer.json | 2 +- tests/AutoloadTest.php | 29 ++++--- tests/DispatcherTest.php | 65 +++++++------- tests/FilterTest.php | 36 ++++---- tests/FlightTest.php | 56 ++++++++----- tests/LoaderTest.php | 97 +++++++++++---------- tests/MapTest.php | 52 +++++++----- tests/RedirectTest.php | 63 +++++++------- tests/RegisterTest.php | 65 +++++++------- tests/RenderTest.php | 30 +++---- tests/RequestTest.php | 130 ++++++++++++++-------------- tests/RouterTest.php | 172 +++++++++++++++++++++----------------- tests/VariableTest.php | 25 +++--- tests/ViewTest.php | 34 ++++---- tests/classes/Factory.php | 13 +-- tests/classes/Hello.php | 10 ++- tests/classes/User.php | 9 +- 17 files changed, 496 insertions(+), 392 deletions(-) diff --git a/composer.json b/composer.json index 6999b21..11df056 100644 --- a/composer.json +++ b/composer.json @@ -19,6 +19,6 @@ "files": [ "flight/autoload.php", "flight/Flight.php" ] }, "require-dev": { - "phpunit/phpunit": "~4.6" + "phpunit/phpunit": "^9.5" } } diff --git a/tests/AutoloadTest.php b/tests/AutoloadTest.php index c8dd4df..0fbb958 100644 --- a/tests/AutoloadTest.php +++ b/tests/AutoloadTest.php @@ -6,36 +6,41 @@ * @license MIT, http://flightphp.com/license */ +use flight\Engine; + require_once 'vendor/autoload.php'; -require_once __DIR__.'/../flight/autoload.php'; +require_once __DIR__ . '/../flight/autoload.php'; -class AutoloadTest extends PHPUnit_Framework_TestCase +class AutoloadTest extends PHPUnit\Framework\TestCase { /** - * @var \flight\Engine + * @var Engine */ private $app; - function setUp() { - $this->app = new \flight\Engine(); - $this->app->path(__DIR__.'/classes'); + protected function setUp(): void + { + $this->app = new Engine(); + $this->app->path(__DIR__ . '/classes'); } // Autoload a class - function testAutoload(){ + public function testAutoload() + { $this->app->register('user', 'User'); $loaders = spl_autoload_functions(); $user = $this->app->user(); - $this->assertTrue(sizeof($loaders) > 0); - $this->assertTrue(is_object($user)); - $this->assertEquals('User', get_class($user)); + self::assertTrue(count($loaders) > 0); + self::assertIsObject($user); + self::assertInstanceOf(User::class, $user); } // Check autoload failure - function testMissingClass(){ + public function testMissingClass() + { $test = null; $this->app->register('test', 'NonExistentClass'); @@ -43,6 +48,6 @@ class AutoloadTest extends PHPUnit_Framework_TestCase $test = $this->app->test(); } - $this->assertEquals(null, $test); + self::assertNull($test); } } diff --git a/tests/DispatcherTest.php b/tests/DispatcherTest.php index a29bd20..63421a4 100644 --- a/tests/DispatcherTest.php +++ b/tests/DispatcherTest.php @@ -6,87 +6,96 @@ * @license MIT, http://flightphp.com/license */ +use flight\core\Dispatcher; + require_once 'vendor/autoload.php'; -require_once __DIR__.'/classes/Hello.php'; +require_once __DIR__ . '/classes/Hello.php'; -class DispatcherTest extends PHPUnit_Framework_TestCase +class DispatcherTest extends PHPUnit\Framework\TestCase { /** - * @var \flight\core\Dispatcher + * @var Dispatcher|null */ - private $dispatcher; + private Dispatcher $dispatcher; - function setUp(){ - $this->dispatcher = new \flight\core\Dispatcher(); + protected function setUp(): void + { + $this->dispatcher = new Dispatcher(); } // Map a closure - function testClosureMapping(){ - $this->dispatcher->set('map1', function(){ + public function testClosureMapping() + { + $this->dispatcher->set('map1', function () { return 'hello'; }); $result = $this->dispatcher->run('map1'); - $this->assertEquals('hello', $result); + self::assertEquals('hello', $result); } // Map a function - function testFunctionMapping(){ - $this->dispatcher->set('map2', function(){ + public function testFunctionMapping() + { + $this->dispatcher->set('map2', function () { return 'hello'; }); $result = $this->dispatcher->run('map2'); - $this->assertEquals('hello', $result); + self::assertEquals('hello', $result); } // Map a class method - function testClassMethodMapping(){ + public function testClassMethodMapping() + { $h = new Hello(); - $this->dispatcher->set('map3', array($h, 'sayHi')); + $this->dispatcher->set('map3', [$h, 'sayHi']); $result = $this->dispatcher->run('map3'); - $this->assertEquals('hello', $result); + self::assertEquals('hello', $result); } // Map a static class method - function testStaticClassMethodMapping(){ - $this->dispatcher->set('map4', array('Hello', 'sayBye')); + public function testStaticClassMethodMapping() + { + $this->dispatcher->set('map4', ['Hello', 'sayBye']); $result = $this->dispatcher->run('map4'); - $this->assertEquals('goodbye', $result); + self::assertEquals('goodbye', $result); } // Run before and after filters - function testBeforeAndAfter() { - $this->dispatcher->set('hello', function($name){ + public function testBeforeAndAfter() + { + $this->dispatcher->set('hello', function ($name) { return "Hello, $name!"; }); - $this->dispatcher->hook('hello', 'before', function(&$params, &$output){ + $this->dispatcher->hook('hello', 'before', function (&$params, &$output) { // Manipulate the parameter $params[0] = 'Fred'; }); - $this->dispatcher->hook('hello', 'after', function(&$params, &$output){ + $this->dispatcher->hook('hello', 'after', function (&$params, &$output) { // Manipulate the output - $output .= " Have a nice day!"; + $output .= ' Have a nice day!'; }); - $result = $this->dispatcher->run('hello', array('Bob')); + $result = $this->dispatcher->run('hello', ['Bob']); - $this->assertEquals('Hello, Fred! Have a nice day!', $result); + self::assertEquals('Hello, Fred! Have a nice day!', $result); } // Test an invalid callback - function testInvalidCallback() { - $this->setExpectedException('Exception', 'Invalid callback specified.'); + public function testInvalidCallback() + { + $this->expectException(Exception::class); - $this->dispatcher->execute(array('NonExistentClass', 'nonExistentMethod')); + $this->dispatcher->execute(['NonExistentClass', 'nonExistentMethod']); } } diff --git a/tests/FilterTest.php b/tests/FilterTest.php index 6fef654..137bb61 100644 --- a/tests/FilterTest.php +++ b/tests/FilterTest.php @@ -6,34 +6,38 @@ * @license MIT, http://flightphp.com/license */ +use flight\Engine; + require_once 'vendor/autoload.php'; -require_once __DIR__.'/../flight/autoload.php'; +require_once __DIR__ . '/../flight/autoload.php'; -class FilterTest extends PHPUnit_Framework_TestCase +class FilterTest extends PHPUnit\Framework\TestCase { /** - * @var \flight\Engine + * @var Engine */ private $app; - function setUp() { - $this->app = new \flight\Engine(); + protected function setUp(): void + { + $this->app = new Engine(); } // Run before and after filters - function testBeforeAndAfter() { - $this->app->map('hello', function($name){ + public function testBeforeAndAfter() + { + $this->app->map('hello', function ($name) { return "Hello, $name!"; }); - $this->app->before('hello', function(&$params, &$output){ + $this->app->before('hello', function (&$params, &$output) { // Manipulate the parameter $params[0] = 'Fred'; }); - $this->app->after('hello', function(&$params, &$output){ + $this->app->after('hello', function (&$params, &$output) { // Manipulate the output - $output .= " Have a nice day!"; + $output .= ' Have a nice day!'; }); $result = $this->app->hello('Bob'); @@ -42,19 +46,21 @@ class FilterTest extends PHPUnit_Framework_TestCase } // Break out of a filter chain by returning false - function testFilterChaining() { - $this->app->map('bye', function($name){ + public function testFilterChaining() + { + $this->app->map('bye', function ($name) { return "Bye, $name!"; }); - $this->app->before('bye', function(&$params, &$output){ + $this->app->before('bye', function (&$params, &$output) { $params[0] = 'Bob'; }); - $this->app->before('bye', function(&$params, &$output){ + $this->app->before('bye', function (&$params, &$output) { $params[0] = 'Fred'; + return false; }); - $this->app->before('bye', function(&$params, &$output){ + $this->app->before('bye', function (&$params, &$output) { $params[0] = 'Ted'; }); diff --git a/tests/FlightTest.php b/tests/FlightTest.php index 2a778c5..19dfdd3 100644 --- a/tests/FlightTest.php +++ b/tests/FlightTest.php @@ -1,35 +1,43 @@ * @license MIT, http://flightphp.com/license */ - require_once 'vendor/autoload.php'; -require_once __DIR__.'/../flight/Flight.php'; +require_once __DIR__ . '/../flight/Flight.php'; -class FlightTest extends PHPUnit_Framework_TestCase +class FlightTest extends PHPUnit\Framework\TestCase { - function setUp() { + protected function setUp(): void + { Flight::init(); } // Checks that default components are loaded - function testDefaultComponents(){ + public function testDefaultComponents() + { $request = Flight::request(); $response = Flight::response(); $router = Flight::router(); $view = Flight::view(); - $this->assertEquals('flight\net\Request', get_class($request)); - $this->assertEquals('flight\net\Response', get_class($response)); - $this->assertEquals('flight\net\Router', get_class($router)); - $this->assertEquals('flight\template\View', get_class($view)); + $this->assertEquals(Request::class, get_class($request)); + $this->assertEquals(Response::class, get_class($response)); + $this->assertEquals(Router::class, get_class($router)); + $this->assertEquals(View::class, get_class($view)); } // Test get/set of variables - function testGetAndSet(){ + public function testGetAndSet() + { Flight::set('a', 1); $var = Flight::get('a'); @@ -38,45 +46,49 @@ class FlightTest extends PHPUnit_Framework_TestCase Flight::clear(); $vars = Flight::get(); - $this->assertEquals(0, count($vars)); + $this->assertCount(0, $vars); Flight::set('a', 1); Flight::set('b', 2); $vars = Flight::get(); - $this->assertEquals(2, count($vars)); + $this->assertCount(2, $vars); $this->assertEquals(1, $vars['a']); $this->assertEquals(2, $vars['b']); } // Register a class - function testRegister(){ - Flight::path(__DIR__.'/classes'); + public function testRegister() + { + Flight::path(__DIR__ . '/classes'); Flight::register('user', 'User'); $user = Flight::user(); $loaders = spl_autoload_functions(); - $this->assertTrue(sizeof($loaders) > 0); - $this->assertTrue(is_object($user)); - $this->assertEquals('User', get_class($user)); + self::assertTrue(count($loaders) > 0); + self::assertIsObject($user); + self::assertInstanceOf(User::class, $user); } // Map a function - function testMap(){ - Flight::map('map1', function(){ + public function testMap() + { + Flight::map('map1', function () { return 'hello'; }); $result = Flight::map1(); - $this->assertEquals('hello', $result); + self::assertEquals('hello', $result); } // Unmapped method - function testUnmapped() { - $this->setExpectedException('Exception', 'doesNotExist must be a mapped method.'); + public function testUnmapped() + { + $this->expectException(Exception::class); + $this->expectExceptionMessage('doesNotExist must be a mapped method.'); Flight::doesNotExist(); } diff --git a/tests/LoaderTest.php b/tests/LoaderTest.php index 98de2bd..2e2de8f 100644 --- a/tests/LoaderTest.php +++ b/tests/LoaderTest.php @@ -6,109 +6,116 @@ * @license MIT, http://flightphp.com/license */ +use flight\core\Loader; + require_once 'vendor/autoload.php'; -require_once __DIR__.'/classes/User.php'; -require_once __DIR__.'/classes/Factory.php'; +require_once __DIR__ . '/classes/User.php'; +require_once __DIR__ . '/classes/Factory.php'; -class LoaderTest extends PHPUnit_Framework_TestCase +class LoaderTest extends PHPUnit\Framework\TestCase { - /** - * @var \flight\core\Loader - */ - private $loader; - - function setUp(){ - $this->loader = new \flight\core\Loader(); - $this->loader->autoload(true, __DIR__.'/classes'); + private Loader $loader; + + protected function setUp(): void + { + $this->loader = new Loader(); + $this->loader->autoload(true, __DIR__ . '/classes'); } // Autoload a class - function testAutoload(){ + public function testAutoload() + { $this->loader->register('tests', 'User'); $test = $this->loader->load('tests'); - $this->assertTrue(is_object($test)); - $this->assertEquals('User', get_class($test)); + self::assertIsObject($test); + self::assertInstanceOf(User::class, $test); } // Register a class - function testRegister(){ + public function testRegister() + { $this->loader->register('a', 'User'); $user = $this->loader->load('a'); - $this->assertTrue(is_object($user)); - $this->assertEquals('User', get_class($user)); - $this->assertEquals('', $user->name); + self::assertIsObject($user); + self::assertInstanceOf(User::class, $user); + self::assertEquals('', $user->name); } // Register a class with constructor parameters - function testRegisterWithConstructor(){ - $this->loader->register('b', 'User', array('Bob')); + public function testRegisterWithConstructor() + { + $this->loader->register('b', 'User', ['Bob']); $user = $this->loader->load('b'); - $this->assertTrue(is_object($user)); - $this->assertEquals('User', get_class($user)); - $this->assertEquals('Bob', $user->name); + self::assertIsObject($user); + self::assertInstanceOf(User::class, $user); + self::assertEquals('Bob', $user->name); } // Register a class with initialization - function testRegisterWithInitialization(){ - $this->loader->register('c', 'User', array('Bob'), function($user){ + public function testRegisterWithInitialization() + { + $this->loader->register('c', 'User', ['Bob'], function ($user) { $user->name = 'Fred'; }); $user = $this->loader->load('c'); - $this->assertTrue(is_object($user)); - $this->assertEquals('User', get_class($user)); - $this->assertEquals('Fred', $user->name); + self::assertIsObject($user); + self::assertInstanceOf(User::class, $user); + self::assertEquals('Fred', $user->name); } // Get a non-shared instance of a class - function testSharedInstance() { + public function testSharedInstance() + { $this->loader->register('d', 'User'); $user1 = $this->loader->load('d'); $user2 = $this->loader->load('d'); $user3 = $this->loader->load('d', false); - $this->assertTrue($user1 === $user2); - $this->assertTrue($user1 !== $user3); + self::assertSame($user1, $user2); + self::assertNotSame($user1, $user3); } // Gets an object from a factory method - function testRegisterUsingCallable(){ - $this->loader->register('e', array('Factory','create')); + public function testRegisterUsingCallable() + { + $this->loader->register('e', ['Factory', 'create']); $obj = $this->loader->load('e'); - $this->assertTrue(is_object($obj)); - $this->assertEquals('Factory', get_class($obj)); + self::assertIsObject($obj); + self::assertInstanceOf(Factory::class, $obj); $obj2 = $this->loader->load('e'); - $this->assertTrue(is_object($obj2)); - $this->assertEquals('Factory', get_class($obj2)); - $this->assertTrue($obj === $obj2); + self::assertIsObject($obj2); + self::assertInstanceOf(Factory::class, $obj2); + self::assertSame($obj, $obj2); $obj3 = $this->loader->load('e', false); - $this->assertTrue(is_object($obj3)); - $this->assertEquals('Factory', get_class($obj3)); - $this->assertTrue($obj !== $obj3); + self::assertIsObject($obj3); + self::assertInstanceOf(Factory::class, $obj3); + self::assertNotSame($obj, $obj3); } // Gets an object from a callback function - function testRegisterUsingCallback(){ - $this->loader->register('f', function(){ + public function testRegisterUsingCallback() + { + $this->loader->register('f', function () { return Factory::create(); }); $obj = $this->loader->load('f'); - $this->assertTrue(is_object($obj)); - $this->assertEquals('Factory', get_class($obj)); + self::assertIsObject($obj); + self::assertInstanceOf(Factory::class, $obj); } } diff --git a/tests/MapTest.php b/tests/MapTest.php index 8a6ea8b..5b0d3df 100644 --- a/tests/MapTest.php +++ b/tests/MapTest.php @@ -6,66 +6,72 @@ * @license MIT, http://flightphp.com/license */ +use flight\Engine; + require_once 'vendor/autoload.php'; -require_once __DIR__.'/../flight/autoload.php'; -require_once __DIR__.'/classes/Hello.php'; +require_once __DIR__ . '/../flight/autoload.php'; +require_once __DIR__ . '/classes/Hello.php'; -class MapTest extends PHPUnit_Framework_TestCase +class MapTest extends PHPUnit\Framework\TestCase { - /** - * @var \flight\Engine - */ - private $app; + private Engine $app; - function setUp() { - $this->app = new \flight\Engine(); + protected function setUp(): void + { + $this->app = new Engine(); } // Map a closure - function testClosureMapping(){ - $this->app->map('map1', function(){ + public function testClosureMapping() + { + $this->app->map('map1', function () { return 'hello'; }); $result = $this->app->map1(); - $this->assertEquals('hello', $result); + self::assertEquals('hello', $result); } // Map a function - function testFunctionMapping(){ - $this->app->map('map2', function(){ + public function testFunctionMapping() + { + $this->app->map('map2', function () { return 'hello'; }); $result = $this->app->map2(); - $this->assertEquals('hello', $result); + self::assertEquals('hello', $result); } // Map a class method - function testClassMethodMapping(){ + public function testClassMethodMapping() + { $h = new Hello(); - $this->app->map('map3', array($h, 'sayHi')); + $this->app->map('map3', [$h, 'sayHi']); $result = $this->app->map3(); - $this->assertEquals('hello', $result); + self::assertEquals('hello', $result); } // Map a static class method - function testStaticClassMethodMapping(){ - $this->app->map('map4', array('Hello', 'sayBye')); + public function testStaticClassMethodMapping() + { + $this->app->map('map4', ['Hello', 'sayBye']); $result = $this->app->map4(); - $this->assertEquals('goodbye', $result); + self::assertEquals('goodbye', $result); } // Unmapped method - function testUnmapped() { - $this->setExpectedException('Exception', 'doesNotExist must be a mapped method.'); + public function testUnmapped() + { + $this->expectException(Exception::class); + $this->expectExceptionMessage('doesNotExist must be a mapped method.'); $this->app->doesNotExist(); } diff --git a/tests/RedirectTest.php b/tests/RedirectTest.php index 9a5dae1..0f8079a 100644 --- a/tests/RedirectTest.php +++ b/tests/RedirectTest.php @@ -6,72 +6,77 @@ * @license MIT, http://flightphp.com/license */ +use flight\Engine; + require_once 'vendor/autoload.php'; -require_once __DIR__.'/../flight/autoload.php'; +require_once __DIR__ . '/../flight/autoload.php'; -class RedirectTest extends PHPUnit_Framework_TestCase +class RedirectTest extends PHPUnit\Framework\TestCase { - /** - * @var \flight\Engine - */ - private $app; - - function getBaseUrl($base, $url){ - if ($base != '/' && strpos($url, '://') === false) { - $url = preg_replace('#/+#', '/', $base.'/'.$url); - } - - return $url; - } + private Engine $app; - function setUp() { + protected function setUp(): void + { $_SERVER['SCRIPT_NAME'] = '/subdir/index.php'; - $this->app = new \flight\Engine(); + $this->app = new Engine(); $this->app->set('flight.base_url', '/testdir'); } + public function getBaseUrl($base, $url) + { + if ('/' !== $base && false === strpos($url, '://')) { + $url = preg_replace('#/+#', '/', $base . '/' . $url); + } + + return $url; + } + // The base should be the subdirectory - function testBase(){ + public function testBase() + { $base = $this->app->request()->base; - $this->assertEquals('/subdir', $base); + self::assertEquals('/subdir', $base); } // Absolute URLs should include the base - function testAbsoluteUrl(){ + public function testAbsoluteUrl() + { $url = '/login'; $base = $this->app->request()->base; - $this->assertEquals('/subdir/login', $this->getBaseUrl($base, $url)); + self::assertEquals('/subdir/login', $this->getBaseUrl($base, $url)); } // Relative URLs should include the base - function testRelativeUrl(){ + public function testRelativeUrl() + { $url = 'login'; $base = $this->app->request()->base; - $this->assertEquals('/subdir/login', $this->getBaseUrl($base, $url)); + self::assertEquals('/subdir/login', $this->getBaseUrl($base, $url)); } // External URLs should ignore the base - function testHttpUrl(){ + public function testHttpUrl() + { $url = 'http://www.yahoo.com'; $base = $this->app->request()->base; - $this->assertEquals('http://www.yahoo.com', $this->getBaseUrl($base, $url)); + self::assertEquals('http://www.yahoo.com', $this->getBaseUrl($base, $url)); } // Configuration should override derived value - function testBaseOverride(){ + public function testBaseOverride() + { $url = 'login'; - if ($this->app->get('flight.base_url') !== null) { + if (null !== $this->app->get('flight.base_url')) { $base = $this->app->get('flight.base_url'); - } - else { + } else { $base = $this->app->request()->base; } - $this->assertEquals('/testdir/login', $this->getBaseUrl($base, $url)); + self::assertEquals('/testdir/login', $this->getBaseUrl($base, $url)); } } diff --git a/tests/RegisterTest.php b/tests/RegisterTest.php index 37ec2b6..dc383cd 100644 --- a/tests/RegisterTest.php +++ b/tests/RegisterTest.php @@ -6,82 +6,87 @@ * @license MIT, http://flightphp.com/license */ +use flight\Engine; + require_once 'vendor/autoload.php'; -require_once __DIR__.'/../flight/autoload.php'; -require_once __DIR__.'/classes/User.php'; +require_once __DIR__ . '/../flight/autoload.php'; +require_once __DIR__ . '/classes/User.php'; -class RegisterTest extends PHPUnit_Framework_TestCase +class RegisterTest extends PHPUnit\Framework\TestCase { - /** - * @var \flight\Engine - */ - private $app; + private Engine $app; - function setUp() { - $this->app = new \flight\Engine(); + protected function setUp(): void + { + $this->app = new Engine(); } // Register a class - function testRegister(){ + public function testRegister() + { $this->app->register('reg1', 'User'); $user = $this->app->reg1(); - $this->assertTrue(is_object($user)); - $this->assertEquals('User', get_class($user)); - $this->assertEquals('', $user->name); + self::assertIsObject($user); + self::assertInstanceOf(User::class, $user); + self::assertEquals('', $user->name); } // Register a class with constructor parameters - function testRegisterWithConstructor(){ - $this->app->register('reg2', 'User', array('Bob')); + public function testRegisterWithConstructor() + { + $this->app->register('reg2', 'User', ['Bob']); $user = $this->app->reg2(); - $this->assertTrue(is_object($user)); - $this->assertEquals('User', get_class($user)); - $this->assertEquals('Bob', $user->name); + self::assertIsObject($user); + self::assertInstanceOf(User::class, $user); + self::assertEquals('Bob', $user->name); } // Register a class with initialization - function testRegisterWithInitialization(){ - $this->app->register('reg3', 'User', array('Bob'), function($user){ + public function testRegisterWithInitialization() + { + $this->app->register('reg3', 'User', ['Bob'], function ($user) { $user->name = 'Fred'; }); $user = $this->app->reg3(); - $this->assertTrue(is_object($user)); - $this->assertEquals('User', get_class($user)); - $this->assertEquals('Fred', $user->name); + self::assertIsObject($user); + self::assertInstanceOf(User::class, $user); + self::assertEquals('Fred', $user->name); } // Get a non-shared instance of a class - function testSharedInstance() { + public function testSharedInstance() + { $this->app->register('reg4', 'User'); $user1 = $this->app->reg4(); $user2 = $this->app->reg4(); $user3 = $this->app->reg4(false); - $this->assertTrue($user1 === $user2); - $this->assertTrue($user1 !== $user3); + self::assertSame($user1, $user2); + self::assertNotSame($user1, $user3); } // Map method takes precedence over register - function testMapOverridesRegister(){ + public function testMapOverridesRegister() + { $this->app->register('reg5', 'User'); $user = $this->app->reg5(); - $this->assertTrue(is_object($user)); + self::assertIsObject($user); - $this->app->map('reg5', function(){ + $this->app->map('reg5', function () { return 123; }); $user = $this->app->reg5(); - $this->assertEquals(123, $user); + self::assertEquals(123, $user); } } diff --git a/tests/RenderTest.php b/tests/RenderTest.php index 7d20d1e..260caa6 100644 --- a/tests/RenderTest.php +++ b/tests/RenderTest.php @@ -6,31 +6,33 @@ * @license MIT, http://flightphp.com/license */ +use flight\Engine; + require_once 'vendor/autoload.php'; -require_once __DIR__.'/../flight/Flight.php'; +require_once __DIR__ . '/../flight/Flight.php'; -class RenderTest extends PHPUnit_Framework_TestCase +class RenderTest extends PHPUnit\Framework\TestCase { - /** - * @var \flight\Engine - */ - private $app; - - function setUp() { - $this->app = new \flight\Engine(); - $this->app->set('flight.views.path', __DIR__.'/views'); + private Engine $app; + + protected function setUp(): void + { + $this->app = new Engine(); + $this->app->set('flight.views.path', __DIR__ . '/views'); } // Render a view - function testRenderView(){ - $this->app->render('hello', array('name' => 'Bob')); + public function testRenderView() + { + $this->app->render('hello', ['name' => 'Bob']); $this->expectOutputString('Hello, Bob!'); } // Renders a view into a layout - function testRenderLayout(){ - $this->app->render('hello', array('name' => 'Bob'), 'content'); + public function testRenderLayout() + { + $this->app->render('hello', ['name' => 'Bob'], 'content'); $this->app->render('layouts/layout'); $this->expectOutputString('Hello, Bob!'); diff --git a/tests/RequestTest.php b/tests/RequestTest.php index 586790e..2232677 100644 --- a/tests/RequestTest.php +++ b/tests/RequestTest.php @@ -6,17 +6,17 @@ * @license MIT, http://flightphp.com/license */ +use flight\net\Request; + require_once 'vendor/autoload.php'; -require_once __DIR__.'/../flight/autoload.php'; +require_once __DIR__ . '/../flight/autoload.php'; -class RequestTest extends PHPUnit_Framework_TestCase +class RequestTest extends PHPUnit\Framework\TestCase { - /** - * @var \flight\net\Request - */ - private $request; + private Request $request; - function setUp() { + protected function setUp(): void + { $_SERVER['REQUEST_URI'] = '/'; $_SERVER['SCRIPT_NAME'] = '/index.php'; $_SERVER['REQUEST_METHOD'] = 'GET'; @@ -25,47 +25,52 @@ class RequestTest extends PHPUnit_Framework_TestCase $_SERVER['HTTP_X_FORWARDED_FOR'] = '32.32.32.32'; $_SERVER['HTTP_HOST'] = 'example.com'; - $this->request = new \flight\net\Request(); + $this->request = new Request(); } - function testDefaults() { - $this->assertEquals('/', $this->request->url); - $this->assertEquals('/', $this->request->base); - $this->assertEquals('GET', $this->request->method); - $this->assertEquals('', $this->request->referrer); - $this->assertEquals(true, $this->request->ajax); - $this->assertEquals('http', $this->request->scheme); - $this->assertEquals('', $this->request->type); - $this->assertEquals(0, $this->request->length); - $this->assertEquals(false, $this->request->secure); - $this->assertEquals('', $this->request->accept); - $this->assertEquals('example.com', $this->request->host); + public function testDefaults() + { + self::assertEquals('/', $this->request->url); + self::assertEquals('/', $this->request->base); + self::assertEquals('GET', $this->request->method); + self::assertEquals('', $this->request->referrer); + self::assertTrue($this->request->ajax); + self::assertEquals('http', $this->request->scheme); + self::assertEquals('', $this->request->type); + self::assertEquals(0, $this->request->length); + self::assertFalse($this->request->secure); + self::assertEquals('', $this->request->accept); + self::assertEquals('example.com', $this->request->host); } - function testIpAddress() { - $this->assertEquals('8.8.8.8', $this->request->ip); - $this->assertEquals('32.32.32.32', $this->request->proxy_ip); + public function testIpAddress() + { + self::assertEquals('8.8.8.8', $this->request->ip); + self::assertEquals('32.32.32.32', $this->request->proxy_ip); } - function testSubdirectory() { + public function testSubdirectory() + { $_SERVER['SCRIPT_NAME'] = '/subdir/index.php'; - $request = new \flight\net\Request(); + $request = new Request(); - $this->assertEquals('/subdir', $request->base); + self::assertEquals('/subdir', $request->base); } - function testQueryParameters() { + public function testQueryParameters() + { $_SERVER['REQUEST_URI'] = '/page?id=1&name=bob'; - $request = new \flight\net\Request(); + $request = new Request(); - $this->assertEquals('/page?id=1&name=bob', $request->url); - $this->assertEquals(1, $request->query->id); - $this->assertEquals('bob', $request->query->name); + self::assertEquals('/page?id=1&name=bob', $request->url); + self::assertEquals(1, $request->query->id); + self::assertEquals('bob', $request->query->name); } - function testCollections() { + public function testCollections() + { $_SERVER['REQUEST_URI'] = '/page?id=1'; $_GET['q'] = 1; @@ -73,58 +78,61 @@ class RequestTest extends PHPUnit_Framework_TestCase $_COOKIE['q'] = 1; $_FILES['q'] = 1; - $request = new \flight\net\Request(); + $request = new Request(); - $this->assertEquals(1, $request->query->q); - $this->assertEquals(1, $request->query->id); - $this->assertEquals(1, $request->data->q); - $this->assertEquals(1, $request->cookies->q); - $this->assertEquals(1, $request->files->q); + self::assertEquals(1, $request->query->q); + self::assertEquals(1, $request->query->id); + self::assertEquals(1, $request->data->q); + self::assertEquals(1, $request->cookies->q); + self::assertEquals(1, $request->files->q); } - function testMethodOverrideWithHeader() { + public function testMethodOverrideWithHeader() + { $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] = 'PUT'; - $request = new \flight\net\Request(); + $request = new Request(); - $this->assertEquals('PUT', $request->method); + self::assertEquals('PUT', $request->method); } - function testMethodOverrideWithPost() { + public function testMethodOverrideWithPost() + { $_REQUEST['_method'] = 'PUT'; - $request = new \flight\net\Request(); + $request = new Request(); - $this->assertEquals('PUT', $request->method); + self::assertEquals('PUT', $request->method); } - function testHttps() { + public function testHttps() + { $_SERVER['HTTPS'] = 'on'; - $request = new \flight\net\Request(); - $this->assertEquals('https', $request->scheme); + $request = new Request(); + self::assertEquals('https', $request->scheme); $_SERVER['HTTPS'] = 'off'; - $request = new \flight\net\Request(); - $this->assertEquals('http', $request->scheme); + $request = new Request(); + self::assertEquals('http', $request->scheme); $_SERVER['HTTP_X_FORWARDED_PROTO'] = 'https'; - $request = new \flight\net\Request(); - $this->assertEquals('https', $request->scheme); + $request = new Request(); + self::assertEquals('https', $request->scheme); $_SERVER['HTTP_X_FORWARDED_PROTO'] = 'http'; - $request = new \flight\net\Request(); - $this->assertEquals('http', $request->scheme); + $request = new Request(); + self::assertEquals('http', $request->scheme); $_SERVER['HTTP_FRONT_END_HTTPS'] = 'on'; - $request = new \flight\net\Request(); - $this->assertEquals('https', $request->scheme); + $request = new Request(); + self::assertEquals('https', $request->scheme); $_SERVER['HTTP_FRONT_END_HTTPS'] = 'off'; - $request = new \flight\net\Request(); - $this->assertEquals('http', $request->scheme); + $request = new Request(); + self::assertEquals('http', $request->scheme); $_SERVER['REQUEST_SCHEME'] = 'https'; - $request = new \flight\net\Request(); - $this->assertEquals('https', $request->scheme); + $request = new Request(); + self::assertEquals('https', $request->scheme); $_SERVER['REQUEST_SCHEME'] = 'http'; - $request = new \flight\net\Request(); - $this->assertEquals('http', $request->scheme); + $request = new Request(); + self::assertEquals('http', $request->scheme); } } diff --git a/tests/RouterTest.php b/tests/RouterTest.php index 8b20853..686d91f 100644 --- a/tests/RouterTest.php +++ b/tests/RouterTest.php @@ -6,39 +6,37 @@ * @license MIT, http://flightphp.com/license */ +use flight\core\Dispatcher; +use flight\net\Request; +use flight\net\Router; + require_once 'vendor/autoload.php'; -require_once __DIR__.'/../flight/autoload.php'; +require_once __DIR__ . '/../flight/autoload.php'; -class RouterTest extends PHPUnit_Framework_TestCase +class RouterTest extends PHPUnit\Framework\TestCase { - /** - * @var \flight\net\Router - */ - private $router; - - /** - * @var \flight\net\Request - */ - private $request; - - /** - * @var \flight\core\Dispatcher - */ - private $dispatcher; - - function setUp(){ - $this->router = new \flight\net\Router(); - $this->request = new \flight\net\Request(); - $this->dispatcher = new \flight\core\Dispatcher(); + private Router $router; + + private Request $request; + + private Dispatcher $dispatcher; + + protected function setUp(): void + { + $this->router = new Router(); + $this->request = new Request(); + $this->dispatcher = new Dispatcher(); } // Simple output - function ok(){ + public function ok() + { echo 'OK'; } // Checks if a route was matched with a given output - function check($str = '') { + public function check($str = '') + { /* $route = $this->router->route($this->request); @@ -53,7 +51,8 @@ class RouterTest extends PHPUnit_Framework_TestCase $this->expectOutputString($str); } - function routeRequest() { + public function routeRequest() + { $dispatched = false; while ($route = $this->router->route($this->request)) { @@ -70,7 +69,9 @@ class RouterTest extends PHPUnit_Framework_TestCase $dispatched = true; - if (!$continue) break; + if (!$continue) { + break; + } $this->router->next(); @@ -83,24 +84,27 @@ class RouterTest extends PHPUnit_Framework_TestCase } // Default route - function testDefaultRoute(){ - $this->router->map('/', array($this, 'ok')); + public function testDefaultRoute() + { + $this->router->map('/', [$this, 'ok']); $this->request->url = '/'; $this->check('OK'); } // Simple path - function testPathRoute(){ - $this->router->map('/path', array($this, 'ok')); + public function testPathRoute() + { + $this->router->map('/path', [$this, 'ok']); $this->request->url = '/path'; $this->check('OK'); } // POST route - function testPostRoute(){ - $this->router->map('POST /', array($this, 'ok')); + public function testPostRoute() + { + $this->router->map('POST /', [$this, 'ok']); $this->request->url = '/'; $this->request->method = 'POST'; @@ -108,8 +112,9 @@ class RouterTest extends PHPUnit_Framework_TestCase } // Either GET or POST route - function testGetPostRoute(){ - $this->router->map('GET|POST /', array($this, 'ok')); + public function testGetPostRoute() + { + $this->router->map('GET|POST /', [$this, 'ok']); $this->request->url = '/'; $this->request->method = 'GET'; @@ -117,16 +122,18 @@ class RouterTest extends PHPUnit_Framework_TestCase } // Test regular expression matching - function testRegEx(){ - $this->router->map('/num/[0-9]+', array($this, 'ok')); + public function testRegEx() + { + $this->router->map('/num/[0-9]+', [$this, 'ok']); $this->request->url = '/num/1234'; $this->check('OK'); } // Passing URL parameters - function testUrlParameters(){ - $this->router->map('/user/@id', function($id){ + public function testUrlParameters() + { + $this->router->map('/user/@id', function ($id) { echo $id; }); $this->request->url = '/user/123'; @@ -135,8 +142,9 @@ class RouterTest extends PHPUnit_Framework_TestCase } // Passing URL parameters matched with regular expression - function testRegExParameters(){ - $this->router->map('/test/@name:[a-z]+', function($name){ + public function testRegExParameters() + { + $this->router->map('/test/@name:[a-z]+', function ($name) { echo $name; }); $this->request->url = '/test/abc'; @@ -145,8 +153,9 @@ class RouterTest extends PHPUnit_Framework_TestCase } // Optional parameters - function testOptionalParameters(){ - $this->router->map('/blog(/@year(/@month(/@day)))', function($year, $month, $day){ + public function testOptionalParameters() + { + $this->router->map('/blog(/@year(/@month(/@day)))', function ($year, $month, $day) { echo "$year,$month,$day"; }); $this->request->url = '/blog/2000'; @@ -155,8 +164,9 @@ class RouterTest extends PHPUnit_Framework_TestCase } // Regex in optional parameters - function testRegexOptionalParameters(){ - $this->router->map('/@controller/@method(/@id:[0-9]+)', function($controller, $method, $id){ + public function testRegexOptionalParameters() + { + $this->router->map('/@controller/@method(/@id:[0-9]+)', function ($controller, $method, $id) { echo "$controller,$method,$id"; }); $this->request->url = '/user/delete/123'; @@ -165,8 +175,9 @@ class RouterTest extends PHPUnit_Framework_TestCase } // Regex in optional parameters - function testRegexEmptyOptionalParameters(){ - $this->router->map('/@controller/@method(/@id:[0-9]+)', function($controller, $method, $id){ + public function testRegexEmptyOptionalParameters() + { + $this->router->map('/@controller/@method(/@id:[0-9]+)', function ($controller, $method, $id) { echo "$controller,$method,$id"; }); $this->request->url = '/user/delete/'; @@ -175,39 +186,42 @@ class RouterTest extends PHPUnit_Framework_TestCase } // Wildcard matching - function testWildcard(){ - $this->router->map('/account/*', array($this, 'ok')); + public function testWildcard() + { + $this->router->map('/account/*', [$this, 'ok']); $this->request->url = '/account/123/abc/xyz'; $this->check('OK'); } // Check if route object was passed - function testRouteObjectPassing(){ - $this->router->map('/yes_route', function($route){ - $this->assertTrue(is_object($route)); - $this->assertTrue(is_array($route->methods)); - $this->assertTrue(is_array($route->params)); - $this->assertEquals(sizeof($route->params), 0); - $this->assertEquals($route->regex, null); - $this->assertEquals($route->splat, ''); + public function testRouteObjectPassing() + { + $this->router->map('/yes_route', function ($route) { + $this->assertIsObject($route); + $this->assertIsArray($route->methods); + $this->assertIsArray($route->params); + $this->assertCount(0, $route->params); + $this->assertNull($route->regex); + $this->assertEquals('', $route->splat); $this->assertTrue($route->pass); }, true); $this->request->url = '/yes_route'; $this->check(); - $this->router->map('/no_route', function($route = null){ - $this->assertTrue(is_null($route)); + $this->router->map('/no_route', function ($route = null) { + $this->assertNull($route); }, false); $this->request->url = '/no_route'; $this->check(); } - function testRouteWithParameters() { - $this->router->map('/@one/@two', function($one, $two, $route){ - $this->assertEquals(sizeof($route->params), 2); + public function testRouteWithParameters() + { + $this->router->map('/@one/@two', function ($one, $two, $route) { + $this->assertCount(2, $route->params); $this->assertEquals($route->params['one'], $one); $this->assertEquals($route->params['two'], $two); }, true); @@ -217,8 +231,9 @@ class RouterTest extends PHPUnit_Framework_TestCase } // Test splat - function testSplatWildcard(){ - $this->router->map('/account/*', function($route){ + public function testSplatWildcard() + { + $this->router->map('/account/*', function ($route) { echo $route->splat; }, true); $this->request->url = '/account/456/def/xyz'; @@ -227,8 +242,9 @@ class RouterTest extends PHPUnit_Framework_TestCase } // Test splat without trailing slash - function testSplatWildcardTrailingSlash(){ - $this->router->map('/account/*', function($route){ + public function testSplatWildcardTrailingSlash() + { + $this->router->map('/account/*', function ($route) { echo $route->splat; }, true); $this->request->url = '/account'; @@ -237,42 +253,44 @@ class RouterTest extends PHPUnit_Framework_TestCase } // Test splat with named parameters - function testSplatNamedPlusWildcard(){ - $this->router->map('/account/@name/*', function($name, $route){ - echo $route->splat; - $this->assertEquals('abc', $name); - }, true); + public function testSplatNamedPlusWildcard() + { + $this->router->map('/account/@name/*', function ($name, $route) { + echo $route->splat; + $this->assertEquals('abc', $name); + }, true); $this->request->url = '/account/abc/456/def/xyz'; $this->check('456/def/xyz'); } // Test not found - function testNotFound() { - $this->router->map('/does_exist', array($this, 'ok')); + public function testNotFound() + { + $this->router->map('/does_exist', [$this, 'ok']); $this->request->url = '/does_not_exist'; $this->check('404'); } // Test case sensitivity - function testCaseSensitivity() { - $this->router->map('/hello', array($this, 'ok')); + public function testCaseSensitivity() + { + $this->router->map('/hello', [$this, 'ok']); $this->request->url = '/HELLO'; $this->router->case_sensitive = true; $this->check('404'); } - // Passing URL parameters matched with regular expression for a URL containing Cyrillic letters: - function testRegExParametersCyrillic(){ - $this->router->map('/категория/@name:[абвгдеёжзийклмнопрстуфхцчшщъыьэюя]+', function($name){ + public function testRegExParametersCyrillic() + { + $this->router->map('/категория/@name:[абвгдеёжзийклмнопрстуфхцчшщъыьэюя]+', function ($name) { echo $name; }); $this->request->url = urlencode('/категория/цветя'); $this->check('цветя'); } - } diff --git a/tests/VariableTest.php b/tests/VariableTest.php index 8c2fc0c..e7a610d 100644 --- a/tests/VariableTest.php +++ b/tests/VariableTest.php @@ -5,45 +5,50 @@ * @copyright Copyright (c) 2012, Mike Cao * @license MIT, http://flightphp.com/license */ - require_once 'vendor/autoload.php'; -require_once __DIR__.'/../flight/autoload.php'; +require_once __DIR__ . '/../flight/autoload.php'; -class VariableTest extends PHPUnit_Framework_TestCase +class VariableTest extends PHPUnit\Framework\TestCase { /** * @var \flight\Engine */ private $app; - function setUp() { + protected function setUp(): void + { $this->app = new \flight\Engine(); } + // Set and get a variable - function testSetAndGet() { + public function testSetAndGet() + { $this->app->set('a', 1); $var = $this->app->get('a'); $this->assertEquals(1, $var); } // Clear a specific variable - function testClear() { + public function testClear() + { $this->app->set('b', 1); $this->app->clear('b'); $var = $this->app->get('b'); - $this->assertEquals(null, $var); + $this->assertNull($var); } // Clear all variables - function testClearAll() { + public function testClearAll() + { $this->app->set('c', 1); $this->app->clear(); $var = $this->app->get('c'); - $this->assertEquals(null, $var); + $this->assertNull($var); } // Check if a variable exists - function testHas() { + public function testHas() + { $this->app->set('d', 1); $this->assertTrue($this->app->has('d')); } diff --git a/tests/ViewTest.php b/tests/ViewTest.php index 82572d4..bdfcfeb 100644 --- a/tests/ViewTest.php +++ b/tests/ViewTest.php @@ -5,24 +5,25 @@ * @copyright Copyright (c) 2012, Mike Cao * @license MIT, http://flightphp.com/license */ - require_once 'vendor/autoload.php'; -require_once __DIR__.'/../flight/autoload.php'; +require_once __DIR__ . '/../flight/autoload.php'; -class ViewTest extends PHPUnit_Framework_TestCase +class ViewTest extends PHPUnit\Framework\TestCase { /** * @var \flight\template\View */ private $view; - function setUp() { + protected function setUp(): void + { $this->view = new \flight\template\View(); - $this->view->path = __DIR__.'/views'; + $this->view->path = __DIR__ . '/views'; } // Set template variables - function testVariables() { + public function testVariables() + { $this->view->set('test', 123); $this->assertEquals(123, $this->view->get('test')); @@ -32,31 +33,35 @@ class ViewTest extends PHPUnit_Framework_TestCase $this->view->clear('test'); - $this->assertEquals(null, $this->view->get('test')); + $this->assertNull($this->view->get('test')); } // Check if template files exist - function testTemplateExists() { + public function testTemplateExists() + { $this->assertTrue($this->view->exists('hello.php')); $this->assertTrue(!$this->view->exists('unknown.php')); } // Render a template - function testRender() { - $this->view->render('hello', array('name' => 'Bob')); + public function testRender() + { + $this->view->render('hello', ['name' => 'Bob']); $this->expectOutputString('Hello, Bob!'); } // Fetch template output - function testFetch() { - $output = $this->view->fetch('hello', array('name' => 'Bob')); + public function testFetch() + { + $output = $this->view->fetch('hello', ['name' => 'Bob']); $this->assertEquals('Hello, Bob!', $output); } // Default extension - function testTemplateWithExtension() { + public function testTemplateWithExtension() + { $this->view->set('name', 'Bob'); $this->view->render('hello.php'); @@ -65,7 +70,8 @@ class ViewTest extends PHPUnit_Framework_TestCase } // Custom extension - function testTemplateWithCustomExtension() { + public function testTemplateWithCustomExtension() + { $this->view->set('name', 'Bob'); $this->view->extension = '.html'; diff --git a/tests/classes/Factory.php b/tests/classes/Factory.php index 1136d22..0d794fc 100644 --- a/tests/classes/Factory.php +++ b/tests/classes/Factory.php @@ -1,11 +1,14 @@ name = $name; } -} \ No newline at end of file +} From 5b8e743e3dafb4e92161f048e0cd51e31956be4f Mon Sep 17 00:00:00 2001 From: Masroor Ehsan Date: Sat, 8 May 2021 23:04:44 +0200 Subject: [PATCH 04/13] refactorings --- flight/Engine.php | 133 ++++++++++++++++++++++++++++-------------- flight/net/Router.php | 6 +- index.php | 3 +- 3 files changed, 95 insertions(+), 47 deletions(-) diff --git a/flight/Engine.php b/flight/Engine.php index 5ad9238..f8c996b 100644 --- a/flight/Engine.php +++ b/flight/Engine.php @@ -86,7 +86,7 @@ class Engine * @param string $name Method name * @param array $params Method parameters * - *@throws Exception + * @throws Exception * * @return mixed Callback results */ @@ -102,7 +102,7 @@ class Engine throw new Exception("{$name} must be a mapped method."); } - $shared = (!empty($params)) ? (bool) $params[0] : true; + $shared = empty($params) || $params[0]; return $this->loader->load($name, $shared); } @@ -124,10 +124,10 @@ class Engine } // Register default components - $this->loader->register('request', '\flight\net\Request'); - $this->loader->register('response', '\flight\net\Response'); - $this->loader->register('router', '\flight\net\Router'); - $this->loader->register('view', '\flight\template\View', [], function ($view) use ($self) { + $this->loader->register('request', Request::class); + $this->loader->register('response', Response::class); + $this->loader->register('router', Router::class); + $this->loader->register('view', View::class, [], function ($view) use ($self) { $view->path = $self->get('flight.views.path'); $view->extension = $self->get('flight.views.extension'); }); @@ -136,13 +136,14 @@ class Engine $methods = [ 'start', 'stop', 'route', 'halt', 'error', 'notFound', 'render', 'redirect', 'etag', 'lastModified', 'json', 'jsonp', + 'post', 'put', 'patch', 'delete', ]; foreach ($methods as $name) { $this->dispatcher->set($name, [$this, '_' . $name]); } // Default configuration settings - $this->set('flight.base_url', null); + $this->set('flight.base_url'); $this->set('flight.case_sensitive', false); $this->set('flight.handle_errors', true); $this->set('flight.log_errors', false); @@ -170,14 +171,14 @@ class Engine /** * Custom error handler. Converts errors into exceptions. * - * @param int $errno Error number - * @param int $errstr Error string - * @param int $errfile Error file name - * @param int $errline Error file line number + * @param int $errno Error number + * @param string $errstr Error string + * @param string $errfile Error file name + * @param int $errline Error file line number * * @throws ErrorException */ - public function handleError(int $errno, int $errstr, int $errfile, int $errline) + public function handleError(int $errno, string $errstr, string $errfile, int $errline) { if ($errno & error_reporting()) { throw new ErrorException($errstr, $errno, 0, $errfile, $errline); @@ -384,6 +385,32 @@ class Engine } } + /** + * Sends an HTTP 500 response for any errors. + * + * @param Throwable $e Thrown exception + */ + public function _error($e): void + { + $msg = sprintf('

500 Internal Server Error

' . + '

%s (%s)

' . + '
%s
', + $e->getMessage(), + $e->getCode(), + $e->getTraceAsString() + ); + + try { + $this->response() + ->clear() + ->status(500) + ->write($msg) + ->send(); + } catch (Throwable $t) { + exit($msg); + } + } + /** * Stops the framework and outputs the current response. * @@ -418,6 +445,54 @@ class Engine $this->router()->map($pattern, $callback, $pass_route); } + /** + * Routes a URL to a callback function. + * + * @param string $pattern URL pattern to match + * @param callback $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 + { + $this->router()->map('POST ' . $pattern, $callback, $pass_route); + } + + /** + * Routes a URL to a callback function. + * + * @param string $pattern URL pattern to match + * @param callback $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 + { + $this->router()->map('PUT ' . $pattern, $callback, $pass_route); + } + + /** + * Routes a URL to a callback function. + * + * @param string $pattern URL pattern to match + * @param callback $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 + { + $this->router()->map('PATCH ' . $pattern, $callback, $pass_route); + } + + /** + * Routes a URL to a callback function. + * + * @param string $pattern URL pattern to match + * @param callback $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 + { + $this->router()->map('DELETE ' . $pattern, $callback, $pass_route); + } + /** * Stops processing and returns a given response. * @@ -434,34 +509,6 @@ class Engine exit(); } - /** - * Sends an HTTP 500 response for any errors. - * - * @param Exception|Throwable $e Thrown exception - */ - public function _error($e): void - { - $msg = sprintf('

500 Internal Server Error

' . - '

%s (%s)

' . - '
%s
', - $e->getMessage(), - $e->getCode(), - $e->getTraceAsString() - ); - - try { - $this->response() - ->clear() - ->status(500) - ->write($msg) - ->send(); - } catch (Throwable $t) { // PHP 7.0+ - exit($msg); - } catch (Exception $e) { // PHP < 7 - exit($msg); - } - } - /** * Sends an HTTP 404 response when a URL is not found. */ @@ -493,7 +540,7 @@ class Engine } // Append base url to redirect url - if ('/' != $base && false === strpos($url, '://')) { + if ('/' !== $base && false === strpos($url, '://')) { $url = $base . preg_replace('#/+#', '/', '/' . $url); } @@ -540,7 +587,7 @@ class Engine string $charset = 'utf-8', int $option = 0 ): void { - $json = ($encode) ? json_encode($data, $option) : $data; + $json = $encode ? json_encode($data, $option) : $data; $this->response() ->status($code) @@ -569,7 +616,7 @@ class Engine string $charset = 'utf-8', int $option = 0 ): void { - $json = ($encode) ? json_encode($data, $option) : $data; + $json = $encode ? json_encode($data, $option) : $data; $callback = $this->request()->query[$param]; diff --git a/flight/net/Router.php b/flight/net/Router.php index c85dba4..dcf1591 100644 --- a/flight/net/Router.php +++ b/flight/net/Router.php @@ -58,11 +58,11 @@ class Router */ public function map(string $pattern, callable $callback, bool $pass_route = false): void { - $url = $pattern; + $url = trim($pattern); $methods = ['*']; - if (false !== strpos($pattern, ' ')) { - [$method, $url] = explode(' ', trim($pattern), 2); + if (false !== strpos($url, ' ')) { + [$method, $url] = explode(' ', $url, 2); $url = trim($url); $methods = explode('|', $method); } diff --git a/index.php b/index.php index b774464..65cdd7e 100644 --- a/index.php +++ b/index.php @@ -1,7 +1,8 @@ Date: Sun, 9 May 2021 10:26:47 +0200 Subject: [PATCH 05/13] minor refactorings --- flight/net/Request.php | 19 +++++++-------- flight/net/Response.php | 54 ++++++++++++++++++++--------------------- 2 files changed, 36 insertions(+), 37 deletions(-) diff --git a/flight/net/Request.php b/flight/net/Request.php index 670ce79..a9f1500 100644 --- a/flight/net/Request.php +++ b/flight/net/Request.php @@ -143,7 +143,7 @@ final class Request 'method' => self::getMethod(), 'referrer' => self::getVar('HTTP_REFERER'), 'ip' => self::getVar('REMOTE_ADDR'), - 'ajax' => 'XMLHttpRequest' == self::getVar('HTTP_X_REQUESTED_WITH'), + 'ajax' => 'XMLHttpRequest' === self::getVar('HTTP_X_REQUESTED_WITH'), 'scheme' => self::getScheme(), 'user_agent' => self::getVar('HTTP_USER_AGENT'), 'type' => self::getVar('CONTENT_TYPE'), @@ -152,7 +152,7 @@ final class Request 'data' => new Collection($_POST), 'cookies' => new Collection($_COOKIE), 'files' => new Collection($_FILES), - 'secure' => 'https' == self::getScheme(), + 'secure' => 'https' === self::getScheme(), 'accept' => self::getVar('HTTP_ACCEPT'), 'proxy_ip' => self::getProxyIpAddress(), 'host' => self::getVar('HTTP_HOST'), @@ -175,27 +175,26 @@ final class Request } // Get the requested URL without the base directory - if ('/' != $this->base && \strlen($this->base) > 0 && 0 === strpos($this->url, $this->base)) { + if ('/' !== $this->base && '' !== $this->base && 0 === strpos($this->url, $this->base)) { $this->url = substr($this->url, \strlen($this->base)); } // Default url if (empty($this->url)) { $this->url = '/'; - } - // Merge URL query parameters with $_GET - else { - $_GET += self::parseQuery($this->url); + } else { + // Merge URL query parameters with $_GET + $_GET = array_merge($_GET, self::parseQuery($this->url)); $this->query->setData($_GET); } // Check for JSON input if (0 === strpos($this->type, 'application/json')) { - $body = $this->getBody(); - if ('' != $body) { + $body = self::getBody(); + if ('' !== $body) { $data = json_decode($body, true); - if (null != $data) { + if (null !== $data) { $this->data->setData($data); } } diff --git a/flight/net/Response.php b/flight/net/Response.php index 38c4dcc..089c276 100644 --- a/flight/net/Response.php +++ b/flight/net/Response.php @@ -10,6 +10,8 @@ declare(strict_types=1); namespace flight\net; +use Exception; + /** * The Response class represents an HTTP response. The object * contains the response headers, HTTP status code, and response @@ -19,15 +21,13 @@ class Response { /** * header Content-Length. - * - * @var bool */ - public $content_length = true; + public bool $content_length = true; /** * @var array HTTP status codes */ - public static $codes = [ + public static array $codes = [ 100 => 'Continue', 101 => 'Switching Protocols', 102 => 'Processing', @@ -100,33 +100,33 @@ class Response /** * @var int HTTP status */ - protected $status = 200; + protected int $status = 200; /** * @var array HTTP headers */ - protected $headers = []; + protected array $headers = []; /** * @var string HTTP response body */ - protected $body; + protected string $body; /** * @var bool HTTP response sent */ - protected $sent = false; + protected bool $sent = false; /** * Sets the HTTP status of the response. * - * @param int $code HTTP status code. + * @param int|null $code HTTP status code. * - * @throws \Exception If invalid status code + * @throws Exception If invalid status code * * @return int|object Self reference */ - public function status($code = null) + public function status(?int $code = null) { if (null === $code) { return $this->status; @@ -135,7 +135,7 @@ class Response if (\array_key_exists($code, self::$codes)) { $this->status = $code; } else { - throw new \Exception('Invalid status code.'); + throw new Exception('Invalid status code.'); } return $this; @@ -145,11 +145,11 @@ class Response * Adds a header to the response. * * @param array|string $name Header name or array of names and values - * @param string $value Header value + * @param string|null $value Header value * * @return object Self reference */ - public function header($name, $value = null) + public function header($name, ?string $value = null) { if (\is_array($name)) { foreach ($name as $k => $v) { @@ -177,9 +177,9 @@ class Response * * @param string $str Response content * - * @return object Self reference + * @return Response Self reference */ - public function write($str) + public function write(string $str): self { $this->body .= $str; @@ -189,9 +189,9 @@ class Response /** * Clears the response. * - * @return object Self reference + * @return Response Self reference */ - public function clear() + public function clear(): self { $this->status = 200; $this->headers = []; @@ -205,9 +205,9 @@ class Response * * @param int|string $expires Expiration time * - * @return object Self reference + * @return Response Self reference */ - public function cache($expires) + public function cache($expires): self { if (false === $expires) { $this->headers['Expires'] = 'Mon, 26 Jul 1997 05:00:00 GMT'; @@ -232,9 +232,9 @@ class Response /** * Sends HTTP headers. * - * @return object Self reference + * @return Response Self reference */ - public function sendHeaders() + public function sendHeaders(): self { // Send status code header if (false !== strpos(\PHP_SAPI, 'cgi')) { @@ -250,7 +250,7 @@ class Response header( sprintf( '%s %d %s', - ($_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.1'), + $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.1', $this->status, self::$codes[$this->status]), true, @@ -284,9 +284,9 @@ class Response /** * Gets the content length. * - * @return string Content length + * @return int Content length */ - public function getContentLength() + public function getContentLength(): int { return \extension_loaded('mbstring') ? mb_strlen($this->body, 'latin1') : @@ -296,7 +296,7 @@ class Response /** * Gets whether response was sent. */ - public function sent() + public function sent(): bool { return $this->sent; } @@ -304,7 +304,7 @@ class Response /** * Sends a HTTP response. */ - public function send() + public function send(): void { if (ob_get_length() > 0) { ob_end_clean(); From 8b513516cbc1e35042003706b1e0d1e1e26bc82a Mon Sep 17 00:00:00 2001 From: Masroor Ehsan Date: Sun, 8 Aug 2021 19:54:17 +0600 Subject: [PATCH 06/13] updated readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 709a45c..61083e9 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Flight::start(); # Requirements -Flight requires `PHP 5.3` or greater. +Flight requires `PHP 7.4` or greater. # License From 132657ab58235d7147b3e1c25430a680861b444f Mon Sep 17 00:00:00 2001 From: Masroor Ehsan Date: Sun, 8 Aug 2021 20:35:54 +0600 Subject: [PATCH 07/13] updated PHP requirements --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 11df056..60fa000 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "mikecao/flight", + "name": "masroore/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", From b7abb4945f2ce63a2009c6ab29ceef4795693cb5 Mon Sep 17 00:00:00 2001 From: Masroor Ehsan Date: Mon, 23 Aug 2021 22:47:36 +0600 Subject: [PATCH 08/13] check for valid json input --- flight/net/Request.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flight/net/Request.php b/flight/net/Request.php index a9f1500..d340510 100644 --- a/flight/net/Request.php +++ b/flight/net/Request.php @@ -194,7 +194,7 @@ final class Request $body = self::getBody(); if ('' !== $body) { $data = json_decode($body, true); - if (null !== $data) { + if (is_array($data)) { $this->data->setData($data); } } From 45e93a67407d3dc51d0a3e6c05a60d2f586f05f0 Mon Sep 17 00:00:00 2001 From: Max Ehsan Date: Wed, 25 Aug 2021 09:23:03 +0600 Subject: [PATCH 09/13] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 61083e9..50be938 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Flight is released under the [MIT](http://flightphp.com/license) license. If you're using [Composer](https://getcomposer.org/), you can run the following command: ``` -composer require mikecao/flight +composer require masroore/flight ``` OR you can [download](https://github.com/mikecao/flight/archive/master.zip) them directly From e18765f7d51ee1865c7105a706316e7503fbc277 Mon Sep 17 00:00:00 2001 From: Masroor Ehsan Date: Wed, 25 Aug 2021 09:32:27 +0600 Subject: [PATCH 10/13] updated package description --- composer.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 60fa000..b5210b5 100644 --- a/composer.json +++ b/composer.json @@ -9,10 +9,15 @@ "email": "mike@mikecao.com", "homepage": "http://www.mikecao.com/", "role": "Original Developer" + }, + { + "name": "Masroor Ehsan", + "email": "masroore@gmail.com", + "homepage": "https://github.com/masroore/flight/" } ], "require": { - "php": "^7.4|^8.0", + "php": ">=7.4", "ext-json": "*" }, "autoload": { From dd73a61c32e96b47d9414aeb522f9bbe764d0fd9 Mon Sep 17 00:00:00 2001 From: Masroor Ehsan Date: Sun, 12 Dec 2021 15:02:53 +0200 Subject: [PATCH 11/13] FIX: Typed property flight\net\Response::$body must not be accessed before initialization --- flight/net/Response.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flight/net/Response.php b/flight/net/Response.php index 089c276..e21fc9a 100644 --- a/flight/net/Response.php +++ b/flight/net/Response.php @@ -110,7 +110,7 @@ class Response /** * @var string HTTP response body */ - protected string $body; + protected string $body = ''; /** * @var bool HTTP response sent From 83fd1e75a2fcc6aecb180697512fe24fdbbe76e8 Mon Sep 17 00:00:00 2001 From: Masroor Ehsan Date: Sun, 12 Dec 2021 15:09:01 +0200 Subject: [PATCH 12/13] Added ReturnTypeWillChange attribute to Collection class methods --- flight/util/Collection.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/flight/util/Collection.php b/flight/util/Collection.php index 883dccf..97658ec 100644 --- a/flight/util/Collection.php +++ b/flight/util/Collection.php @@ -93,6 +93,7 @@ final class Collection implements ArrayAccess, Iterator, Countable, JsonSerializ * * @return mixed Value */ + #[\ReturnTypeWillChange] public function offsetGet($offset) { return $this->data[$offset] ?? null; @@ -104,6 +105,7 @@ final class Collection implements ArrayAccess, Iterator, Countable, JsonSerializ * @param string $offset Offset * @param mixed $value Value */ + #[\ReturnTypeWillChange] public function offsetSet($offset, $value) { if (null === $offset) { @@ -148,6 +150,7 @@ final class Collection implements ArrayAccess, Iterator, Countable, JsonSerializ * * @return mixed Value */ + #[\ReturnTypeWillChange] public function current() { return current($this->data); @@ -158,6 +161,7 @@ final class Collection implements ArrayAccess, Iterator, Countable, JsonSerializ * * @return mixed Value */ + #[\ReturnTypeWillChange] public function key() { return key($this->data); @@ -168,6 +172,7 @@ final class Collection implements ArrayAccess, Iterator, Countable, JsonSerializ * * @return mixed Value */ + #[\ReturnTypeWillChange] public function next() { return next($this->data); From c0f83495cc2bdb802a969de799a354d6d768a450 Mon Sep 17 00:00:00 2001 From: Masroor Ehsan Date: Sun, 12 Dec 2021 15:15:07 +0200 Subject: [PATCH 13/13] updated README --- README.md | 2 +- composer.json | 9 ++------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 50be938..61083e9 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Flight is released under the [MIT](http://flightphp.com/license) license. If you're using [Composer](https://getcomposer.org/), you can run the following command: ``` -composer require masroore/flight +composer require mikecao/flight ``` OR you can [download](https://github.com/mikecao/flight/archive/master.zip) them directly diff --git a/composer.json b/composer.json index b5210b5..8e2611b 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "masroore/flight", + "name": "mikecao/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,15 +9,10 @@ "email": "mike@mikecao.com", "homepage": "http://www.mikecao.com/", "role": "Original Developer" - }, - { - "name": "Masroor Ehsan", - "email": "masroore@gmail.com", - "homepage": "https://github.com/masroore/flight/" } ], "require": { - "php": ">=7.4", + "php": "^7.4|^8.0|^8.1", "ext-json": "*" }, "autoload": {