pull/457/head v2.0.0
Mike Cao 3 years ago
commit 212490ff61

@ -17,7 +17,7 @@ Flight::start();
# Requirements
Flight requires `PHP 5.3` or greater.
Flight requires `PHP 7.4` or greater.
# License
@ -194,6 +194,8 @@ Flight::route('/@name/@id:[0-9]{3}', function($name, $id){
});
```
Matching regex groups `()` with named parameters isn't supported.
## Optional Parameters
You can specify named parameters that are optional for matching by wrapping

@ -12,12 +12,13 @@
}
],
"require": {
"php": ">=5.3.0"
"php": "^7.4|^8.0|^8.1",
"ext-json": "*"
},
"autoload": {
"files": [ "flight/autoload.php", "flight/Flight.php" ]
},
"require-dev": {
"phpunit/phpunit": "~4.6"
"phpunit/phpunit": "^9.5"
}
}

@ -1,4 +1,6 @@
<?php
declare(strict_types=1);
/**
* Flight: An extensible micro-framework.
*
@ -8,8 +10,15 @@
namespace flight;
use flight\core\Loader;
use ErrorException;
use Exception;
use flight\core\Dispatcher;
use flight\core\Loader;
use flight\net\Request;
use flight\net\Response;
use flight\net\Router;
use flight\template\View;
use Throwable;
/**
* The Engine class contains the core functionality of the framework.
@ -17,23 +26,21 @@ use flight\core\Dispatcher;
* and generating an HTTP response.
*
* Core methods
*
* @method void start() Starts engine
* @method void stop() Stops framework and outputs current response
* @method void halt(int $code = 200, string $message = '') Stops processing and returns a given response.
*
*
* Routing
* @method void route(string $pattern, callable $callback, bool $pass_route = false) Routes a URL to a callback function.
* @method \flight\net\Router router() Gets router
* @method Router router() Gets router
*
* Views
* @method void render(string $file, array $data = null, string $key = null) Renders template
* @method \flight\template\View view() Gets current view
* @method View view() Gets current view
*
* Request-response
* @method \flight\net\Request request() Gets current request
* @method \flight\net\Response response() Gets current response
* @method void error(\Exception $e) Sends an HTTP 500 response for any errors.
* @method Request request() Gets current request
* @method Response response() Gets current response
* @method void error(Exception $e) Sends an HTTP 500 response for any errors.
* @method void notFound() Sends an HTTP 404 response when a URL is not found.
* @method void redirect(string $url, int $code = 303) Redirects the current request to another URL.
* @method void json(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf-8', int $option = 0) Sends a JSON response.
@ -43,33 +50,29 @@ use flight\core\Dispatcher;
* @method void etag($id, string $type = 'strong') Handles ETag HTTP caching.
* @method void lastModified(int $time) Handles last modified HTTP caching.
*/
class Engine {
class Engine
{
/**
* Stored variables.
*
* @var array
*/
protected $vars;
protected array $vars;
/**
* Class loader.
*
* @var Loader
*/
protected $loader;
protected Loader $loader;
/**
* Event dispatcher.
*
* @var Dispatcher
*/
protected $dispatcher;
protected Dispatcher $dispatcher;
/**
* Constructor.
*/
public function __construct() {
$this->vars = array();
public function __construct()
{
$this->vars = [];
$this->loader = new Loader();
$this->dispatcher = new Dispatcher();
@ -80,62 +83,67 @@ 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) || $params[0];
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();
}
// 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', array(), 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');
});
// 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',
'post', 'put', 'patch', 'delete',
];
foreach ($methods as $name) {
$this->dispatcher->set($name, array($this, '_'.$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);
@ -144,11 +152,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 +171,27 @@ 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
* @throws \ErrorException
* @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($errno, $errstr, $errfile, $errline) {
public function handleError(int $errno, string $errstr, string $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 +202,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 +219,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 +238,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 +294,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 +321,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 +342,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 +371,9 @@ class Engine {
$dispatched = true;
if (!$continue) break;
if (!$continue) {
break;
}
$router->next();
@ -357,17 +385,45 @@ class Engine {
}
}
/**
* Sends an HTTP 500 response for any errors.
*
* @param Throwable $e Thrown exception
*/
public function _error($e): void
{
$msg = sprintf('<h1>500 Internal Server Error</h1>' .
'<h3>%s (%s)</h3>' .
'<pre>%s</pre>',
$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.
*
* @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 +436,71 @@ 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);
}
/**
* 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.
*
* @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)
@ -403,44 +509,17 @@ class Engine {
exit();
}
/**
* Sends an HTTP 500 response for any errors.
*
* @param \Exception|\Throwable $e Thrown exception
*/
public function _error($e) {
$msg = sprintf('<h1>500 Internal Server Error</h1>'.
'<h3>%s (%s)</h3>'.
'<pre>%s</pre>',
$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.
*/
public function _notFound() {
public function _notFound(): void
{
$this->response()
->clear()
->status(404)
->write(
'<h1>404 Not Found</h1>'.
'<h3>The page you have requested could not be found.</h3>'.
'<h1>404 Not Found</h1>' .
'<h3>The page you have requested could not be found.</h3>' .
str_repeat(' ', 512)
)
->send();
@ -449,18 +528,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 +554,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 +572,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
) {
$json = ($encode) ? json_encode($data, $option) : $data;
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
) {
$json = ($encode) ? json_encode($data, $option) : $data;
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 +650,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']) &&

@ -1,4 +1,6 @@
<?php
declare(strict_types=1);
/**
* Flight: An extensible micro-framework.
*
@ -6,10 +8,18 @@
* @license MIT, http://flightphp.com/license
*/
use flight\core\Dispatcher;
use flight\Engine;
use flight\net\Request;
use flight\net\Response;
use flight\net\Router;
use flight\template\View;
/**
* The Flight class is a static representation of the framework.
*
* Core.
*
* @method static void start() Starts the framework.
* @method static void path($path) Adds a path for autoloading classes.
* @method static void stop() Stops the framework and sends a response.
@ -17,7 +27,7 @@
*
* Routing.
* @method static void route($pattern, $callback) Maps a URL pattern to a callback.
* @method static \flight\net\Router router() Returns Router instance.
* @method static Router router() Returns Router instance.
*
* Extending & Overriding.
* @method static void map($name, $callback) Creates a custom framework method.
@ -35,11 +45,11 @@
*
* Views.
* @method static void render($file, array $data = null, $key = null) Renders a template file.
* @method static \flight\template\View view() Returns View instance.
* @method static View view() Returns View instance.
*
* Request & Response.
* @method static \flight\net\Request request() Returns Request instance.
* @method static \flight\net\Response response() Returns Response instance.
* @method static Request request() Returns Request instance.
* @method static Response response() Returns Response instance.
* @method static void redirect($url, $code = 303) Redirects to another URL.
* @method static void json($data, $code = 200, $encode = true, $charset = "utf8", $encodeOption = 0, $encodeDepth = 512) Sends a JSON response.
* @method static void jsonp($data, $param = 'jsonp', $code = 200, $encode = true, $charset = "utf8", $encodeOption = 0, $encodeDepth = 512) Sends a JSONP response.
@ -50,43 +60,54 @@
* @method static void etag($id, $type = 'strong') Performs ETag HTTP caching.
* @method static void lastModified($time) Performs last modified HTTP caching.
*/
class Flight {
class Flight
{
/**
* Framework engine.
*
* @var \flight\Engine
*/
private static $engine;
private static Engine $engine;
// Don't allow object instantiation
private function __construct() {}
private function __destruct() {}
private function __clone() {}
private function __construct()
{
}
private function __destruct()
{
}
private function __clone()
{
}
/**
* Handles calls to static 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 static function __callStatic($name, $params) {
$app = Flight::app();
public static function __callStatic(string $name, array $params)
{
$app = self::app();
return \flight\core\Dispatcher::invokeMethod(array($app, $name), $params);
return Dispatcher::invokeMethod([$app, $name], $params);
}
/**
* @return \flight\Engine Application instance
* @return Engine Application instance
*/
public static function app() {
public static function app(): Engine
{
static $initialized = false;
if (!$initialized) {
require_once __DIR__.'/autoload.php';
require_once __DIR__ . '/autoload.php';
self::$engine = new \flight\Engine();
self::$engine = new Engine();
$initialized = true;
}

@ -1,4 +1,6 @@
<?php
declare(strict_types=1);
/**
* Flight: An extensible micro-framework.
*
@ -6,6 +8,8 @@
* @license MIT, http://flightphp.com/license
*/
require_once __DIR__.'/core/Loader.php';
use flight\core\Loader;
require_once __DIR__ . '/core/Loader.php';
\flight\core\Loader::autoload(true, dirname(__DIR__));
Loader::autoload(true, [dirname(__DIR__)]);

@ -1,4 +1,6 @@
<?php
declare(strict_types=1);
/**
* Flight: An extensible micro-framework.
*
@ -8,36 +10,39 @@
namespace flight\core;
use Exception;
use InvalidArgumentException;
/**
* The Dispatcher class is responsible for dispatching events. Events
* are simply aliases for class methods or functions. The Dispatcher
* allows you to hook other functions to an event that can modify the
* input parameters and/or the output.
*/
class Dispatcher {
class Dispatcher
{
/**
* Mapped events.
*
* @var array
*/
protected $events = array();
protected array $events = [];
/**
* Method filters.
*
* @var array
*/
protected $filters = array();
protected array $filters = [];
/**
* Dispatches an event.
*
* @param string $name Event name
* @param array $params Callback parameters
* @return string Output of callback
* @throws \Exception
* @param string $name Event name
* @param array $params Callback parameters
*
*@throws Exception
*
* @return mixed|null Output of callback
*/
public function run($name, array $params = array()) {
final public function run(string $name, array $params = [])
{
$output = '';
// Run pre-filters
@ -46,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'])) {
@ -59,10 +64,11 @@ class Dispatcher {
/**
* Assigns a callback to an event.
*
* @param string $name Event name
* @param string $name Event name
* @param callback $callback Callback function
*/
public function set($name, $callback) {
final public function set(string $name, callable $callback): void
{
$this->events[$name] = $callback;
}
@ -70,19 +76,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;
final 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) {
final public function has(string $name): bool
{
return isset($this->events[$name]);
}
@ -90,27 +100,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) {
final 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) {
final public function hook(string $name, string $type, callable $callback): void
{
$this->filters[$name][$type][] = $callback;
}
@ -118,51 +129,59 @@ 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);
final 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;
$continue = self::execute($callback, $args);
if (false === $continue) {
break;
}
}
}
/**
* 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
* @throws \Exception
*/
public static function execute($callback, array &$params = array()) {
if (is_callable($callback)) {
return is_array($callback) ?
public static function execute($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 InvalidArgumentException('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 +195,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 +239,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();
final public function reset(): void
{
$this->events = [];
$this->filters = [];
}
}

@ -1,4 +1,6 @@
<?php
declare(strict_types=1);
/**
* Flight: An extensible micro-framework.
*
@ -8,46 +10,46 @@
namespace flight\core;
use Exception;
use ReflectionClass;
use ReflectionException;
/**
* The Loader class is responsible for loading objects. It maintains
* a list of reusable class instances and can generate a new class
* instances with custom initialization parameters. It also performs
* class autoloading.
*/
class Loader {
class Loader
{
/**
* Registered classes.
*
* @var array
*/
protected $classes = array();
protected array $classes = [];
/**
* Class instances.
*
* @var array
*/
protected $instances = array();
protected array $instances = [];
/**
* Autoload directories.
*
* @var array
*/
protected static $dirs = array();
protected static array $dirs = [];
/**
* Registers a class.
*
* @param string $name Registry name
* @param string|callable $class Class name or function to instantiate class
* @param array $params Class initialization parameters
* @param callback $callback Function to call after object instantiation
* @param string $name Registry name
* @param callable|string $class Class name or function to instantiate class
* @param array $params Class initialization parameters
* @param callable|null $callback $callback Function to call after object instantiation
*/
public function register($name, $class, array $params = array(), $callback = null) {
public function register(string $name, $class, array $params = [], ?callable $callback = null): void
{
unset($this->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 mixed $dirs Autoload directories
*/
public static function autoload($enabled = true, $dirs = array()) {
public static function autoload(bool $enabled = true, $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;
}
}
}
}

@ -1,4 +1,6 @@
<?php
declare(strict_types=1);
/**
* Flight: An extensible micro-framework.
*
@ -34,112 +36,114 @@ use flight\util\Collection;
* accept - HTTP accept parameters
* proxy_ip - Proxy IP address of the client
*/
class Request {
final class Request
{
/**
* @var string URL being requested
*/
public $url;
public string $url;
/**
* @var string Parent subdirectory of the URL
*/
public $base;
public string $base;
/**
* @var string Request method (GET, POST, PUT, DELETE)
*/
public $method;
public string $method;
/**
* @var string Referrer URL
*/
public $referrer;
public string $referrer;
/**
* @var string IP address of the client
*/
public $ip;
public string $ip;
/**
* @var bool Whether the request is an AJAX request
*/
public $ajax;
public bool $ajax;
/**
* @var string Server protocol (http, https)
*/
public $scheme;
public string $scheme;
/**
* @var string Browser information
*/
public $user_agent;
public string $user_agent;
/**
* @var string Content type
*/
public $type;
public string $type;
/**
* @var int Content length
*/
public $length;
public int $length;
/**
* @var \flight\util\Collection Query string parameters
* @var Collection Query string parameters
*/
public $query;
public Collection $query;
/**
* @var \flight\util\Collection Post parameters
* @var Collection Post parameters
*/
public $data;
public Collection $data;
/**
* @var \flight\util\Collection Cookie parameters
* @var Collection Cookie parameters
*/
public $cookies;
public Collection $cookies;
/**
* @var \flight\util\Collection Uploaded files
* @var Collection Uploaded files
*/
public $files;
public Collection $files;
/**
* @var bool Whether the connection is secure
*/
public $secure;
public bool $secure;
/**
* @var string HTTP accept parameters
*/
public $accept;
public string $accept;
/**
* @var string Proxy IP address of the client
*/
public $proxy_ip;
public string $proxy_ip;
/**
* @var string HTTP host name
*/
public $host;
public string $host;
/**
* Constructor.
*
* @param array $config Request configuration
*/
public function __construct($config = array()) {
public function __construct(array $config = [])
{
// Default properties
if (empty($config)) {
$config = array(
$config = [
'url' => 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,34 +167,34 @@ 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 && '' !== $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 (strpos($this->type, 'application/json') === 0) {
$body = $this->getBody();
if ($body != '') {
if (0 === strpos($this->type, 'application/json')) {
$body = self::getBody();
if ('' !== $body) {
$data = json_decode($body, true);
if ($data != null) {
if (is_array($data)) {
$this->data->setData($data);
}
}
@ -202,16 +206,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 +225,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 +244,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 +272,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 +301,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';
}
}

@ -1,4 +1,6 @@
<?php
declare(strict_types=1);
/**
* Flight: An extensible micro-framework.
*
@ -8,43 +10,24 @@
namespace flight\net;
use Exception;
/**
* The Response class represents an HTTP response. The object
* contains the response headers, HTTP status code, and response
* body.
*/
class Response {
/**
* @var int HTTP status
*/
protected $status = 200;
/**
* @var array HTTP headers
*/
protected $headers = array();
/**
* @var string HTTP response body
*/
protected $body;
class Response
{
/**
* @var bool HTTP response sent
* header Content-Length.
*/
protected $sent = false;
/**
* header Content-Length
*
* @var boolean
*/
public $content_length = true;
public bool $content_length = true;
/**
* @var array HTTP status codes
*/
public static $codes = array(
public static array $codes = [
100 => 'Continue',
101 => 'Switching Protocols',
102 => 'Processing',
@ -112,26 +95,47 @@ class Response {
508 => 'Loop Detected',
510 => 'Not Extended',
511 => 'Network Authentication Required'
);
511 => 'Network Authentication Required',
];
/**
* @var int HTTP status
*/
protected int $status = 200;
/**
* @var array HTTP headers
*/
protected array $headers = [];
/**
* @var string HTTP response body
*/
protected string $body = '';
/**
* @var bool HTTP response sent
*/
protected bool $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
* @param int|null $code HTTP status code.
*
* @throws Exception If invalid status code
*
* @return int|object Self reference
*/
public function status($code = null) {
if ($code === null) {
public function status(?int $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 {
throw new \Exception('Invalid status code.');
} else {
throw new Exception('Invalid status code.');
}
return $this;
@ -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|null $value Header value
*
* @return object Self reference
*/
public function header($name, $value = null) {
if (is_array($name)) {
public function header($name, ?string $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
*
* @return Response Self reference
*/
public function write($str) {
public function write(string $str): self
{
$this->body .= $str;
return $this;
@ -180,11 +189,12 @@ 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 = array();
$this->headers = [];
$this->body = '';
return $this;
@ -194,37 +204,40 @@ class Response {
* Sets caching headers for the response.
*
* @param int|string $expires Expiration time
* @return object Self reference
*
* @return Response Self reference
*/
public function cache($expires) {
if ($expires === false) {
public function cache($expires): self
{
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;
}
/**
* Sends HTTP headers.
*
* @return object Self reference
* @return Response Self reference
*/
public function sendHeaders() {
public function sendHeaders(): self
{
// 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);
}
}
@ -273,25 +284,28 @@ class Response {
/**
* Gets the content length.
*
* @return string Content length
* @return int Content length
*/
public function getContentLength() {
return extension_loaded('mbstring') ?
public function getContentLength(): int
{
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(): bool
{
return $this->sent;
}
/**
* Sends a HTTP response.
*/
public function send() {
public function send(): void
{
if (ob_get_length() > 0) {
ob_end_clean();
}
@ -305,4 +319,3 @@ class Response {
$this->sent = true;
}
}

@ -1,4 +1,6 @@
<?php
declare(strict_types=1);
/**
* Flight: An extensible micro-framework.
*
@ -13,11 +15,12 @@ namespace flight\net;
* an assigned callback function. The Router tries to match the
* requested URL against a series of URL patterns.
*/
class Route {
final class Route
{
/**
* @var string URL pattern
*/
public $pattern;
public string $pattern;
/**
* @var mixed Callback function
@ -27,37 +30,38 @@ class Route {
/**
* @var array HTTP methods
*/
public $methods = array();
public array $methods = [];
/**
* @var array Route parameters
*/
public $params = array();
public array $params = [];
/**
* @var string Matching regular expression
* @var string|null Matching regular expression
*/
public $regex;
public ?string $regex = null;
/**
* @var string URL splat content
*/
public $splat = '';
public string $splat = '';
/**
* @var boolean Pass self in callback parameters
* @var bool Pass self in callback parameters
*/
public $pass = false;
public bool $pass = false;
/**
* Constructor.
*
* @param string $pattern URL pattern
* @param mixed $callback Callback function
* @param array $methods HTTP methods
* @param boolean $pass Pass self in callback parameters
* @param string $pattern URL pattern
* @param mixed $callback Callback function
* @param array $methods HTTP methods
* @param bool $pass Pass self in callback parameters
*/
public function __construct($pattern, $callback, $methods, $pass) {
public function __construct(string $pattern, $callback, array $methods, bool $pass)
{
$this->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;
}
}

@ -1,4 +1,6 @@
<?php
declare(strict_types=1);
/**
* Flight: An extensible micro-framework.
*
@ -11,59 +13,56 @@ namespace flight\net;
/**
* The Router class is responsible for routing an HTTP request to
* an assigned callback function. The Router tries to match the
* requested URL against a series of URL patterns.
* requested URL against a series of URL patterns.
*/
class Router {
class Router
{
/**
* Mapped routes.
*
* @var array
* Case sensitive matching.
*/
protected $routes = array();
public bool $case_sensitive = false;
/**
* Pointer to current route.
*
* @var int
* Mapped routes.
*/
protected $index = 0;
protected array $routes = [];
/**
* Case sensitive matching.
*
* @var boolean
* Pointer to current route.
*/
public $case_sensitive = false;
protected int $index = 0;
/**
* Gets mapped routes.
*
* @return array Array of routes
*/
public function getRoutes() {
public function getRoutes(): array
{
return $this->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) {
$url = $pattern;
$methods = array('*');
public function map(string $pattern, callable $callback, bool $pass_route = false): void
{
$url = trim($pattern);
$methods = ['*'];
if (strpos($pattern, ' ') !== false) {
list($method, $url) = explode(' ', trim($pattern), 2);
if (false !== strpos($url, ' ')) {
[$method, $url] = explode(' ', $url, 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;
}
}

@ -1,4 +1,6 @@
<?php
declare(strict_types=1);
/**
* Flight: An extensible micro-framework.
*
@ -13,7 +15,8 @@ namespace flight\template;
* methods for managing view data and inserts the data into
* view templates upon rendering.
*/
class View {
class View
{
/**
* Location of view templates.
*
@ -33,7 +36,7 @@ class View {
*
* @var array
*/
protected $vars = array();
protected $vars = [];
/**
* Template file.
@ -47,7 +50,8 @@ class View {
*
* @param string $path Path to templates directory
*/
public function __construct($path = '.') {
public function __construct($path = '.')
{
$this->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);
}
}

@ -1,4 +1,6 @@
<?php
declare(strict_types=1);
/**
* Flight: An extensible micro-framework.
*
@ -8,28 +10,34 @@
namespace flight\util;
use ArrayAccess;
use function count;
use Countable;
use Iterator;
use JsonSerializable;
if (!interface_exists('JsonSerializable')) {
require_once dirname(__FILE__) . '/LegacyJsonSerializable.php';
require_once __DIR__ . '/LegacyJsonSerializable.php';
}
/**
* The Collection class allows you to access a set of data
* using both array and object notation.
*/
class Collection implements \ArrayAccess, \Iterator, \Countable, \JsonSerializable {
final class Collection implements ArrayAccess, Iterator, Countable, JsonSerializable
{
/**
* Collection data.
*
* @var array
*/
private $data;
private array $data;
/**
* Constructor.
*
* @param array $data Initial data
*/
public function __construct(array $data = array()) {
public function __construct(array $data = [])
{
$this->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,27 @@ 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;
#[\ReturnTypeWillChange]
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)) {
#[\ReturnTypeWillChange]
public function offsetSet($offset, $value)
{
if (null === $offset) {
$this->data[] = $value;
}
else {
} else {
$this->data[$offset] = $value;
}
}
@ -101,9 +119,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 +132,62 @@ 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() {
*/
#[\ReturnTypeWillChange]
public function current()
{
return current($this->data);
}
/**
* Gets current collection key.
*
* @return mixed Value
*/
public function key() {
*/
#[\ReturnTypeWillChange]
public function key()
{
return key($this->data);
}
/**
* Gets the next collection value.
*
* @return mixed Value
*/
public function next()
*/
#[\ReturnTypeWillChange]
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 +195,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 +205,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 +215,8 @@ class Collection implements \ArrayAccess, \Iterator, \Countable, \JsonSerializab
*
* @return array Collection data
*/
public function getData() {
public function getData(): array
{
return $this->data;
}
@ -194,23 +225,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 <b>json_encode</b>
*/
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 = [];
}
}

@ -1,11 +1,13 @@
<?php
declare(strict_types=1);
/**
* Flight: An extensible micro-framework.
*
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
* @license MIT, http://flightphp.com/license
*/
interface JsonSerializable {
interface LegacyJsonSerializable
{
public function jsonSerialize();
}
}

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

@ -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);
}
}

@ -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']);
}
}

@ -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';
});

@ -1,35 +1,43 @@
<?php
use flight\net\Request;
use flight\net\Response;
use flight\net\Router;
use flight\template\View;
/**
* Flight: An extensible micro-framework.
*
* @copyright Copyright (c) 2012, Mike Cao <mike@mikecao.com>
* @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();
}

@ -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);
}
}

@ -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();
}

@ -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));
}
}

@ -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);
}
}

@ -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('<html>Hello, Bob!</html>');

@ -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);
}
}

@ -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('цветя');
}
}

@ -5,45 +5,50 @@
* @copyright Copyright (c) 2012, Mike Cao <mike@mikecao.com>
* @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'));
}

@ -5,24 +5,25 @@
* @copyright Copyright (c) 2012, Mike Cao <mike@mikecao.com>
* @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';

@ -1,11 +1,14 @@
<?php
class Factory {
// Cannot be instantiated
private function __construct() {
class Factory
{
// Cannot be instantiated
private function __construct()
{
}
public static function create() {
public static function create()
{
return new self();
}
}
}

@ -1,10 +1,14 @@
<?php
class Hello {
public function sayHi() {
class Hello
{
public function sayHi()
{
return 'hello';
}
public static function sayBye() {
public static function sayBye()
{
return 'goodbye';
}
}

@ -1,8 +1,11 @@
<?php
class User {
class User
{
public $name;
public function __construct($name = ''){
public function __construct($name = '')
{
$this->name = $name;
}
}
}

Loading…
Cancel
Save