Merge pull request #538 from flightphp/dev

Dispatcher improvements.
pull/539/head v3.4.0
n0nag0n 12 months ago committed by GitHub
commit 6e40f791c2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -5,3 +5,6 @@ indent_style = space
indent_size = 4 indent_size = 4
end_of_line = lf end_of_line = lf
charset = utf-8 charset = utf-8
[*.md]
indent_size = 2

@ -28,8 +28,7 @@
}, },
"autoload": { "autoload": {
"files": [ "files": [
"flight/autoload.php", "flight/autoload.php"
"flight/Flight.php"
] ]
}, },
"autoload-dev": { "autoload-dev": {

@ -6,6 +6,7 @@
} }
], ],
"settings": { "settings": {
"SublimeLinter.linters.phpstan.executable": "${project_path}/vendor/bin/phpstan.bat",
"LSP": { "LSP": {
"LSP-intelephense": { "LSP-intelephense": {
"settings": { "settings": {
@ -40,12 +41,12 @@
}, },
{ {
"name": "Linter - Default", "name": "Linter - Default",
"quiet": true, "quiet": false,
"shell_cmd": "composer lint -- --no-ansi & composer phpcs -- --no-colors", "shell_cmd": "composer lint -- --no-ansi & composer phpcs -- --no-colors",
}, },
{ {
"name": "PHPCS", "name": "PHPCS",
"quiet": true, "quiet": false,
"shell_cmd": "composer phpcs -- --no-colors" "shell_cmd": "composer phpcs -- --no-colors"
}, },
{ {

@ -32,7 +32,7 @@ use flight\net\Route;
* # Routing * # Routing
* @method Route route(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') * @method Route route(string $pattern, callable $callback, bool $pass_route = false, string $alias = '')
* Routes a URL to a callback function with all applicable methods * Routes a URL to a callback function with all applicable methods
* @method void group(string $pattern, callable $callback, array $group_middlewares = []) * @method void group(string $pattern, callable $callback, array<int, callable|object> $group_middlewares = [])
* Groups a set of routes together under a common prefix. * Groups a set of routes together under a common prefix.
* @method Route post(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') * @method Route post(string $pattern, callable $callback, bool $pass_route = false, string $alias = '')
* Routes a POST URL to a callback function. * Routes a POST URL to a callback function.
@ -46,7 +46,7 @@ use flight\net\Route;
* @method string getUrl(string $alias) Gets a url from an alias * @method string getUrl(string $alias) Gets a url from an alias
* *
* # Views * # Views
* @method void render(string $file, array $data = null, string $key = null) Renders template * @method void render(string $file, ?array $data = null, ?string $key = null) Renders template
* @method View view() Gets current view * @method View view() Gets current view
* *
* # Request-Response * # Request-Response
@ -57,48 +57,31 @@ use flight\net\Route;
* @method void redirect(string $url, int $code = 303) Redirects the current request to another URL. * @method void redirect(string $url, int $code = 303) Redirects the current request to another URL.
* @method void json(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf-8', int $option = 0) * @method void json(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf-8', int $option = 0)
* Sends a JSON response. * Sends a JSON response.
* @method void jsonp(mixed $data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = 'utf-8', int $option = 0) Sends a JSONP response. * @method void jsonp(mixed $data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = 'utf-8', int $option = 0)
* Sends a JSONP response.
* *
* # HTTP caching * # HTTP caching
* @method void etag($id, string $type = 'strong') Handles ETag HTTP caching. * @method void etag(string $id, ('strong'|'weak') $type = 'strong') Handles ETag HTTP caching.
* @method void lastModified(int $time) Handles last modified HTTP caching. * @method void lastModified(int $time) Handles last modified HTTP caching.
*/ */
// phpcs:ignoreFile Generic.Files.LineLength.TooLong, PSR2.Methods.MethodDeclaration.Underscore
class Engine class Engine
{ {
/** /** @var array<string, mixed> Stored variables. */
* Stored variables. protected array $vars = [];
* @var array<string, mixed>
*/
protected array $vars;
/** /** Class loader. */
* Class loader.
*/
protected Loader $loader; protected Loader $loader;
/** /** Event dispatcher. */
* Event dispatcher.
*/
protected Dispatcher $dispatcher; protected Dispatcher $dispatcher;
/** /** If the framework has been initialized or not. */
* If the framework has been initialized or not
*
* @var boolean
*/
protected bool $initialized = false; protected bool $initialized = false;
/**
* Constructor.
*/
public function __construct() public function __construct()
{ {
$this->vars = [];
$this->loader = new Loader(); $this->loader = new Loader();
$this->dispatcher = new Dispatcher(); $this->dispatcher = new Dispatcher();
$this->init(); $this->init();
} }
@ -109,7 +92,6 @@ class Engine
* @param array<int, mixed> $params Method parameters * @param array<int, mixed> $params Method parameters
* *
* @throws Exception * @throws Exception
*
* @return mixed Callback results * @return mixed Callback results
*/ */
public function __call(string $name, array $params) public function __call(string $name, array $params)
@ -121,7 +103,7 @@ class Engine
} }
if (!$this->loader->get($name)) { 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) || $params[0]; $shared = empty($params) || $params[0];
@ -129,11 +111,11 @@ class Engine
return $this->loader->load($name, $shared); return $this->loader->load($name, $shared);
} }
// Core Methods //////////////////
// Core Methods //
//////////////////
/** /** Initializes the framework. */
* Initializes the framework.
*/
public function init(): void public function init(): void
{ {
$initialized = $this->initialized; $initialized = $this->initialized;
@ -149,7 +131,8 @@ class Engine
$this->loader->register('request', Request::class); $this->loader->register('request', Request::class);
$this->loader->register('response', Response::class); $this->loader->register('response', Response::class);
$this->loader->register('router', Router::class); $this->loader->register('router', Router::class);
$this->loader->register('view', View::class, [], function ($view) use ($self) {
$this->loader->register('view', View::class, [], function (View $view) use ($self) {
$view->path = $self->get('flight.views.path'); $view->path = $self->get('flight.views.path');
$view->extension = $self->get('flight.views.extension'); $view->extension = $self->get('flight.views.extension');
}); });
@ -160,8 +143,9 @@ class Engine
'render', 'redirect', 'etag', 'lastModified', 'json', 'jsonp', 'render', 'redirect', 'etag', 'lastModified', 'json', 'jsonp',
'post', 'put', 'patch', 'delete', 'group', 'getUrl', 'post', 'put', 'patch', 'delete', 'group', 'getUrl',
]; ];
foreach ($methods as $name) { foreach ($methods as $name) {
$this->dispatcher->set($name, [$this, '_' . $name]); $this->dispatcher->set($name, [$this, "_$name"]);
} }
// Default configuration settings // Default configuration settings
@ -198,10 +182,10 @@ class Engine
* @param string $errfile Error file name * @param string $errfile Error file name
* @param int $errline Error file line number * @param int $errline Error file line number
* *
* @return false
* @throws ErrorException * @throws ErrorException
* @return bool
*/ */
public function handleError(int $errno, string $errstr, string $errfile, int $errline) public function handleError(int $errno, string $errstr, string $errfile, int $errline): bool
{ {
if ($errno & error_reporting()) { if ($errno & error_reporting()) {
throw new ErrorException($errstr, $errno, 0, $errfile, $errline); throw new ErrorException($errstr, $errno, 0, $errfile, $errline);
@ -215,7 +199,7 @@ class Engine
* *
* @param Throwable $e Thrown exception * @param Throwable $e Thrown exception
*/ */
public function handleException($e): void public function handleException(Throwable $e): void
{ {
if ($this->get('flight.log_errors')) { if ($this->get('flight.log_errors')) {
error_log($e->getMessage()); // @codeCoverageIgnore error_log($e->getMessage()); // @codeCoverageIgnore
@ -243,13 +227,21 @@ class Engine
/** /**
* Registers a class to a framework method. * Registers a class to a framework method.
* @template T of object *
* # Usage example:
* ```
* $app = new Engine;
* $app->register('user', User::class);
*
* $app->user(); # <- Return a User instance
* ```
* *
* @param string $name Method name * @param string $name Method name
* @param class-string<T> $class Class name * @param class-string<T> $class Class name
* @param array<int, mixed> $params Class initialization parameters * @param array<int, mixed> $params Class initialization parameters
* @param ?callable(T $instance): void $callback Function to call after object instantiation * @param ?Closure(T $instance): void $callback Function to call after object instantiation
* *
* @template T of object
* @throws Exception If trying to map over a framework method * @throws Exception If trying to map over a framework method
*/ */
public function register(string $name, string $class, array $params = [], ?callable $callback = null): void public function register(string $name, string $class, array $params = [], ?callable $callback = null): void
@ -271,7 +263,7 @@ class Engine
* Adds a pre-filter to a method. * Adds a pre-filter to a method.
* *
* @param string $name Method name * @param string $name Method name
* @param callable $callback Callback function * @param Closure(array<int, mixed> &$params, string &$output): (void|false) $callback
*/ */
public function before(string $name, callable $callback): void public function before(string $name, callable $callback): void
{ {
@ -282,7 +274,7 @@ class Engine
* Adds a post-filter to a method. * Adds a post-filter to a method.
* *
* @param string $name Method name * @param string $name Method name
* @param callable $callback Callback function * @param Closure(array<int, mixed> &$params, string &$output): (void|false) $callback
*/ */
public function after(string $name, callable $callback): void public function after(string $name, callable $callback): void
{ {
@ -292,9 +284,9 @@ class Engine
/** /**
* Gets a variable. * Gets a variable.
* *
* @param string|null $key Key * @param ?string $key Variable name
* *
* @return array|mixed|null * @return mixed Variable value or `null` if `$key` doesn't exists.
*/ */
public function get(?string $key = null) public function get(?string $key = null)
{ {
@ -308,24 +300,27 @@ class Engine
/** /**
* Sets a variable. * Sets a variable.
* *
* @param mixed $key Key * @param string|iterable<string, mixed> $key
* @param mixed|null $value Value * Variable name as `string` or an iterable of `'varName' => $varValue`
* @param mixed $value Ignored if `$key` is an `iterable`
*/ */
public function set($key, $value = null): void public function set($key, $value = null): void
{ {
if (\is_array($key) || \is_object($key)) { if (\is_iterable($key)) {
foreach ($key as $k => $v) { foreach ($key as $k => $v) {
$this->vars[$k] = $v; $this->vars[$k] = $v;
} }
} else {
$this->vars[$key] = $value; return;
} }
$this->vars[$key] = $value;
} }
/** /**
* Checks if a variable has been set. * Checks if a variable has been set.
* *
* @param string $key Key * @param string $key Variable name
* *
* @return bool Variable status * @return bool Variable status
*/ */
@ -337,15 +332,16 @@ class Engine
/** /**
* Unsets a variable. If no key is passed in, clear all variables. * Unsets a variable. If no key is passed in, clear all variables.
* *
* @param string|null $key Key * @param ?string $key Variable name, if `$key` isn't provided, it clear all variables.
*/ */
public function clear(?string $key = null): void public function clear(?string $key = null): void
{ {
if (null === $key) { if (null === $key) {
$this->vars = []; $this->vars = [];
} else { return;
unset($this->vars[$key]);
} }
unset($this->vars[$key]);
} }
/** /**
@ -478,7 +474,7 @@ class Engine
* *
* @param Throwable $e Thrown exception * @param Throwable $e Thrown exception
*/ */
public function _error($e): void public function _error(Throwable $e): void
{ {
$msg = sprintf( $msg = sprintf(
'<h1>500 Internal Server Error</h1>' . '<h1>500 Internal Server Error</h1>' .
@ -505,7 +501,7 @@ class Engine
/** /**
* Stops the framework and outputs the current response. * Stops the framework and outputs the current response.
* *
* @param int|null $code HTTP status code * @param ?int $code HTTP status code
* *
* @throws Exception * @throws Exception
*/ */
@ -531,8 +527,7 @@ class Engine
* @param string $pattern URL pattern to match * @param string $pattern URL pattern to match
* @param callable $callback Callback function * @param callable $callback Callback function
* @param bool $pass_route Pass the matching route object to the callback * @param bool $pass_route Pass the matching route object to the callback
* @param string $alias the alias for the route * @param string $alias The alias for the route
* @return Route
*/ */
public function _route(string $pattern, callable $callback, bool $pass_route = false, string $alias = ''): Route public function _route(string $pattern, callable $callback, bool $pass_route = false, string $alias = ''): Route
{ {
@ -544,7 +539,7 @@ class Engine
* *
* @param string $pattern URL pattern to match * @param string $pattern URL pattern to match
* @param callable $callback Callback function that includes the Router class as first parameter * @param callable $callback Callback function that includes the Router class as first parameter
* @param array<callable> $group_middlewares The middleware to be applied to the route * @param array<int, callable|object> $group_middlewares The middleware to be applied to the route
*/ */
public function _group(string $pattern, callable $callback, array $group_middlewares = []): void public function _group(string $pattern, callable $callback, array $group_middlewares = []): void
{ {
@ -558,9 +553,9 @@ class Engine
* @param callable $callback Callback function * @param callable $callback Callback function
* @param bool $pass_route Pass the matching route object to the callback * @param bool $pass_route Pass the matching route object to the callback
*/ */
public function _post(string $pattern, callable $callback, bool $pass_route = false): void public function _post(string $pattern, callable $callback, bool $pass_route = false, string $route_alias = ''): void
{ {
$this->router()->map('POST ' . $pattern, $callback, $pass_route); $this->router()->map('POST ' . $pattern, $callback, $pass_route, $route_alias);
} }
/** /**
@ -570,9 +565,9 @@ class Engine
* @param callable $callback Callback function * @param callable $callback Callback function
* @param bool $pass_route Pass the matching route object to the callback * @param bool $pass_route Pass the matching route object to the callback
*/ */
public function _put(string $pattern, callable $callback, bool $pass_route = false): void public function _put(string $pattern, callable $callback, bool $pass_route = false, string $route_alias = ''): void
{ {
$this->router()->map('PUT ' . $pattern, $callback, $pass_route); $this->router()->map('PUT ' . $pattern, $callback, $pass_route, $route_alias);
} }
/** /**
@ -582,9 +577,9 @@ class Engine
* @param callable $callback Callback function * @param callable $callback Callback function
* @param bool $pass_route Pass the matching route object to the callback * @param bool $pass_route Pass the matching route object to the callback
*/ */
public function _patch(string $pattern, callable $callback, bool $pass_route = false): void public function _patch(string $pattern, callable $callback, bool $pass_route = false, string $route_alias = ''): void
{ {
$this->router()->map('PATCH ' . $pattern, $callback, $pass_route); $this->router()->map('PATCH ' . $pattern, $callback, $pass_route, $route_alias);
} }
/** /**
@ -594,9 +589,9 @@ class Engine
* @param callable $callback Callback function * @param callable $callback Callback function
* @param bool $pass_route Pass the matching route object to the callback * @param bool $pass_route Pass the matching route object to the callback
*/ */
public function _delete(string $pattern, callable $callback, bool $pass_route = false): void public function _delete(string $pattern, callable $callback, bool $pass_route = false, string $route_alias = ''): void
{ {
$this->router()->map('DELETE ' . $pattern, $callback, $pass_route); $this->router()->map('DELETE ' . $pattern, $callback, $pass_route, $route_alias);
} }
/** /**
@ -618,25 +613,21 @@ class Engine
} }
} }
/** /** Sends an HTTP 404 response when a URL is not found. */
* Sends an HTTP 404 response when a URL is not found.
*/
public function _notFound(): void public function _notFound(): void
{ {
$output = '<h1>404 Not Found</h1><h3>The page you have requested could not be found.</h3>';
$this->response() $this->response()
->clear() ->clear()
->status(404) ->status(404)
->write( ->write($output)
'<h1>404 Not Found</h1>' .
'<h3>The page you have requested could not be found.</h3>'
)
->send(); ->send();
} }
/** /**
* Redirects the current request to another URL. * Redirects the current request to another URL.
* *
* @param string $url URL
* @param int $code HTTP status code * @param int $code HTTP status code
*/ */
public function _redirect(string $url, int $code = 303): void public function _redirect(string $url, int $code = 303): void
@ -664,17 +655,18 @@ class Engine
* *
* @param string $file Template file * @param string $file Template file
* @param ?array<string, mixed> $data Template data * @param ?array<string, mixed> $data Template data
* @param string|null $key View variable name * @param ?string $key View variable name
* *
* @throws Exception * @throws Exception If template file wasn't found
*/ */
public function _render(string $file, ?array $data = null, ?string $key = null): void public function _render(string $file, ?array $data = null, ?string $key = null): void
{ {
if (null !== $key) { if (null !== $key) {
$this->view()->set($key, $this->view()->fetch($file, $data)); $this->view()->set($key, $this->view()->fetch($file, $data));
} else { return;
$this->view()->render($file, $data);
} }
$this->view()->render($file, $data);
} }
/** /**
@ -725,7 +717,6 @@ class Engine
int $option = 0 int $option = 0
): void { ): void {
$json = $encode ? json_encode($data, $option) : $data; $json = $encode ? json_encode($data, $option) : $data;
$callback = $this->request()->query[$param]; $callback = $this->request()->query[$param];
$this->response() $this->response()
@ -739,7 +730,7 @@ class Engine
* Handles ETag HTTP caching. * Handles ETag HTTP caching.
* *
* @param string $id ETag identifier * @param string $id ETag identifier
* @param string $type ETag type * @param 'strong'|'weak' $type ETag type
*/ */
public function _etag(string $id, string $type = 'strong'): void public function _etag(string $id, string $type = 'strong'): void
{ {

@ -10,22 +10,25 @@ use flight\net\Router;
use flight\template\View; use flight\template\View;
use flight\net\Route; use flight\net\Route;
require_once __DIR__ . '/autoload.php';
/** /**
* The Flight class is a static representation of the framework. * The Flight class is a static representation of the framework.
*
* @license MIT, http://flightphp.com/license * @license MIT, http://flightphp.com/license
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com> * @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
* *
* # Core methods * # Core methods
* @method static void start() Starts the framework. * @method static void start() Starts the framework.
* @method static void path(string $path) Adds a path for autoloading classes. * @method static void path(string $path) Adds a path for autoloading classes.
* @method static void stop() Stops the framework and sends a response. * @method static void stop(?int $code = null) Stops the framework and sends a response.
* @method static void halt(int $code = 200, string $message = '') * @method static void halt(int $code = 200, string $message = '')
* Stop the framework with an optional status code and message. * Stop the framework with an optional status code and message.
* *
* # Routing * # Routing
* @method static Route route(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') * @method static Route route(string $pattern, callable $callback, bool $pass_route = false, string $alias = '')
* Maps a URL pattern to a callback with all applicable methods. * Maps a URL pattern to a callback with all applicable methods.
* @method static void group(string $pattern, callable $callback, array $group_middlewares = []) * @method static void group(string $pattern, callable $callback, callable[] $group_middlewares = [])
* Groups a set of routes together under a common prefix. * Groups a set of routes together under a common prefix.
* @method static Route post(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') * @method static Route post(string $pattern, callable $callback, bool $pass_route = false, string $alias = '')
* Routes a POST URL to a callback function. * Routes a POST URL to a callback function.
@ -36,50 +39,46 @@ use flight\net\Route;
* @method static Route delete(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') * @method static Route delete(string $pattern, callable $callback, bool $pass_route = false, string $alias = '')
* Routes a DELETE URL to a callback function. * Routes a DELETE URL to a callback function.
* @method static Router router() Returns Router instance. * @method static Router router() Returns Router instance.
* @method static string getUrl(string $alias) Gets a url from an alias * @method static string getUrl(string $alias, array<string, mixed> $params = []) Gets a url from an alias
* *
* @method static void map(string $name, callable $callback) Creates a custom framework method. * @method static void map(string $name, callable $callback) Creates a custom framework method.
* *
* @method static void before($name, $callback) Adds a filter before a framework method. * @method static void before(string $name, Closure(array<int, mixed> &$params, string &$output): (void|false) $callback)
* @method static void after($name, $callback) Adds a filter after a framework method. * Adds a filter before a framework method.
* @method static void after(string $name, Closure(array<int, mixed> &$params, string &$output): (void|false) $callback)
* Adds a filter after a framework method.
* *
* @method static void set($key, $value) Sets a variable. * @method static void set(string|iterable<string, mixed> $key, mixed $value) Sets a variable.
* @method static mixed get($key) Gets a variable. * @method static mixed get(?string $key) Gets a variable.
* @method static bool has($key) Checks if a variable is set. * @method static bool has(string $key) Checks if a variable is set.
* @method static void clear($key = null) Clears a variable. * @method static void clear(?string $key = null) Clears a variable.
* *
* # Views * # Views
* @method static void render($file, array $data = null, $key = null) Renders a template file. * @method static void render(string $file, ?array<string, mixed> $data = null, ?string $key = null)
* Renders a template file.
* @method static View view() Returns View instance. * @method static View view() Returns View instance.
* *
* # Request-Response * # Request-Response
* @method static Request request() Returns Request instance. * @method static Request request() Returns Request instance.
* @method static Response response() Returns Response instance. * @method static Response response() Returns Response instance.
* @method static void redirect($url, $code = 303) Redirects to another URL. * @method static void redirect(string $url, int $code = 303) Redirects to another URL.
* @method static void json($data, $code = 200, $encode = true, $charset = "utf8", $encodeOption = 0, $encodeDepth = 512) Sends a JSON response. * @method static void json(mixed $data, int $code = 200, bool $encode = true, string $charset = "utf8", int $encodeOption = 0, int $encodeDepth = 512)
* @method static void jsonp($data, $param = 'jsonp', $code = 200, $encode = true, $charset = "utf8", $encodeOption = 0, $encodeDepth = 512) Sends a JSONP response. * Sends a JSON response.
* @method static void error($exception) Sends an HTTP 500 response. * @method static void jsonp(mixed $data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = "utf8", int $encodeOption = 0, int $encodeDepth = 512)
* Sends a JSONP response.
* @method static void error(Throwable $exception) Sends an HTTP 500 response.
* @method static void notFound() Sends an HTTP 404 response. * @method static void notFound() Sends an HTTP 404 response.
* *
* # HTTP caching * # HTTP caching
* @method static void etag($id, $type = 'strong') Performs ETag HTTP caching. * @method static void etag(string $id, ('strong'|'weak') $type = 'strong') Performs ETag HTTP caching.
* @method static void lastModified($time) Performs last modified HTTP caching. * @method static void lastModified(int $time) Performs last modified HTTP caching.
*/ */
// phpcs:ignoreFile Generic.Files.LineLength.TooLong, PSR1.Classes.ClassDeclaration.MissingNamespace
class Flight class Flight
{ {
/** /** Framework engine. */
* Framework engine.
*
* @var Engine $engine
*/
private static Engine $engine; private static Engine $engine;
/** /** Whether or not the app has been initialized. */
* Whether or not the app has been initialized
*
* @var boolean
*/
private static bool $initialized = false; private static bool $initialized = false;
/** /**
@ -104,27 +103,30 @@ class Flight
/** /**
* Registers a class to a framework method. * Registers a class to a framework method.
* @template T of object *
* @param string $name Static method name * # Usage example:
* ``` * ```
* Flight::register('user', User::class); * Flight::register('user', User::class);
* *
* Flight::user(); # <- Return a User instance * Flight::user(); # <- Return a User instance
* ``` * ```
*
* @param string $name Static method name
* @param class-string<T> $class Fully Qualified Class Name * @param class-string<T> $class Fully Qualified Class Name
* @param array<int, mixed> $params Class constructor params * @param array<int, mixed> $params Class constructor params
* @param ?Closure(T $instance): void $callback Perform actions with the instance * @param ?Closure(T $instance): void $callback Perform actions with the instance
* @return void *
* @template T of object
*/ */
public static function register($name, $class, $params = [], $callback = null) public static function register($name, $class, $params = [], $callback = null): void
{ {
static::__callStatic('register', func_get_args()); static::__callStatic('register', [$name, $class, $params, $callback]);
} }
/** Unregisters a class. */ /** Unregisters a class. */
public static function unregister(string $methodName): void public static function unregister(string $methodName): void
{ {
static::__callStatic('unregister', func_get_args()); static::__callStatic('unregister', [$methodName]);
} }
/** /**
@ -133,27 +135,21 @@ class Flight
* @param string $name Method name * @param string $name Method name
* @param array<int, mixed> $params Method parameters * @param array<int, mixed> $params Method parameters
* *
* @throws Exception
*
* @return mixed Callback results * @return mixed Callback results
* @throws Exception
*/ */
public static function __callStatic(string $name, array $params) public static function __callStatic(string $name, array $params)
{ {
$app = self::app(); return Dispatcher::invokeMethod([self::app(), $name], $params);
return Dispatcher::invokeMethod([$app, $name], $params);
} }
/** /** @return Engine Application instance */
* @return Engine Application instance
*/
public static function app(): Engine public static function app(): Engine
{ {
if (!self::$initialized) { if (!self::$initialized) {
require_once __DIR__ . '/autoload.php'; require_once __DIR__ . '/autoload.php';
self::setEngine(new Engine()); self::setEngine(new Engine());
self::$initialized = true; self::$initialized = true;
} }

@ -4,6 +4,7 @@ declare(strict_types=1);
use flight\core\Loader; use flight\core\Loader;
require_once __DIR__ . '/Flight.php';
require_once __DIR__ . '/core/Loader.php'; require_once __DIR__ . '/core/Loader.php';
Loader::autoload(true, [dirname(__DIR__)]); Loader::autoload(true, [dirname(__DIR__)]);

@ -4,8 +4,11 @@ declare(strict_types=1);
namespace flight\core; namespace flight\core;
use Closure;
use Exception; use Exception;
use InvalidArgumentException; use InvalidArgumentException;
use ReflectionClass;
use TypeError;
/** /**
* The Dispatcher class is responsible for dispatching events. Events * The Dispatcher class is responsible for dispatching events. Events
@ -18,17 +21,17 @@ use InvalidArgumentException;
*/ */
class Dispatcher class Dispatcher
{ {
/** public const FILTER_BEFORE = 'before';
* Mapped events. public const FILTER_AFTER = 'after';
* private const FILTER_TYPES = [self::FILTER_BEFORE, self::FILTER_AFTER];
* @var array<string, callable>
*/ /** @var array<string, Closure(): (void|mixed)> Mapped events. */
protected array $events = []; protected array $events = [];
/** /**
* Method filters. * Method filters.
* *
* @var array<string, array<'before'|'after', array<int, callable>>> * @var array<string, array<'before'|'after', array<int, Closure(array<int, mixed> &$params, mixed &$output): (void|false)>>>
*/ */
protected array $filters = []; protected array $filters = [];
@ -36,27 +39,35 @@ class Dispatcher
* Dispatches an event. * Dispatches an event.
* *
* @param string $name Event name * @param string $name Event name
* @param array<int, mixed> $params Callback parameters * @param array<int, mixed> $params Callback parameters.
*
* @throws Exception
* *
* @return mixed Output of callback * @return mixed Output of callback
* @throws Exception If event name isn't found or if event throws an `Exception`
*/ */
public function run(string $name, array $params = []) public function run(string $name, array $params = [])
{ {
$output = ''; $output = '';
// Run pre-filters // Run pre-filters
if (!empty($this->filters[$name]['before'])) { $thereAreBeforeFilters = !empty($this->filters[$name]['before']);
if ($thereAreBeforeFilters) {
$this->filter($this->filters[$name]['before'], $params, $output); $this->filter($this->filters[$name]['before'], $params, $output);
} }
// Run requested method // Run requested method
$callback = $this->get($name); $callback = $this->get($name);
if ($callback === null) {
throw new Exception("Event '$name' isn't found.");
}
$output = $callback(...$params); $output = $callback(...$params);
// Run post-filters // Run post-filters
if (!empty($this->filters[$name]['after'])) { $thereAreAfterFilters = !empty($this->filters[$name]['after']);
if ($thereAreAfterFilters) {
$this->filter($this->filters[$name]['after'], $params, $output); $this->filter($this->filters[$name]['after'], $params, $output);
} }
@ -67,11 +78,19 @@ class Dispatcher
* Assigns a callback to an event. * Assigns a callback to an event.
* *
* @param string $name Event name * @param string $name Event name
* @param callable $callback Callback function * @param Closure(): (void|mixed) $callback Callback function
*
* @return $this
*/ */
public function set(string $name, callable $callback): void public function set(string $name, callable $callback): self
{ {
if ($this->get($name) !== null) {
trigger_error("Event '$name' has been overriden!", E_USER_NOTICE);
}
$this->events[$name] = $callback; $this->events[$name] = $callback;
return $this;
} }
/** /**
@ -79,7 +98,7 @@ class Dispatcher
* *
* @param string $name Event name * @param string $name Event name
* *
* @return ?callable $callback Callback function * @return null|(Closure(): (void|mixed)) $callback Callback function
*/ */
public function get(string $name): ?callable public function get(string $name): ?callable
{ {
@ -105,42 +124,59 @@ class Dispatcher
*/ */
public function clear(?string $name = null): void public function clear(?string $name = null): void
{ {
if (null !== $name) { if ($name !== null) {
unset($this->events[$name]); unset($this->events[$name]);
unset($this->filters[$name]); unset($this->filters[$name]);
} else {
return;
}
$this->events = []; $this->events = [];
$this->filters = []; $this->filters = [];
} }
}
/** /**
* Hooks a callback to an event. * Hooks a callback to an event.
* *
* @param string $name Event name * @param string $name Event name
* @param string $type Filter type * @param 'before'|'after' $type Filter type
* @param callable $callback Callback function * @param Closure(array<int, mixed> &$params, string &$output): (void|false) $callback
*
* @return $this
*/ */
public function hook(string $name, string $type, callable $callback): void public function hook(string $name, string $type, callable $callback): self
{ {
if (!in_array($type, self::FILTER_TYPES, true)) {
$noticeMessage = "Invalid filter type '$type', use " . join('|', self::FILTER_TYPES);
trigger_error($noticeMessage, E_USER_NOTICE);
}
$this->filters[$name][$type][] = $callback; $this->filters[$name][$type][] = $callback;
return $this;
} }
/** /**
* Executes a chain of method filters. * Executes a chain of method filters.
* *
* @param array<int, callable> $filters Chain of filters * @param array<int, Closure(array<int, mixed> &$params, mixed &$output): (void|false)> $filters
* Chain of filters-
* @param array<int, mixed> $params Method parameters * @param array<int, mixed> $params Method parameters
* @param mixed $output Method output * @param mixed $output Method output
* *
* @throws Exception * @throws Exception If an event throws an `Exception`.
*/ */
public function filter(array $filters, array &$params, &$output): void public static function filter(array $filters, array &$params, &$output): void
{ {
$args = [&$params, &$output]; foreach ($filters as $key => $callback) {
foreach ($filters as $callback) { if (!is_callable($callback)) {
$continue = $callback(...$args); throw new InvalidArgumentException("Invalid callable \$filters[$key].");
if (false === $continue) { }
$continue = $callback($params, $output);
if ($continue === false) {
break; break;
} }
} }
@ -149,33 +185,40 @@ class Dispatcher
/** /**
* Executes a callback function. * Executes a callback function.
* *
* @param callable|array<class-string|object, string> $callback Callback function * @param callable-string|(Closure(): mixed)|array{class-string|object, string} $callback
* Callback function
* @param array<int, mixed> $params Function parameters * @param array<int, mixed> $params Function parameters
* *
* @throws Exception
*
* @return mixed Function results * @return mixed Function results
* @throws Exception
*/ */
public static function execute($callback, array &$params = []) public static function execute($callback, array &$params = [])
{ {
if (\is_callable($callback)) { $isInvalidFunctionName = (
return \is_array($callback) ? is_string($callback)
self::invokeMethod($callback, $params) : && !function_exists($callback)
self::callFunction($callback, $params); );
}
if ($isInvalidFunctionName) {
throw new InvalidArgumentException('Invalid callback specified.'); throw new InvalidArgumentException('Invalid callback specified.');
} }
if (is_array($callback)) {
return self::invokeMethod($callback, $params);
}
return self::callFunction($callback, $params);
}
/** /**
* Calls a function. * Calls a function.
* *
* @param callable|string $func Name of function to call * @param callable $func Name of function to call
* @param array<int, mixed> $params Function parameters * @param array<int, mixed> &$params Function parameters
* *
* @return mixed Function results * @return mixed Function results
*/ */
public static function callFunction($func, array &$params = []) public static function callFunction(callable $func, array &$params = [])
{ {
return call_user_func_array($func, $params); return call_user_func_array($func, $params);
} }
@ -183,28 +226,50 @@ class Dispatcher
/** /**
* Invokes a method. * Invokes a method.
* *
* @param mixed $func Class method * @param array{class-string|object, string} $func Class method
* @param array<int, mixed> $params Class method parameters * @param array<int, mixed> &$params Class method parameters
* *
* @return mixed Function results * @return mixed Function results
* @throws TypeError For unexistent class name.
*/ */
public static function invokeMethod($func, array &$params = []) public static function invokeMethod(array $func, array &$params = [])
{ {
[$class, $method] = $func; [$class, $method] = $func;
$instance = \is_object($class); if (is_string($class) && class_exists($class)) {
$constructor = (new ReflectionClass($class))->getConstructor();
$constructorParamsNumber = 0;
if ($constructor !== null) {
$constructorParamsNumber = count($constructor->getParameters());
}
if ($constructorParamsNumber > 0) {
$exceptionMessage = "Method '$class::$method' cannot be called statically. ";
$exceptionMessage .= sprintf(
"$class::__construct require $constructorParamsNumber parameter%s",
$constructorParamsNumber > 1 ? 's' : ''
);
return ($instance) ? throw new InvalidArgumentException($exceptionMessage, E_ERROR);
$class->$method(...$params) : }
$class::$method();
$class = new $class();
}
return call_user_func_array([$class, $method], $params);
} }
/** /**
* Resets the object to the initial state. * Resets the object to the initial state.
*
* @return $this
*/ */
public function reset(): void public function reset(): self
{ {
$this->events = []; $this->events = [];
$this->filters = []; $this->filters = [];
return $this;
} }
} }

@ -45,11 +45,9 @@ class Loader
* @param string $name Registry name * @param string $name Registry name
* @param class-string<T>|Closure(): T $class Class name or function to instantiate class * @param class-string<T>|Closure(): T $class Class name or function to instantiate class
* @param array<int, mixed> $params Class initialization parameters * @param array<int, mixed> $params Class initialization parameters
* @param ?callable(T $instance): void $callback $callback Function to call after object instantiation * @param ?Closure(T $instance): void $callback $callback Function to call after object instantiation
* *
* @template T of object * @template T of object
*
* @return void
*/ */
public function register(string $name, $class, array $params = [], ?callable $callback = null): void public function register(string $name, $class, array $params = [], ?callable $callback = null): void
{ {
@ -192,12 +190,13 @@ class Loader
*/ */
public static function loadClass(string $class): void public static function loadClass(string $class): void
{ {
$class_file = str_replace(['\\', '_'], '/', $class) . '.php'; $classFile = str_replace(['\\', '_'], '/', $class) . '.php';
foreach (self::$dirs as $dir) { foreach (self::$dirs as $dir) {
$file = $dir . '/' . $class_file; $filePath = "$dir/$classFile";
if (file_exists($file)) {
require $file; if (file_exists($filePath)) {
require_once $filePath;
return; return;
} }

@ -67,10 +67,10 @@ class Router
/** /**
* Maps a URL pattern to a callback function. * Maps a URL pattern to a callback function.
* *
* @param string $pattern URL pattern to match * @param string $pattern URL pattern to match.
* @param callable $callback Callback function * @param callable $callback Callback function.
* @param bool $pass_route Pass the matching route object to the callback * @param bool $pass_route Pass the matching route object to the callback.
* @param string $route_alias Alias for the route * @param string $route_alias Alias for the route.
*/ */
public function map(string $pattern, callable $callback, bool $pass_route = false, string $route_alias = ''): Route public function map(string $pattern, callable $callback, bool $pass_route = false, string $route_alias = ''): Route
{ {
@ -165,8 +165,8 @@ class Router
* *
* @param string $group_prefix group URL prefix (such as /api/v1) * @param string $group_prefix group URL prefix (such as /api/v1)
* @param callable $callback The necessary calling that holds the Router class * @param callable $callback The necessary calling that holds the Router class
* @param array<int, callable|object> $group_middlewares The middlewares to be * @param array<int, callable|object> $group_middlewares
* applied to the group Ex: [ $middleware1, $middleware2 ] * The middlewares to be applied to the group. Example: `[$middleware1, $middleware2]`
*/ */
public function group(string $group_prefix, callable $callback, array $group_middlewares = []): void public function group(string $group_prefix, callable $callback, array $group_middlewares = []): void
{ {

@ -1,6 +1,7 @@
<?php <?php
require 'flight/Flight.php'; // require 'flight/Flight.php';
require 'flight/autoload.php';
Flight::route('/', function () { Flight::route('/', function () {
echo 'hello world!'; echo 'hello world!';

@ -34,9 +34,12 @@
<exclude name="Generic.ControlStructures.DisallowYodaConditions.Found" /> <exclude name="Generic.ControlStructures.DisallowYodaConditions.Found" />
<exclude name="Generic.Strings.UnnecessaryStringConcat.Found" /> <exclude name="Generic.Strings.UnnecessaryStringConcat.Found" />
<exclude name="Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition" /> <exclude name="Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition" />
<exclude name="Generic.Commenting.DocComment.MissingShort" />
<exclude name="Generic.Commenting.DocComment.SpacingBeforeTags" />
<exclude name="Generic.WhiteSpace.ArbitraryParenthesesSpacing.SpaceAfterOpen" />
<exclude name="Generic.WhiteSpace.ArbitraryParenthesesSpacing.SpaceBeforeClose" />
</rule> </rule>
<file>flight/</file> <file>flight/</file>
<file>tests/</file> <file>tests/</file>
<exclude-pattern>tests/views/*</exclude-pattern> <exclude-pattern>tests/views/*</exclude-pattern>
<ignore>tests/views/</ignore>
</ruleset> </ruleset>

@ -8,3 +8,4 @@ parameters:
paths: paths:
- flight - flight
- index.php - index.php
treatPhpDocTypesAsCertain: false

@ -4,10 +4,13 @@ declare(strict_types=1);
namespace tests; namespace tests;
use Closure;
use Exception; use Exception;
use flight\core\Dispatcher; use flight\core\Dispatcher;
use PharIo\Manifest\InvalidEmailException;
use tests\classes\Hello; use tests\classes\Hello;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use TypeError;
class DispatcherTest extends TestCase class DispatcherTest extends TestCase
{ {
@ -18,166 +21,233 @@ class DispatcherTest extends TestCase
$this->dispatcher = new Dispatcher(); $this->dispatcher = new Dispatcher();
} }
// Map a closure public function testClosureMapping(): void
public function testClosureMapping()
{ {
$this->dispatcher->set('map1', function () { $this->dispatcher->set('map1', Closure::fromCallable(function (): string {
return 'hello'; return 'hello';
}); }));
$result = $this->dispatcher->run('map1');
self::assertEquals('hello', $result); $this->assertSame('hello', $this->dispatcher->run('map1'));
} }
// Map a function public function testFunctionMapping(): void
public function testFunctionMapping()
{ {
$this->dispatcher->set('map2', function () { $this->dispatcher->set('map2', function (): string {
return 'hello'; return 'hello';
}); });
$result = $this->dispatcher->run('map2'); $this->assertSame('hello', $this->dispatcher->run('map2'));
self::assertEquals('hello', $result);
} }
public function testHasEvent() public function testHasEvent(): void
{ {
$this->dispatcher->set('map-event', function () { $this->dispatcher->set('map-event', function (): void {
return 'hello';
}); });
$result = $this->dispatcher->has('map-event'); $this->assertTrue($this->dispatcher->has('map-event'));
$this->assertTrue($result);
} }
public function testClearAllRegisteredEvents() public function testClearAllRegisteredEvents(): void
{ {
$this->dispatcher->set('map-event', function () { $customFunction = $anotherFunction = function (): void {
return 'hello'; };
});
$this->dispatcher->set('map-event-2', function () { $this->dispatcher
return 'there'; ->set('map-event', $customFunction)
}); ->set('map-event-2', $anotherFunction);
$this->dispatcher->clear(); $this->dispatcher->clear();
$result = $this->dispatcher->has('map-event'); $this->assertFalse($this->dispatcher->has('map-event'));
$this->assertFalse($result); $this->assertFalse($this->dispatcher->has('map-event-2'));
$result = $this->dispatcher->has('map-event-2');
$this->assertFalse($result);
} }
public function testClearDeclaredRegisteredEvent() public function testClearDeclaredRegisteredEvent(): void
{ {
$this->dispatcher->set('map-event', function () { $customFunction = $anotherFunction = function (): void {
return 'hello'; };
$this->dispatcher
->set('map-event', $customFunction)
->set('map-event-2', $anotherFunction);
$this->dispatcher->clear('map-event');
$this->assertFalse($this->dispatcher->has('map-event'));
$this->assertTrue($this->dispatcher->has('map-event-2'));
}
public function testStaticFunctionMapping(): void
{
$this->dispatcher->set('map2', Hello::class . '::sayBye');
$this->assertSame('goodbye', $this->dispatcher->run('map2'));
}
public function testClassMethodMapping(): void
{
$this->dispatcher->set('map3', [new Hello(), 'sayHi']);
$this->assertSame('hello', $this->dispatcher->run('map3'));
}
public function testStaticClassMethodMapping(): void
{
$this->dispatcher->set('map4', [Hello::class, 'sayBye']);
$this->assertSame('goodbye', $this->dispatcher->run('map4'));
}
public function testBeforeAndAfter(): void
{
$this->dispatcher->set('hello', function (string $name): string {
return "Hello, $name!";
}); });
$this->dispatcher->set('map-event-2', function () { $this->dispatcher
return 'there'; ->hook('hello', $this->dispatcher::FILTER_BEFORE, function (array &$params): void {
// Manipulate the parameter
$params[0] = 'Fred';
})
->hook('hello', $this->dispatcher::FILTER_AFTER, function (array &$params, string &$output): void {
// Manipulate the output
$output .= ' Have a nice day!';
}); });
$this->dispatcher->clear('map-event'); $result = $this->dispatcher->run('hello', ['Bob']);
$result = $this->dispatcher->has('map-event'); $this->assertSame('Hello, Fred! Have a nice day!', $result);
$this->assertFalse($result);
$result = $this->dispatcher->has('map-event-2');
$this->assertTrue($result);
} }
// Map a static function public function testInvalidCallback(): void
public function testStaticFunctionMapping()
{ {
$this->dispatcher->set('map2', 'tests\classes\Hello::sayBye'); $this->expectException(TypeError::class);
$result = $this->dispatcher->run('map2'); Dispatcher::execute(['NonExistentClass', 'nonExistentMethod']);
}
self::assertEquals('goodbye', $result); // It will be useful for executing instance Controller methods statically
public function testCanExecuteAnNonStaticMethodStatically(): void
{
$this->assertSame('hello', Dispatcher::execute([Hello::class, 'sayHi']));
} }
// Map a class method public function testItThrowsAnExceptionWhenRunAnUnregistedEventName(): void
public function testClassMethodMapping()
{ {
$h = new Hello(); $this->expectException(Exception::class);
$this->dispatcher->set('map3', [$h, 'sayHi']); $this->dispatcher->run('nonExistentEvent');
}
$result = $this->dispatcher->run('map3'); public function testWhenAnEventThrowsAnExceptionItPersistUntilNextCatchBlock(): void
{
$this->dispatcher->set('myMethod', function (): void {
throw new Exception('myMethod Exception');
});
$this->expectException(Exception::class);
$this->expectExceptionMessage('myMethod Exception');
self::assertEquals('hello', $result); $this->dispatcher->run('myMethod');
} }
// Map a static class method public function testWhenAnEventThrowsCustomExceptionItPersistUntilNextCatchBlock(): void
public function testStaticClassMethodMapping()
{ {
$this->dispatcher->set('map4', ['\tests\classes\Hello', 'sayBye']); $this->dispatcher->set('checkEmail', function (string $email): void {
throw new InvalidEmailException("Invalid email $email");
});
$result = $this->dispatcher->run('map4'); $this->expectException(InvalidEmailException::class);
$this->expectExceptionMessage('Invalid email mail@mail,com');
self::assertEquals('goodbye', $result); $this->dispatcher->run('checkEmail', ['mail@mail,com']);
} }
// Run before and after filters public function testItThrowsNoticeForOverrideRegisteredEvents(): void
public function testBeforeAndAfter()
{ {
$this->dispatcher->set('hello', function ($name) { set_error_handler(function (int $errno, string $errstr): void {
return "Hello, $name!"; $this->assertSame(E_USER_NOTICE, $errno);
$this->assertSame("Event 'myMethod' has been overriden!", $errstr);
}); });
$this->dispatcher->hook('hello', 'before', function (&$params) { $this->dispatcher->set('myMethod', function (): string {
// Manipulate the parameter return 'Original';
$params[0] = 'Fred';
}); });
$this->dispatcher->hook('hello', 'after', function (&$params, &$output) { $this->dispatcher->set('myMethod', function (): string {
// Manipulate the output return 'Overriden';
$output .= ' Have a nice day!';
}); });
$result = $this->dispatcher->run('hello', ['Bob']); $this->assertSame('Overriden', $this->dispatcher->run('myMethod'));
restore_error_handler();
}
public function testItThrowsNoticeForInvalidFilterTypes(): void
{
set_error_handler(function (int $errno, string $errstr): void {
$this->assertSame(E_USER_NOTICE, $errno);
$this->assertStringStartsWith("Invalid filter type 'invalid', use ", $errstr);
});
$this->dispatcher
->set('myMethod', function (): string {
return 'Original';
})
->hook('myMethod', 'invalid', function (array &$params, $output): void {
$output = 'Overriden';
});
self::assertEquals('Hello, Fred! Have a nice day!', $result); $this->assertSame('Original', $this->dispatcher->run('myMethod'));
restore_error_handler();
} }
// Test an invalid callback public function testItThrowsAnExceptionForInvalidFilters(): void
public function testInvalidCallback()
{ {
$this->expectException(Exception::class); $this->expectException(Exception::class);
$this->expectExceptionMessage('Invalid callable $filters[1]');
$this->dispatcher->execute(['NonExistentClass', 'nonExistentMethod']); $params = [];
$output = '';
$validCallable = function (): void {
};
$invalidCallable = 'invalidGlobalFunction';
Dispatcher::filter([$validCallable, $invalidCallable], $params, $output);
} }
public function testCallFunction4Params() public function testCallFunction4Params(): void
{ {
$closure = function ($param1, $params2, $params3, $param4) { $myFunction = function ($param1, $param2, $param3, $param4) {
return 'hello' . $param1 . $params2 . $params3 . $param4; return "hello{$param1}{$param2}{$param3}{$param4}";
}; };
$params = ['param1', 'param2', 'param3', 'param4']; $params = ['param1', 'param2', 'param3', 'param4'];
$result = $this->dispatcher->callFunction($closure, $params); $result = Dispatcher::callFunction($myFunction, $params);
$this->assertEquals('helloparam1param2param3param4', $result);
$this->assertSame('helloparam1param2param3param4', $result);
} }
public function testCallFunction5Params() public function testCallFunction5Params(): void
{ {
$closure = function ($param1, $params2, $params3, $param4, $param5) { $myFunction = function ($param1, $param2, $param3, $param4, $param5) {
return 'hello' . $param1 . $params2 . $params3 . $param4 . $param5; return "hello{$param1}{$param2}{$param3}{$param4}{$param5}";
}; };
$params = ['param1', 'param2', 'param3', 'param4', 'param5']; $params = ['param1', 'param2', 'param3', 'param4', 'param5'];
$result = $this->dispatcher->callFunction($closure, $params); $result = Dispatcher::callFunction($myFunction, $params);
$this->assertEquals('helloparam1param2param3param4param5', $result);
$this->assertSame('helloparam1param2param3param4param5', $result);
} }
public function testCallFunction6Params() public function testCallFunction6Params(): void
{ {
$closure = function ($param1, $params2, $params3, $param4, $param5, $param6) { $func = function ($param1, $param2, $param3, $param4, $param5, $param6) {
return 'hello' . $param1 . $params2 . $params3 . $param4 . $param5 . $param6; return "hello{$param1}{$param2}{$param3}{$param4}{$param5}{$param6}";
}; };
$params = ['param1', 'param2', 'param3', 'param4', 'param5', 'param6']; $params = ['param1', 'param2', 'param3', 'param4', 'param5', 'param6'];
$result = $this->dispatcher->callFunction($closure, $params); $result = Dispatcher::callFunction($func, $params);
$this->assertEquals('helloparam1param2param3param4param5param6', $result);
$this->assertSame('helloparam1param2param3param4param5param6', $result);
} }
} }

@ -164,7 +164,6 @@ class FlightTest extends TestCase
public function testStaticRoutePut() public function testStaticRoutePut()
{ {
Flight::put('/test', function () { Flight::put('/test', function () {
echo 'test put'; echo 'test put';
}); });

Loading…
Cancel
Save