Merge pull request #501 from flightphp/master

Merging all new changes with OG framework
pull/504/head v3.0.0
n0nag0n 1 year ago committed by GitHub
commit 2df12f963e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,12 @@
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
indent_size = 2

5
.gitignore vendored

@ -2,3 +2,8 @@
vendor/ vendor/
composer.phar composer.phar
composer.lock composer.lock
.phpunit.result.cache
coverage/
.vscode/settings.json
*.sublime-workspace
.vscode/

@ -1,13 +1,28 @@
![](https://user-images.githubusercontent.com/104888/50957476-9c4acb80-14be-11e9-88ce-6447364dc1bb.png)
![](https://img.shields.io/badge/PHPStan-level%206-brightgreen.svg?style=flat)
![](https://img.shields.io/matrix/flight-php-framework%3Amatrix.org?server_fqdn=matrix.org&style=social&logo=matrix)
[![HitCount](https://hits.dwyl.com/flightphp/core.svg?style=flat-square&show=unique)](http://hits.dwyl.com/flightphp/core)
# What the fork?
This is a fork of the original project [https://github.com/mikecao/flight](https://github.com/mikecao/flight). That project hasn't seen updates in quite some time, so this fork is to help maintain the project going forward.
# What is Flight? # What is Flight?
Flight is a fast, simple, extensible framework for PHP. Flight enables you to Flight is a fast, simple, extensible framework for PHP. Flight enables you to
quickly and easily build RESTful web applications. quickly and easily build RESTful web applications.
Chat with us on Matrix IRC [#flight-php-framework:matrix.org](https://matrix.to/#/#flight-php-framework:matrix.org)
# Basic Usage
```php ```php
require 'flight/Flight.php';
Flight::route('/', function(){ // if installed with composer
echo 'hello world!'; require 'vendor/autoload.php';
// or if installed manually by zip file
//require 'flight/Flight.php';
Flight::route('/', function() {
echo 'hello world!';
}); });
Flight::start(); Flight::start();
@ -27,14 +42,15 @@ Flight is released under the [MIT](http://flightphp.com/license) license.
1\. Download the files. 1\. Download the files.
If you're using [Composer](https://getcomposer.org/), you can run the following command: If you're using [Composer](https://getcomposer.org/), you can run the following
command:
``` ```bash
composer require mikecao/flight composer require n0nag0n/flight
``` ```
OR you can [download](https://github.com/mikecao/flight/archive/master.zip) them directly OR you can [download](https://github.com/n0nag0n/flight/archive/master.zip)
and extract them to your web directory. them directly and extract them to your web directory.
2\. Configure your webserver. 2\. Configure your webserver.
@ -47,15 +63,16 @@ RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L] RewriteRule ^(.*)$ index.php [QSA,L]
``` ```
**Note**: If you need to use flight in a subdirectory add the line `RewriteBase /subdir/` just after `RewriteEngine On`. **Note**: If you need to use flight in a subdirectory add the line
`RewriteBase /subdir/` just after `RewriteEngine On`.
For *Nginx*, add the following to your server declaration: For *Nginx*, add the following to your server declaration:
``` ```
server { server {
location / { location / {
try_files $uri $uri/ /index.php; try_files $uri $uri/ /index.php;
} }
} }
``` ```
3\. Create your `index.php` file. 3\. Create your `index.php` file.
@ -75,8 +92,8 @@ require 'vendor/autoload.php';
Then define a route and assign a function to handle the request. Then define a route and assign a function to handle the request.
```php ```php
Flight::route('/', function(){ Flight::route('/', function () {
echo 'hello world!'; echo 'hello world!';
}); });
``` ```
@ -91,16 +108,16 @@ Flight::start();
Routing in Flight is done by matching a URL pattern with a callback function. Routing in Flight is done by matching a URL pattern with a callback function.
```php ```php
Flight::route('/', function(){ Flight::route('/', function () {
echo 'hello world!'; echo 'hello world!';
}); });
``` ```
The callback can be any object that is callable. So you can use a regular function: The callback can be any object that is callable. So you can use a regular function:
```php ```php
function hello(){ function hello() {
echo 'hello world!'; echo 'hello world!';
} }
Flight::route('/', 'hello'); Flight::route('/', 'hello');
@ -110,9 +127,9 @@ Or a class method:
```php ```php
class Greeting { class Greeting {
public static function hello() { public static function hello() {
echo 'hello world!'; echo 'hello world!';
} }
} }
Flight::route('/', array('Greeting', 'hello')); Flight::route('/', array('Greeting', 'hello'));
@ -123,18 +140,18 @@ Or an object method:
```php ```php
class Greeting class Greeting
{ {
public function __construct() { public function __construct() {
$this->name = 'John Doe'; $this->name = 'John Doe';
} }
public function hello() { public function hello() {
echo "Hello, {$this->name}!"; echo "Hello, {$this->name}!";
} }
} }
$greeting = new Greeting(); $greeting = new Greeting();
Flight::route('/', array($greeting, 'hello')); Flight::route('/', array($greeting, 'hello'));
``` ```
Routes are matched in the order they are defined. The first route to match a Routes are matched in the order they are defined. The first route to match a
@ -146,20 +163,20 @@ By default, route patterns are matched against all request methods. You can resp
to specific methods by placing an identifier before the URL. to specific methods by placing an identifier before the URL.
```php ```php
Flight::route('GET /', function(){ Flight::route('GET /', function () {
echo 'I received a GET request.'; echo 'I received a GET request.';
}); });
Flight::route('POST /', function(){ Flight::route('POST /', function () {
echo 'I received a POST request.'; echo 'I received a POST request.';
}); });
``` ```
You can also map multiple methods to a single callback by using a `|` delimiter: You can also map multiple methods to a single callback by using a `|` delimiter:
```php ```php
Flight::route('GET|POST /', function(){ Flight::route('GET|POST /', function () {
echo 'I received either a GET or a POST request.'; echo 'I received either a GET or a POST request.';
}); });
``` ```
@ -168,8 +185,8 @@ Flight::route('GET|POST /', function(){
You can use regular expressions in your routes: You can use regular expressions in your routes:
```php ```php
Flight::route('/user/[0-9]+', function(){ Flight::route('/user/[0-9]+', function () {
// This will match /user/1234 // This will match /user/1234
}); });
``` ```
@ -179,8 +196,8 @@ You can specify named parameters in your routes which will be passed along to
your callback function. your callback function.
```php ```php
Flight::route('/@name/@id', function($name, $id){ Flight::route('/@name/@id', function(string $name, string $id) {
echo "hello, $name ($id)!"; echo "hello, $name ($id)!";
}); });
``` ```
@ -188,9 +205,9 @@ You can also include regular expressions with your named parameters by using
the `:` delimiter: the `:` delimiter:
```php ```php
Flight::route('/@name/@id:[0-9]{3}', function($name, $id){ Flight::route('/@name/@id:[0-9]{3}', function(string $name, string $id) {
// This will match /bob/123 // This will match /bob/123
// But will not match /bob/12345 // But will not match /bob/12345
}); });
``` ```
@ -202,13 +219,16 @@ You can specify named parameters that are optional for matching by wrapping
segments in parentheses. segments in parentheses.
```php ```php
Flight::route('/blog(/@year(/@month(/@day)))', function($year, $month, $day){ Flight::route(
'/blog(/@year(/@month(/@day)))',
function(?string $year, ?string $month, ?string $day) {
// This will match the following URLS: // This will match the following URLS:
// /blog/2012/12/10 // /blog/2012/12/10
// /blog/2012/12 // /blog/2012/12
// /blog/2012 // /blog/2012
// /blog // /blog
}); }
);
``` ```
Any optional parameters that are not matched will be passed in as NULL. Any optional parameters that are not matched will be passed in as NULL.
@ -219,16 +239,16 @@ Matching is only done on individual URL segments. If you want to match multiple
segments you can use the `*` wildcard. segments you can use the `*` wildcard.
```php ```php
Flight::route('/blog/*', function(){ Flight::route('/blog/*', function () {
// This will match /blog/2000/02/01 // This will match /blog/2000/02/01
}); });
``` ```
To route all requests to a single callback, you can do: To route all requests to a single callback, you can do:
```php ```php
Flight::route('*', function(){ Flight::route('*', function () {
// Do something // Do something
}); });
``` ```
@ -238,16 +258,16 @@ You can pass execution on to the next matching route by returning `true` from
your callback function. your callback function.
```php ```php
Flight::route('/user/@name', function($name){ Flight::route('/user/@name', function (string $name) {
// Check some condition // Check some condition
if ($name != "Bob") { if ($name != "Bob") {
// Continue to next route // Continue to next route
return true; return true;
} }
}); });
Flight::route('/user/*', function(){ Flight::route('/user/*', function () {
// This will get called // This will get called
}); });
``` ```
@ -259,18 +279,18 @@ the route method. The route object will always be the last parameter passed to y
callback function. callback function.
```php ```php
Flight::route('/', function($route){ Flight::route('/', function(\flight\net\Route $route) {
// Array of HTTP methods matched against // Array of HTTP methods matched against
$route->methods; $route->methods;
// Array of named parameters // Array of named parameters
$route->params; $route->params;
// Matching regular expression // Matching regular expression
$route->regex; $route->regex;
// Contains the contents of any '*' used in the URL pattern // Contains the contents of any '*' used in the URL pattern
$route->splat; $route->splat;
}, true); }, true);
``` ```
@ -286,8 +306,8 @@ To map your own custom method, you use the `map` function:
```php ```php
// Map your method // Map your method
Flight::map('hello', function($name){ Flight::map('hello', function ($name) {
echo "hello $name!"; echo "hello $name!";
}); });
// Call your custom method // Call your custom method
@ -318,7 +338,7 @@ Flight::register('db', 'PDO', array('mysql:host=localhost;dbname=test','user','p
// Get an instance of your class // Get an instance of your class
// This will create an object with the defined parameters // This will create an object with the defined parameters
// //
// new PDO('mysql:host=localhost;dbname=test','user','pass'); // new PDO('mysql:host=localhost;dbname=test','user','pass');
// //
$db = Flight::db(); $db = Flight::db();
``` ```
@ -329,8 +349,8 @@ new object. The callback function takes one parameter, an instance of the new ob
```php ```php
// The callback will be passed the object that was constructed // The callback will be passed the object that was constructed
Flight::register('db', 'PDO', array('mysql:host=localhost;dbname=test','user','pass'), function($db){ Flight::register('db', 'PDO', array('mysql:host=localhost;dbname=test','user','pass'), function (PDO $db): void {
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}); });
``` ```
@ -359,8 +379,8 @@ by using the `map` method:
```php ```php
Flight::map('notFound', function(){ Flight::map('notFound', function(){
// Display custom 404 page // Display custom 404 page
include 'errors/404.html'; include 'errors/404.html';
}); });
``` ```
@ -387,8 +407,8 @@ methods as well as any custom methods that you've mapped.
A filter function looks like this: A filter function looks like this:
```php ```php
function(&$params, &$output) { function (array &$params, string &$output) {
// Filter code // Filter code
} }
``` ```
@ -397,16 +417,16 @@ Using the passed in variables you can manipulate the input parameters and/or the
You can have a filter run before a method by doing: You can have a filter run before a method by doing:
```php ```php
Flight::before('start', function(&$params, &$output){ Flight::before('start', function (array &$params, string &$output) {
// Do something // Do something
}); });
``` ```
You can have a filter run after a method by doing: You can have a filter run after a method by doing:
```php ```php
Flight::after('start', function(&$params, &$output){ Flight::after('start', function (array &$params, string &$output) {
// Do something // Do something
}); });
``` ```
@ -417,20 +437,20 @@ Here's an example of the filtering process:
```php ```php
// Map a custom method // Map a custom method
Flight::map('hello', function($name){ Flight::map('hello', function ($name) {
return "Hello, $name!"; return "Hello, $name!";
}); });
// Add a before filter // Add a before filter
Flight::before('hello', function(&$params, &$output){ Flight::before('hello', function (array &$params, string &$output) {
// Manipulate the parameter // Manipulate the parameter
$params[0] = 'Fred'; $params[0] = 'Fred';
}); });
// Add an after filter // Add an after filter
Flight::after('hello', function(&$params, &$output){ Flight::after('hello', function (array &$params, string &$output) {
// Manipulate the output // Manipulate the output
$output .= " Have a nice day!"; $output .= " Have a nice day!";
}); });
// Invoke the custom method // Invoke the custom method
@ -439,26 +459,28 @@ echo Flight::hello('Bob');
This should display: This should display:
Hello Fred! Have a nice day! ```
Hello Fred! Have a nice day!
```
If you have defined multiple filters, you can break the chain by returning `false` If you have defined multiple filters, you can break the chain by returning `false`
in any of your filter functions: in any of your filter functions:
```php ```php
Flight::before('start', function(&$params, &$output){ Flight::before('start', function (array &$params, string &$output){
echo 'one'; echo 'one';
}); });
Flight::before('start', function(&$params, &$output){ Flight::before('start', function (array &$params, string &$output): bool {
echo 'two'; echo 'two';
// This will end the chain // This will end the chain
return false; return false;
}); });
// This will not get called // This will not get called
Flight::before('start', function(&$params, &$output){ Flight::before('start', function (array &$params, string &$output){
echo 'three'; echo 'three';
}); });
``` ```
@ -480,7 +502,7 @@ To see if a variable has been set you can do:
```php ```php
if (Flight::has('id')) { if (Flight::has('id')) {
// Do something // Do something
} }
``` ```
@ -515,12 +537,14 @@ be reference like a local variable. Template files are simply PHP files. If the
content of the `hello.php` template file is: content of the `hello.php` template file is:
```php ```php
Hello, '<?php echo $name; ?>'! Hello, <?php echo $name; ?>!
``` ```
The output would be: The output would be:
Hello, Bob! ```
Hello, Bob!
```
You can also manually set view variables by using the set method: You can also manually set view variables by using the set method:
@ -580,26 +604,26 @@ If the template files looks like this:
```php ```php
<html> <html>
<head> <head>
<title><?php echo $title; ?></title> <title><?php echo $title; ?></title>
</head> </head>
<body> <body>
<?php echo $header_content; ?> <?php echo $header_content; ?>
<?php echo $body_content; ?> <?php echo $body_content; ?>
</body> </body>
</html> </html>
``` ```
The output would be: The output would be:
```html ```html
<html> <html>
<head> <head>
<title>Home Page</title> <title>Home Page</title>
</head> </head>
<body> <body>
<h1>Hello</h1> <h1>Hello</h1>
<div>World</div> <div>World</div>
</body> </body>
</html> </html>
``` ```
@ -615,11 +639,11 @@ require './Smarty/libs/Smarty.class.php';
// Register Smarty as the view class // Register Smarty as the view class
// Also pass a callback function to configure Smarty on load // Also pass a callback function to configure Smarty on load
Flight::register('view', 'Smarty', array(), function($smarty){ Flight::register('view', 'Smarty', array(), function (Smarty $smarty) {
$smarty->template_dir = './templates/'; $smarty->setTemplateDir() = './templates/';
$smarty->compile_dir = './templates_c/'; $smarty->setCompileDir() = './templates_c/';
$smarty->config_dir = './config/'; $smarty->setConfigDir() = './config/';
$smarty->cache_dir = './cache/'; $smarty->setCacheDir() = './cache/';
}); });
// Assign template data // Assign template data
@ -633,8 +657,8 @@ For completeness, you should also override Flight's default render method:
```php ```php
Flight::map('render', function($template, $data){ Flight::map('render', function($template, $data){
Flight::view()->assign($data); Flight::view()->assign($data);
Flight::view()->display($template); Flight::view()->display($template);
}); });
``` ```
# Error Handling # Error Handling
@ -648,9 +672,9 @@ response with some error information.
You can override this behavior for your own needs: You can override this behavior for your own needs:
```php ```php
Flight::map('error', function(Exception $ex){ Flight::map('error', function(Throwable $ex){
// Handle error // Handle error
echo $ex->getTraceAsString(); echo $ex->getTraceAsString();
}); });
``` ```
@ -669,8 +693,8 @@ behavior is to send an `HTTP 404 Not Found` response with a simple message.
You can override this behavior for your own needs: You can override this behavior for your own needs:
```php ```php
Flight::map('notFound', function(){ Flight::map('notFound', function () {
// Handle not found // Handle not found
}); });
``` ```
@ -701,26 +725,24 @@ $request = Flight::request();
The request object provides the following properties: The request object provides the following properties:
``` - **url** - The URL being requested
url - The URL being requested - **base** - The parent subdirectory of the URL
base - The parent subdirectory of the URL - **method** - The request method (GET, POST, PUT, DELETE)
method - The request method (GET, POST, PUT, DELETE) - **referrer** - The referrer URL
referrer - The referrer URL - **ip** - IP address of the client
ip - IP address of the client - **ajax** - Whether the request is an AJAX request
ajax - Whether the request is an AJAX request - **scheme** - The server protocol (http, https)
scheme - The server protocol (http, https) - **user_agent** - Browser information
user_agent - Browser information - **type** - The content type
type - The content type - **length** - The content length
length - The content length - **query** - Query string parameters
query - Query string parameters - **data** - Post data or JSON data
data - Post data or JSON data - **cookies** - Cookie data
cookies - Cookie data - **files** - Uploaded files
files - Uploaded files - **secure** - Whether the connection is secure
secure - Whether the connection is secure - **accept** - HTTP accept parameters
accept - HTTP accept parameters - **proxy_ip** - Proxy IP address of the client
proxy_ip - Proxy IP address of the client - **host** - The request host name
host - The request host name
```
You can access the `query`, `data`, `cookies`, and `files` properties You can access the `query`, `data`, `cookies`, and `files` properties
as arrays or objects. as arrays or objects.
@ -739,7 +761,8 @@ $id = Flight::request()->query->id;
## RAW Request Body ## RAW Request Body
To get the raw HTTP request body, for example when dealing with PUT requests, you can do: To get the raw HTTP request body, for example when dealing with PUT requests,
you can do:
```php ```php
$body = Flight::request()->getBody(); $body = Flight::request()->getBody();
@ -747,8 +770,8 @@ $body = Flight::request()->getBody();
## JSON Input ## JSON Input
If you send a request with the type `application/json` and the data `{"id": 123}` it will be available If you send a request with the type `application/json` and the data `{"id": 123}`
from the `data` property: it will be available from the `data` property:
```php ```php
$id = Flight::request()->data->id; $id = Flight::request()->data->id;
@ -768,9 +791,9 @@ and time a page was last modified. The client will continue to use their cache u
the last modified value is changed. the last modified value is changed.
```php ```php
Flight::route('/news', function(){ Flight::route('/news', function () {
Flight::lastModified(1234567890); Flight::lastModified(1234567890);
echo 'This content will be cached.'; echo 'This content will be cached.';
}); });
``` ```
@ -780,9 +803,9 @@ Flight::route('/news', function(){
want for the resource: want for the resource:
```php ```php
Flight::route('/news', function(){ Flight::route('/news', function () {
Flight::etag('my-unique-id'); Flight::etag('my-unique-id');
echo 'This content will be cached.'; echo 'This content will be cached.';
}); });
``` ```
@ -847,12 +870,12 @@ Flight::set('flight.log_errors', true);
The following is a list of all the available configuration settings: The following is a list of all the available configuration settings:
flight.base_url - Override the base url of the request. (default: null) - **flight.base_url** - Override the base url of the request. (default: null)
flight.case_sensitive - Case sensitive matching for URLs. (default: false) - **flight.case_sensitive** - Case sensitive matching for URLs. (default: false)
flight.handle_errors - Allow Flight to handle all errors internally. (default: true) - **flight.handle_errors** - Allow Flight to handle all errors internally. (default: true)
flight.log_errors - Log errors to the web server's error log file. (default: false) - **flight.log_errors** - Log errors to the web server's error log file. (default: false)
flight.views.path - Directory containing view template files. (default: ./views) - **flight.views.path** - Directory containing view template files. (default: ./views)
flight.views.extension - View template file extension. (default: .php) - **flight.views.extension** - View template file extension. (default: .php)
# Framework Methods # Framework Methods
@ -864,15 +887,15 @@ or overridden.
## Core Methods ## Core Methods
```php ```php
Flight::map($name, $callback) // Creates a custom framework method. Flight::map(string $name, callable $callback, bool $pass_route = false) // Creates a custom framework method.
Flight::register($name, $class, [$params], [$callback]) // Registers a class to a framework method. Flight::register(string $name, string $class, array $params = [], ?callable $callback = null) // Registers a class to a framework method.
Flight::before($name, $callback) // Adds a filter before a framework method. Flight::before(string $name, callable $callback) // Adds a filter before a framework method.
Flight::after($name, $callback) // Adds a filter after a framework method. Flight::after(string $name, callable $callback) // Adds a filter after a framework method.
Flight::path($path) // Adds a path for autoloading classes. Flight::path(string $path) // Adds a path for autoloading classes.
Flight::get($key) // Gets a variable. Flight::get(string $key) // Gets a variable.
Flight::set($key, $value) // Sets a variable. Flight::set(string $key, mixed $value) // Sets a variable.
Flight::has($key) // Checks if a variable is set. Flight::has(string $key) // Checks if a variable is set.
Flight::clear([$key]) // Clears a variable. Flight::clear(array|string $key = []) // Clears a variable.
Flight::init() // Initializes the framework to its default settings. Flight::init() // Initializes the framework to its default settings.
Flight::app() // Gets the application object instance Flight::app() // Gets the application object instance
``` ```
@ -882,16 +905,16 @@ Flight::app() // Gets the application object instance
```php ```php
Flight::start() // Starts the framework. Flight::start() // Starts the framework.
Flight::stop() // Stops the framework and sends a response. Flight::stop() // Stops the framework and sends a response.
Flight::halt([$code], [$message]) // Stop the framework with an optional status code and message. Flight::halt(int $code = 200, string $message = '') // Stop the framework with an optional status code and message.
Flight::route($pattern, $callback) // Maps a URL pattern to a callback. Flight::route(string $pattern, callable $callback, bool $pass_route = false) // Maps a URL pattern to a callback.
Flight::redirect($url, [$code]) // Redirects to another URL. Flight::redirect(string $url, int $code) // Redirects to another URL.
Flight::render($file, [$data], [$key]) // Renders a template file. Flight::render(string $file, array $data, ?string $key = null) // Renders a template file.
Flight::error($exception) // Sends an HTTP 500 response. Flight::error(Throwable $exception) // Sends an HTTP 500 response.
Flight::notFound() // Sends an HTTP 404 response. Flight::notFound() // Sends an HTTP 404 response.
Flight::etag($id, [$type]) // Performs ETag HTTP caching. Flight::etag(string $id, string $type = 'string') // Performs ETag HTTP caching.
Flight::lastModified($time) // Performs last modified HTTP caching. Flight::lastModified(int $time) // Performs last modified HTTP caching.
Flight::json($data, [$code], [$encode], [$charset], [$option]) // Sends a JSON response. Flight::json(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // Sends a JSON response.
Flight::jsonp($data, [$param], [$code], [$encode], [$charset], [$option]) // Sends a JSONP response. Flight::jsonp(mixed $data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // Sends a JSONP response.
``` ```
Any custom methods added with `map` and `register` can also be filtered. Any custom methods added with `map` and `register` can also be filtered.
@ -905,12 +928,10 @@ as an object instance.
```php ```php
require 'flight/autoload.php'; require 'flight/autoload.php';
use flight\Engine; $app = new flight\Engine();
$app = new Engine(); $app->route('/', function () {
echo 'hello world!';
$app->route('/', function(){
echo 'hello world!';
}); });
$app->start(); $app->start();

@ -1 +1 @@
2.0.1 3.0.0

@ -1,6 +1,6 @@
{ {
"name": "mikecao/flight", "name": "flightphp/core",
"description": "Flight is a fast, simple, extensible framework for PHP. Flight enables you to quickly and easily build RESTful web applications.", "description": "Flight is a fast, simple, extensible framework for PHP. Flight enables you to quickly and easily build RESTful web applications. This is the maintained fork of mikecao/flight",
"homepage": "http://flightphp.com", "homepage": "http://flightphp.com",
"license": "MIT", "license": "MIT",
"authors": [ "authors": [
@ -9,16 +9,52 @@
"email": "mike@mikecao.com", "email": "mike@mikecao.com",
"homepage": "http://www.mikecao.com/", "homepage": "http://www.mikecao.com/",
"role": "Original Developer" "role": "Original Developer"
},
{
"name": "Franyer Sánchez",
"email": "franyeradriansanchez@gmail.com",
"homepage": "https://faslatam.000webhostapp.com",
"role": "Maintainer"
},
{
"name": "n0nag0n",
"email": "n0nag0n@sky-9.com",
"role": "Maintainer"
} }
], ],
"require": { "require": {
"php": "^7.4|^8.0|^8.1", "php": "^7.4|^8.0|^8.1|^8.2",
"ext-json": "*" "ext-json": "*"
}, },
"autoload": { "autoload": {
"files": [ "flight/autoload.php", "flight/Flight.php" ] "files": [
"flight/autoload.php",
"flight/Flight.php"
]
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^9.5" "phpunit/phpunit": "^9.5",
} "phpstan/phpstan": "^1.10",
"phpstan/extension-installer": "^1.3"
},
"config": {
"allow-plugins": {
"phpstan/extension-installer": true
}
},
"scripts": {
"test": "phpunit",
"test-coverage": "XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-html=coverage",
"lint": "phpstan --no-progress -cphpstan.neon"
},
"suggest": {
"phpstan/phpstan": "PHP Static Analysis Tool",
"latte/latte": "Latte template engine"
},
"suggest-dev": {
"tracy/tracy": "Tracy debugger"
},
"replace": {
"mikecao/flight": "2.0.2"
}
} }

@ -0,0 +1,21 @@
{
"folders": [
{
"path": ".",
}
],
"settings": {
"LSP": {
"LSP-intelephense": {
"settings": {
"intelephense.environment.phpVersion": "7.4.0",
"intelephense.format.braces": "psr12",
},
},
"formatters":
{
"embedding.php": "LSP-intelephense"
},
},
},
}

@ -30,7 +30,14 @@ use Throwable;
* @method void start() Starts engine * @method void start() Starts engine
* @method void stop() Stops framework and outputs current response * @method void stop() Stops framework and outputs current response
* @method void halt(int $code = 200, string $message = '') Stops processing and returns a given 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 void route(string $pattern, callable $callback, bool $pass_route = false) Routes a URL to a callback function.
* @method void get(string $pattern, callable $callback, bool $pass_route = false) Routes a GET URL to a callback function.
* @method void post(string $pattern, callable $callback, bool $pass_route = false) Routes a POST URL to a callback function.
* @method void put(string $pattern, callable $callback, bool $pass_route = false) Routes a PUT URL to a callback function.
* @method void patch(string $pattern, callable $callback, bool $pass_route = false) Routes a PATCH URL to a callback function.
* @method void delete(string $pattern, callable $callback, bool $pass_route = false) Routes a DELETE URL to a callback function.
* @method Router router() Gets router * @method Router router() Gets router
* *
* Views * Views
@ -40,7 +47,7 @@ use Throwable;
* Request-response * Request-response
* @method Request request() Gets current request * @method Request request() Gets current request
* @method Response response() Gets current response * @method Response response() Gets current response
* @method void error(Exception $e) Sends an HTTP 500 response for any errors. * @method void error(Throwable $e) Sends an HTTP 500 response for any errors.
* @method void notFound() Sends an HTTP 404 response when a URL is not found. * @method void 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 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. * @method void json(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf-8', int $option = 0) Sends a JSON response.
@ -54,6 +61,7 @@ class Engine
{ {
/** /**
* Stored variables. * Stored variables.
* @var array<string, mixed>
*/ */
protected array $vars; protected array $vars;
@ -67,6 +75,13 @@ class Engine
*/ */
protected Dispatcher $dispatcher; protected Dispatcher $dispatcher;
/**
* If the framework has been initialized or not
*
* @var boolean
*/
protected bool $initialized = false;
/** /**
* Constructor. * Constructor.
*/ */
@ -84,7 +99,7 @@ class Engine
* Handles calls to class methods. * Handles calls to class methods.
* *
* @param string $name Method name * @param string $name Method name
* @param array $params Method parameters * @param array<int, mixed> $params Method parameters
* *
* @throws Exception * @throws Exception
* *
@ -114,7 +129,7 @@ class Engine
*/ */
public function init(): void public function init(): void
{ {
static $initialized = false; $initialized = $this->initialized;
$self = $this; $self = $this;
if ($initialized) { if ($initialized) {
@ -155,8 +170,8 @@ class Engine
$this->before('start', function () use ($self) { $this->before('start', function () use ($self) {
// Enable error handling // Enable error handling
if ($self->get('flight.handle_errors')) { if ($self->get('flight.handle_errors')) {
set_error_handler([$self, 'handleError']); set_error_handler(array($self, 'handleError'));
set_exception_handler([$self, 'handleException']); set_exception_handler(array($self, 'handleException'));
} }
// Set case-sensitivity // Set case-sensitivity
@ -165,7 +180,7 @@ class Engine
$self->response()->content_length = $self->get('flight.content_length'); $self->response()->content_length = $self->get('flight.content_length');
}); });
$initialized = true; $this->initialized = true;
} }
/** /**
@ -177,23 +192,26 @@ class Engine
* @param int $errline Error file line number * @param int $errline Error file line number
* *
* @throws ErrorException * @throws ErrorException
* @return bool
*/ */
public function handleError(int $errno, string $errstr, string $errfile, int $errline) public function handleError(int $errno, string $errstr, string $errfile, int $errline)
{ {
if ($errno & error_reporting()) { if ($errno & error_reporting()) {
throw new ErrorException($errstr, $errno, 0, $errfile, $errline); throw new ErrorException($errstr, $errno, 0, $errfile, $errline);
} }
return false;
} }
/** /**
* Custom exception handler. Logs exceptions. * Custom exception handler. Logs exceptions.
* *
* @param Exception $e Thrown exception * @param Throwable $e Thrown exception
*/ */
public function handleException($e): void public function handleException($e): void
{ {
if ($this->get('flight.log_errors')) { if ($this->get('flight.log_errors')) {
error_log($e->getMessage()); error_log($e->getMessage()); // @codeCoverageIgnore
} }
$this->error($e); $this->error($e);
@ -203,7 +221,7 @@ class Engine
* Maps a callback to a framework method. * Maps a callback to a framework method.
* *
* @param string $name Method name * @param string $name Method name
* @param callback $callback Callback function * @param callable $callback Callback function
* *
* @throws Exception If trying to map over a framework method * @throws Exception If trying to map over a framework method
*/ */
@ -218,11 +236,12 @@ class Engine
/** /**
* Registers a class to a framework method. * Registers a class to a framework method.
* @template T of object
* *
* @param string $name Method name * @param string $name Method name
* @param string $class Class name * @param class-string<T> $class Class name
* @param array $params Class initialization parameters * @param array<int, mixed> $params Class initialization parameters
* @param callable|null $callback $callback Function to call after object instantiation * @param ?callable(T $instance): void $callback Function to call after object instantiation
* *
* @throws Exception If trying to map over a framework method * @throws Exception If trying to map over a framework method
*/ */
@ -239,7 +258,7 @@ class Engine
* Adds a pre-filter to a method. * Adds a pre-filter to a method.
* *
* @param string $name Method name * @param string $name Method name
* @param callback $callback Callback function * @param callable $callback Callback function
*/ */
public function before(string $name, callable $callback): void public function before(string $name, callable $callback): void
{ {
@ -250,7 +269,7 @@ class Engine
* Adds a post-filter to a method. * Adds a post-filter to a method.
* *
* @param string $name Method name * @param string $name Method name
* @param callback $callback Callback function * @param callable $callback Callback function
*/ */
public function after(string $name, callable $callback): void public function after(string $name, callable $callback): void
{ {
@ -348,7 +367,7 @@ class Engine
// Flush any existing output // Flush any existing output
if (ob_get_length() > 0) { if (ob_get_length() > 0) {
$response->write(ob_get_clean()); $response->write(ob_get_clean()); // @codeCoverageIgnore
} }
// Enable output buffering // Enable output buffering
@ -392,9 +411,10 @@ class Engine
*/ */
public function _error($e): void public function _error($e): void
{ {
$msg = sprintf('<h1>500 Internal Server Error</h1>' . $msg = sprintf(
'<h3>%s (%s)</h3>' . '<h1>500 Internal Server Error</h1>' .
'<pre>%s</pre>', '<h3>%s (%s)</h3>' .
'<pre>%s</pre>',
$e->getMessage(), $e->getMessage(),
$e->getCode(), $e->getCode(),
$e->getTraceAsString() $e->getTraceAsString()
@ -406,9 +426,11 @@ class Engine
->status(500) ->status(500)
->write($msg) ->write($msg)
->send(); ->send();
// @codeCoverageIgnoreStart
} catch (Throwable $t) { } catch (Throwable $t) {
exit($msg); exit($msg);
} }
// @codeCoverageIgnoreEnd
} }
/** /**
@ -427,7 +449,8 @@ class Engine
$response->status($code); $response->status($code);
} }
$response->write(ob_get_clean()); $content = ob_get_clean();
$response->write($content ?: '');
$response->send(); $response->send();
} }
@ -437,7 +460,7 @@ class Engine
* Routes a URL to a callback function. * Routes a URL to a callback function.
* *
* @param string $pattern URL pattern to match * @param string $pattern URL pattern to match
* @param callback $callback Callback function * @param callable $callback Callback function
* @param bool $pass_route Pass the matching route object to the callback * @param bool $pass_route Pass the matching route object to the callback
*/ */
public function _route(string $pattern, callable $callback, bool $pass_route = false): void public function _route(string $pattern, callable $callback, bool $pass_route = false): void
@ -449,7 +472,7 @@ class Engine
* Routes a URL to a callback function. * Routes a URL to a callback function.
* *
* @param string $pattern URL pattern to match * @param string $pattern URL pattern to match
* @param callback $callback Callback function * @param callable $callback Callback function
* @param bool $pass_route Pass the matching route object to the callback * @param bool $pass_route Pass the matching route object to the callback
*/ */
public function _post(string $pattern, callable $callback, bool $pass_route = false): void public function _post(string $pattern, callable $callback, bool $pass_route = false): void
@ -461,7 +484,7 @@ class Engine
* Routes a URL to a callback function. * Routes a URL to a callback function.
* *
* @param string $pattern URL pattern to match * @param string $pattern URL pattern to match
* @param callback $callback Callback function * @param callable $callback Callback function
* @param bool $pass_route Pass the matching route object to the callback * @param bool $pass_route Pass the matching route object to the callback
*/ */
public function _put(string $pattern, callable $callback, bool $pass_route = false): void public function _put(string $pattern, callable $callback, bool $pass_route = false): void
@ -473,7 +496,7 @@ class Engine
* Routes a URL to a callback function. * Routes a URL to a callback function.
* *
* @param string $pattern URL pattern to match * @param string $pattern URL pattern to match
* @param callback $callback Callback function * @param callable $callback Callback function
* @param bool $pass_route Pass the matching route object to the callback * @param bool $pass_route Pass the matching route object to the callback
*/ */
public function _patch(string $pattern, callable $callback, bool $pass_route = false): void public function _patch(string $pattern, callable $callback, bool $pass_route = false): void
@ -485,7 +508,7 @@ class Engine
* Routes a URL to a callback function. * Routes a URL to a callback function.
* *
* @param string $pattern URL pattern to match * @param string $pattern URL pattern to match
* @param callback $callback Callback function * @param callable $callback Callback function
* @param bool $pass_route Pass the matching route object to the callback * @param bool $pass_route Pass the matching route object to the callback
*/ */
public function _delete(string $pattern, callable $callback, bool $pass_route = false): void public function _delete(string $pattern, callable $callback, bool $pass_route = false): void
@ -498,6 +521,7 @@ class Engine
* *
* @param int $code HTTP status code * @param int $code HTTP status code
* @param string $message Response message * @param string $message Response message
*
*/ */
public function _halt(int $code = 200, string $message = ''): void public function _halt(int $code = 200, string $message = ''): void
{ {
@ -506,7 +530,10 @@ class Engine
->status($code) ->status($code)
->write($message) ->write($message)
->send(); ->send();
exit(); // apologies for the crappy hack here...
if($message !== 'skip---exit') {
exit(); // @codeCoverageIgnore
}
} }
/** /**
@ -519,8 +546,8 @@ class Engine
->status(404) ->status(404)
->write( ->write(
'<h1>404 Not Found</h1>' . '<h1>404 Not Found</h1>' .
'<h3>The page you have requested could not be found.</h3>' . '<h3>The page you have requested could not be found.</h3>' .
str_repeat(' ', 512) str_repeat(' ', 512)
) )
->send(); ->send();
} }
@ -555,7 +582,7 @@ class Engine
* Renders a template. * Renders a template.
* *
* @param string $file Template file * @param string $file Template file
* @param array|null $data Template data * @param ?array<string, mixed> $data Template data
* @param string|null $key View variable name * @param string|null $key View variable name
* *
* @throws Exception * @throws Exception
@ -639,8 +666,10 @@ class Engine
$this->response()->header('ETag', $id); $this->response()->header('ETag', $id);
if (isset($_SERVER['HTTP_IF_NONE_MATCH']) && if (
$_SERVER['HTTP_IF_NONE_MATCH'] === $id) { isset($_SERVER['HTTP_IF_NONE_MATCH']) &&
$_SERVER['HTTP_IF_NONE_MATCH'] === $id
) {
$this->halt(304); $this->halt(304);
} }
} }
@ -654,8 +683,10 @@ class Engine
{ {
$this->response()->header('Last-Modified', gmdate('D, d M Y H:i:s \G\M\T', $time)); $this->response()->header('Last-Modified', gmdate('D, d M Y H:i:s \G\M\T', $time));
if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && if (
strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) === $time) { isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) &&
strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) === $time
) {
$this->halt(304); $this->halt(304);
} }
} }

@ -18,36 +18,27 @@ use flight\template\View;
/** /**
* The Flight class is a static representation of the framework. * The Flight class is a static representation of the framework.
* *
* Core.
*
* @method static void start() Starts the framework. * @method static void start() Starts the framework.
* @method static void path($path) Adds a path for autoloading classes. * @method static void path(string $path) Adds a path for autoloading classes.
* @method static void stop() Stops the framework and sends a response. * @method static void stop() Stops the framework and sends a response.
* @method static void halt($code = 200, $message = '') Stop the framework with an optional status code and message. * @method static void halt(int $code = 200, string $message = '') Stop the framework with an optional status code and message.
* *
* Routing. * @method static void route(string $pattern, callable $callback, bool $pass_route = false) Maps a URL pattern to a callback.
* @method static void route($pattern, $callback) Maps a URL pattern to a callback.
* @method static Router router() Returns Router instance. * @method static Router router() Returns Router instance.
* *
* Extending & Overriding. * @method static void map(string $name, callable $callback) Creates a custom framework method.
* @method static void map($name, $callback) Creates a custom framework method.
* @method static void register($name, $class, array $params = array(), $callback = null) Registers a class to a framework method.
* *
* Filtering.
* @method static void before($name, $callback) Adds a filter before a framework method. * @method static void before($name, $callback) Adds a filter before a framework method.
* @method static void after($name, $callback) Adds a filter after a framework method. * @method static void after($name, $callback) Adds a filter after a framework method.
* *
* Variables.
* @method static void set($key, $value) Sets a variable. * @method static void set($key, $value) Sets a variable.
* @method static mixed get($key) Gets a variable. * @method static mixed get($key) Gets a variable.
* @method static bool has($key) Checks if a variable is set. * @method static bool has($key) Checks if a variable is set.
* @method static void clear($key = null) Clears a variable. * @method static void clear($key = null) Clears a variable.
* *
* Views.
* @method static void render($file, array $data = null, $key = null) Renders a template file. * @method static void render($file, array $data = null, $key = null) Renders a template file.
* @method static View view() Returns View instance. * @method static View view() Returns View instance.
* *
* Request & Response.
* @method static Request request() Returns Request instance. * @method static Request request() Returns Request instance.
* @method static Response response() Returns Response instance. * @method static Response response() Returns Response instance.
* @method static void redirect($url, $code = 303) Redirects to another URL. * @method static void redirect($url, $code = 303) Redirects to another URL.
@ -56,7 +47,6 @@ use flight\template\View;
* @method static void error($exception) Sends an HTTP 500 response. * @method static void error($exception) Sends an HTTP 500 response.
* @method static void notFound() Sends an HTTP 404 response. * @method static void notFound() Sends an HTTP 404 response.
* *
* HTTP Caching.
* @method static void etag($id, $type = 'strong') Performs ETag HTTP caching. * @method static void etag($id, $type = 'strong') Performs ETag HTTP caching.
* @method static void lastModified($time) Performs last modified HTTP caching. * @method static void lastModified($time) Performs last modified HTTP caching.
*/ */
@ -67,24 +57,50 @@ class Flight
*/ */
private static Engine $engine; private static Engine $engine;
// Don't allow object instantiation /**
* Don't allow object instantiation
*
* @codeCoverageIgnore
* @return void
*/
private function __construct() private function __construct()
{ {
} }
private function __destruct() /**
* Forbid cloning the class
*
* @codeCoverageIgnore
* @return void
*/
private function __clone()
{ {
} }
private function __clone() /**
* Registers a class to a framework method.
* @template T of object
* @param string $name Static method name
* ```
* Flight::register('user', User::class);
*
* Flight::user(); # <- Return a User instance
* ```
* @param class-string<T> $class Fully Qualified Class Name
* @param array<int, mixed> $params Class constructor params
* @param ?Closure(T $instance): void $callback Perform actions with the instance
* @return void
*/
static function register($name, $class, $params = array(), $callback = null)
{ {
static::__callStatic('register', func_get_args());
} }
/** /**
* Handles calls to static methods. * Handles calls to static methods.
* *
* @param string $name Method name * @param string $name Method name
* @param array $params Method parameters * @param array<int, mixed> $params Method parameters
* *
* @throws Exception * @throws Exception
* *

@ -23,11 +23,13 @@ class Dispatcher
{ {
/** /**
* Mapped events. * Mapped events.
* @var array<string, callable>
*/ */
protected array $events = []; protected array $events = [];
/** /**
* Method filters. * Method filters.
* @var array<string, array<'before'|'after', array<int, callable>>>
*/ */
protected array $filters = []; protected array $filters = [];
@ -35,9 +37,9 @@ class Dispatcher
* Dispatches an event. * Dispatches an event.
* *
* @param string $name Event name * @param string $name Event name
* @param array $params Callback parameters * @param array<int, mixed> $params Callback parameters
* *
*@throws Exception * @throws Exception
* *
* @return mixed|null Output of callback * @return mixed|null Output of callback
*/ */
@ -65,7 +67,7 @@ class Dispatcher
* Assigns a callback to an event. * Assigns a callback to an event.
* *
* @param string $name Event name * @param string $name Event name
* @param callback $callback Callback function * @param callable $callback Callback function
*/ */
final public function set(string $name, callable $callback): void final public function set(string $name, callable $callback): void
{ {
@ -77,7 +79,7 @@ class Dispatcher
* *
* @param string $name Event name * @param string $name Event name
* *
* @return callback $callback Callback function * @return callable $callback Callback function
*/ */
final public function get(string $name): ?callable final public function get(string $name): ?callable
{ {
@ -118,7 +120,7 @@ class Dispatcher
* *
* @param string $name Event name * @param string $name Event name
* @param string $type Filter type * @param string $type Filter type
* @param callback $callback Callback function * @param callable $callback Callback function
*/ */
final public function hook(string $name, string $type, callable $callback): void final public function hook(string $name, string $type, callable $callback): void
{ {
@ -128,8 +130,8 @@ class Dispatcher
/** /**
* Executes a chain of method filters. * Executes a chain of method filters.
* *
* @param array $filters Chain of filters * @param array<int, callable> $filters Chain of filters
* @param array $params Method parameters * @param array<int, mixed> $params Method parameters
* @param mixed $output Method output * @param mixed $output Method output
* *
* @throws Exception * @throws Exception
@ -148,10 +150,10 @@ class Dispatcher
/** /**
* Executes a callback function. * Executes a callback function.
* *
* @param array|callback $callback Callback function * @param callable|array<class-string|object, string> $callback Callback function
* @param array $params Function parameters * @param array<int, mixed> $params Function parameters
* *
*@throws Exception * @throws Exception
* *
* @return mixed Function results * @return mixed Function results
*/ */
@ -170,7 +172,7 @@ class Dispatcher
* Calls a function. * Calls a function.
* *
* @param callable|string $func Name of function to call * @param callable|string $func Name of function to call
* @param array $params Function parameters * @param array<int, mixed> $params Function parameters
* *
* @return mixed Function results * @return mixed Function results
*/ */
@ -203,7 +205,7 @@ class Dispatcher
* Invokes a method. * Invokes a method.
* *
* @param mixed $func Class method * @param mixed $func Class method
* @param array $params Class method parameters * @param array<int, mixed> $params Class method parameters
* *
* @return mixed Function results * @return mixed Function results
*/ */
@ -230,6 +232,8 @@ class Dispatcher
return ($instance) ? return ($instance) ?
$class->$method($params[0], $params[1], $params[2]) : $class->$method($params[0], $params[1], $params[2]) :
$class::$method($params[0], $params[1], $params[2]); $class::$method($params[0], $params[1], $params[2]);
// This will be refactored soon enough
// @codeCoverageIgnoreStart
case 4: case 4:
return ($instance) ? return ($instance) ?
$class->$method($params[0], $params[1], $params[2], $params[3]) : $class->$method($params[0], $params[1], $params[2], $params[3]) :
@ -240,6 +244,7 @@ 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: default:
return \call_user_func_array($func, $params); return \call_user_func_array($func, $params);
// @codeCoverageIgnoreEnd
} }
} }

@ -10,6 +10,7 @@ declare(strict_types=1);
namespace flight\core; namespace flight\core;
use Closure;
use Exception; use Exception;
use ReflectionClass; use ReflectionClass;
use ReflectionException; use ReflectionException;
@ -24,26 +25,30 @@ class Loader
{ {
/** /**
* Registered classes. * Registered classes.
* @var array<string, array{class-string, array<int, mixed>, ?callable}> $classes
*/ */
protected array $classes = []; protected array $classes = [];
/** /**
* Class instances. * Class instances.
* @var array<string, object>
*/ */
protected array $instances = []; protected array $instances = [];
/** /**
* Autoload directories. * Autoload directories.
* @var array<int, string>
*/ */
protected static array $dirs = []; protected static array $dirs = [];
/** /**
* Registers a class. * Registers a class.
* @template T of object
* *
* @param string $name Registry name * @param string $name Registry name
* @param callable|string $class Class name or function to instantiate class * @param class-string<T> $class Class name or function to instantiate class
* @param array $params Class initialization parameters * @param array<int, mixed> $params Class initialization parameters
* @param callable|null $callback $callback Function to call after object instantiation * @param ?callable(T $instance): void $callback $callback Function to call after object instantiation
*/ */
public function register(string $name, $class, array $params = [], ?callable $callback = null): void public function register(string $name, $class, array $params = [], ?callable $callback = null): void
{ {
@ -77,7 +82,7 @@ class Loader
$obj = null; $obj = null;
if (isset($this->classes[$name])) { if (isset($this->classes[$name])) {
[$class, $params, $callback] = $this->classes[$name]; [0 => $class, 1 => $params, 2 => $callback] = $this->classes[$name];
$exists = isset($this->instances[$name]); $exists = isset($this->instances[$name]);
@ -116,15 +121,16 @@ class Loader
/** /**
* Gets a new instance of a class. * Gets a new instance of a class.
* @template T of object
* *
* @param callable|string $class Class name or callback function to instantiate class * @param class-string<T>|Closure(): class-string<T> $class Class name or callback function to instantiate class
* @param array $params Class initialization parameters * @param array<int, string> $params Class initialization parameters
* *
* @throws Exception * @throws Exception
* *
* @return object Class instance * @return T Class instance
*/ */
public function newInstance($class, array $params = []): object public function newInstance($class, array $params = [])
{ {
if (\is_callable($class)) { if (\is_callable($class)) {
return \call_user_func_array($class, $params); return \call_user_func_array($class, $params);
@ -135,6 +141,7 @@ class Loader
return new $class(); return new $class();
case 1: case 1:
return new $class($params[0]); return new $class($params[0]);
// @codeCoverageIgnoreStart
case 2: case 2:
return new $class($params[0], $params[1]); return new $class($params[0], $params[1]);
case 3: case 3:
@ -143,6 +150,7 @@ class Loader
return new $class($params[0], $params[1], $params[2], $params[3]); return new $class($params[0], $params[1], $params[2], $params[3]);
case 5: case 5:
return new $class($params[0], $params[1], $params[2], $params[3], $params[4]); return new $class($params[0], $params[1], $params[2], $params[3], $params[4]);
// @codeCoverageIgnoreEnd
default: default:
try { try {
$refClass = new ReflectionClass($class); $refClass = new ReflectionClass($class);
@ -179,14 +187,14 @@ class Loader
* Starts/stops autoloader. * Starts/stops autoloader.
* *
* @param bool $enabled Enable/disable autoloading * @param bool $enabled Enable/disable autoloading
* @param mixed $dirs Autoload directories * @param string|iterable<int, string> $dirs Autoload directories
*/ */
public static function autoload(bool $enabled = true, $dirs = []): void public static function autoload(bool $enabled = true, $dirs = []): void
{ {
if ($enabled) { if ($enabled) {
spl_autoload_register([__CLASS__, 'loadClass']); spl_autoload_register([__CLASS__, 'loadClass']);
} else { } else {
spl_autoload_unregister([__CLASS__, 'loadClass']); spl_autoload_unregister([__CLASS__, 'loadClass']); // @codeCoverageIgnore
} }
if (!empty($dirs)) { if (!empty($dirs)) {
@ -216,7 +224,7 @@ class Loader
/** /**
* Adds a directory for autoloading classes. * Adds a directory for autoloading classes.
* *
* @param mixed $dir Directory path * @param string|iterable<int, string> $dir Directory path
*/ */
public static function addDirectory($dir): void public static function addDirectory($dir): void
{ {

@ -18,23 +18,24 @@ use flight\util\Collection;
* are stored and accessible via the Request object. * are stored and accessible via the Request object.
* *
* The default request properties are: * The default request properties are:
* url - The URL being requested *
* base - The parent subdirectory of the URL * - **url** - The URL being requested
* method - The request method (GET, POST, PUT, DELETE) * - **base** - The parent subdirectory of the URL
* referrer - The referrer URL * - **method** - The request method (GET, POST, PUT, DELETE)
* ip - IP address of the client * - **referrer** - The referrer URL
* ajax - Whether the request is an AJAX request * - **ip** - IP address of the client
* scheme - The server protocol (http, https) * - **ajax** - Whether the request is an AJAX request
* user_agent - Browser information * - **scheme** - The server protocol (http, https)
* type - The content type * - **user_agent** - Browser information
* length - The content length * - **type** - The content type
* query - Query string parameters * - **length** - The content length
* data - Post parameters * - **query** - Query string parameters
* cookies - Cookie parameters * - **data** - Post parameters
* files - Uploaded files * - **cookies** - Cookie parameters
* secure - Connection is secure * - **files** - Uploaded files
* accept - HTTP accept parameters * - **secure** - Connection is secure
* proxy_ip - Proxy IP address of the client * - **accept** - HTTP accept parameters
* - **proxy_ip** - Proxy IP address of the client
*/ */
final class Request final class Request
{ {
@ -128,12 +129,25 @@ final class Request
*/ */
public string $host; public string $host;
/**
* Stream path for where to pull the request body from
*
* @var string
*/
private string $stream_path = 'php://input';
/**
* @var string Raw HTTP request body
*/
public string $body = '';
/** /**
* Constructor. * Constructor.
* *
* @param array $config Request configuration * @param array<string, mixed> $config Request configuration
* @param string
*/ */
public function __construct(array $config = []) public function __construct($config = array())
{ {
// Default properties // Default properties
if (empty($config)) { if (empty($config)) {
@ -165,7 +179,8 @@ final class Request
/** /**
* Initialize request properties. * Initialize request properties.
* *
* @param array $properties Array of request properties * @param array<string, mixed> $properties Array of request properties
* @return static
*/ */
public function init(array $properties = []) public function init(array $properties = [])
{ {
@ -175,6 +190,9 @@ final class Request
} }
// Get the requested URL without the base directory // Get the requested URL without the base directory
// This rewrites the url in case the public url and base directories match
// (such as installing on a subdirectory in a web server)
// @see testInitUrlSameAsBaseDirectory
if ('/' !== $this->base && '' !== $this->base && 0 === strpos($this->url, $this->base)) { if ('/' !== $this->base && '' !== $this->base && 0 === strpos($this->url, $this->base)) {
$this->url = substr($this->url, \strlen($this->base)); $this->url = substr($this->url, \strlen($this->base));
} }
@ -191,7 +209,7 @@ final class Request
// Check for JSON input // Check for JSON input
if (0 === strpos($this->type, 'application/json')) { if (0 === strpos($this->type, 'application/json')) {
$body = self::getBody(); $body = $this->getBody();
if ('' !== $body && null !== $body) { if ('' !== $body && null !== $body) {
$data = json_decode($body, true); $data = json_decode($body, true);
if (is_array($data)) { if (is_array($data)) {
@ -199,6 +217,8 @@ final class Request
} }
} }
} }
return $this;
} }
/** /**
@ -206,20 +226,22 @@ final class Request
* *
* @return string Raw HTTP request body * @return string Raw HTTP request body
*/ */
public static function getBody(): ?string public function getBody(): ?string
{ {
static $body; $body = $this->body;
if (null !== $body) { if ('' !== $body) {
return $body; return $body;
} }
$method = self::getMethod(); $method = self::getMethod();
if ('POST' === $method || 'PUT' === $method || 'DELETE' === $method || 'PATCH' === $method) { if ('POST' === $method || 'PUT' === $method || 'DELETE' === $method || 'PATCH' === $method) {
$body = file_get_contents('php://input'); $body = file_get_contents($this->stream_path);
} }
$this->body = $body;
return $body; return $body;
} }
@ -287,11 +309,11 @@ final class Request
* *
* @param string $url URL string * @param string $url URL string
* *
* @return array Query parameters * @return array<string, int|string|array<int|string, int|string>>
*/ */
public static function parseQuery(string $url): array public static function parseQuery(string $url): array
{ {
$params = []; $params = array();
$args = parse_url($url); $args = parse_url($url);
if (isset($args['query'])) { if (isset($args['query'])) {

@ -25,7 +25,7 @@ class Response
public bool $content_length = true; public bool $content_length = true;
/** /**
* @var array HTTP status codes * @var array<int, ?string> HTTP status codes
*/ */
public static array $codes = [ public static array $codes = [
100 => 'Continue', 100 => 'Continue',
@ -103,7 +103,7 @@ class Response
protected int $status = 200; protected int $status = 200;
/** /**
* @var array HTTP headers * @var array<string, int|string|array<int, string>> HTTP headers
*/ */
protected array $headers = []; protected array $headers = [];
@ -124,7 +124,7 @@ class Response
* *
* @throws Exception If invalid status code * @throws Exception If invalid status code
* *
* @return int|object Self reference * @return int|static Self reference
*/ */
public function status(?int $code = null) public function status(?int $code = null)
{ {
@ -144,10 +144,10 @@ class Response
/** /**
* Adds a header to the response. * Adds a header to the response.
* *
* @param array|string $name Header name or array of names and values * @param array<string, int|string>|string $name Header name or array of names and values
* @param string|null $value Header value * @param string|null $value Header value
* *
* @return object Self reference * @return static Self reference
*/ */
public function header($name, ?string $value = null) public function header($name, ?string $value = null)
{ {
@ -164,8 +164,7 @@ class Response
/** /**
* Returns the headers from the response. * Returns the headers from the response.
* * @return array<string, int|string|array<int, string>>
* @return array
*/ */
public function headers() public function headers()
{ {
@ -203,7 +202,7 @@ class Response
/** /**
* Sets caching headers for the response. * Sets caching headers for the response.
* *
* @param int|string $expires Expiration time * @param int|string|false $expires Expiration time as time() or as strtotime() string value
* *
* @return Response Self reference * @return Response Self reference
*/ */
@ -238,7 +237,8 @@ class Response
{ {
// Send status code header // Send status code header
if (false !== strpos(\PHP_SAPI, 'cgi')) { if (false !== strpos(\PHP_SAPI, 'cgi')) {
header( // @codeCoverageIgnoreStart
$this->setRealHeader(
sprintf( sprintf(
'Status: %d %s', 'Status: %d %s',
$this->status, $this->status,
@ -246,13 +246,15 @@ class Response
), ),
true true
); );
// @codeCoverageIgnoreEnd
} else { } else {
header( $this->setRealHeader(
sprintf( sprintf(
'%s %d %s', '%s %d %s',
$_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.1', $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.1',
$this->status, $this->status,
self::$codes[$this->status]), self::$codes[$this->status]
),
true, true,
$this->status $this->status
); );
@ -262,10 +264,10 @@ class Response
foreach ($this->headers as $field => $value) { foreach ($this->headers as $field => $value) {
if (\is_array($value)) { if (\is_array($value)) {
foreach ($value as $v) { foreach ($value as $v) {
header($field . ': ' . $v, false); $this->setRealHeader($field . ': ' . $v, false);
} }
} else { } else {
header($field . ': ' . $value); $this->setRealHeader($field . ': ' . $value);
} }
} }
@ -274,13 +276,27 @@ class Response
$length = $this->getContentLength(); $length = $this->getContentLength();
if ($length > 0) { if ($length > 0) {
header('Content-Length: ' . $length); $this->setRealHeader('Content-Length: ' . $length);
} }
} }
return $this; return $this;
} }
/**
* Sets a real header. Mostly used for test mocking.
*
* @param string $header_string The header string you would pass to header()
* @param bool $replace The optional replace parameter indicates whether the header should replace a previous similar header, or add a second header of the same type. By default it will replace, but if you pass in false as the second argument you can force multiple headers of the same type.
* @param int $response_code The response code to send
* @return self
* @codeCoverageIgnore
*/
public function setRealHeader(string $header_string, bool $replace = true, int $response_code = 0): self {
header($header_string, $replace, $response_code);
return $this;
}
/** /**
* Gets the content length. * Gets the content length.
* *
@ -294,7 +310,7 @@ class Response
} }
/** /**
* Gets whether response was sent. * Gets whether response body was sent.
*/ */
public function sent(): bool public function sent(): bool
{ {
@ -307,11 +323,11 @@ class Response
public function send(): void public function send(): void
{ {
if (ob_get_length() > 0) { if (ob_get_length() > 0) {
ob_end_clean(); ob_end_clean(); // @codeCoverageIgnore
} }
if (!headers_sent()) { if (!headers_sent()) {
$this->sendHeaders(); $this->sendHeaders(); // @codeCoverageIgnore
} }
echo $this->body; echo $this->body;

@ -28,12 +28,12 @@ final class Route
public $callback; public $callback;
/** /**
* @var array HTTP methods * @var array<int, string> HTTP methods
*/ */
public array $methods = []; public array $methods = [];
/** /**
* @var array Route parameters * @var array<int, ?string> Route parameters
*/ */
public array $params = []; public array $params = [];
@ -56,8 +56,8 @@ final class Route
* Constructor. * Constructor.
* *
* @param string $pattern URL pattern * @param string $pattern URL pattern
* @param mixed $callback Callback function * @param callable $callback Callback function
* @param array $methods HTTP methods * @param array<int, string> $methods HTTP methods
* @param bool $pass Pass self in callback parameters * @param bool $pass Pass self in callback parameters
*/ */
public function __construct(string $pattern, $callback, array $methods, bool $pass) public function __construct(string $pattern, $callback, array $methods, bool $pass)

@ -23,6 +23,7 @@ class Router
public bool $case_sensitive = false; public bool $case_sensitive = false;
/** /**
* Mapped routes. * Mapped routes.
* @var array<int, Route>
*/ */
protected array $routes = []; protected array $routes = [];
@ -34,7 +35,7 @@ class Router
/** /**
* Gets mapped routes. * Gets mapped routes.
* *
* @return array Array of routes * @return array<int, Route> Array of routes
*/ */
public function getRoutes(): array public function getRoutes(): array
{ {
@ -53,7 +54,7 @@ class Router
* Maps a URL pattern to a callback function. * Maps a URL pattern to a callback function.
* *
* @param string $pattern URL pattern to match * @param string $pattern URL pattern to match
* @param callback $callback Callback function * @param callable $callback Callback function
* @param bool $pass_route Pass the matching route object to the callback * @param bool $pass_route Pass the matching route object to the callback
*/ */
public function map(string $pattern, callable $callback, bool $pass_route = false): void public function map(string $pattern, callable $callback, bool $pass_route = false): void
@ -81,7 +82,7 @@ class Router
{ {
$url_decoded = urldecode($request->url); $url_decoded = urldecode($request->url);
while ($route = $this->current()) { while ($route = $this->current()) {
if (false !== $route && $route->matchMethod($request->method) && $route->matchUrl($url_decoded, $this->case_sensitive)) { if ($route->matchMethod($request->method) && $route->matchUrl($url_decoded, $this->case_sensitive)) {
return $route; return $route;
} }
$this->next(); $this->next();

@ -34,7 +34,7 @@ class View
/** /**
* View variables. * View variables.
* *
* @var array * @var array<string, mixed>
*/ */
protected $vars = []; protected $vars = [];
@ -70,8 +70,9 @@ class View
/** /**
* Sets a template variable. * Sets a template variable.
* *
* @param mixed $key Key * @param string|iterable<string, mixed> $key Key
* @param string $value Value * @param mixed $value Value
* @return static
*/ */
public function set($key, $value = null) public function set($key, $value = null)
{ {
@ -82,6 +83,8 @@ class View
} else { } else {
$this->vars[$key] = $value; $this->vars[$key] = $value;
} }
return $this;
} }
/** /**
@ -100,6 +103,7 @@ class View
* Unsets a template variable. If no key is passed in, clear all variables. * Unsets a template variable. If no key is passed in, clear all variables.
* *
* @param string $key Key * @param string $key Key
* @return static
*/ */
public function clear($key = null) public function clear($key = null)
{ {
@ -108,15 +112,18 @@ class View
} else { } else {
unset($this->vars[$key]); unset($this->vars[$key]);
} }
return $this;
} }
/** /**
* Renders a template. * Renders a template.
* *
* @param string $file Template file * @param string $file Template file
* @param array $data Template data * @param array<string, mixed> $data Template data
* *
* @throws \Exception If template not found * @throws \Exception If template not found
* @return void
*/ */
public function render($file, $data = null) public function render($file, $data = null)
{ {
@ -139,7 +146,7 @@ class View
* Gets the output of a template. * Gets the output of a template.
* *
* @param string $file Template file * @param string $file Template file
* @param array $data Template data * @param array<string, mixed> $data Template data
* *
* @return string Output of template * @return string Output of template
*/ */
@ -179,11 +186,13 @@ class View
$file .= $ext; $file .= $ext;
} }
if (('/' == substr($file, 0, 1))) { $is_windows = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';
if (('/' == substr($file, 0, 1)) || ($is_windows === true && ':' == substr($file, 1, 1))) {
return $file; return $file;
} }
return $this->path . '/' . $file; return $this->path . DIRECTORY_SEPARATOR . $file;
} }
/** /**
@ -195,6 +204,8 @@ class View
*/ */
public function e($str) public function e($str)
{ {
echo htmlentities($str); $value = htmlentities($str);
echo $value;
return $value;
} }
} }

@ -11,30 +11,32 @@ declare(strict_types=1);
namespace flight\util; namespace flight\util;
use ArrayAccess; use ArrayAccess;
use function count;
use Countable; use Countable;
use Iterator; use Iterator;
use JsonSerializable; use JsonSerializable;
if (!interface_exists('JsonSerializable')) { if (!interface_exists('JsonSerializable')) {
require_once __DIR__ . '/LegacyJsonSerializable.php'; require_once __DIR__ . '/LegacyJsonSerializable.php'; // @codeCoverageIgnore
} }
/** /**
* The Collection class allows you to access a set of data * The Collection class allows you to access a set of data
* using both array and object notation. * using both array and object notation.
* @implements ArrayAccess<string, mixed>
* @implements Iterator<string, mixed>
*/ */
final class Collection implements ArrayAccess, Iterator, Countable, JsonSerializable final class Collection implements ArrayAccess, Iterator, Countable, JsonSerializable
{ {
/** /**
* Collection data. * Collection data.
* @var array<string, mixed>
*/ */
private array $data; private array $data;
/** /**
* Constructor. * Constructor.
* *
* @param array $data Initial data * @param array<string, mixed> $data Initial data
*/ */
public function __construct(array $data = []) public function __construct(array $data = [])
{ {
@ -102,11 +104,11 @@ final class Collection implements ArrayAccess, Iterator, Countable, JsonSerializ
/** /**
* Sets an item at the offset. * Sets an item at the offset.
* *
* @param string $offset Offset * @param ?string $offset Offset
* @param mixed $value Value * @param mixed $value Value
*/ */
#[\ReturnTypeWillChange] #[\ReturnTypeWillChange]
public function offsetSet($offset, $value) public function offsetSet($offset, $value): void
{ {
if (null === $offset) { if (null === $offset) {
$this->data[] = $value; $this->data[] = $value;
@ -169,13 +171,11 @@ final class Collection implements ArrayAccess, Iterator, Countable, JsonSerializ
/** /**
* Gets the next collection value. * Gets the next collection value.
*
* @return mixed Value
*/ */
#[\ReturnTypeWillChange] #[\ReturnTypeWillChange]
public function next() public function next(): void
{ {
return next($this->data); next($this->data);
} }
/** /**
@ -187,7 +187,7 @@ final class Collection implements ArrayAccess, Iterator, Countable, JsonSerializ
{ {
$key = key($this->data); $key = key($this->data);
return null !== $key && false !== $key; return null !== $key;
} }
/** /**
@ -203,7 +203,7 @@ final class Collection implements ArrayAccess, Iterator, Countable, JsonSerializ
/** /**
* Gets the item keys. * Gets the item keys.
* *
* @return array Collection keys * @return array<int, string> Collection keys
*/ */
public function keys(): array public function keys(): array
{ {
@ -213,7 +213,7 @@ final class Collection implements ArrayAccess, Iterator, Countable, JsonSerializ
/** /**
* Gets the collection data. * Gets the collection data.
* *
* @return array Collection data * @return array<string, mixed> Collection data
*/ */
public function getData(): array public function getData(): array
{ {
@ -223,19 +223,15 @@ final class Collection implements ArrayAccess, Iterator, Countable, JsonSerializ
/** /**
* Sets the collection data. * Sets the collection data.
* *
* @param array $data New collection data * @param array<string, mixed> $data New collection data
*/ */
public function setData(array $data): void public function setData(array $data): void
{ {
$this->data = $data; $this->data = $data;
} }
/** #[\ReturnTypeWillChange]
* Gets the collection data which can be serialized to JSON. public function jsonSerialize()
*
* @return array Collection data which can be serialized by <b>json_encode</b>
*/
public function jsonSerialize(): array
{ {
return $this->data; return $this->data;
} }

@ -9,5 +9,9 @@ declare(strict_types=1);
*/ */
interface LegacyJsonSerializable interface LegacyJsonSerializable
{ {
/**
* Gets the collection data which can be serialized to JSON.
* @return mixed Collection data which can be serialized by <b>json_encode</b>
*/
public function jsonSerialize(); public function jsonSerialize();
} }

@ -0,0 +1,3 @@
<?php
class ReturnTypeWillChange {}

@ -0,0 +1,10 @@
includes:
- vendor/phpstan/phpstan/conf/bleedingEdge.neon
parameters:
level: 6
excludePaths:
- vendor
paths:
- flight
- index.php

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
bootstrap="tests/phpunit_autoload.php"
executionOrder="random"
beStrictAboutOutputDuringTests="true"
beStrictAboutTodoAnnotatedTests="true"
convertDeprecationsToExceptions="true"
stopOnError="true"
stopOnFailure="true"
verbose="true"
colors="true">
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">flight/</directory>
</include>
</coverage>
<testsuites>
<testsuite name="default">
<directory>tests/</directory>
</testsuite>
</testsuites>
<logging/>
</phpunit>

@ -8,9 +8,6 @@
use flight\Engine; use flight\Engine;
require_once 'vendor/autoload.php';
require_once __DIR__ . '/../flight/autoload.php';
class AutoloadTest extends PHPUnit\Framework\TestCase class AutoloadTest extends PHPUnit\Framework\TestCase
{ {
/** /**

@ -0,0 +1,98 @@
<?php
/**
* Flight: An extensible micro-framework.
*
* @copyright Copyright (c) 2012, Mike Cao <mike@mikecao.com>
* @license MIT, http://flightphp.com/license
*/
class CollectionTest extends PHPUnit\Framework\TestCase
{
/**
* @var \flight\util\Collection
*/
private $collection;
protected function setUp(): void
{
$this->collection = new \flight\util\Collection(['a' => 1, 'b' => 2]);
}
// Get an item
public function testGet()
{
$this->assertEquals(1, $this->collection->a);
}
// Set an item
public function testSet()
{
$this->collection->c = 3;
$this->assertEquals(3, $this->collection->c);
}
// Check if an item exists
public function testExists()
{
$this->assertTrue(isset($this->collection->a));
}
// Unset an item
public function testUnset()
{
unset($this->collection->a);
$this->assertFalse(isset($this->collection->a));
}
// Count items
public function testCount()
{
$this->assertEquals(2, count($this->collection));
}
// Iterate through items
public function testIterate()
{
$items = [];
foreach ($this->collection as $key => $value) {
$items[$key] = $value;
}
$this->assertEquals(['a' => 1, 'b' => 2], $items);
}
public function testJsonSerialize()
{
$this->assertEquals(['a' => 1, 'b' => 2], $this->collection->jsonSerialize());
}
public function testOffsetSetWithNullOffset() {
$this->collection->offsetSet(null, 3);
$this->assertEquals(3, $this->collection->offsetGet(0));
}
public function testOffsetExists() {
$this->collection->a = 1;
$this->assertTrue($this->collection->offsetExists('a'));
}
public function testOffsetUnset() {
$this->collection->a = 1;
$this->assertTrue($this->collection->offsetExists('a'));
$this->collection->offsetUnset('a');
$this->assertFalse($this->collection->offsetExists('a'));
}
public function testKeys() {
$this->collection->a = 1;
$this->collection->b = 2;
$this->assertEquals(['a', 'b'], $this->collection->keys());
}
public function testClear() {
$this->collection->a = 1;
$this->collection->b = 2;
$this->assertEquals(['a', 'b'], $this->collection->keys());
$this->collection->clear();
$this->assertEquals(0, $this->collection->count());
}
}

@ -8,9 +8,6 @@
use flight\core\Dispatcher; use flight\core\Dispatcher;
require_once 'vendor/autoload.php';
require_once __DIR__ . '/classes/Hello.php';
class DispatcherTest extends PHPUnit\Framework\TestCase class DispatcherTest extends PHPUnit\Framework\TestCase
{ {
/** /**
@ -47,6 +44,61 @@ class DispatcherTest extends PHPUnit\Framework\TestCase
self::assertEquals('hello', $result); self::assertEquals('hello', $result);
} }
public function testHasEvent()
{
$this->dispatcher->set('map-event', function () {
return 'hello';
});
$result = $this->dispatcher->has('map-event');
$this->assertTrue($result);
}
public function testClearAllRegisteredEvents() {
$this->dispatcher->set('map-event', function () {
return 'hello';
});
$this->dispatcher->set('map-event-2', function () {
return 'there';
});
$this->dispatcher->clear();
$result = $this->dispatcher->has('map-event');
$this->assertFalse($result);
$result = $this->dispatcher->has('map-event-2');
$this->assertFalse($result);
}
public function testClearDeclaredRegisteredEvent() {
$this->dispatcher->set('map-event', function () {
return 'hello';
});
$this->dispatcher->set('map-event-2', function () {
return 'there';
});
$this->dispatcher->clear('map-event');
$result = $this->dispatcher->has('map-event');
$this->assertFalse($result);
$result = $this->dispatcher->has('map-event-2');
$this->assertTrue($result);
}
// Map a static function
public function testStaticFunctionMapping()
{
$this->dispatcher->set('map2', 'Hello::sayHi');
$result = $this->dispatcher->run('map2');
self::assertEquals('hello', $result);
}
// Map a class method // Map a class method
public function testClassMethodMapping() public function testClassMethodMapping()
{ {
@ -98,4 +150,31 @@ class DispatcherTest extends PHPUnit\Framework\TestCase
$this->dispatcher->execute(['NonExistentClass', 'nonExistentMethod']); $this->dispatcher->execute(['NonExistentClass', 'nonExistentMethod']);
} }
public function testCallFunction4Params() {
$closure = function($param1, $params2, $params3, $param4) {
return 'hello'.$param1.$params2.$params3.$param4;
};
$params = ['param1', 'param2', 'param3', 'param4'];
$result = $this->dispatcher->callFunction($closure, $params);
$this->assertEquals('helloparam1param2param3param4', $result);
}
public function testCallFunction5Params() {
$closure = function($param1, $params2, $params3, $param4, $param5) {
return 'hello'.$param1.$params2.$params3.$param4.$param5;
};
$params = ['param1', 'param2', 'param3', 'param4', 'param5'];
$result = $this->dispatcher->callFunction($closure, $params);
$this->assertEquals('helloparam1param2param3param4param5', $result);
}
public function testCallFunction6Params() {
$closure = function($param1, $params2, $params3, $param4, $param5, $param6) {
return 'hello'.$param1.$params2.$params3.$param4.$param5.$param6;
};
$params = ['param1', 'param2', 'param3', 'param4', 'param5', 'param6'];
$result = $this->dispatcher->callFunction($closure, $params);
$this->assertEquals('helloparam1param2param3param4param5param6', $result);
}
} }

@ -0,0 +1,268 @@
<?php
use flight\Engine;
use flight\net\Response;
/**
* Flight: An extensible micro-framework.
*
* @copyright Copyright (c) 2012, Mike Cao <mike@mikecao.com>
* @license MIT, http://flightphp.com/license
*/
class EngineTest extends PHPUnit\Framework\TestCase
{
public function setUp(): void {
$_SERVER = [];
}
public function tearDown(): void {
$_SERVER = [];
}
public function testInitBeforeStart() {
$engine = new class extends Engine {
public function getInitializedVar() {
return $this->initialized;
}
};
$this->assertTrue($engine->getInitializedVar());
$engine->start();
// this is necessary cause it doesn't actually send the response correctly
ob_end_clean();
$this->assertFalse($engine->router()->case_sensitive);
$this->assertTrue($engine->response()->content_length);
}
public function testHandleErrorNoErrorNumber() {
$engine = new Engine();
$result = $engine->handleError(0, '', '', 0);
$this->assertFalse($result);
}
public function testHandleErrorWithException() {
$engine = new Engine();
$this->expectException(Exception::class);
$this->expectExceptionCode(5);
$this->expectExceptionMessage('thrown error message');
$engine->handleError(5, 'thrown error message', '', 0);
}
public function testHandleException() {
$engine = new Engine();
$regex_message = preg_quote('<h1>500 Internal Server Error</h1><h3>thrown exception message (20)</h3>');
$this->expectOutputRegex('~'.$regex_message.'~');
$engine->handleException(new Exception('thrown exception message', 20));
}
public function testMapExistingMethod() {
$engine = new Engine();
$this->expectException(Exception::class);
$this->expectExceptionMessage('Cannot override an existing framework method.');
$engine->map('_start', function() {});
}
public function testRegisterExistingMethod() {
$engine = new Engine();
$this->expectException(Exception::class);
$this->expectExceptionMessage('Cannot override an existing framework method.');
$engine->register('_error', 'stdClass');
}
public function testSetArrayOfValues() {
$engine = new Engine();
$engine->set([ 'key1' => 'value1', 'key2' => 'value2']);
$this->assertEquals('value1', $engine->get('key1'));
$this->assertEquals('value2', $engine->get('key2'));
}
public function testStartWithRoute() {
$_SERVER['REQUEST_METHOD'] = 'GET';
$_SERVER['REQUEST_URI'] = '/someRoute';
$engine = new class extends Engine {
public function getInitializedVar() {
return $this->initialized;
}
};
$engine->route('/someRoute', function() { echo 'i ran'; }, true);
$this->expectOutputString('i ran');
$engine->start();
}
// n0nag0n - I don't know why this does what it does, but it's existing framework functionality 1/1/24
public function testStartWithRouteButReturnedValueThrows404() {
$_SERVER['REQUEST_METHOD'] = 'GET';
$_SERVER['REQUEST_URI'] = '/someRoute';
$engine = new class extends Engine {
public function getInitializedVar() {
return $this->initialized;
}
};
$engine->route('/someRoute', function() { echo 'i ran'; return true; }, true);
$this->expectOutputString('<h1>404 Not Found</h1><h3>The page you have requested could not be found.</h3> ');
$engine->start();
}
public function testStopWithCode() {
$engine = new class extends Engine {
public function getLoader() {
return $this->loader;
}
};
// doing this so we can overwrite some parts of the response
$engine->getLoader()->register('response', function() {
return new class extends \flight\net\Response {
public function __construct() {}
public function setRealHeader(string $header_string, bool $replace = true, int $response_code = 0): Response
{
return $this;
}
};
});
// need to add another one of these because _stop() stops and gets clean, but $response->send() does too.....
ob_start();
$engine->response()->write('I am a teapot');
$this->expectOutputString('I am a teapot');
$engine->stop(500);
$this->assertEquals(500, $engine->response()->status());
}
public function testPostRoute() {
$engine = new Engine();
$engine->post('/someRoute', function() { echo 'i ran'; }, true);
$routes = $engine->router()->getRoutes();
$this->assertEquals('POST', $routes[0]->methods[0]);
$this->assertEquals('/someRoute', $routes[0]->pattern);
}
public function testPutRoute() {
$engine = new Engine();
$engine->put('/someRoute', function() { echo 'i ran'; }, true);
$routes = $engine->router()->getRoutes();
$this->assertEquals('PUT', $routes[0]->methods[0]);
$this->assertEquals('/someRoute', $routes[0]->pattern);
}
public function testPatchRoute() {
$engine = new Engine();
$engine->patch('/someRoute', function() { echo 'i ran'; }, true);
$routes = $engine->router()->getRoutes();
$this->assertEquals('PATCH', $routes[0]->methods[0]);
$this->assertEquals('/someRoute', $routes[0]->pattern);
}
public function testDeleteRoute() {
$engine = new Engine();
$engine->delete('/someRoute', function() { echo 'i ran'; }, true);
$routes = $engine->router()->getRoutes();
$this->assertEquals('DELETE', $routes[0]->methods[0]);
$this->assertEquals('/someRoute', $routes[0]->pattern);
}
public function testHalt() {
$engine = new class extends Engine {
public function getLoader() {
return $this->loader;
}
};
// doing this so we can overwrite some parts of the response
$engine->getLoader()->register('response', function() {
return new class extends \flight\net\Response {
public function __construct() {}
public function setRealHeader(string $header_string, bool $replace = true, int $response_code = 0): Response
{
return $this;
}
};
});
$this->expectOutputString('skip---exit');
$engine->halt(500, 'skip---exit');
$this->assertEquals(500, $engine->response()->status());
}
public function testRedirect() {
$engine = new Engine();
$engine->redirect('https://github.com', 302);
$this->assertEquals('https://github.com', $engine->response()->headers()['Location']);
$this->assertEquals(302, $engine->response()->status());
}
public function testRedirectWithBaseUrl() {
$engine = new Engine();
$engine->set('flight.base_url', '/subdirectory');
$engine->redirect('/someRoute', 301);
$this->assertEquals('/subdirectory/someRoute', $engine->response()->headers()['Location']);
$this->assertEquals(301, $engine->response()->status());
}
public function testJson() {
$engine = new Engine();
$engine->json(['key1' => 'value1', 'key2' => 'value2']);
$this->expectOutputString('{"key1":"value1","key2":"value2"}');
$this->assertEquals('application/json; charset=utf-8', $engine->response()->headers()['Content-Type']);
$this->assertEquals(200, $engine->response()->status());
}
public function testJsonP() {
$engine = new Engine();
$engine->request()->query['jsonp'] = 'whatever';
$engine->jsonp(['key1' => 'value1', 'key2' => 'value2']);
$this->expectOutputString('whatever({"key1":"value1","key2":"value2"});');
$this->assertEquals('application/javascript; charset=utf-8', $engine->response()->headers()['Content-Type']);
$this->assertEquals(200, $engine->response()->status());
}
public function testJsonPBadParam() {
$engine = new Engine();
$engine->jsonp(['key1' => 'value1', 'key2' => 'value2']);
$this->expectOutputString('({"key1":"value1","key2":"value2"});');
$this->assertEquals('application/javascript; charset=utf-8', $engine->response()->headers()['Content-Type']);
$this->assertEquals(200, $engine->response()->status());
}
public function testEtagSimple() {
$engine = new Engine();
$engine->etag('etag');
$this->assertEquals('etag', $engine->response()->headers()['ETag']);
}
public function testEtagWithHttpIfNoneMatch() {
// just need this not to exit...
$engine = new class extends Engine {
public function _halt(int $code = 200, string $message = ''): void
{
$this->response()->status($code);
$this->response()->write($message);
}
};
$_SERVER['HTTP_IF_NONE_MATCH'] = 'etag';
$engine->etag('etag');
$this->assertEquals('etag', $engine->response()->headers()['ETag']);
$this->assertEquals(304, $engine->response()->status());
}
public function testLastModifiedSimple() {
$engine = new Engine();
$engine->lastModified(1234567890);
$this->assertEquals('Fri, 13 Feb 2009 23:31:30 GMT', $engine->response()->headers()['Last-Modified']);
}
public function testLastModifiedWithHttpIfModifiedSince() {
// just need this not to exit...
$engine = new class extends Engine {
public function _halt(int $code = 200, string $message = ''): void
{
$this->response()->status($code);
$this->response()->write($message);
}
};
$_SERVER['HTTP_IF_MODIFIED_SINCE'] = 'Fri, 13 Feb 2009 23:31:30 GMT';
$engine->lastModified(1234567890);
$this->assertEquals('Fri, 13 Feb 2009 23:31:30 GMT', $engine->response()->headers()['Last-Modified']);
$this->assertEquals(304, $engine->response()->status());
}
}

@ -8,9 +8,6 @@
use flight\Engine; use flight\Engine;
require_once 'vendor/autoload.php';
require_once __DIR__ . '/../flight/autoload.php';
class FilterTest extends PHPUnit\Framework\TestCase class FilterTest extends PHPUnit\Framework\TestCase
{ {
/** /**

@ -11,16 +11,20 @@ use flight\template\View;
* @copyright Copyright (c) 2012, Mike Cao <mike@mikecao.com> * @copyright Copyright (c) 2012, Mike Cao <mike@mikecao.com>
* @license MIT, http://flightphp.com/license * @license MIT, http://flightphp.com/license
*/ */
require_once 'vendor/autoload.php';
require_once __DIR__ . '/../flight/Flight.php';
class FlightTest extends PHPUnit\Framework\TestCase class FlightTest extends PHPUnit\Framework\TestCase
{ {
protected function setUp(): void protected function setUp(): void
{ {
$_SERVER = [];
$_REQUEST = [];
Flight::init(); Flight::init();
} }
protected function tearDown(): void {
unset($_REQUEST);
unset($_SERVER);
}
// Checks that default components are loaded // Checks that default components are loaded
public function testDefaultComponents() public function testDefaultComponents()
{ {

@ -8,9 +8,9 @@
use flight\core\Loader; use flight\core\Loader;
require_once 'vendor/autoload.php';
require_once __DIR__ . '/classes/User.php'; require_once __DIR__ . '/classes/User.php';
require_once __DIR__ . '/classes/Factory.php'; require_once __DIR__ . '/classes/Factory.php';
require_once __DIR__ . '/classes/TesterClass.php';
class LoaderTest extends PHPUnit\Framework\TestCase class LoaderTest extends PHPUnit\Framework\TestCase
{ {
@ -118,4 +118,42 @@ class LoaderTest extends PHPUnit\Framework\TestCase
self::assertIsObject($obj); self::assertIsObject($obj);
self::assertInstanceOf(Factory::class, $obj); self::assertInstanceOf(Factory::class, $obj);
} }
public function testUnregisterClass() {
$this->loader->register('g', 'User');
$current_class = $this->loader->get('g');
$this->assertEquals([ 'User', [], null ], $current_class);
$this->loader->unregister('g');
$unregistered_class_result = $this->loader->get('g');
$this->assertNull($unregistered_class_result);
}
public function testNewInstance6Params() {
$TesterClass = $this->loader->newInstance('TesterClass', ['Bob','Fred', 'Joe', 'Jane', 'Sally', 'Suzie']);
$this->assertEquals('Bob', $TesterClass->param1);
$this->assertEquals('Fred', $TesterClass->param2);
$this->assertEquals('Joe', $TesterClass->param3);
$this->assertEquals('Jane', $TesterClass->param4);
$this->assertEquals('Sally', $TesterClass->param5);
$this->assertEquals('Suzie', $TesterClass->param6);
}
public function testNewInstance6ParamsBadClass() {
$this->expectException(Exception::class);
$this->expectExceptionMessage('Cannot instantiate BadClass');
$TesterClass = $this->loader->newInstance('BadClass', ['Bob','Fred', 'Joe', 'Jane', 'Sally', 'Suzie']);
}
public function testAddDirectoryAsArray() {
$loader = new class extends Loader {
public function getDirectories() {
return self::$dirs;
}
};
$loader->addDirectory([__DIR__ . '/classes']);
self::assertEquals([
dirname(__DIR__),
__DIR__ . '/classes'
], $loader->getDirectories());
}
} }

@ -8,8 +8,6 @@
use flight\Engine; use flight\Engine;
require_once 'vendor/autoload.php';
require_once __DIR__ . '/../flight/autoload.php';
require_once __DIR__ . '/classes/Hello.php'; require_once __DIR__ . '/classes/Hello.php';
class MapTest extends PHPUnit\Framework\TestCase class MapTest extends PHPUnit\Framework\TestCase

@ -8,9 +8,6 @@
use flight\Engine; use flight\Engine;
require_once 'vendor/autoload.php';
require_once __DIR__ . '/../flight/autoload.php';
class RedirectTest extends PHPUnit\Framework\TestCase class RedirectTest extends PHPUnit\Framework\TestCase
{ {
private Engine $app; private Engine $app;

@ -8,8 +8,6 @@
use flight\Engine; use flight\Engine;
require_once 'vendor/autoload.php';
require_once __DIR__ . '/../flight/autoload.php';
require_once __DIR__ . '/classes/User.php'; require_once __DIR__ . '/classes/User.php';
class RegisterTest extends PHPUnit\Framework\TestCase class RegisterTest extends PHPUnit\Framework\TestCase

@ -8,9 +8,6 @@
use flight\Engine; use flight\Engine;
require_once 'vendor/autoload.php';
require_once __DIR__ . '/../flight/Flight.php';
class RenderTest extends PHPUnit\Framework\TestCase class RenderTest extends PHPUnit\Framework\TestCase
{ {
private Engine $app; private Engine $app;

@ -7,9 +7,7 @@
*/ */
use flight\net\Request; use flight\net\Request;
use flight\util\Collection;
require_once 'vendor/autoload.php';
require_once __DIR__ . '/../flight/autoload.php';
class RequestTest extends PHPUnit\Framework\TestCase class RequestTest extends PHPUnit\Framework\TestCase
{ {
@ -17,6 +15,8 @@ class RequestTest extends PHPUnit\Framework\TestCase
protected function setUp(): void protected function setUp(): void
{ {
$_SERVER = [];
$_REQUEST = [];
$_SERVER['REQUEST_URI'] = '/'; $_SERVER['REQUEST_URI'] = '/';
$_SERVER['SCRIPT_NAME'] = '/index.php'; $_SERVER['SCRIPT_NAME'] = '/index.php';
$_SERVER['REQUEST_METHOD'] = 'GET'; $_SERVER['REQUEST_METHOD'] = 'GET';
@ -34,6 +34,11 @@ class RequestTest extends PHPUnit\Framework\TestCase
$this->request = new Request(); $this->request = new Request();
} }
protected function tearDown(): void {
unset($_REQUEST);
unset($_SERVER);
}
public function testDefaults() public function testDefaults()
{ {
self::assertEquals('/', $this->request->url); self::assertEquals('/', $this->request->url);
@ -150,4 +155,42 @@ class RequestTest extends PHPUnit\Framework\TestCase
$request = new Request(); $request = new Request();
self::assertEquals('http', $request->scheme); self::assertEquals('http', $request->scheme);
} }
public function testInitUrlSameAsBaseDirectory() {
$request = new Request([
'url' => '/vagrant/public/flightphp',
'base' => '/vagrant/public',
'query' => new Collection(),
'type' => ''
]);
$this->assertEquals('/flightphp', $request->url);
}
public function testInitNoUrl() {
$request = new Request([
'url' => '',
'base' => '/vagrant/public',
'type' => ''
]);
$this->assertEquals('/', $request->url);
}
public function testInitWithJsonBody() {
// create dummy file to pull request body from
$tmpfile = tmpfile();
$stream_path = stream_get_meta_data($tmpfile)['uri'];
file_put_contents($stream_path, '{"foo":"bar"}');
$_SERVER['REQUEST_METHOD'] = 'POST';
$request = new Request([
'url' => '/something/fancy',
'base' => '/vagrant/public',
'type' => 'application/json',
'length' => 13,
'data' => new Collection(),
'query' => new Collection(),
'stream_path' => $stream_path
]);
$this->assertEquals([ 'foo' => 'bar' ], $request->data->getData());
$this->assertEquals('{"foo":"bar"}', $request->getBody());
}
} }

@ -0,0 +1,227 @@
<?php
/**
* Flight: An extensible micro-framework.
*
* @copyright Copyright (c) 2012, Mike Cao <mike@mikecao.com>
* @license MIT, http://flightphp.com/license
*/
use flight\net\Request;
use flight\net\Response;
use flight\util\Collection;
class ResponseTest extends PHPUnit\Framework\TestCase
{
protected function setUp(): void
{
$_SERVER = [];
$_REQUEST = [];
$_GET = [];
$_POST = [];
$_COOKIE = [];
$_FILES = [];
}
protected function tearDown(): void {
unset($_REQUEST);
unset($_SERVER);
}
public function testStatusDefault() {
$response = new Response();
$this->assertSame(200, $response->status());
}
public function testStatusValidCode() {
$response = new Response();
$response->status(200);
$this->assertEquals(200, $response->status());
}
public function testStatusInvalidCode() {
$response = new Response();
$this->expectException(Exception::class);
$this->expectExceptionMessage('Invalid status code.');
$response->status(999);
}
public function testStatusReturnObject() {
$response = new Response();
$this->assertEquals($response, $response->status(200));
}
public function testHeaderSingle() {
$response = new Response();
$response->header('Content-Type', 'text/html');
$this->assertEquals(['Content-Type' => 'text/html'], $response->headers());
}
public function testHeaderSingleKeepCaseSensitive() {
$response = new Response();
$response->header('content-type', 'text/html');
$response->header('x-test', 'test');
$this->assertEquals(['content-type' => 'text/html', 'x-test' => 'test'], $response->headers());
}
public function testHeaderArray() {
$response = new Response();
$response->header(['Content-Type' => 'text/html', 'X-Test' => 'test']);
$this->assertEquals(['Content-Type' => 'text/html', 'X-Test' => 'test'], $response->headers());
}
public function testHeaderReturnObject() {
$response = new Response();
$this->assertEquals($response, $response->header('Content-Type', 'text/html'));
}
public function testWrite() {
$response = new class extends Response {
public function getBody() {
return $this->body;
}
};
$response->write('test');
$this->assertEquals('test', $response->getBody());
}
public function testWriteEmptyString() {
$response = new class extends Response {
public function getBody() {
return $this->body;
}
};
$response->write('');
$this->assertEquals('', $response->getBody());
}
public function testWriteReturnObject() {
$response = new Response();
$this->assertEquals($response, $response->write('test'));
}
public function testClear() {
$response = new class extends Response {
public function getBody() {
return $this->body;
}
};
$response->write('test');
$response->status(404);
$response->header('Content-Type', 'text/html');
$response->clear();
$this->assertEquals('', $response->getBody());
$this->assertEquals(200, $response->status());
$this->assertEquals([], $response->headers());
}
public function testCacheSimple() {
$response = new Response();
$cache_time = time() + 60;
$response->cache($cache_time);
$this->assertEquals([
'Expires' => gmdate('D, d M Y H:i:s', $cache_time) . ' GMT',
'Cache-Control' => 'max-age=60'
], $response->headers());
}
public function testCacheSimpleWithString() {
$response = new Response();
$cache_time = time() + 60;
$response->cache('now +60 seconds');
$this->assertEquals([
'Expires' => gmdate('D, d M Y H:i:s', $cache_time) . ' GMT',
'Cache-Control' => 'max-age=60'
], $response->headers());
}
public function testCacheSimpleWithPragma() {
$response = new Response();
$cache_time = time() + 60;
$response->header('Pragma', 'no-cache');
$response->cache($cache_time);
$this->assertEquals([
'Expires' => gmdate('D, d M Y H:i:s', $cache_time) . ' GMT',
'Cache-Control' => 'max-age=60'
], $response->headers());
}
public function testCacheFalseExpiresValue() {
$response = new Response();
$response->cache(false);
$this->assertEquals([
'Expires' => 'Mon, 26 Jul 1997 05:00:00 GMT',
'Cache-Control' => [
'no-store, no-cache, must-revalidate',
'post-check=0, pre-check=0',
'max-age=0',
],
'Pragma' => 'no-cache'
], $response->headers());
}
public function testSendHeadersRegular() {
$response = new class extends Response {
protected $test_sent_headers = [];
protected array $headers = [
'Cache-Control' => [
'no-store, no-cache, must-revalidate',
'post-check=0, pre-check=0',
'max-age=0',
]
];
public function setRealHeader(string $header_string, bool $replace = true, int $response_code = 0): Response
{
$this->test_sent_headers[] = $header_string;
return $this;
}
public function getSentHeaders(): array
{
return $this->test_sent_headers;
}
};
$response->header('Content-Type', 'text/html');
$response->header('X-Test', 'test');
$response->write('Something');
$response->sendHeaders();
$sent_headers = $response->getSentHeaders();
$this->assertEquals([
'HTTP/1.1 200 OK',
'Cache-Control: no-store, no-cache, must-revalidate',
'Cache-Control: post-check=0, pre-check=0',
'Cache-Control: max-age=0',
'Content-Type: text/html',
'X-Test: test',
'Content-Length: 9'
], $sent_headers);
}
public function testSentDefault() {
$response = new Response();
$this->assertFalse($response->sent());
}
public function testSentTrue() {
$response = new class extends Response {
protected $test_sent_headers = [];
public function setRealHeader(string $header_string, bool $replace = true, int $response_code = 0): Response
{
$this->test_sent_headers[] = $header_string;
return $this;
}
};
$response->header('Content-Type', 'text/html');
$response->header('X-Test', 'test');
$response->write('Something');
$this->expectOutputString('Something');
$response->send();
$this->assertTrue($response->sent());
}
}

@ -10,9 +10,6 @@ use flight\core\Dispatcher;
use flight\net\Request; use flight\net\Request;
use flight\net\Router; use flight\net\Router;
require_once 'vendor/autoload.php';
require_once __DIR__ . '/../flight/autoload.php';
class RouterTest extends PHPUnit\Framework\TestCase class RouterTest extends PHPUnit\Framework\TestCase
{ {
private Router $router; private Router $router;
@ -23,11 +20,18 @@ class RouterTest extends PHPUnit\Framework\TestCase
protected function setUp(): void protected function setUp(): void
{ {
$_SERVER = [];
$_REQUEST = [];
$this->router = new Router(); $this->router = new Router();
$this->request = new Request(); $this->request = new Request();
$this->dispatcher = new Dispatcher(); $this->dispatcher = new Dispatcher();
} }
protected function tearDown(): void {
unset($_REQUEST);
unset($_SERVER);
}
// Simple output // Simple output
public function ok() public function ok()
{ {
@ -101,6 +105,16 @@ class RouterTest extends PHPUnit\Framework\TestCase
$this->check('OK'); $this->check('OK');
} }
// Simple path with trailing slash
// Simple path
public function testPathRouteTrailingSlash()
{
$this->router->map('/path/', [$this, 'ok']);
$this->request->url = '/path';
$this->check('OK');
}
// POST route // POST route
public function testPostRoute() public function testPostRoute()
{ {
@ -293,4 +307,49 @@ class RouterTest extends PHPUnit\Framework\TestCase
$this->check('цветя'); $this->check('цветя');
} }
public function testGetAndClearRoutes() {
$this->router->map('/path1', [$this, 'ok']);
$this->router->map('/path2', [$this, 'ok']);
$this->router->map('/path3', [$this, 'ok']);
$this->router->map('/path4', [$this, 'ok']);
$this->router->map('/path5', [$this, 'ok']);
$this->router->map('/path6', [$this, 'ok']);
$this->router->map('/path7', [$this, 'ok']);
$this->router->map('/path8', [$this, 'ok']);
$this->router->map('/path9', [$this, 'ok']);
$routes = $this->router->getRoutes();
$this->assertEquals(9, count($routes));
$this->router->clear();
$this->assertEquals(0, count($this->router->getRoutes()));
}
public function testResetRoutes() {
$router = new class extends Router {
public function getIndex() {
return $this->index;
}
};
$router->map('/path1', [$this, 'ok']);
$router->map('/path2', [$this, 'ok']);
$router->map('/path3', [$this, 'ok']);
$router->map('/path4', [$this, 'ok']);
$router->map('/path5', [$this, 'ok']);
$router->map('/path6', [$this, 'ok']);
$router->map('/path7', [$this, 'ok']);
$router->map('/path8', [$this, 'ok']);
$router->map('/path9', [$this, 'ok']);
$router->next();
$router->next();
$router->next();
$this->assertEquals(3, $router->getIndex());
$router->reset();
$this->assertEquals(0, $router->getIndex());
}
} }

@ -5,9 +5,6 @@
* @copyright Copyright (c) 2012, Mike Cao <mike@mikecao.com> * @copyright Copyright (c) 2012, Mike Cao <mike@mikecao.com>
* @license MIT, http://flightphp.com/license * @license MIT, http://flightphp.com/license
*/ */
require_once 'vendor/autoload.php';
require_once __DIR__ . '/../flight/autoload.php';
class VariableTest extends PHPUnit\Framework\TestCase class VariableTest extends PHPUnit\Framework\TestCase
{ {
/** /**

@ -5,9 +5,6 @@
* @copyright Copyright (c) 2012, Mike Cao <mike@mikecao.com> * @copyright Copyright (c) 2012, Mike Cao <mike@mikecao.com>
* @license MIT, http://flightphp.com/license * @license MIT, http://flightphp.com/license
*/ */
require_once 'vendor/autoload.php';
require_once __DIR__ . '/../flight/autoload.php';
class ViewTest extends PHPUnit\Framework\TestCase class ViewTest extends PHPUnit\Framework\TestCase
{ {
/** /**
@ -36,6 +33,21 @@ class ViewTest extends PHPUnit\Framework\TestCase
$this->assertNull($this->view->get('test')); $this->assertNull($this->view->get('test'));
} }
public function testMultipleVariables() {
$this->view->set([
'test' => 123,
'foo' => 'bar'
]);
$this->assertEquals(123, $this->view->get('test'));
$this->assertEquals('bar', $this->view->get('foo'));
$this->view->clear();
$this->assertNull($this->view->get('test'));
$this->assertNull($this->view->get('foo'));
}
// Check if template files exist // Check if template files exist
public function testTemplateExists() public function testTemplateExists()
{ {
@ -51,6 +63,13 @@ class ViewTest extends PHPUnit\Framework\TestCase
$this->expectOutputString('Hello, Bob!'); $this->expectOutputString('Hello, Bob!');
} }
public function testRenderBadFilePath() {
$this->expectException(Exception::class);
$this->expectExceptionMessage('Template file not found: '.__DIR__ . '/views/badfile.php');
$this->view->render('badfile');
}
// Fetch template output // Fetch template output
public function testFetch() public function testFetch()
{ {
@ -79,4 +98,23 @@ class ViewTest extends PHPUnit\Framework\TestCase
$this->expectOutputString('Hello world, Bob!'); $this->expectOutputString('Hello world, Bob!');
} }
public function testGetTemplateAbsolutePath() {
$tmpfile = tmpfile();
$this->view->extension = '';
$file_path = stream_get_meta_data($tmpfile)['uri'];
$this->assertEquals($file_path, $this->view->getTemplate($file_path));
}
public function testE() {
$this->expectOutputString('&lt;script&gt;');
$result = $this->view->e('<script>');
$this->assertEquals('&lt;script&gt;', $result);
}
public function testENoNeedToEscape() {
$this->expectOutputString('script');
$result = $this->view->e('script');
$this->assertEquals('script', $result);
}
} }

@ -0,0 +1,17 @@
<?php
class TesterClass {
public $param1;
public $param2;
public $param3;
public $param4;
public $param5;
public $param6;
public function __construct($param1, $param2, $param3, $param4, $param5, $param6) {
$this->param1 = $param1;
$this->param2 = $param2;
$this->param3 = $param3;
$this->param4 = $param4;
$this->param5 = $param5;
$this->param6 = $param6;
}
};

@ -0,0 +1,6 @@
<?php
$path = file_exists(__DIR__ . '/../vendor/autoload.php')
? __DIR__ . '/../vendor/autoload.php'
: __DIR__ . '/../flight/autoload.php';
require_once($path);
Loading…
Cancel
Save