Commit version 24.12.13800
This commit is contained in:
4
libs/flight/.gitignore
vendored
4
libs/flight/.gitignore
vendored
@ -1,4 +0,0 @@
|
||||
.idea
|
||||
vendor/
|
||||
composer.phar
|
||||
composer.lock
|
File diff suppressed because it is too large
Load Diff
@ -1,96 +1,149 @@
|
||||
<?php
|
||||
/**
|
||||
* Flight: An extensible micro-framework.
|
||||
*
|
||||
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
|
||||
* @license MIT, http://flightphp.com/license
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use flight\Engine;
|
||||
use flight\net\Request;
|
||||
use flight\net\Response;
|
||||
use flight\net\Router;
|
||||
use flight\template\View;
|
||||
use flight\net\Route;
|
||||
|
||||
require_once __DIR__ . '/autoload.php';
|
||||
|
||||
/**
|
||||
* The Flight class is a static representation of the framework.
|
||||
*
|
||||
* Core.
|
||||
* @method static void start() Starts the framework.
|
||||
* @method static void path($path) Adds a path for autoloading classes.
|
||||
* @method static void stop() Stops the framework and sends a response.
|
||||
* @method static void halt($code = 200, $message = '') Stop the framework with an optional status code and message.
|
||||
* @license MIT, http://flightphp.com/license
|
||||
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
|
||||
*
|
||||
* Routing.
|
||||
* @method static void route($pattern, $callback) Maps a URL pattern to a callback.
|
||||
* @method static \flight\net\Router router() Returns Router instance.
|
||||
* # Core methods
|
||||
* @method static void start() Starts the framework.
|
||||
* @method static void path(string $path) Adds a path for autoloading classes.
|
||||
* @method static void stop(?int $code = null) Stops the framework and sends a response.
|
||||
* @method static void halt(int $code = 200, string $message = '', bool $actuallyExit = true)
|
||||
* Stop the framework with an optional status code and message.
|
||||
* @method static void register(string $name, string $class, array $params = [], ?callable $callback = null)
|
||||
* Registers a class to a framework method.
|
||||
* @method static void unregister(string $methodName)
|
||||
* Unregisters a class to a framework method.
|
||||
* @method static void registerContainerHandler(callable|object $containerHandler) Registers a container handler.
|
||||
*
|
||||
* Extending & Overriding.
|
||||
* @method static void map($name, $callback) Creates a custom framework method.
|
||||
* @method static void register($name, $class, array $params = array(), $callback = null) Registers a class to a framework method.
|
||||
* # Routing
|
||||
* @method static Route route(string $pattern, callable|string $callback, bool $pass_route = false, string $alias = '')
|
||||
* Maps a URL pattern to a callback with all applicable methods.
|
||||
* @method static void group(string $pattern, callable $callback, callable[] $group_middlewares = [])
|
||||
* Groups a set of routes together under a common prefix.
|
||||
* @method static Route post(string $pattern, callable|string $callback, bool $pass_route = false, string $alias = '')
|
||||
* Routes a POST URL to a callback function.
|
||||
* @method static Route put(string $pattern, callable|string $callback, bool $pass_route = false, string $alias = '')
|
||||
* Routes a PUT URL to a callback function.
|
||||
* @method static Route patch(string $pattern, callable|string $callback, bool $pass_route = false, string $alias = '')
|
||||
* Routes a PATCH URL to a callback function.
|
||||
* @method static Route delete(string $pattern, callable|string $callback, bool $pass_route = false, string $alias = '')
|
||||
* Routes a DELETE URL to a callback function.
|
||||
* @method static void resource(string $pattern, string $controllerClass, array $methods = [])
|
||||
* Adds standardized RESTful routes for a controller.
|
||||
* @method static Router router() Returns Router instance.
|
||||
* @method static string getUrl(string $alias, array<string, mixed> $params = []) Gets a url from an alias
|
||||
*
|
||||
* Filtering.
|
||||
* @method static void before($name, $callback) Adds a filter before a framework method.
|
||||
* @method static void after($name, $callback) Adds a filter after a framework method.
|
||||
* @method static void map(string $name, callable $callback) Creates a custom framework method.
|
||||
*
|
||||
* Variables.
|
||||
* @method static void set($key, $value) Sets a variable.
|
||||
* @method static mixed get($key) Gets a variable.
|
||||
* @method static bool has($key) Checks if a variable is set.
|
||||
* @method static void clear($key = null) Clears a variable.
|
||||
* @method static void before(string $name, Closure(array<int, mixed> &$params, string &$output): (void|false) $callback)
|
||||
* Adds a filter before a framework method.
|
||||
* @method static void after(string $name, Closure(array<int, mixed> &$params, string &$output): (void|false) $callback)
|
||||
* Adds a filter after a framework method.
|
||||
*
|
||||
* Views.
|
||||
* @method static void render($file, array $data = null, $key = null) Renders a template file.
|
||||
* @method static \flight\template\View view() Returns View instance.
|
||||
* @method static void set(string|iterable<string, mixed> $key, mixed $value) Sets a variable.
|
||||
* @method static mixed get(?string $key) Gets a variable.
|
||||
* @method static bool has(string $key) Checks if a variable is set.
|
||||
* @method static void clear(?string $key = null) Clears a variable.
|
||||
*
|
||||
* Request & Response.
|
||||
* @method static \flight\net\Request request() Returns Request instance.
|
||||
* @method static \flight\net\Response response() Returns Response instance.
|
||||
* @method static void redirect($url, $code = 303) Redirects to another URL.
|
||||
* @method static void json($data, $code = 200, $encode = true, $charset = "utf8", $encodeOption = 0, $encodeDepth = 512) Sends a JSON response.
|
||||
* @method static void jsonp($data, $param = 'jsonp', $code = 200, $encode = true, $charset = "utf8", $encodeOption = 0, $encodeDepth = 512) Sends a JSONP response.
|
||||
* @method static void error($exception) Sends an HTTP 500 response.
|
||||
* @method static void notFound() Sends an HTTP 404 response.
|
||||
* # Views
|
||||
* @method static void render(string $file, ?array<string, mixed> $data = null, ?string $key = null)
|
||||
* Renders a template file.
|
||||
* @method static View view() Returns View instance.
|
||||
*
|
||||
* HTTP Caching.
|
||||
* @method static void etag($id, $type = 'strong') Performs ETag HTTP caching.
|
||||
* @method static void lastModified($time) Performs last modified HTTP caching.
|
||||
* # Request-Response
|
||||
* @method static Request request() Returns Request instance.
|
||||
* @method static Response response() Returns Response instance.
|
||||
* @method static void redirect(string $url, int $code = 303) Redirects to another URL.
|
||||
* @method static void json(mixed $data, int $code = 200, bool $encode = true, string $charset = "utf8", int $encodeOption = 0, int $encodeDepth = 512)
|
||||
* Sends a JSON response.
|
||||
* @method static void jsonHalt(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf-8', int $option = 0)
|
||||
* Sends a JSON response and immediately halts the request.
|
||||
* @method static void jsonp(mixed $data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = "utf8", int $encodeOption = 0, int $encodeDepth = 512)
|
||||
* Sends a JSONP response.
|
||||
* @method static void error(Throwable $exception) Sends an HTTP 500 response.
|
||||
* @method static void notFound() Sends an HTTP 404 response.
|
||||
*
|
||||
* # HTTP methods
|
||||
* @method static void etag(string $id, ('strong'|'weak') $type = 'strong') Performs ETag HTTP caching.
|
||||
* @method static void lastModified(int $time) Performs last modified HTTP caching.
|
||||
* @method static void download(string $filePath) Downloads a file
|
||||
*/
|
||||
class Flight {
|
||||
/**
|
||||
* Framework engine.
|
||||
*
|
||||
* @var \flight\Engine
|
||||
*/
|
||||
private static $engine;
|
||||
class Flight
|
||||
{
|
||||
/** Framework engine. */
|
||||
private static Engine $engine;
|
||||
|
||||
// Don't allow object instantiation
|
||||
private function __construct() {}
|
||||
private function __destruct() {}
|
||||
private function __clone() {}
|
||||
/** Whether or not the app has been initialized. */
|
||||
private static bool $initialized = false;
|
||||
|
||||
/**
|
||||
* Don't allow object instantiation
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @return void
|
||||
*/
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Forbid cloning the class
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @return void
|
||||
*/
|
||||
private function __clone()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles calls to static methods.
|
||||
*
|
||||
* @param string $name Method name
|
||||
* @param array $params Method parameters
|
||||
* @param array<int, mixed> $params Method parameters
|
||||
*
|
||||
* @return mixed Callback results
|
||||
* @throws \Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function __callStatic($name, $params) {
|
||||
$app = Flight::app();
|
||||
|
||||
return \flight\core\Dispatcher::invokeMethod(array($app, $name), $params);
|
||||
public static function __callStatic(string $name, array $params)
|
||||
{
|
||||
return self::app()->{$name}(...$params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \flight\Engine Application instance
|
||||
*/
|
||||
public static function app() {
|
||||
static $initialized = false;
|
||||
/** @return Engine Application instance */
|
||||
public static function app(): Engine
|
||||
{
|
||||
if (!self::$initialized) {
|
||||
require_once __DIR__ . '/autoload.php';
|
||||
|
||||
if (!$initialized) {
|
||||
require_once __DIR__.'/autoload.php';
|
||||
|
||||
self::$engine = new \flight\Engine();
|
||||
|
||||
$initialized = true;
|
||||
self::setEngine(new Engine());
|
||||
self::$initialized = true;
|
||||
}
|
||||
|
||||
return self::$engine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the engine instance
|
||||
*
|
||||
* @param Engine $engine Vroom vroom!
|
||||
*/
|
||||
public static function setEngine(Engine $engine): void
|
||||
{
|
||||
self::$engine = $engine;
|
||||
}
|
||||
}
|
||||
|
@ -1,918 +1,59 @@
|
||||
[](https://packagist.org/packages/flightphp/core)
|
||||
[](https://packagist.org/packages/flightphp/core)
|
||||

|
||||
[](https://packagist.org/packages/flightphp/core)
|
||||
[](https://packagist.org/packages/flightphp/core)
|
||||

|
||||
|
||||
# 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.
|
||||
|
||||
```php
|
||||
require 'flight/Flight.php';
|
||||
# Basic Usage
|
||||
|
||||
Flight::route('/', function(){
|
||||
echo 'hello world!';
|
||||
```php
|
||||
// if installed with composer
|
||||
require 'vendor/autoload.php';
|
||||
// or if installed manually by zip file
|
||||
// require 'flight/Flight.php';
|
||||
|
||||
Flight::route('/', function () {
|
||||
echo 'hello world!';
|
||||
});
|
||||
|
||||
Flight::start();
|
||||
```
|
||||
|
||||
[Learn more](http://flightphp.com/learn)
|
||||
## Skeleton App
|
||||
|
||||
You can also install a skeleton app. Go to [flightphp/skeleton](https://github.com/flightphp/skeleton) for instructions on how to get started!
|
||||
|
||||
# Documentation
|
||||
|
||||
We have our own documentation website that is built with Flight (naturally). Learn more about the framework at [docs.flightphp.com](https://docs.flightphp.com).
|
||||
|
||||
# Community
|
||||
|
||||
Chat with us on Matrix IRC [#flight-php-framework:matrix.org](https://matrix.to/#/#flight-php-framework:matrix.org)
|
||||
|
||||
# Upgrading From v2
|
||||
|
||||
If you have a current project on v2, you should be able to upgrade to v2 with no issues depending on how your project was built. If there are any issues with upgrade, they are documented in the [migrating to v3](https://docs.flightphp.com/learn/migrating-to-v3) documentation page. It is the intention of Flight to maintain longterm stability of the project and to not add rewrites with major version changes.
|
||||
|
||||
# Requirements
|
||||
|
||||
Flight requires `PHP 5.3` or greater.
|
||||
> [!IMPORTANT]
|
||||
> Flight requires `PHP 7.4` or greater.
|
||||
|
||||
**Note:** PHP 7.4 is supported because at the current time of writing (2024) PHP 7.4 is the default version for some LTS Linux distributions. Forcing a move to PHP >8 would cause a lot of heartburn for those users.
|
||||
|
||||
The framework also supports PHP >8.
|
||||
|
||||
# Roadmap
|
||||
|
||||
To see the current and future roadmap for the Flight Framework, visit the [project roadmap](https://github.com/orgs/flightphp/projects/1/views/1)
|
||||
|
||||
# License
|
||||
|
||||
Flight is released under the [MIT](http://flightphp.com/license) license.
|
||||
|
||||
# Installation
|
||||
|
||||
1\. Download the files.
|
||||
|
||||
If you're using [Composer](https://getcomposer.org/), you can run the following command:
|
||||
|
||||
```
|
||||
composer require mikecao/flight
|
||||
```
|
||||
|
||||
OR you can [download](https://github.com/mikecao/flight/archive/master.zip) them directly
|
||||
and extract them to your web directory.
|
||||
|
||||
2\. Configure your webserver.
|
||||
|
||||
For *Apache*, edit your `.htaccess` file with the following:
|
||||
|
||||
```
|
||||
RewriteEngine On
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule ^(.*)$ index.php [QSA,L]
|
||||
```
|
||||
|
||||
**Note**: If you need to use flight in a subdirectory add the line `RewriteBase /subdir/` just after `RewriteEngine On`.
|
||||
|
||||
For *Nginx*, add the following to your server declaration:
|
||||
|
||||
```
|
||||
server {
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php;
|
||||
}
|
||||
}
|
||||
```
|
||||
3\. Create your `index.php` file.
|
||||
|
||||
First include the framework.
|
||||
|
||||
```php
|
||||
require 'flight/Flight.php';
|
||||
```
|
||||
|
||||
If you're using Composer, run the autoloader instead.
|
||||
|
||||
```php
|
||||
require 'vendor/autoload.php';
|
||||
```
|
||||
|
||||
Then define a route and assign a function to handle the request.
|
||||
|
||||
```php
|
||||
Flight::route('/', function(){
|
||||
echo 'hello world!';
|
||||
});
|
||||
```
|
||||
|
||||
Finally, start the framework.
|
||||
|
||||
```php
|
||||
Flight::start();
|
||||
```
|
||||
|
||||
# Routing
|
||||
|
||||
Routing in Flight is done by matching a URL pattern with a callback function.
|
||||
|
||||
```php
|
||||
Flight::route('/', function(){
|
||||
echo 'hello world!';
|
||||
});
|
||||
```
|
||||
|
||||
The callback can be any object that is callable. So you can use a regular function:
|
||||
|
||||
```php
|
||||
function hello(){
|
||||
echo 'hello world!';
|
||||
}
|
||||
|
||||
Flight::route('/', 'hello');
|
||||
```
|
||||
|
||||
Or a class method:
|
||||
|
||||
```php
|
||||
class Greeting {
|
||||
public static function hello() {
|
||||
echo 'hello world!';
|
||||
}
|
||||
}
|
||||
|
||||
Flight::route('/', array('Greeting', 'hello'));
|
||||
```
|
||||
|
||||
Or an object method:
|
||||
|
||||
```php
|
||||
class Greeting
|
||||
{
|
||||
public function __construct() {
|
||||
$this->name = 'John Doe';
|
||||
}
|
||||
|
||||
public function hello() {
|
||||
echo "Hello, {$this->name}!";
|
||||
}
|
||||
}
|
||||
|
||||
$greeting = new Greeting();
|
||||
|
||||
Flight::route('/', array($greeting, 'hello'));
|
||||
```
|
||||
|
||||
Routes are matched in the order they are defined. The first route to match a
|
||||
request will be invoked.
|
||||
|
||||
## Method Routing
|
||||
|
||||
By default, route patterns are matched against all request methods. You can respond
|
||||
to specific methods by placing an identifier before the URL.
|
||||
|
||||
```php
|
||||
Flight::route('GET /', function(){
|
||||
echo 'I received a GET request.';
|
||||
});
|
||||
|
||||
Flight::route('POST /', function(){
|
||||
echo 'I received a POST request.';
|
||||
});
|
||||
```
|
||||
|
||||
You can also map multiple methods to a single callback by using a `|` delimiter:
|
||||
|
||||
```php
|
||||
Flight::route('GET|POST /', function(){
|
||||
echo 'I received either a GET or a POST request.';
|
||||
});
|
||||
```
|
||||
|
||||
## Regular Expressions
|
||||
|
||||
You can use regular expressions in your routes:
|
||||
|
||||
```php
|
||||
Flight::route('/user/[0-9]+', function(){
|
||||
// This will match /user/1234
|
||||
});
|
||||
```
|
||||
|
||||
## Named Parameters
|
||||
|
||||
You can specify named parameters in your routes which will be passed along to
|
||||
your callback function.
|
||||
|
||||
```php
|
||||
Flight::route('/@name/@id', function($name, $id){
|
||||
echo "hello, $name ($id)!";
|
||||
});
|
||||
```
|
||||
|
||||
You can also include regular expressions with your named parameters by using
|
||||
the `:` delimiter:
|
||||
|
||||
```php
|
||||
Flight::route('/@name/@id:[0-9]{3}', function($name, $id){
|
||||
// This will match /bob/123
|
||||
// But will not match /bob/12345
|
||||
});
|
||||
```
|
||||
|
||||
## Optional Parameters
|
||||
|
||||
You can specify named parameters that are optional for matching by wrapping
|
||||
segments in parentheses.
|
||||
|
||||
```php
|
||||
Flight::route('/blog(/@year(/@month(/@day)))', function($year, $month, $day){
|
||||
// This will match the following URLS:
|
||||
// /blog/2012/12/10
|
||||
// /blog/2012/12
|
||||
// /blog/2012
|
||||
// /blog
|
||||
});
|
||||
```
|
||||
|
||||
Any optional parameters that are not matched will be passed in as NULL.
|
||||
|
||||
## Wildcards
|
||||
|
||||
Matching is only done on individual URL segments. If you want to match multiple
|
||||
segments you can use the `*` wildcard.
|
||||
|
||||
```php
|
||||
Flight::route('/blog/*', function(){
|
||||
// This will match /blog/2000/02/01
|
||||
});
|
||||
```
|
||||
|
||||
To route all requests to a single callback, you can do:
|
||||
|
||||
```php
|
||||
Flight::route('*', function(){
|
||||
// Do something
|
||||
});
|
||||
```
|
||||
|
||||
## Passing
|
||||
|
||||
You can pass execution on to the next matching route by returning `true` from
|
||||
your callback function.
|
||||
|
||||
```php
|
||||
Flight::route('/user/@name', function($name){
|
||||
// Check some condition
|
||||
if ($name != "Bob") {
|
||||
// Continue to next route
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
Flight::route('/user/*', function(){
|
||||
// This will get called
|
||||
});
|
||||
```
|
||||
|
||||
## Route Info
|
||||
|
||||
If you want to inspect the matching route information, you can request for the route
|
||||
object to be passed to your callback by passing in `true` as the third parameter in
|
||||
the route method. The route object will always be the last parameter passed to your
|
||||
callback function.
|
||||
|
||||
```php
|
||||
Flight::route('/', function($route){
|
||||
// Array of HTTP methods matched against
|
||||
$route->methods;
|
||||
|
||||
// Array of named parameters
|
||||
$route->params;
|
||||
|
||||
// Matching regular expression
|
||||
$route->regex;
|
||||
|
||||
// Contains the contents of any '*' used in the URL pattern
|
||||
$route->splat;
|
||||
}, true);
|
||||
```
|
||||
|
||||
# Extending
|
||||
|
||||
Flight is designed to be an extensible framework. The framework comes with a set
|
||||
of default methods and components, but it allows you to map your own methods,
|
||||
register your own classes, or even override existing classes and methods.
|
||||
|
||||
## Mapping Methods
|
||||
|
||||
To map your own custom method, you use the `map` function:
|
||||
|
||||
```php
|
||||
// Map your method
|
||||
Flight::map('hello', function($name){
|
||||
echo "hello $name!";
|
||||
});
|
||||
|
||||
// Call your custom method
|
||||
Flight::hello('Bob');
|
||||
```
|
||||
|
||||
## Registering Classes
|
||||
|
||||
To register your own class, you use the `register` function:
|
||||
|
||||
```php
|
||||
// Register your class
|
||||
Flight::register('user', 'User');
|
||||
|
||||
// Get an instance of your class
|
||||
$user = Flight::user();
|
||||
```
|
||||
|
||||
The register method also allows you to pass along parameters to your class
|
||||
constructor. So when you load your custom class, it will come pre-initialized.
|
||||
You can define the constructor parameters by passing in an additional array.
|
||||
Here's an example of loading a database connection:
|
||||
|
||||
```php
|
||||
// Register class with constructor parameters
|
||||
Flight::register('db', 'PDO', array('mysql:host=localhost;dbname=test','user','pass'));
|
||||
|
||||
// Get an instance of your class
|
||||
// This will create an object with the defined parameters
|
||||
//
|
||||
// new PDO('mysql:host=localhost;dbname=test','user','pass');
|
||||
//
|
||||
$db = Flight::db();
|
||||
```
|
||||
|
||||
If you pass in an additional callback parameter, it will be executed immediately
|
||||
after class construction. This allows you to perform any set up procedures for your
|
||||
new object. The callback function takes one parameter, an instance of the new object.
|
||||
|
||||
```php
|
||||
// The callback will be passed the object that was constructed
|
||||
Flight::register('db', 'PDO', array('mysql:host=localhost;dbname=test','user','pass'), function($db){
|
||||
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
});
|
||||
```
|
||||
|
||||
By default, every time you load your class you will get a shared instance.
|
||||
To get a new instance of a class, simply pass in `false` as a parameter:
|
||||
|
||||
```php
|
||||
// Shared instance of the class
|
||||
$shared = Flight::db();
|
||||
|
||||
// New instance of the class
|
||||
$new = Flight::db(false);
|
||||
```
|
||||
|
||||
Keep in mind that mapped methods have precedence over registered classes. If you
|
||||
declare both using the same name, only the mapped method will be invoked.
|
||||
|
||||
# Overriding
|
||||
|
||||
Flight allows you to override its default functionality to suit your own needs,
|
||||
without having to modify any code.
|
||||
|
||||
For example, when Flight cannot match a URL to a route, it invokes the `notFound`
|
||||
method which sends a generic `HTTP 404` response. You can override this behavior
|
||||
by using the `map` method:
|
||||
|
||||
```php
|
||||
Flight::map('notFound', function(){
|
||||
// Display custom 404 page
|
||||
include 'errors/404.html';
|
||||
});
|
||||
```
|
||||
|
||||
Flight also allows you to replace core components of the framework.
|
||||
For example you can replace the default Router class with your own custom class:
|
||||
|
||||
```php
|
||||
// Register your custom class
|
||||
Flight::register('router', 'MyRouter');
|
||||
|
||||
// When Flight loads the Router instance, it will load your class
|
||||
$myrouter = Flight::router();
|
||||
```
|
||||
|
||||
Framework methods like `map` and `register` however cannot be overridden. You will
|
||||
get an error if you try to do so.
|
||||
|
||||
# Filtering
|
||||
|
||||
Flight allows you to filter methods before and after they are called. There are no
|
||||
predefined hooks you need to memorize. You can filter any of the default framework
|
||||
methods as well as any custom methods that you've mapped.
|
||||
|
||||
A filter function looks like this:
|
||||
|
||||
```php
|
||||
function(&$params, &$output) {
|
||||
// Filter code
|
||||
}
|
||||
```
|
||||
|
||||
Using the passed in variables you can manipulate the input parameters and/or the output.
|
||||
|
||||
You can have a filter run before a method by doing:
|
||||
|
||||
```php
|
||||
Flight::before('start', function(&$params, &$output){
|
||||
// Do something
|
||||
});
|
||||
```
|
||||
|
||||
You can have a filter run after a method by doing:
|
||||
|
||||
```php
|
||||
Flight::after('start', function(&$params, &$output){
|
||||
// Do something
|
||||
});
|
||||
```
|
||||
|
||||
You can add as many filters as you want to any method. They will be called in the
|
||||
order that they are declared.
|
||||
|
||||
Here's an example of the filtering process:
|
||||
|
||||
```php
|
||||
// Map a custom method
|
||||
Flight::map('hello', function($name){
|
||||
return "Hello, $name!";
|
||||
});
|
||||
|
||||
// Add a before filter
|
||||
Flight::before('hello', function(&$params, &$output){
|
||||
// Manipulate the parameter
|
||||
$params[0] = 'Fred';
|
||||
});
|
||||
|
||||
// Add an after filter
|
||||
Flight::after('hello', function(&$params, &$output){
|
||||
// Manipulate the output
|
||||
$output .= " Have a nice day!";
|
||||
});
|
||||
|
||||
// Invoke the custom method
|
||||
echo Flight::hello('Bob');
|
||||
```
|
||||
|
||||
This should display:
|
||||
|
||||
Hello Fred! Have a nice day!
|
||||
|
||||
If you have defined multiple filters, you can break the chain by returning `false`
|
||||
in any of your filter functions:
|
||||
|
||||
```php
|
||||
Flight::before('start', function(&$params, &$output){
|
||||
echo 'one';
|
||||
});
|
||||
|
||||
Flight::before('start', function(&$params, &$output){
|
||||
echo 'two';
|
||||
|
||||
// This will end the chain
|
||||
return false;
|
||||
});
|
||||
|
||||
// This will not get called
|
||||
Flight::before('start', function(&$params, &$output){
|
||||
echo 'three';
|
||||
});
|
||||
```
|
||||
|
||||
Note, core methods such as `map` and `register` cannot be filtered because they
|
||||
are called directly and not invoked dynamically.
|
||||
|
||||
# Variables
|
||||
|
||||
Flight allows you to save variables so that they can be used anywhere in your application.
|
||||
|
||||
```php
|
||||
// Save your variable
|
||||
Flight::set('id', 123);
|
||||
|
||||
// Elsewhere in your application
|
||||
$id = Flight::get('id');
|
||||
```
|
||||
To see if a variable has been set you can do:
|
||||
|
||||
```php
|
||||
if (Flight::has('id')) {
|
||||
// Do something
|
||||
}
|
||||
```
|
||||
|
||||
You can clear a variable by doing:
|
||||
|
||||
```php
|
||||
// Clears the id variable
|
||||
Flight::clear('id');
|
||||
|
||||
// Clears all variables
|
||||
Flight::clear();
|
||||
```
|
||||
|
||||
Flight also uses variables for configuration purposes.
|
||||
|
||||
```php
|
||||
Flight::set('flight.log_errors', true);
|
||||
```
|
||||
|
||||
# Views
|
||||
|
||||
Flight provides some basic templating functionality by default. To display a view
|
||||
template call the `render` method with the name of the template file and optional
|
||||
template data:
|
||||
|
||||
```php
|
||||
Flight::render('hello.php', array('name' => 'Bob'));
|
||||
```
|
||||
|
||||
The template data you pass in is automatically injected into the template and can
|
||||
be reference like a local variable. Template files are simply PHP files. If the
|
||||
content of the `hello.php` template file is:
|
||||
|
||||
```php
|
||||
Hello, '<?php echo $name; ?>'!
|
||||
```
|
||||
|
||||
The output would be:
|
||||
|
||||
Hello, Bob!
|
||||
|
||||
You can also manually set view variables by using the set method:
|
||||
|
||||
```php
|
||||
Flight::view()->set('name', 'Bob');
|
||||
```
|
||||
|
||||
The variable `name` is now available across all your views. So you can simply do:
|
||||
|
||||
```php
|
||||
Flight::render('hello');
|
||||
```
|
||||
|
||||
Note that when specifying the name of the template in the render method, you can
|
||||
leave out the `.php` extension.
|
||||
|
||||
By default Flight will look for a `views` directory for template files. You can
|
||||
set an alternate path for your templates by setting the following config:
|
||||
|
||||
```php
|
||||
Flight::set('flight.views.path', '/path/to/views');
|
||||
```
|
||||
|
||||
## Layouts
|
||||
|
||||
It is common for websites to have a single layout template file with interchanging
|
||||
content. To render content to be used in a layout, you can pass in an optional
|
||||
parameter to the `render` method.
|
||||
|
||||
```php
|
||||
Flight::render('header', array('heading' => 'Hello'), 'header_content');
|
||||
Flight::render('body', array('body' => 'World'), 'body_content');
|
||||
```
|
||||
|
||||
Your view will then have saved variables called `header_content` and `body_content`.
|
||||
You can then render your layout by doing:
|
||||
|
||||
```php
|
||||
Flight::render('layout', array('title' => 'Home Page'));
|
||||
```
|
||||
|
||||
If the template files looks like this:
|
||||
|
||||
`header.php`:
|
||||
|
||||
```php
|
||||
<h1><?php echo $heading; ?></h1>
|
||||
```
|
||||
|
||||
`body.php`:
|
||||
|
||||
```php
|
||||
<div><?php echo $body; ?></div>
|
||||
```
|
||||
|
||||
`layout.php`:
|
||||
|
||||
```php
|
||||
<html>
|
||||
<head>
|
||||
<title><?php echo $title; ?></title>
|
||||
</head>
|
||||
<body>
|
||||
<?php echo $header_content; ?>
|
||||
<?php echo $body_content; ?>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
The output would be:
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<title>Home Page</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello</h1>
|
||||
<div>World</div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Custom Views
|
||||
|
||||
Flight allows you to swap out the default view engine simply by registering your
|
||||
own view class. Here's how you would use the [Smarty](http://www.smarty.net/)
|
||||
template engine for your views:
|
||||
|
||||
```php
|
||||
// Load Smarty library
|
||||
require './Smarty/libs/Smarty.class.php';
|
||||
|
||||
// Register Smarty as the view class
|
||||
// Also pass a callback function to configure Smarty on load
|
||||
Flight::register('view', 'Smarty', array(), function($smarty){
|
||||
$smarty->template_dir = './templates/';
|
||||
$smarty->compile_dir = './templates_c/';
|
||||
$smarty->config_dir = './config/';
|
||||
$smarty->cache_dir = './cache/';
|
||||
});
|
||||
|
||||
// Assign template data
|
||||
Flight::view()->assign('name', 'Bob');
|
||||
|
||||
// Display the template
|
||||
Flight::view()->display('hello.tpl');
|
||||
```
|
||||
|
||||
For completeness, you should also override Flight's default render method:
|
||||
|
||||
```php
|
||||
Flight::map('render', function($template, $data){
|
||||
Flight::view()->assign($data);
|
||||
Flight::view()->display($template);
|
||||
});
|
||||
```
|
||||
# Error Handling
|
||||
|
||||
## Errors and Exceptions
|
||||
|
||||
All errors and exceptions are caught by Flight and passed to the `error` method.
|
||||
The default behavior is to send a generic `HTTP 500 Internal Server Error`
|
||||
response with some error information.
|
||||
|
||||
You can override this behavior for your own needs:
|
||||
|
||||
```php
|
||||
Flight::map('error', function(Exception $ex){
|
||||
// Handle error
|
||||
echo $ex->getTraceAsString();
|
||||
});
|
||||
```
|
||||
|
||||
By default errors are not logged to the web server. You can enable this by
|
||||
changing the config:
|
||||
|
||||
```php
|
||||
Flight::set('flight.log_errors', true);
|
||||
```
|
||||
|
||||
## Not Found
|
||||
|
||||
When a URL can't be found, Flight calls the `notFound` method. The default
|
||||
behavior is to send an `HTTP 404 Not Found` response with a simple message.
|
||||
|
||||
You can override this behavior for your own needs:
|
||||
|
||||
```php
|
||||
Flight::map('notFound', function(){
|
||||
// Handle not found
|
||||
});
|
||||
```
|
||||
|
||||
# Redirects
|
||||
|
||||
You can redirect the current request by using the `redirect` method and passing
|
||||
in a new URL:
|
||||
|
||||
```php
|
||||
Flight::redirect('/new/location');
|
||||
```
|
||||
|
||||
By default Flight sends a HTTP 303 status code. You can optionally set a
|
||||
custom code:
|
||||
|
||||
```php
|
||||
Flight::redirect('/new/location', 401);
|
||||
```
|
||||
|
||||
# Requests
|
||||
|
||||
Flight encapsulates the HTTP request into a single object, which can be
|
||||
accessed by doing:
|
||||
|
||||
```php
|
||||
$request = Flight::request();
|
||||
```
|
||||
|
||||
The request object provides the following properties:
|
||||
|
||||
```
|
||||
url - The URL being requested
|
||||
base - The parent subdirectory of the URL
|
||||
method - The request method (GET, POST, PUT, DELETE)
|
||||
referrer - The referrer URL
|
||||
ip - IP address of the client
|
||||
ajax - Whether the request is an AJAX request
|
||||
scheme - The server protocol (http, https)
|
||||
user_agent - Browser information
|
||||
type - The content type
|
||||
length - The content length
|
||||
query - Query string parameters
|
||||
data - Post data or JSON data
|
||||
cookies - Cookie data
|
||||
files - Uploaded files
|
||||
secure - Whether the connection is secure
|
||||
accept - HTTP accept parameters
|
||||
proxy_ip - Proxy IP address of the client
|
||||
host - The request host name
|
||||
```
|
||||
|
||||
You can access the `query`, `data`, `cookies`, and `files` properties
|
||||
as arrays or objects.
|
||||
|
||||
So, to get a query string parameter, you can do:
|
||||
|
||||
```php
|
||||
$id = Flight::request()->query['id'];
|
||||
```
|
||||
|
||||
Or you can do:
|
||||
|
||||
```php
|
||||
$id = Flight::request()->query->id;
|
||||
```
|
||||
|
||||
## RAW Request Body
|
||||
|
||||
To get the raw HTTP request body, for example when dealing with PUT requests, you can do:
|
||||
|
||||
```php
|
||||
$body = Flight::request()->getBody();
|
||||
```
|
||||
|
||||
## JSON Input
|
||||
|
||||
If you send a request with the type `application/json` and the data `{"id": 123}` it will be available
|
||||
from the `data` property:
|
||||
|
||||
```php
|
||||
$id = Flight::request()->data->id;
|
||||
```
|
||||
|
||||
# HTTP Caching
|
||||
|
||||
Flight provides built-in support for HTTP level caching. If the caching condition
|
||||
is met, Flight will return an HTTP `304 Not Modified` response. The next time the
|
||||
client requests the same resource, they will be prompted to use their locally
|
||||
cached version.
|
||||
|
||||
## Last-Modified
|
||||
|
||||
You can use the `lastModified` method and pass in a UNIX timestamp to set the date
|
||||
and time a page was last modified. The client will continue to use their cache until
|
||||
the last modified value is changed.
|
||||
|
||||
```php
|
||||
Flight::route('/news', function(){
|
||||
Flight::lastModified(1234567890);
|
||||
echo 'This content will be cached.';
|
||||
});
|
||||
```
|
||||
|
||||
## ETag
|
||||
|
||||
`ETag` caching is similar to `Last-Modified`, except you can specify any id you
|
||||
want for the resource:
|
||||
|
||||
```php
|
||||
Flight::route('/news', function(){
|
||||
Flight::etag('my-unique-id');
|
||||
echo 'This content will be cached.';
|
||||
});
|
||||
```
|
||||
|
||||
Keep in mind that calling either `lastModified` or `etag` will both set and check the
|
||||
cache value. If the cache value is the same between requests, Flight will immediately
|
||||
send an `HTTP 304` response and stop processing.
|
||||
|
||||
# Stopping
|
||||
|
||||
You can stop the framework at any point by calling the `halt` method:
|
||||
|
||||
```php
|
||||
Flight::halt();
|
||||
```
|
||||
|
||||
You can also specify an optional `HTTP` status code and message:
|
||||
|
||||
```php
|
||||
Flight::halt(200, 'Be right back...');
|
||||
```
|
||||
|
||||
Calling `halt` will discard any response content up to that point. If you want to stop
|
||||
the framework and output the current response, use the `stop` method:
|
||||
|
||||
```php
|
||||
Flight::stop();
|
||||
```
|
||||
|
||||
# JSON
|
||||
|
||||
Flight provides support for sending JSON and JSONP responses. To send a JSON response you
|
||||
pass some data to be JSON encoded:
|
||||
|
||||
```php
|
||||
Flight::json(array('id' => 123));
|
||||
```
|
||||
|
||||
For JSONP requests you, can optionally pass in the query parameter name you are
|
||||
using to define your callback function:
|
||||
|
||||
```php
|
||||
Flight::jsonp(array('id' => 123), 'q');
|
||||
```
|
||||
|
||||
So, when making a GET request using `?q=my_func`, you should receive the output:
|
||||
|
||||
```
|
||||
my_func({"id":123});
|
||||
```
|
||||
|
||||
If you don't pass in a query parameter name it will default to `jsonp`.
|
||||
|
||||
|
||||
# Configuration
|
||||
|
||||
You can customize certain behaviors of Flight by setting configuration values
|
||||
through the `set` method.
|
||||
|
||||
```php
|
||||
Flight::set('flight.log_errors', true);
|
||||
```
|
||||
|
||||
The following is a list of all the available configuration settings:
|
||||
|
||||
flight.base_url - Override the base url of the request. (default: null)
|
||||
flight.case_sensitive - Case sensitive matching for URLs. (default: false)
|
||||
flight.handle_errors - Allow Flight to handle all errors internally. (default: true)
|
||||
flight.log_errors - Log errors to the web server's error log file. (default: false)
|
||||
flight.views.path - Directory containing view template files. (default: ./views)
|
||||
flight.views.extension - View template file extension. (default: .php)
|
||||
|
||||
# Framework Methods
|
||||
|
||||
Flight is designed to be easy to use and understand. The following is the complete
|
||||
set of methods for the framework. It consists of core methods, which are regular
|
||||
static methods, and extensible methods, which are mapped methods that can be filtered
|
||||
or overridden.
|
||||
|
||||
## Core Methods
|
||||
|
||||
```php
|
||||
Flight::map($name, $callback) // Creates a custom framework method.
|
||||
Flight::register($name, $class, [$params], [$callback]) // Registers a class to a framework method.
|
||||
Flight::before($name, $callback) // Adds a filter before a framework method.
|
||||
Flight::after($name, $callback) // Adds a filter after a framework method.
|
||||
Flight::path($path) // Adds a path for autoloading classes.
|
||||
Flight::get($key) // Gets a variable.
|
||||
Flight::set($key, $value) // Sets a variable.
|
||||
Flight::has($key) // Checks if a variable is set.
|
||||
Flight::clear([$key]) // Clears a variable.
|
||||
Flight::init() // Initializes the framework to its default settings.
|
||||
Flight::app() // Gets the application object instance
|
||||
```
|
||||
|
||||
## Extensible Methods
|
||||
|
||||
```php
|
||||
Flight::start() // Starts the framework.
|
||||
Flight::stop() // Stops the framework and sends a response.
|
||||
Flight::halt([$code], [$message]) // Stop the framework with an optional status code and message.
|
||||
Flight::route($pattern, $callback) // Maps a URL pattern to a callback.
|
||||
Flight::redirect($url, [$code]) // Redirects to another URL.
|
||||
Flight::render($file, [$data], [$key]) // Renders a template file.
|
||||
Flight::error($exception) // Sends an HTTP 500 response.
|
||||
Flight::notFound() // Sends an HTTP 404 response.
|
||||
Flight::etag($id, [$type]) // Performs ETag HTTP caching.
|
||||
Flight::lastModified($time) // Performs last modified HTTP caching.
|
||||
Flight::json($data, [$code], [$encode], [$charset], [$option]) // Sends a JSON response.
|
||||
Flight::jsonp($data, [$param], [$code], [$encode], [$charset], [$option]) // Sends a JSONP response.
|
||||
```
|
||||
|
||||
Any custom methods added with `map` and `register` can also be filtered.
|
||||
|
||||
|
||||
# Framework Instance
|
||||
|
||||
Instead of running Flight as a global static class, you can optionally run it
|
||||
as an object instance.
|
||||
|
||||
```php
|
||||
require 'flight/autoload.php';
|
||||
|
||||
use flight\Engine;
|
||||
|
||||
$app = new Engine();
|
||||
|
||||
$app->route('/', function(){
|
||||
echo 'hello world!';
|
||||
});
|
||||
|
||||
$app->start();
|
||||
```
|
||||
|
||||
So instead of calling the static method, you would call the instance method with
|
||||
the same name on the Engine object.
|
||||
Flight is released under the [MIT](http://docs.flightphp.com/license) license.
|
||||
|
@ -1 +1 @@
|
||||
1.3.9
|
||||
3.13.0
|
||||
|
@ -1,11 +1,10 @@
|
||||
<?php
|
||||
/**
|
||||
* Flight: An extensible micro-framework.
|
||||
*
|
||||
* @copyright Copyright (c) 2013, Mike Cao <mike@mikecao.com>
|
||||
* @license MIT, http://flightphp.com/license
|
||||
*/
|
||||
|
||||
require_once __DIR__.'/core/Loader.php';
|
||||
declare(strict_types=1);
|
||||
|
||||
\flight\core\Loader::autoload(true, dirname(__DIR__));
|
||||
use flight\core\Loader;
|
||||
|
||||
require_once __DIR__ . '/Flight.php';
|
||||
require_once __DIR__ . '/core/Loader.php';
|
||||
|
||||
Loader::autoload(true, [dirname(__DIR__)]);
|
||||
|
91
libs/flight/commands/ControllerCommand.php
Normal file
91
libs/flight/commands/ControllerCommand.php
Normal file
@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace flight\commands;
|
||||
|
||||
use Nette\PhpGenerator\ClassType;
|
||||
use Nette\PhpGenerator\PhpFile;
|
||||
use Nette\PhpGenerator\PhpNamespace;
|
||||
|
||||
class ControllerCommand extends AbstractBaseCommand
|
||||
{
|
||||
/**
|
||||
* Construct
|
||||
*
|
||||
* @param array<string,mixed> $config JSON config from .runway-config.json
|
||||
*/
|
||||
public function __construct(array $config)
|
||||
{
|
||||
parent::__construct('make:controller', 'Create a controller', $config);
|
||||
$this->argument('<controller>', 'The name of the controller to create (with or without the Controller suffix)');
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the function
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function execute(string $controller)
|
||||
{
|
||||
$io = $this->app()->io();
|
||||
if (isset($this->config['app_root']) === false) {
|
||||
$io->error('app_root not set in .runway-config.json', true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!preg_match('/Controller$/', $controller)) {
|
||||
$controller .= 'Controller';
|
||||
}
|
||||
|
||||
$controllerPath = getcwd() . DIRECTORY_SEPARATOR . $this->config['app_root'] . 'controllers' . DIRECTORY_SEPARATOR . $controller . '.php';
|
||||
if (file_exists($controllerPath) === true) {
|
||||
$io->error($controller . ' already exists.', true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_dir(dirname($controllerPath)) === false) {
|
||||
$io->info('Creating directory ' . dirname($controllerPath), true);
|
||||
mkdir(dirname($controllerPath), 0755, true);
|
||||
}
|
||||
|
||||
$file = new PhpFile();
|
||||
$file->setStrictTypes();
|
||||
|
||||
$namespace = new PhpNamespace('app\\controllers');
|
||||
$namespace->addUse('flight\\Engine');
|
||||
|
||||
$class = new ClassType($controller);
|
||||
$class->addProperty('app')
|
||||
->setVisibility('protected')
|
||||
->setType('flight\\Engine')
|
||||
->addComment('@var Engine');
|
||||
$method = $class->addMethod('__construct')
|
||||
->addComment('Constructor')
|
||||
->setVisibility('public')
|
||||
->setBody('$this->app = $app;');
|
||||
$method->addParameter('app')
|
||||
->setType('flight\\Engine');
|
||||
|
||||
$namespace->add($class);
|
||||
$file->addNamespace($namespace);
|
||||
|
||||
$this->persistClass($controller, $file);
|
||||
|
||||
$io->ok('Controller successfully created at ' . $controllerPath, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the class name to a file
|
||||
*
|
||||
* @param string $controllerName Name of the Controller
|
||||
* @param PhpFile $file Class Object from Nette\PhpGenerator
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function persistClass(string $controllerName, PhpFile $file)
|
||||
{
|
||||
$printer = new \Nette\PhpGenerator\PsrPrinter();
|
||||
file_put_contents(getcwd() . DIRECTORY_SEPARATOR . $this->config['app_root'] . 'controllers' . DIRECTORY_SEPARATOR . $controllerName . '.php', $printer->printFile($file));
|
||||
}
|
||||
}
|
126
libs/flight/commands/RouteCommand.php
Normal file
126
libs/flight/commands/RouteCommand.php
Normal file
@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace flight\commands;
|
||||
|
||||
use Flight;
|
||||
use flight\net\Route;
|
||||
|
||||
/**
|
||||
* @property-read ?bool $get
|
||||
* @property-read ?bool $post
|
||||
* @property-read ?bool $delete
|
||||
* @property-read ?bool $put
|
||||
* @property-read ?bool $patch
|
||||
*/
|
||||
class RouteCommand extends AbstractBaseCommand
|
||||
{
|
||||
/**
|
||||
* Construct
|
||||
*
|
||||
* @param array<string,mixed> $config JSON config from .runway-config.json
|
||||
*/
|
||||
public function __construct(array $config)
|
||||
{
|
||||
parent::__construct('routes', 'Gets all routes for an application', $config);
|
||||
|
||||
$this->option('--get', 'Only return GET requests');
|
||||
$this->option('--post', 'Only return POST requests');
|
||||
$this->option('--delete', 'Only return DELETE requests');
|
||||
$this->option('--put', 'Only return PUT requests');
|
||||
$this->option('--patch', 'Only return PATCH requests');
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the function
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
$io = $this->app()->io();
|
||||
|
||||
if (isset($this->config['index_root']) === false) {
|
||||
$io->error('index_root not set in .runway-config.json', true);
|
||||
return;
|
||||
}
|
||||
|
||||
$io->bold('Routes', true);
|
||||
|
||||
$cwd = getcwd();
|
||||
|
||||
$index_root = $cwd . '/' . $this->config['index_root'];
|
||||
|
||||
// This makes it so the framework doesn't actually execute
|
||||
Flight::map('start', function () {
|
||||
return;
|
||||
});
|
||||
include($index_root);
|
||||
$routes = Flight::router()->getRoutes();
|
||||
$arrayOfRoutes = [];
|
||||
foreach ($routes as $route) {
|
||||
if ($this->shouldAddRoute($route) === true) {
|
||||
$middlewares = [];
|
||||
if (!empty($route->middleware)) {
|
||||
try {
|
||||
$middlewares = array_map(function ($middleware) {
|
||||
$middleware_class_name = explode("\\", get_class($middleware));
|
||||
return preg_match("/^class@anonymous/", end($middleware_class_name)) ? 'Anonymous' : end($middleware_class_name);
|
||||
}, $route->middleware);
|
||||
} catch (\TypeError $e) {
|
||||
$middlewares[] = 'Bad Middleware';
|
||||
} finally {
|
||||
if (is_string($route->middleware) === true) {
|
||||
$middlewares[] = $route->middleware;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$arrayOfRoutes[] = [
|
||||
'Pattern' => $route->pattern,
|
||||
'Methods' => implode(', ', $route->methods),
|
||||
'Alias' => $route->alias ?? '',
|
||||
'Streamed' => $route->is_streamed ? 'Yes' : 'No',
|
||||
'Middleware' => !empty($middlewares) ? implode(",", $middlewares) : '-'
|
||||
];
|
||||
}
|
||||
}
|
||||
$io->table($arrayOfRoutes, [
|
||||
'head' => 'boldGreen'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not to add the route based on the request
|
||||
*
|
||||
* @param Route $route Flight Route object
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function shouldAddRoute(Route $route)
|
||||
{
|
||||
$boolval = false;
|
||||
|
||||
$showAll = !$this->get && !$this->post && !$this->put && !$this->delete && !$this->patch;
|
||||
if ($showAll === true) {
|
||||
$boolval = true;
|
||||
} else {
|
||||
$methods = [ 'GET', 'POST', 'PUT', 'DELETE', 'PATCH' ];
|
||||
foreach ($methods as $method) {
|
||||
$lowercaseMethod = strtolower($method);
|
||||
if (
|
||||
$this->{$lowercaseMethod} === true &&
|
||||
(
|
||||
$route->methods[0] === '*' ||
|
||||
in_array($method, $route->methods, true) === true
|
||||
)
|
||||
) {
|
||||
$boolval = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $boolval;
|
||||
}
|
||||
}
|
@ -1,56 +1,150 @@
|
||||
<?php
|
||||
/**
|
||||
* Flight: An extensible micro-framework.
|
||||
*
|
||||
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
|
||||
* @license MIT, http://flightphp.com/license
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace flight\core;
|
||||
|
||||
use Exception;
|
||||
use flight\Engine;
|
||||
use InvalidArgumentException;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use ReflectionFunction;
|
||||
use Throwable;
|
||||
use TypeError;
|
||||
|
||||
/**
|
||||
* The Dispatcher class is responsible for dispatching events. Events
|
||||
* are simply aliases for class methods or functions. The Dispatcher
|
||||
* allows you to hook other functions to an event that can modify the
|
||||
* input parameters and/or the output.
|
||||
*
|
||||
* @license MIT, http://flightphp.com/license
|
||||
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
|
||||
*/
|
||||
class Dispatcher {
|
||||
/**
|
||||
* Mapped events.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $events = array();
|
||||
class Dispatcher
|
||||
{
|
||||
public const FILTER_BEFORE = 'before';
|
||||
public const FILTER_AFTER = 'after';
|
||||
|
||||
/** Exception message if thrown by setting the container as a callable method. */
|
||||
protected ?Throwable $containerException = null;
|
||||
|
||||
/** @var ?Engine $engine Engine instance. */
|
||||
protected ?Engine $engine = null;
|
||||
|
||||
/** @var array<string, callable(): (void|mixed)> Mapped events. */
|
||||
protected array $events = [];
|
||||
|
||||
/**
|
||||
* Method filters.
|
||||
*
|
||||
* @var array
|
||||
* @var array<string, array<'before'|'after', array<int, callable(array<int, mixed> &$params, mixed &$output): (void|false)>>>
|
||||
*/
|
||||
protected $filters = array();
|
||||
protected array $filters = [];
|
||||
|
||||
/**
|
||||
* This is a container for the dependency injection.
|
||||
*
|
||||
* @var null|ContainerInterface|(callable(string $classString, array<int, mixed> $params): (null|object))
|
||||
*/
|
||||
protected $containerHandler = null;
|
||||
|
||||
/**
|
||||
* Sets the dependency injection container handler.
|
||||
*
|
||||
* @param ContainerInterface|(callable(string $classString, array<int, mixed> $params): (null|object)) $containerHandler
|
||||
* Dependency injection container.
|
||||
*
|
||||
* @throws InvalidArgumentException If $containerHandler is not a `callable` or instance of `Psr\Container\ContainerInterface`.
|
||||
*/
|
||||
public function setContainerHandler($containerHandler): void
|
||||
{
|
||||
$containerInterfaceNS = '\Psr\Container\ContainerInterface';
|
||||
|
||||
if (
|
||||
is_a($containerHandler, $containerInterfaceNS)
|
||||
|| is_callable($containerHandler)
|
||||
) {
|
||||
$this->containerHandler = $containerHandler;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException(
|
||||
"\$containerHandler must be of type callable or instance $containerInterfaceNS"
|
||||
);
|
||||
}
|
||||
|
||||
public function setEngine(Engine $engine): void
|
||||
{
|
||||
$this->engine = $engine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an event.
|
||||
*
|
||||
* @param string $name Event name
|
||||
* @param array $params Callback parameters
|
||||
* @return string Output of callback
|
||||
* @throws \Exception
|
||||
* @param string $name Event name.
|
||||
* @param array<int, mixed> $params Callback parameters.
|
||||
*
|
||||
* @return mixed Output of callback
|
||||
* @throws Exception If event name isn't found or if event throws an `Exception`.
|
||||
*/
|
||||
public function run($name, array $params = array()) {
|
||||
$output = '';
|
||||
public function run(string $name, array $params = [])
|
||||
{
|
||||
$this->runPreFilters($name, $params);
|
||||
$output = $this->runEvent($name, $params);
|
||||
|
||||
// Run pre-filters
|
||||
if (!empty($this->filters[$name]['before'])) {
|
||||
$this->filter($this->filters[$name]['before'], $params, $output);
|
||||
return $this->runPostFilters($name, $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, mixed> &$params
|
||||
*
|
||||
* @return $this
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function runPreFilters(string $eventName, array &$params): self
|
||||
{
|
||||
$thereAreBeforeFilters = !empty($this->filters[$eventName][self::FILTER_BEFORE]);
|
||||
|
||||
if ($thereAreBeforeFilters) {
|
||||
$this->filter($this->filters[$eventName][self::FILTER_BEFORE], $params, $output);
|
||||
}
|
||||
|
||||
// Run requested method
|
||||
$output = $this->execute($this->get($name), $params);
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Run post-filters
|
||||
if (!empty($this->filters[$name]['after'])) {
|
||||
$this->filter($this->filters[$name]['after'], $params, $output);
|
||||
/**
|
||||
* @param array<int, mixed> &$params
|
||||
*
|
||||
* @return void|mixed
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function runEvent(string $eventName, array &$params)
|
||||
{
|
||||
$requestedMethod = $this->get($eventName);
|
||||
|
||||
if ($requestedMethod === null) {
|
||||
throw new Exception("Event '$eventName' isn't found.");
|
||||
}
|
||||
|
||||
return $this->execute($requestedMethod, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed &$output
|
||||
*
|
||||
* @return mixed
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function runPostFilters(string $eventName, &$output)
|
||||
{
|
||||
static $params = [];
|
||||
|
||||
$thereAreAfterFilters = !empty($this->filters[$eventName][self::FILTER_AFTER]);
|
||||
|
||||
if ($thereAreAfterFilters) {
|
||||
$this->filter($this->filters[$eventName][self::FILTER_AFTER], $params, $output);
|
||||
}
|
||||
|
||||
return $output;
|
||||
@ -59,174 +153,352 @@ class Dispatcher {
|
||||
/**
|
||||
* Assigns a callback to an event.
|
||||
*
|
||||
* @param string $name Event name
|
||||
* @param callback $callback Callback function
|
||||
* @param string $name Event name.
|
||||
* @param callable(): (void|mixed) $callback Callback function.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function set($name, $callback) {
|
||||
public function set(string $name, callable $callback): self
|
||||
{
|
||||
$this->events[$name] = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an assigned callback.
|
||||
*
|
||||
* @param string $name Event name
|
||||
* @return callback $callback Callback function
|
||||
* @param string $name Event name.
|
||||
*
|
||||
* @return null|(callable(): (void|mixed)) $callback Callback function.
|
||||
*/
|
||||
public function get($name) {
|
||||
return isset($this->events[$name]) ? $this->events[$name] : null;
|
||||
public function get(string $name): ?callable
|
||||
{
|
||||
return $this->events[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an event has been set.
|
||||
*
|
||||
* @param string $name Event name
|
||||
* @return bool Event status
|
||||
* @param string $name Event name.
|
||||
*
|
||||
* @return bool If event exists or doesn't exists.
|
||||
*/
|
||||
public function has($name) {
|
||||
public function has(string $name): bool
|
||||
{
|
||||
return isset($this->events[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears an event. If no name is given,
|
||||
* all events are removed.
|
||||
* Clears an event. If no name is given, all events will be removed.
|
||||
*
|
||||
* @param string $name Event name
|
||||
* @param ?string $name Event name.
|
||||
*/
|
||||
public function clear($name = null) {
|
||||
public function clear(?string $name = null): void
|
||||
{
|
||||
if ($name !== null) {
|
||||
unset($this->events[$name]);
|
||||
unset($this->filters[$name]);
|
||||
|
||||
return;
|
||||
}
|
||||
else {
|
||||
$this->events = array();
|
||||
$this->filters = array();
|
||||
}
|
||||
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks a callback to an event.
|
||||
*
|
||||
* @param string $name Event name
|
||||
* @param string $type Filter type
|
||||
* @param callback $callback Callback function
|
||||
* @param 'before'|'after' $type Filter type.
|
||||
* @param callable(array<int, mixed> &$params, mixed &$output): (void|false)|callable(mixed &$output): (void|false) $callback
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function hook($name, $type, $callback) {
|
||||
public function hook(string $name, string $type, callable $callback): self
|
||||
{
|
||||
static $filterTypes = [self::FILTER_BEFORE, self::FILTER_AFTER];
|
||||
|
||||
if (!in_array($type, $filterTypes, true)) {
|
||||
$noticeMessage = "Invalid filter type '$type', use " . join('|', $filterTypes);
|
||||
|
||||
trigger_error($noticeMessage, E_USER_NOTICE);
|
||||
}
|
||||
|
||||
if ($type === self::FILTER_AFTER) {
|
||||
$callbackInfo = new ReflectionFunction($callback);
|
||||
$parametersNumber = $callbackInfo->getNumberOfParameters();
|
||||
|
||||
if ($parametersNumber === 1) {
|
||||
/** @disregard &$params in after filters are deprecated. */
|
||||
$callback = fn (array &$params, &$output) => $callback($output);
|
||||
}
|
||||
}
|
||||
|
||||
$this->filters[$name][$type][] = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a chain of method filters.
|
||||
*
|
||||
* @param array $filters Chain of filters
|
||||
* @param array $params Method parameters
|
||||
* @param mixed $output Method output
|
||||
* @throws \Exception
|
||||
* @param array<int, callable(array<int, mixed> &$params, mixed &$output): (void|false)> $filters
|
||||
* Chain of filters.
|
||||
* @param array<int, mixed> $params Method parameters.
|
||||
* @param mixed $output Method output.
|
||||
*
|
||||
* @throws Exception If an event throws an `Exception` or if `$filters` contains an invalid filter.
|
||||
*/
|
||||
public function filter($filters, &$params, &$output) {
|
||||
$args = array(&$params, &$output);
|
||||
foreach ($filters as $callback) {
|
||||
$continue = $this->execute($callback, $args);
|
||||
if ($continue === false) break;
|
||||
public function filter(array $filters, array &$params, &$output): void
|
||||
{
|
||||
foreach ($filters as $key => $callback) {
|
||||
if (!is_callable($callback)) {
|
||||
throw new InvalidArgumentException("Invalid callable \$filters[$key].");
|
||||
}
|
||||
|
||||
$continue = $callback($params, $output);
|
||||
|
||||
if ($continue === false) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a callback function.
|
||||
*
|
||||
* @param callback $callback Callback function
|
||||
* @param array $params Function parameters
|
||||
* @return mixed Function results
|
||||
* @throws \Exception
|
||||
* @param callable-string|(callable(): mixed)|array{class-string|object, string} $callback
|
||||
* Callback function.
|
||||
* @param array<int, mixed> $params Function parameters.
|
||||
*
|
||||
* @return mixed Function results.
|
||||
* @throws Exception If `$callback` also throws an `Exception`.
|
||||
*/
|
||||
public static function execute($callback, array &$params = array()) {
|
||||
if (is_callable($callback)) {
|
||||
return is_array($callback) ?
|
||||
self::invokeMethod($callback, $params) :
|
||||
self::callFunction($callback, $params);
|
||||
public function execute($callback, array &$params = [])
|
||||
{
|
||||
if (
|
||||
is_string($callback) === true
|
||||
&& (strpos($callback, '->') !== false || strpos($callback, '::') !== false)
|
||||
) {
|
||||
$callback = $this->parseStringClassAndMethod($callback);
|
||||
}
|
||||
else {
|
||||
throw new \Exception('Invalid callback specified.');
|
||||
|
||||
return $this->invokeCallable($callback, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a string into a class and method.
|
||||
*
|
||||
* @param string $classAndMethod Class and method
|
||||
*
|
||||
* @return array{0: class-string|object, 1: string} Class and method
|
||||
*/
|
||||
public function parseStringClassAndMethod(string $classAndMethod): array
|
||||
{
|
||||
$classParts = explode('->', $classAndMethod);
|
||||
|
||||
if (count($classParts) === 1) {
|
||||
$classParts = explode('::', $classParts[0]);
|
||||
}
|
||||
|
||||
return $classParts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a function.
|
||||
*
|
||||
* @param string $func Name of function to call
|
||||
* @param array $params Function parameters
|
||||
* @return mixed Function results
|
||||
* @param callable $func Name of function to call.
|
||||
* @param array<int, mixed> &$params Function parameters.
|
||||
*
|
||||
* @return mixed Function results.
|
||||
* @deprecated 3.7.0 Use invokeCallable instead
|
||||
*/
|
||||
public static function callFunction($func, array &$params = array()) {
|
||||
// Call static method
|
||||
if (is_string($func) && strpos($func, '::') !== false) {
|
||||
return call_user_func_array($func, $params);
|
||||
}
|
||||
|
||||
switch (count($params)) {
|
||||
case 0:
|
||||
return $func();
|
||||
case 1:
|
||||
return $func($params[0]);
|
||||
case 2:
|
||||
return $func($params[0], $params[1]);
|
||||
case 3:
|
||||
return $func($params[0], $params[1], $params[2]);
|
||||
case 4:
|
||||
return $func($params[0], $params[1], $params[2], $params[3]);
|
||||
case 5:
|
||||
return $func($params[0], $params[1], $params[2], $params[3], $params[4]);
|
||||
default:
|
||||
return call_user_func_array($func, $params);
|
||||
}
|
||||
public function callFunction(callable $func, array &$params = [])
|
||||
{
|
||||
return $this->invokeCallable($func, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes a method.
|
||||
*
|
||||
* @param mixed $func Class method
|
||||
* @param array $params Class method parameters
|
||||
* @return mixed Function results
|
||||
* @param array{0: class-string|object, 1: string} $func Class method.
|
||||
* @param array<int, mixed> &$params Class method parameters.
|
||||
*
|
||||
* @return mixed Function results.
|
||||
* @throws TypeError For nonexistent class name.
|
||||
* @deprecated 3.7.0 Use invokeCallable instead.
|
||||
*/
|
||||
public static function invokeMethod($func, array &$params = array()) {
|
||||
list($class, $method) = $func;
|
||||
public function invokeMethod(array $func, array &$params = [])
|
||||
{
|
||||
return $this->invokeCallable($func, $params);
|
||||
}
|
||||
|
||||
$instance = is_object($class);
|
||||
|
||||
switch (count($params)) {
|
||||
case 0:
|
||||
return ($instance) ?
|
||||
$class->$method() :
|
||||
$class::$method();
|
||||
case 1:
|
||||
return ($instance) ?
|
||||
$class->$method($params[0]) :
|
||||
$class::$method($params[0]);
|
||||
case 2:
|
||||
return ($instance) ?
|
||||
$class->$method($params[0], $params[1]) :
|
||||
$class::$method($params[0], $params[1]);
|
||||
case 3:
|
||||
return ($instance) ?
|
||||
$class->$method($params[0], $params[1], $params[2]) :
|
||||
$class::$method($params[0], $params[1], $params[2]);
|
||||
case 4:
|
||||
return ($instance) ?
|
||||
$class->$method($params[0], $params[1], $params[2], $params[3]) :
|
||||
$class::$method($params[0], $params[1], $params[2], $params[3]);
|
||||
case 5:
|
||||
return ($instance) ?
|
||||
$class->$method($params[0], $params[1], $params[2], $params[3], $params[4]) :
|
||||
$class::$method($params[0], $params[1], $params[2], $params[3], $params[4]);
|
||||
default:
|
||||
return call_user_func_array($func, $params);
|
||||
/**
|
||||
* Invokes a callable (anonymous function or Class->method).
|
||||
*
|
||||
* @param array{0: class-string|object, 1: string}|callable $func Class method.
|
||||
* @param array<int, mixed> &$params Class method parameters.
|
||||
*
|
||||
* @return mixed Function results.
|
||||
* @throws TypeError For nonexistent class name.
|
||||
* @throws InvalidArgumentException If the constructor requires parameters.
|
||||
* @version 3.7.0
|
||||
*/
|
||||
public function invokeCallable($func, array &$params = [])
|
||||
{
|
||||
// If this is a directly callable function, call it
|
||||
if (is_array($func) === false) {
|
||||
$this->verifyValidFunction($func);
|
||||
|
||||
return call_user_func_array($func, $params);
|
||||
}
|
||||
|
||||
[$class, $method] = $func;
|
||||
|
||||
$mustUseTheContainer = $this->mustUseContainer($class);
|
||||
|
||||
if ($mustUseTheContainer === true) {
|
||||
$resolvedClass = $this->resolveContainerClass($class, $params);
|
||||
|
||||
if ($resolvedClass) {
|
||||
$class = $resolvedClass;
|
||||
}
|
||||
}
|
||||
|
||||
$this->verifyValidClassCallable($class, $method, $resolvedClass ?? null);
|
||||
|
||||
// Class is a string, and method exists, create the object by hand and inject only the Engine
|
||||
if (is_string($class)) {
|
||||
$class = new $class($this->engine);
|
||||
}
|
||||
|
||||
return call_user_func_array([$class, $method], $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles invalid callback types.
|
||||
*
|
||||
* @param callable-string|(callable(): mixed)|array{0: class-string|object, 1: string} $callback
|
||||
* Callback function.
|
||||
*
|
||||
* @throws InvalidArgumentException If `$callback` is an invalid type.
|
||||
*/
|
||||
protected function verifyValidFunction($callback): void
|
||||
{
|
||||
if (is_string($callback) && !function_exists($callback)) {
|
||||
throw new InvalidArgumentException('Invalid callback specified.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verifies if the provided class and method are valid callable.
|
||||
*
|
||||
* @param class-string|object $class The class name.
|
||||
* @param string $method The method name.
|
||||
* @param object|null $resolvedClass The resolved class.
|
||||
*
|
||||
* @throws Exception If the class or method is not found.
|
||||
*/
|
||||
protected function verifyValidClassCallable($class, $method, $resolvedClass): void
|
||||
{
|
||||
$exception = null;
|
||||
|
||||
// Final check to make sure it's actually a class and a method, or throw an error
|
||||
if (is_object($class) === false && class_exists($class) === false) {
|
||||
$exception = new Exception("Class '$class' not found. Is it being correctly autoloaded with Flight::path()?");
|
||||
|
||||
// If this tried to resolve a class in a container and failed somehow, throw the exception
|
||||
} elseif (!$resolvedClass && $this->containerException !== null) {
|
||||
$exception = $this->containerException;
|
||||
|
||||
// Class is there, but no method
|
||||
} elseif (is_object($class) === true && method_exists($class, $method) === false) {
|
||||
$classNamespace = get_class($class);
|
||||
$exception = new Exception("Class found, but method '$classNamespace::$method' not found.");
|
||||
}
|
||||
|
||||
if ($exception !== null) {
|
||||
$this->fixOutputBuffering();
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the container class.
|
||||
*
|
||||
* @param class-string $class Class name.
|
||||
* @param array<int, mixed> &$params Class constructor parameters.
|
||||
*
|
||||
* @return ?object Class object.
|
||||
*/
|
||||
public function resolveContainerClass(string $class, array &$params)
|
||||
{
|
||||
// PSR-11
|
||||
if (
|
||||
is_a($this->containerHandler, '\Psr\Container\ContainerInterface')
|
||||
&& $this->containerHandler->has($class)
|
||||
) {
|
||||
return $this->containerHandler->get($class);
|
||||
}
|
||||
|
||||
// Just a callable where you configure the behavior (Dice, PHP-DI, etc.)
|
||||
if (is_callable($this->containerHandler)) {
|
||||
/* This is to catch all the error that could be thrown by whatever
|
||||
container you are using */
|
||||
try {
|
||||
return ($this->containerHandler)($class, $params);
|
||||
|
||||
// could not resolve a class for some reason
|
||||
} catch (Exception $exception) {
|
||||
// If the container throws an exception, we need to catch it
|
||||
// and store it somewhere. If we just let it throw itself, it
|
||||
// doesn't properly close the output buffers and can cause other
|
||||
// issues.
|
||||
// This is thrown in the verifyValidClassCallable method.
|
||||
$this->containerException = $exception;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if a container should be used or not.
|
||||
*
|
||||
* @param string|object $class the class to verify
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function mustUseContainer($class): bool
|
||||
{
|
||||
return $this->containerHandler !== null && (
|
||||
(is_object($class) === true && strpos(get_class($class), 'flight\\') === false)
|
||||
|| is_string($class)
|
||||
);
|
||||
}
|
||||
|
||||
/** Because this could throw an exception in the middle of an output buffer, */
|
||||
protected function fixOutputBuffering(): void
|
||||
{
|
||||
// Cause PHPUnit has 1 level of output buffering by default
|
||||
if (ob_get_level() > (getenv('PHPUNIT_TEST') ? 1 : 0)) {
|
||||
ob_end_clean();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the object to the initial state.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function reset() {
|
||||
$this->events = array();
|
||||
$this->filters = array();
|
||||
public function reset(): self
|
||||
{
|
||||
$this->events = [];
|
||||
$this->filters = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@ -1,53 +1,64 @@
|
||||
<?php
|
||||
/**
|
||||
* Flight: An extensible micro-framework.
|
||||
*
|
||||
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
|
||||
* @license MIT, http://flightphp.com/license
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace flight\core;
|
||||
|
||||
use Closure;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* The Loader class is responsible for loading objects. It maintains
|
||||
* a list of reusable class instances and can generate a new class
|
||||
* instances with custom initialization parameters. It also performs
|
||||
* class autoloading.
|
||||
*
|
||||
* @license MIT, http://flightphp.com/license
|
||||
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
|
||||
*/
|
||||
class Loader {
|
||||
class Loader
|
||||
{
|
||||
/**
|
||||
* Registered classes.
|
||||
*
|
||||
* @var array
|
||||
* @var array<string, array{class-string|Closure(): object, array<int, mixed>, ?callable}> $classes
|
||||
*/
|
||||
protected $classes = array();
|
||||
protected array $classes = [];
|
||||
|
||||
/**
|
||||
* If this is disabled, classes can load with underscores
|
||||
*/
|
||||
protected static bool $v2ClassLoading = true;
|
||||
|
||||
/**
|
||||
* Class instances.
|
||||
*
|
||||
* @var array
|
||||
* @var array<string, object>
|
||||
*/
|
||||
protected $instances = array();
|
||||
protected array $instances = [];
|
||||
|
||||
/**
|
||||
* Autoload directories.
|
||||
*
|
||||
* @var array
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected static $dirs = array();
|
||||
protected static array $dirs = [];
|
||||
|
||||
/**
|
||||
* Registers a class.
|
||||
*
|
||||
* @param string $name Registry name
|
||||
* @param string|callable $class Class name or function to instantiate class
|
||||
* @param array $params Class initialization parameters
|
||||
* @param callback $callback Function to call after object instantiation
|
||||
* @param class-string<T>|Closure(): T $class Class name or function to instantiate class
|
||||
* @param array<int, mixed> $params Class initialization parameters
|
||||
* @param ?Closure(T $instance): void $callback $callback Function to call after object instantiation
|
||||
*
|
||||
* @template T of object
|
||||
*/
|
||||
public function register($name, $class, array $params = array(), $callback = null) {
|
||||
public function register(string $name, $class, array $params = [], ?callable $callback = null): void
|
||||
{
|
||||
unset($this->instances[$name]);
|
||||
|
||||
$this->classes[$name] = array($class, $params, $callback);
|
||||
$this->classes[$name] = [$class, $params, $callback];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -55,23 +66,27 @@ class Loader {
|
||||
*
|
||||
* @param string $name Registry name
|
||||
*/
|
||||
public function unregister($name) {
|
||||
public function unregister(string $name): void
|
||||
{
|
||||
unset($this->classes[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a registered class.
|
||||
*
|
||||
* @param string $name Method name
|
||||
* @param bool $shared Shared instance
|
||||
* @return object Class instance
|
||||
* @throws \Exception
|
||||
* @param string $name Method name
|
||||
* @param bool $shared Shared instance
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return ?object Class instance
|
||||
*/
|
||||
public function load($name, $shared = true) {
|
||||
public function load(string $name, bool $shared = true): ?object
|
||||
{
|
||||
$obj = null;
|
||||
|
||||
if (isset($this->classes[$name])) {
|
||||
list($class, $params, $callback) = $this->classes[$name];
|
||||
[0 => $class, 1 => $params, 2 => $callback] = $this->classes[$name];
|
||||
|
||||
$exists = isset($this->instances[$name]);
|
||||
|
||||
@ -79,18 +94,17 @@ class Loader {
|
||||
$obj = ($exists) ?
|
||||
$this->getInstance($name) :
|
||||
$this->newInstance($class, $params);
|
||||
|
||||
|
||||
if (!$exists) {
|
||||
$this->instances[$name] = $obj;
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
$obj = $this->newInstance($class, $params);
|
||||
}
|
||||
|
||||
if ($callback && (!$shared || !$exists)) {
|
||||
$ref = array(&$obj);
|
||||
call_user_func_array($callback, $ref);
|
||||
$ref = [&$obj];
|
||||
\call_user_func_array($callback, $ref);
|
||||
}
|
||||
}
|
||||
|
||||
@ -101,78 +115,70 @@ class Loader {
|
||||
* Gets a single instance of a class.
|
||||
*
|
||||
* @param string $name Instance name
|
||||
* @return object Class instance
|
||||
*
|
||||
* @return ?object Class instance
|
||||
*/
|
||||
public function getInstance($name) {
|
||||
return isset($this->instances[$name]) ? $this->instances[$name] : null;
|
||||
public function getInstance(string $name): ?object
|
||||
{
|
||||
return $this->instances[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a new instance of a class.
|
||||
*
|
||||
* @param string|callable $class Class name or callback function to instantiate class
|
||||
* @param array $params Class initialization parameters
|
||||
* @return object Class instance
|
||||
* @throws \Exception
|
||||
* @param class-string<T>|Closure(): class-string<T> $class Class name or callback function to instantiate class
|
||||
* @param array<int, string> $params Class initialization parameters
|
||||
*
|
||||
* @template T of object
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return T Class instance
|
||||
*/
|
||||
public function newInstance($class, array $params = array()) {
|
||||
if (is_callable($class)) {
|
||||
return call_user_func_array($class, $params);
|
||||
public function newInstance($class, array $params = [])
|
||||
{
|
||||
if (\is_callable($class)) {
|
||||
return \call_user_func_array($class, $params);
|
||||
}
|
||||
|
||||
switch (count($params)) {
|
||||
case 0:
|
||||
return new $class();
|
||||
case 1:
|
||||
return new $class($params[0]);
|
||||
case 2:
|
||||
return new $class($params[0], $params[1]);
|
||||
case 3:
|
||||
return new $class($params[0], $params[1], $params[2]);
|
||||
case 4:
|
||||
return new $class($params[0], $params[1], $params[2], $params[3]);
|
||||
case 5:
|
||||
return new $class($params[0], $params[1], $params[2], $params[3], $params[4]);
|
||||
default:
|
||||
try {
|
||||
$refClass = new \ReflectionClass($class);
|
||||
return $refClass->newInstanceArgs($params);
|
||||
} catch (\ReflectionException $e) {
|
||||
throw new \Exception("Cannot instantiate {$class}", 0, $e);
|
||||
}
|
||||
}
|
||||
return new $class(...$params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a registered callable
|
||||
*
|
||||
* @param string $name Registry name
|
||||
*
|
||||
* @return mixed Class information or null if not registered
|
||||
*/
|
||||
public function get($name) {
|
||||
return isset($this->classes[$name]) ? $this->classes[$name] : null;
|
||||
public function get(string $name)
|
||||
{
|
||||
return $this->classes[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the object to the initial state.
|
||||
*/
|
||||
public function reset() {
|
||||
$this->classes = array();
|
||||
$this->instances = array();
|
||||
public function reset(): void
|
||||
{
|
||||
$this->classes = [];
|
||||
$this->instances = [];
|
||||
}
|
||||
|
||||
/*** Autoloading Functions ***/
|
||||
// Autoloading Functions
|
||||
|
||||
/**
|
||||
* Starts/stops autoloader.
|
||||
*
|
||||
* @param bool $enabled Enable/disable autoloading
|
||||
* @param array $dirs Autoload directories
|
||||
* @param bool $enabled Enable/disable autoloading
|
||||
* @param string|iterable<int, string> $dirs Autoload directories
|
||||
*/
|
||||
public static function autoload($enabled = true, $dirs = array()) {
|
||||
public static function autoload(bool $enabled = true, $dirs = []): void
|
||||
{
|
||||
if ($enabled) {
|
||||
spl_autoload_register(array(__CLASS__, 'loadClass'));
|
||||
}
|
||||
else {
|
||||
spl_autoload_unregister(array(__CLASS__, 'loadClass'));
|
||||
spl_autoload_register([__CLASS__, 'loadClass']);
|
||||
} else {
|
||||
spl_autoload_unregister([__CLASS__, 'loadClass']); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
if (!empty($dirs)) {
|
||||
@ -183,15 +189,20 @@ class Loader {
|
||||
/**
|
||||
* Autoloads classes.
|
||||
*
|
||||
* Classes are not allowed to have underscores in their names.
|
||||
*
|
||||
* @param string $class Class name
|
||||
*/
|
||||
public static function loadClass($class) {
|
||||
$class_file = str_replace(array('\\', '_'), '/', $class).'.php';
|
||||
public static function loadClass(string $class): void
|
||||
{
|
||||
$replace_chars = self::$v2ClassLoading === true ? ['\\', '_'] : ['\\'];
|
||||
$classFile = str_replace($replace_chars, '/', $class) . '.php';
|
||||
|
||||
foreach (self::$dirs as $dir) {
|
||||
$file = $dir.'/'.$class_file;
|
||||
if (file_exists($file)) {
|
||||
require $file;
|
||||
$filePath = "$dir/$classFile";
|
||||
|
||||
if (file_exists($filePath)) {
|
||||
require_once $filePath;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -200,16 +211,31 @@ class Loader {
|
||||
/**
|
||||
* Adds a directory for autoloading classes.
|
||||
*
|
||||
* @param mixed $dir Directory path
|
||||
* @param string|iterable<int, string> $dir Directory path
|
||||
*/
|
||||
public static function addDirectory($dir) {
|
||||
if (is_array($dir) || is_object($dir)) {
|
||||
public static function addDirectory($dir): void
|
||||
{
|
||||
if (\is_array($dir) || \is_object($dir)) {
|
||||
foreach ($dir as $value) {
|
||||
self::addDirectory($value);
|
||||
}
|
||||
}
|
||||
else if (is_string($dir)) {
|
||||
if (!in_array($dir, self::$dirs)) self::$dirs[] = $dir;
|
||||
} elseif (\is_string($dir)) {
|
||||
if (!\in_array($dir, self::$dirs, true)) {
|
||||
self::$dirs[] = $dir;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the value for V2 class loading.
|
||||
*
|
||||
* @param bool $value The value to set for V2 class loading.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function setV2ClassLoading(bool $value): void
|
||||
{
|
||||
self::$v2ClassLoading = $value;
|
||||
}
|
||||
}
|
||||
|
150
libs/flight/database/PdoWrapper.php
Normal file
150
libs/flight/database/PdoWrapper.php
Normal file
@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace flight\database;
|
||||
|
||||
use flight\util\Collection;
|
||||
use PDO;
|
||||
use PDOStatement;
|
||||
|
||||
class PdoWrapper extends PDO
|
||||
{
|
||||
/**
|
||||
* Use this for INSERTS, UPDATES, or if you plan on using a SELECT in a while loop
|
||||
*
|
||||
* Ex: $statement = $db->runQuery("SELECT * FROM table WHERE something = ?", [ $something ]);
|
||||
* while($row = $statement->fetch()) {
|
||||
* // ...
|
||||
* }
|
||||
*
|
||||
* $db->runQuery("INSERT INTO table (name) VALUES (?)", [ $name ]);
|
||||
* $db->runQuery("UPDATE table SET name = ? WHERE id = ?", [ $name, $id ]);
|
||||
*
|
||||
* @param string $sql - Ex: "SELECT * FROM table WHERE something = ?"
|
||||
* @param array<int|string,mixed> $params - Ex: [ $something ]
|
||||
*
|
||||
* @return PDOStatement
|
||||
*/
|
||||
public function runQuery(string $sql, array $params = []): PDOStatement
|
||||
{
|
||||
$processed_sql_data = $this->processInStatementSql($sql, $params);
|
||||
$sql = $processed_sql_data['sql'];
|
||||
$params = $processed_sql_data['params'];
|
||||
$statement = $this->prepare($sql);
|
||||
$statement->execute($params);
|
||||
return $statement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pulls one field from the query
|
||||
*
|
||||
* Ex: $id = $db->fetchField("SELECT id FROM table WHERE something = ?", [ $something ]);
|
||||
*
|
||||
* @param string $sql - Ex: "SELECT id FROM table WHERE something = ?"
|
||||
* @param array<int|string,mixed> $params - Ex: [ $something ]
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function fetchField(string $sql, array $params = [])
|
||||
{
|
||||
$result = $this->fetchRow($sql, $params);
|
||||
$data = $result->getData();
|
||||
return reset($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pulls one row from the query
|
||||
*
|
||||
* Ex: $row = $db->fetchRow("SELECT * FROM table WHERE something = ?", [ $something ]);
|
||||
*
|
||||
* @param string $sql - Ex: "SELECT * FROM table WHERE something = ?"
|
||||
* @param array<int|string,mixed> $params - Ex: [ $something ]
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function fetchRow(string $sql, array $params = []): Collection
|
||||
{
|
||||
$sql .= stripos($sql, 'LIMIT') === false ? ' LIMIT 1' : '';
|
||||
$result = $this->fetchAll($sql, $params);
|
||||
return count($result) > 0 ? $result[0] : new Collection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pulls all rows from the query
|
||||
*
|
||||
* Ex: $rows = $db->fetchAll("SELECT * FROM table WHERE something = ?", [ $something ]);
|
||||
* foreach($rows as $row) {
|
||||
* // ...
|
||||
* }
|
||||
*
|
||||
* @param string $sql - Ex: "SELECT * FROM table WHERE something = ?"
|
||||
* @param array<int|string,mixed> $params - Ex: [ $something ]
|
||||
*
|
||||
* @return array<int,Collection>
|
||||
*/
|
||||
public function fetchAll(string $sql, array $params = [])
|
||||
{
|
||||
$processed_sql_data = $this->processInStatementSql($sql, $params);
|
||||
$sql = $processed_sql_data['sql'];
|
||||
$params = $processed_sql_data['params'];
|
||||
$statement = $this->prepare($sql);
|
||||
$statement->execute($params);
|
||||
$results = $statement->fetchAll();
|
||||
if (is_array($results) === true && count($results) > 0) {
|
||||
foreach ($results as &$result) {
|
||||
$result = new Collection($result);
|
||||
}
|
||||
} else {
|
||||
$results = [];
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't worry about this guy. Converts stuff for IN statements
|
||||
*
|
||||
* Ex: $row = $db->fetchAll("SELECT * FROM table WHERE id = ? AND something IN(?), [ $id, [1,2,3] ]);
|
||||
* Converts this to "SELECT * FROM table WHERE id = ? AND something IN(?,?,?)"
|
||||
*
|
||||
* @param string $sql the sql statement
|
||||
* @param array<int|string,mixed> $params the params for the sql statement
|
||||
*
|
||||
* @return array<string,string|array<int|string,mixed>>
|
||||
*/
|
||||
protected function processInStatementSql(string $sql, array $params = []): array
|
||||
{
|
||||
// Replace "IN(?)" with "IN(?,?,?)"
|
||||
$sql = preg_replace('/IN\s*\(\s*\?\s*\)/i', 'IN(?)', $sql);
|
||||
|
||||
$current_index = 0;
|
||||
while (($current_index = strpos($sql, 'IN(?)', $current_index)) !== false) {
|
||||
$preceeding_count = substr_count($sql, '?', 0, $current_index - 1);
|
||||
|
||||
$param = $params[$preceeding_count];
|
||||
$question_marks = '?';
|
||||
|
||||
if (is_string($param) || is_array($param)) {
|
||||
$params_to_use = $param;
|
||||
if (is_string($param)) {
|
||||
$params_to_use = explode(',', $param);
|
||||
}
|
||||
|
||||
foreach ($params_to_use as $key => $value) {
|
||||
if (is_string($value)) {
|
||||
$params_to_use[$key] = trim($value);
|
||||
}
|
||||
}
|
||||
|
||||
$question_marks = join(',', array_fill(0, count($params_to_use), '?'));
|
||||
$sql = substr_replace($sql, $question_marks, $current_index + 3, 1);
|
||||
|
||||
array_splice($params, $preceeding_count, 1, $params_to_use);
|
||||
}
|
||||
|
||||
$current_index += strlen($question_marks) + 4;
|
||||
}
|
||||
|
||||
return ['sql' => $sql, 'params' => $params];
|
||||
}
|
||||
}
|
@ -1,10 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* Flight: An extensible micro-framework.
|
||||
*
|
||||
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
|
||||
* @license MIT, http://flightphp.com/license
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace flight\net;
|
||||
|
||||
@ -15,144 +11,160 @@ use flight\util\Collection;
|
||||
* all the super globals $_GET, $_POST, $_COOKIE, and $_FILES
|
||||
* are stored and accessible via the Request object.
|
||||
*
|
||||
* @license MIT, http://flightphp.com/license
|
||||
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
|
||||
*
|
||||
* The default request properties are:
|
||||
* url - The URL being requested
|
||||
* base - The parent subdirectory of the URL
|
||||
* method - The request method (GET, POST, PUT, DELETE)
|
||||
* referrer - The referrer URL
|
||||
* ip - IP address of the client
|
||||
* ajax - Whether the request is an AJAX request
|
||||
* scheme - The server protocol (http, https)
|
||||
* user_agent - Browser information
|
||||
* type - The content type
|
||||
* length - The content length
|
||||
* query - Query string parameters
|
||||
* data - Post parameters
|
||||
* cookies - Cookie parameters
|
||||
* files - Uploaded files
|
||||
* secure - Connection is secure
|
||||
* accept - HTTP accept parameters
|
||||
* proxy_ip - Proxy IP address of the client
|
||||
*
|
||||
* - **url** - The URL being requested
|
||||
* - **base** - The parent subdirectory of the URL
|
||||
* - **method** - The request method (GET, POST, PUT, DELETE)
|
||||
* - **referrer** - The referrer URL
|
||||
* - **ip** - IP address of the client
|
||||
* - **ajax** - Whether the request is an AJAX request
|
||||
* - **scheme** - The server protocol (http, https)
|
||||
* - **user_agent** - Browser information
|
||||
* - **type** - The content type
|
||||
* - **length** - The content length
|
||||
* - **query** - Query string parameters
|
||||
* - **data** - Post parameters
|
||||
* - **cookies** - Cookie parameters
|
||||
* - **files** - Uploaded files
|
||||
* - **secure** - Connection is secure
|
||||
* - **accept** - HTTP accept parameters
|
||||
* - **proxy_ip** - Proxy IP address of the client
|
||||
*/
|
||||
class Request {
|
||||
class Request
|
||||
{
|
||||
/**
|
||||
* @var string URL being requested
|
||||
* URL being requested
|
||||
*/
|
||||
public $url;
|
||||
public string $url;
|
||||
|
||||
/**
|
||||
* @var string Parent subdirectory of the URL
|
||||
* Parent subdirectory of the URL
|
||||
*/
|
||||
public $base;
|
||||
public string $base;
|
||||
|
||||
/**
|
||||
* @var string Request method (GET, POST, PUT, DELETE)
|
||||
* Request method (GET, POST, PUT, DELETE)
|
||||
*/
|
||||
public $method;
|
||||
public string $method;
|
||||
|
||||
/**
|
||||
* @var string Referrer URL
|
||||
* Referrer URL
|
||||
*/
|
||||
public $referrer;
|
||||
public string $referrer;
|
||||
|
||||
/**
|
||||
* @var string IP address of the client
|
||||
* IP address of the client
|
||||
*/
|
||||
public $ip;
|
||||
public string $ip;
|
||||
|
||||
/**
|
||||
* @var bool Whether the request is an AJAX request
|
||||
* Whether the request is an AJAX request
|
||||
*/
|
||||
public $ajax;
|
||||
public bool $ajax;
|
||||
|
||||
/**
|
||||
* @var string Server protocol (http, https)
|
||||
* Server protocol (http, https)
|
||||
*/
|
||||
public $scheme;
|
||||
public string $scheme;
|
||||
|
||||
/**
|
||||
* @var string Browser information
|
||||
* Browser information
|
||||
*/
|
||||
public $user_agent;
|
||||
public string $user_agent;
|
||||
|
||||
/**
|
||||
* @var string Content type
|
||||
* Content type
|
||||
*/
|
||||
public $type;
|
||||
public string $type;
|
||||
|
||||
/**
|
||||
* @var int Content length
|
||||
* Content length
|
||||
*/
|
||||
public $length;
|
||||
public int $length;
|
||||
|
||||
/**
|
||||
* @var \flight\util\Collection Query string parameters
|
||||
* Query string parameters
|
||||
*/
|
||||
public $query;
|
||||
public Collection $query;
|
||||
|
||||
/**
|
||||
* @var \flight\util\Collection Post parameters
|
||||
* Post parameters
|
||||
*/
|
||||
public $data;
|
||||
public Collection $data;
|
||||
|
||||
/**
|
||||
* @var \flight\util\Collection Cookie parameters
|
||||
* Cookie parameters
|
||||
*/
|
||||
public $cookies;
|
||||
public Collection $cookies;
|
||||
|
||||
/**
|
||||
* @var \flight\util\Collection Uploaded files
|
||||
* Uploaded files
|
||||
*/
|
||||
public $files;
|
||||
public Collection $files;
|
||||
|
||||
/**
|
||||
* @var bool Whether the connection is secure
|
||||
* Whether the connection is secure
|
||||
*/
|
||||
public $secure;
|
||||
public bool $secure;
|
||||
|
||||
/**
|
||||
* @var string HTTP accept parameters
|
||||
* HTTP accept parameters
|
||||
*/
|
||||
public $accept;
|
||||
public string $accept;
|
||||
|
||||
/**
|
||||
* @var string Proxy IP address of the client
|
||||
* Proxy IP address of the client
|
||||
*/
|
||||
public $proxy_ip;
|
||||
public string $proxy_ip;
|
||||
|
||||
/**
|
||||
* @var string HTTP host name
|
||||
* HTTP host name
|
||||
*/
|
||||
public $host;
|
||||
public string $host;
|
||||
|
||||
/**
|
||||
* Stream path for where to pull the request body from
|
||||
*/
|
||||
private string $stream_path = 'php://input';
|
||||
|
||||
/**
|
||||
* Raw HTTP request body
|
||||
*/
|
||||
public string $body = '';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $config Request configuration
|
||||
* @param array<string, mixed> $config Request configuration
|
||||
*/
|
||||
public function __construct($config = array()) {
|
||||
public function __construct(array $config = [])
|
||||
{
|
||||
// Default properties
|
||||
if (empty($config)) {
|
||||
$config = array(
|
||||
'url' => str_replace('@', '%40', self::getVar('REQUEST_URI', '/')),
|
||||
'base' => str_replace(array('\\',' '), array('/','%20'), dirname(self::getVar('SCRIPT_NAME'))),
|
||||
'method' => self::getMethod(),
|
||||
'referrer' => self::getVar('HTTP_REFERER'),
|
||||
'ip' => self::getVar('REMOTE_ADDR'),
|
||||
'ajax' => self::getVar('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest',
|
||||
'scheme' => self::getScheme(),
|
||||
$config = [
|
||||
'url' => str_replace('@', '%40', self::getVar('REQUEST_URI', '/')),
|
||||
'base' => str_replace(['\\', ' '], ['/', '%20'], \dirname(self::getVar('SCRIPT_NAME'))),
|
||||
'method' => self::getMethod(),
|
||||
'referrer' => self::getVar('HTTP_REFERER'),
|
||||
'ip' => self::getVar('REMOTE_ADDR'),
|
||||
'ajax' => self::getVar('HTTP_X_REQUESTED_WITH') === 'XMLHttpRequest',
|
||||
'scheme' => self::getScheme(),
|
||||
'user_agent' => self::getVar('HTTP_USER_AGENT'),
|
||||
'type' => self::getVar('CONTENT_TYPE'),
|
||||
'length' => self::getVar('CONTENT_LENGTH', 0),
|
||||
'query' => new Collection($_GET),
|
||||
'data' => new Collection($_POST),
|
||||
'cookies' => new Collection($_COOKIE),
|
||||
'files' => new Collection($_FILES),
|
||||
'secure' => self::getScheme() == 'https',
|
||||
'accept' => self::getVar('HTTP_ACCEPT'),
|
||||
'proxy_ip' => self::getProxyIpAddress(),
|
||||
'host' => self::getVar('HTTP_HOST'),
|
||||
);
|
||||
'type' => self::getVar('CONTENT_TYPE'),
|
||||
'length' => intval(self::getVar('CONTENT_LENGTH', 0)),
|
||||
'query' => new Collection($_GET),
|
||||
'data' => new Collection($_POST),
|
||||
'cookies' => new Collection($_COOKIE),
|
||||
'files' => new Collection($_FILES),
|
||||
'secure' => self::getScheme() === 'https',
|
||||
'accept' => self::getVar('HTTP_ACCEPT'),
|
||||
'proxy_ip' => self::getProxyIpAddress(),
|
||||
'host' => self::getVar('HTTP_HOST'),
|
||||
];
|
||||
}
|
||||
|
||||
$this->init($config);
|
||||
@ -161,26 +173,31 @@ class Request {
|
||||
/**
|
||||
* Initialize request properties.
|
||||
*
|
||||
* @param array $properties Array of request properties
|
||||
* @param array<string, mixed> $properties Array of request properties
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function init($properties = array()) {
|
||||
public function init(array $properties = []): self
|
||||
{
|
||||
// Set all the defined properties
|
||||
foreach ($properties as $name => $value) {
|
||||
$this->$name = $value;
|
||||
$this->{$name} = $value;
|
||||
}
|
||||
|
||||
// Get the requested URL without the base directory
|
||||
if ($this->base != '/' && strlen($this->base) > 0 && strpos($this->url, $this->base) === 0) {
|
||||
$this->url = substr($this->url, strlen($this->base));
|
||||
// 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 !== '' && strpos($this->url, $this->base) === 0) {
|
||||
$this->url = substr($this->url, \strlen($this->base));
|
||||
}
|
||||
|
||||
// Default url
|
||||
if (empty($this->url)) {
|
||||
if (empty($this->url) === true) {
|
||||
$this->url = '/';
|
||||
}
|
||||
// Merge URL query parameters with $_GET
|
||||
else {
|
||||
$_GET += self::parseQuery($this->url);
|
||||
} else {
|
||||
// Merge URL query parameters with $_GET
|
||||
$_GET = array_merge($_GET, self::parseQuery($this->url));
|
||||
|
||||
$this->query->setData($_GET);
|
||||
}
|
||||
@ -188,13 +205,15 @@ class Request {
|
||||
// Check for JSON input
|
||||
if (strpos($this->type, 'application/json') === 0) {
|
||||
$body = $this->getBody();
|
||||
if ($body != '') {
|
||||
if ($body !== '') {
|
||||
$data = json_decode($body, true);
|
||||
if ($data != null) {
|
||||
if (is_array($data) === true) {
|
||||
$this->data->setData($data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -202,34 +221,35 @@ class Request {
|
||||
*
|
||||
* @return string Raw HTTP request body
|
||||
*/
|
||||
public static function getBody() {
|
||||
static $body;
|
||||
public function getBody(): string
|
||||
{
|
||||
$body = $this->body;
|
||||
|
||||
if (!is_null($body)) {
|
||||
if ($body !== '') {
|
||||
return $body;
|
||||
}
|
||||
|
||||
$method = self::getMethod();
|
||||
$method = $this->method ?? self::getMethod();
|
||||
|
||||
if ($method == 'POST' || $method == 'PUT' || $method == 'DELETE' || $method == 'PATCH') {
|
||||
$body = file_get_contents('php://input');
|
||||
if ($method === 'POST' || $method === 'PUT' || $method === 'DELETE' || $method === 'PATCH') {
|
||||
$body = file_get_contents($this->stream_path);
|
||||
}
|
||||
|
||||
$this->body = $body;
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the request method.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getMethod() {
|
||||
public static function getMethod(): string
|
||||
{
|
||||
$method = self::getVar('REQUEST_METHOD', 'GET');
|
||||
|
||||
if (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) {
|
||||
if (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']) === true) {
|
||||
$method = $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'];
|
||||
}
|
||||
elseif (isset($_REQUEST['_method'])) {
|
||||
} elseif (isset($_REQUEST['_method']) === true) {
|
||||
$method = $_REQUEST['_method'];
|
||||
}
|
||||
|
||||
@ -241,20 +261,21 @@ class Request {
|
||||
*
|
||||
* @return string IP address
|
||||
*/
|
||||
public static function getProxyIpAddress() {
|
||||
static $forwarded = array(
|
||||
public static function getProxyIpAddress(): string
|
||||
{
|
||||
$forwarded = [
|
||||
'HTTP_CLIENT_IP',
|
||||
'HTTP_X_FORWARDED_FOR',
|
||||
'HTTP_X_FORWARDED',
|
||||
'HTTP_X_CLUSTER_CLIENT_IP',
|
||||
'HTTP_FORWARDED_FOR',
|
||||
'HTTP_FORWARDED'
|
||||
);
|
||||
'HTTP_FORWARDED',
|
||||
];
|
||||
|
||||
$flags = \FILTER_FLAG_NO_PRIV_RANGE | \FILTER_FLAG_NO_RES_RANGE;
|
||||
|
||||
foreach ($forwarded as $key) {
|
||||
if (array_key_exists($key, $_SERVER)) {
|
||||
if (\array_key_exists($key, $_SERVER) === true) {
|
||||
sscanf($_SERVER[$key], '%[^,]', $ip);
|
||||
if (filter_var($ip, \FILTER_VALIDATE_IP, $flags) !== false) {
|
||||
return $ip;
|
||||
@ -268,43 +289,188 @@ class Request {
|
||||
/**
|
||||
* Gets a variable from $_SERVER using $default if not provided.
|
||||
*
|
||||
* @param string $var Variable name
|
||||
* @param string $default Default value to substitute
|
||||
* @return string Server variable value
|
||||
* @param string $var Variable name
|
||||
* @param mixed $default Default value to substitute
|
||||
*
|
||||
* @return mixed Server variable value
|
||||
*/
|
||||
public static function getVar($var, $default = '') {
|
||||
return isset($_SERVER[$var]) ? $_SERVER[$var] : $default;
|
||||
public static function getVar(string $var, $default = '')
|
||||
{
|
||||
return $_SERVER[$var] ?? $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* This will pull a header from the request.
|
||||
*
|
||||
* @param string $header Header name. Can be caps, lowercase, or mixed.
|
||||
* @param string $default Default value if the header does not exist
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getHeader(string $header, $default = ''): string
|
||||
{
|
||||
$header = 'HTTP_' . strtoupper(str_replace('-', '_', $header));
|
||||
return self::getVar($header, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the request headers
|
||||
*
|
||||
* @return array<string, string|int>
|
||||
*/
|
||||
public static function getHeaders(): array
|
||||
{
|
||||
$headers = [];
|
||||
foreach ($_SERVER as $key => $value) {
|
||||
if (strpos($key, 'HTTP_') === 0) {
|
||||
// converts headers like HTTP_CUSTOM_HEADER to Custom-Header
|
||||
$key = str_replace(' ', '-', ucwords(str_replace('_', ' ', strtolower(substr($key, 5)))));
|
||||
$headers[$key] = $value;
|
||||
}
|
||||
}
|
||||
return $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias of Request->getHeader(). Gets a single header.
|
||||
*
|
||||
* @param string $header Header name. Can be caps, lowercase, or mixed.
|
||||
* @param string $default Default value if the header does not exist
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function header(string $header, $default = '')
|
||||
{
|
||||
return self::getHeader($header, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias of Request->getHeaders(). Gets all the request headers
|
||||
*
|
||||
* @return array<string, string|int>
|
||||
*/
|
||||
public static function headers(): array
|
||||
{
|
||||
return self::getHeaders();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the full request URL.
|
||||
*
|
||||
* @return string URL
|
||||
*/
|
||||
public function getFullUrl(): string
|
||||
{
|
||||
return $this->scheme . '://' . $this->host . $this->url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Grabs the scheme and host. Does not end with a /
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBaseUrl(): string
|
||||
{
|
||||
return $this->scheme . '://' . $this->host;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse query parameters from a URL.
|
||||
*
|
||||
* @param string $url URL string
|
||||
* @return array Query parameters
|
||||
*
|
||||
* @return array<string, int|string|array<int|string, int|string>>
|
||||
*/
|
||||
public static function parseQuery($url) {
|
||||
$params = array();
|
||||
public static function parseQuery(string $url): array
|
||||
{
|
||||
$params = [];
|
||||
|
||||
$args = parse_url($url);
|
||||
if (isset($args['query'])) {
|
||||
if (isset($args['query']) === true) {
|
||||
parse_str($args['query'], $params);
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
public static function getScheme() {
|
||||
/**
|
||||
* Gets the URL Scheme
|
||||
*
|
||||
* @return string 'http'|'https'
|
||||
*/
|
||||
public static function getScheme(): string
|
||||
{
|
||||
if (
|
||||
(isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) === 'on')
|
||||
(isset($_SERVER['HTTPS']) === true && strtolower($_SERVER['HTTPS']) === 'on')
|
||||
||
|
||||
(isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https')
|
||||
(isset($_SERVER['HTTP_X_FORWARDED_PROTO']) === true && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https')
|
||||
||
|
||||
(isset($_SERVER['HTTP_FRONT_END_HTTPS']) && $_SERVER['HTTP_FRONT_END_HTTPS'] === 'on')
|
||||
(isset($_SERVER['HTTP_FRONT_END_HTTPS']) === true && $_SERVER['HTTP_FRONT_END_HTTPS'] === 'on')
|
||||
||
|
||||
(isset($_SERVER['REQUEST_SCHEME']) && $_SERVER['REQUEST_SCHEME'] === 'https')
|
||||
(isset($_SERVER['REQUEST_SCHEME']) === true && $_SERVER['REQUEST_SCHEME'] === 'https')
|
||||
) {
|
||||
return 'https';
|
||||
}
|
||||
|
||||
return 'http';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the array of uploaded files.
|
||||
*
|
||||
* @return array<string, array<string,UploadedFile>|array<string,array<string,UploadedFile>>> The array of uploaded files.
|
||||
*/
|
||||
public function getUploadedFiles(): array
|
||||
{
|
||||
$files = [];
|
||||
$correctedFilesArray = $this->reArrayFiles($this->files);
|
||||
foreach ($correctedFilesArray as $keyName => $files) {
|
||||
foreach ($files as $file) {
|
||||
$UploadedFile = new UploadedFile(
|
||||
$file['name'],
|
||||
$file['type'],
|
||||
$file['size'],
|
||||
$file['tmp_name'],
|
||||
$file['error']
|
||||
);
|
||||
if (count($files) > 1) {
|
||||
$files[$keyName][] = $UploadedFile;
|
||||
} else {
|
||||
$files[$keyName] = $UploadedFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-arranges the files in the given files collection.
|
||||
*
|
||||
* @param Collection $filesCollection The collection of files to be re-arranged.
|
||||
*
|
||||
* @return array<string, array<int, array<string, mixed>>> The re-arranged files collection.
|
||||
*/
|
||||
protected function reArrayFiles(Collection $filesCollection): array
|
||||
{
|
||||
|
||||
$fileArray = [];
|
||||
foreach ($filesCollection as $fileKeyName => $file) {
|
||||
$isMulti = is_array($file['name']) === true && count($file['name']) > 1;
|
||||
$fileCount = $isMulti === true ? count($file['name']) : 1;
|
||||
$fileKeys = array_keys($file);
|
||||
|
||||
for ($i = 0; $i < $fileCount; $i++) {
|
||||
foreach ($fileKeys as $key) {
|
||||
if ($isMulti === true) {
|
||||
$fileArray[$fileKeyName][$i][$key] = $file[$key][$i];
|
||||
} else {
|
||||
$fileArray[$fileKeyName][$i][$key] = $file[$key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $fileArray;
|
||||
}
|
||||
}
|
||||
|
@ -1,50 +1,41 @@
|
||||
<?php
|
||||
/**
|
||||
* Flight: An extensible micro-framework.
|
||||
*
|
||||
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
|
||||
* @license MIT, http://flightphp.com/license
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace flight\net;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* The Response class represents an HTTP response. The object
|
||||
* contains the response headers, HTTP status code, and response
|
||||
* body.
|
||||
*
|
||||
* @license MIT, http://flightphp.com/license
|
||||
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
|
||||
*/
|
||||
class Response {
|
||||
class Response
|
||||
{
|
||||
/**
|
||||
* @var int HTTP status
|
||||
* Content-Length header.
|
||||
*/
|
||||
protected $status = 200;
|
||||
public bool $content_length = true;
|
||||
|
||||
/**
|
||||
* @var array HTTP headers
|
||||
*/
|
||||
protected $headers = array();
|
||||
|
||||
/**
|
||||
* @var string HTTP response body
|
||||
*/
|
||||
protected $body;
|
||||
|
||||
/**
|
||||
* @var bool HTTP response sent
|
||||
*/
|
||||
protected $sent = false;
|
||||
|
||||
/**
|
||||
* header Content-Length
|
||||
* This is to maintain legacy handling of output buffering
|
||||
* which causes a lot of problems. This will be removed
|
||||
* in v4
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public $content_length = true;
|
||||
public bool $v2_output_buffering = false;
|
||||
|
||||
/**
|
||||
* @var array HTTP status codes
|
||||
* HTTP status codes
|
||||
*
|
||||
* @var array<int, ?string> $codes
|
||||
*/
|
||||
public static $codes = array(
|
||||
public static array $codes = [
|
||||
100 => 'Continue',
|
||||
101 => 'Switching Protocols',
|
||||
102 => 'Processing',
|
||||
@ -112,26 +103,57 @@ class Response {
|
||||
508 => 'Loop Detected',
|
||||
|
||||
510 => 'Not Extended',
|
||||
511 => 'Network Authentication Required'
|
||||
);
|
||||
511 => 'Network Authentication Required',
|
||||
];
|
||||
|
||||
/**
|
||||
* HTTP status
|
||||
*/
|
||||
protected int $status = 200;
|
||||
|
||||
/**
|
||||
* HTTP response headers
|
||||
*
|
||||
* @var array<string,int|string|array<int,string>> $headers
|
||||
*/
|
||||
protected array $headers = [];
|
||||
|
||||
/**
|
||||
* HTTP response body
|
||||
*/
|
||||
protected string $body = '';
|
||||
|
||||
/**
|
||||
* HTTP response sent
|
||||
*/
|
||||
protected bool $sent = false;
|
||||
|
||||
/**
|
||||
* These are callbacks that can process the response body before it's sent
|
||||
*
|
||||
* @var array<int, callable> $responseBodyCallbacks
|
||||
*/
|
||||
protected array $responseBodyCallbacks = [];
|
||||
|
||||
/**
|
||||
* Sets the HTTP status of the response.
|
||||
*
|
||||
* @param int $code HTTP status code.
|
||||
* @return object|int Self reference
|
||||
* @throws \Exception If invalid status code
|
||||
* @param ?int $code HTTP status code.
|
||||
*
|
||||
* @throws Exception If invalid status code
|
||||
*
|
||||
* @return int|$this Self reference
|
||||
*/
|
||||
public function status($code = null) {
|
||||
public function status(?int $code = null)
|
||||
{
|
||||
if ($code === null) {
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
if (array_key_exists($code, self::$codes)) {
|
||||
if (\array_key_exists($code, self::$codes)) {
|
||||
$this->status = $code;
|
||||
}
|
||||
else {
|
||||
throw new \Exception('Invalid status code.');
|
||||
} else {
|
||||
throw new Exception('Invalid status code.');
|
||||
}
|
||||
|
||||
return $this;
|
||||
@ -140,17 +162,18 @@ class Response {
|
||||
/**
|
||||
* Adds a header to the response.
|
||||
*
|
||||
* @param string|array $name Header name or array of names and values
|
||||
* @param string $value Header value
|
||||
* @return object Self reference
|
||||
* @param array<string, int|string>|string $name Header name or array of names and values
|
||||
* @param ?string $value Header value
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function header($name, $value = null) {
|
||||
if (is_array($name)) {
|
||||
public function header($name, ?string $value = null): self
|
||||
{
|
||||
if (\is_array($name)) {
|
||||
foreach ($name as $k => $v) {
|
||||
$this->headers[$k] = $v;
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
$this->headers[$name] = $value;
|
||||
}
|
||||
|
||||
@ -158,34 +181,98 @@ class Response {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the headers from the response
|
||||
* @return array
|
||||
* Gets a single header from the response.
|
||||
*
|
||||
* @param string $name the name of the header
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function headers() {
|
||||
public function getHeader(string $name): ?string
|
||||
{
|
||||
$headers = $this->headers;
|
||||
// lowercase all the header keys
|
||||
$headers = array_change_key_case($headers, CASE_LOWER);
|
||||
return $headers[strtolower($name)] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias of Response->header(). Adds a header to the response.
|
||||
*
|
||||
* @param array<string, int|string>|string $name Header name or array of names and values
|
||||
* @param ?string $value Header value
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setHeader($name, ?string $value): self
|
||||
{
|
||||
return $this->header($name, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the headers from the response.
|
||||
*
|
||||
* @return array<string, int|string|array<int, string>>
|
||||
*/
|
||||
public function headers(): array
|
||||
{
|
||||
return $this->headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for Response->headers(). Returns the headers from the response.
|
||||
*
|
||||
* @return array<string, int|string|array<int, string>>
|
||||
*/
|
||||
public function getHeaders(): array
|
||||
{
|
||||
return $this->headers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes content to the response body.
|
||||
*
|
||||
* @param string $str Response content
|
||||
* @return object Self reference
|
||||
* @param bool $overwrite Overwrite the response body
|
||||
*
|
||||
* @return $this Self reference
|
||||
*/
|
||||
public function write($str) {
|
||||
public function write(string $str, bool $overwrite = false): self
|
||||
{
|
||||
if ($overwrite === true) {
|
||||
$this->clearBody();
|
||||
}
|
||||
|
||||
$this->body .= $str;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the response body.
|
||||
*
|
||||
* @return $this Self reference
|
||||
*/
|
||||
public function clearBody(): self
|
||||
{
|
||||
$this->body = '';
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the response.
|
||||
*
|
||||
* @return object Self reference
|
||||
* @return $this Self reference
|
||||
*/
|
||||
public function clear() {
|
||||
public function clear(): self
|
||||
{
|
||||
$this->status = 200;
|
||||
$this->headers = array();
|
||||
$this->body = '';
|
||||
$this->headers = [];
|
||||
$this->clearBody();
|
||||
|
||||
// This needs to clear the output buffer if it's on
|
||||
if ($this->v2_output_buffering === false && ob_get_length() > 0) {
|
||||
ob_clean();
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -193,39 +280,40 @@ class Response {
|
||||
/**
|
||||
* Sets caching headers for the response.
|
||||
*
|
||||
* @param int|string $expires Expiration time
|
||||
* @return object Self reference
|
||||
* @param int|string|false $expires Expiration time as time() or as strtotime() string value
|
||||
*
|
||||
* @return $this Self reference
|
||||
*/
|
||||
public function cache($expires) {
|
||||
if ($expires === false) {
|
||||
public function cache($expires): self
|
||||
{
|
||||
if ($expires === false || $expires === 0) {
|
||||
$this->headers['Expires'] = 'Mon, 26 Jul 1997 05:00:00 GMT';
|
||||
$this->headers['Cache-Control'] = array(
|
||||
'no-store, no-cache, must-revalidate',
|
||||
'post-check=0, pre-check=0',
|
||||
'max-age=0'
|
||||
);
|
||||
$this->headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0';
|
||||
$this->headers['Pragma'] = 'no-cache';
|
||||
}
|
||||
else {
|
||||
$expires = is_int($expires) ? $expires : strtotime($expires);
|
||||
} else {
|
||||
$expires = \is_int($expires) ? $expires : strtotime($expires);
|
||||
$this->headers['Expires'] = gmdate('D, d M Y H:i:s', $expires) . ' GMT';
|
||||
$this->headers['Cache-Control'] = 'max-age='.($expires - time());
|
||||
if (isset($this->headers['Pragma']) && $this->headers['Pragma'] == 'no-cache'){
|
||||
$this->headers['Cache-Control'] = 'max-age=' . ($expires - time());
|
||||
|
||||
if (isset($this->headers['Pragma']) && $this->headers['Pragma'] === 'no-cache') {
|
||||
unset($this->headers['Pragma']);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends HTTP headers.
|
||||
*
|
||||
* @return object Self reference
|
||||
* @return $this Self reference
|
||||
*/
|
||||
public function sendHeaders() {
|
||||
public function sendHeaders(): self
|
||||
{
|
||||
// Send status code header
|
||||
if (strpos(php_sapi_name(), 'cgi') !== false) {
|
||||
header(
|
||||
if (strpos(\PHP_SAPI, 'cgi') !== false) {
|
||||
// @codeCoverageIgnoreStart
|
||||
$this->setRealHeader(
|
||||
sprintf(
|
||||
'Status: %d %s',
|
||||
$this->status,
|
||||
@ -233,37 +321,37 @@ class Response {
|
||||
),
|
||||
true
|
||||
);
|
||||
}
|
||||
else {
|
||||
header(
|
||||
// @codeCoverageIgnoreEnd
|
||||
} else {
|
||||
$this->setRealHeader(
|
||||
sprintf(
|
||||
'%s %d %s',
|
||||
(isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.1'),
|
||||
$_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.1',
|
||||
$this->status,
|
||||
self::$codes[$this->status]),
|
||||
self::$codes[$this->status]
|
||||
),
|
||||
true,
|
||||
$this->status
|
||||
);
|
||||
}
|
||||
|
||||
// Send other headers
|
||||
foreach ($this->headers as $field => $value) {
|
||||
if (is_array($value)) {
|
||||
foreach ($value as $v) {
|
||||
header($field.': '.$v, false);
|
||||
}
|
||||
}
|
||||
else {
|
||||
header($field.': '.$value);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->content_length) {
|
||||
if ($this->content_length === true) {
|
||||
// Send content length
|
||||
$length = $this->getContentLength();
|
||||
|
||||
if ($length > 0) {
|
||||
header('Content-Length: '.$length);
|
||||
$this->setHeader('Content-Length', (string) $length);
|
||||
}
|
||||
}
|
||||
|
||||
// Send other headers
|
||||
foreach ($this->headers as $field => $value) {
|
||||
if (\is_array($value)) {
|
||||
foreach ($value as $v) {
|
||||
$this->setRealHeader($field . ': ' . $v, false);
|
||||
}
|
||||
} else {
|
||||
$this->setRealHeader($field . ': ' . $value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -271,32 +359,84 @@ class Response {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the content length.
|
||||
* Sets a real header. Mostly used for test mocking.
|
||||
*
|
||||
* @return string Content length
|
||||
* @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 getContentLength() {
|
||||
return extension_loaded('mbstring') ?
|
||||
mb_strlen($this->body, 'latin1') :
|
||||
strlen($this->body);
|
||||
public function setRealHeader(string $header_string, bool $replace = true, int $response_code = 0): self
|
||||
{
|
||||
header($header_string, $replace, $response_code);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether response was sent.
|
||||
* Gets the content length.
|
||||
*/
|
||||
public function sent() {
|
||||
public function getContentLength(): int
|
||||
{
|
||||
return \extension_loaded('mbstring') ?
|
||||
mb_strlen($this->body, 'latin1') :
|
||||
\strlen($this->body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the response body
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBody(): string
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether response body was sent.
|
||||
*/
|
||||
public function sent(): bool
|
||||
{
|
||||
return $this->sent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the response as sent.
|
||||
*/
|
||||
public function markAsSent(): void
|
||||
{
|
||||
$this->sent = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a HTTP response.
|
||||
*/
|
||||
public function send() {
|
||||
if (ob_get_length() > 0) {
|
||||
ob_end_clean();
|
||||
public function send(): void
|
||||
{
|
||||
// legacy way of handling this
|
||||
if ($this->v2_output_buffering === true) {
|
||||
if (ob_get_length() > 0) {
|
||||
ob_end_clean(); // @codeCoverageIgnore
|
||||
}
|
||||
}
|
||||
|
||||
if (!headers_sent()) {
|
||||
// Only for the v3 output buffering.
|
||||
if ($this->v2_output_buffering === false) {
|
||||
$this->processResponseCallbacks();
|
||||
}
|
||||
|
||||
if ($this->headersSent() === false) {
|
||||
// If you haven't set a Cache-Control header, we'll assume you don't want caching
|
||||
if ($this->getHeader('Cache-Control') === null) {
|
||||
$this->cache(false);
|
||||
}
|
||||
|
||||
$this->sendHeaders();
|
||||
}
|
||||
|
||||
@ -304,5 +444,78 @@ class Response {
|
||||
|
||||
$this->sent = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Headers have been sent
|
||||
*
|
||||
* @return bool
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function headersSent(): bool
|
||||
{
|
||||
return headers_sent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a callback to process the response body before it's sent. These are processed in the order
|
||||
* they are added
|
||||
*
|
||||
* @param callable $callback The callback to process the response body
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addResponseBodyCallback(callable $callback): void
|
||||
{
|
||||
$this->responseBodyCallbacks[] = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cycles through the response body callbacks and processes them in order
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function processResponseCallbacks(): void
|
||||
{
|
||||
foreach ($this->responseBodyCallbacks as $callback) {
|
||||
$this->body = $callback($this->body);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads a file.
|
||||
*
|
||||
* @param string $filePath The path to the file to be downloaded.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function downloadFile(string $filePath): void
|
||||
{
|
||||
if (file_exists($filePath) === false) {
|
||||
throw new Exception("$filePath cannot be found.");
|
||||
}
|
||||
|
||||
$fileSize = filesize($filePath);
|
||||
|
||||
$mimeType = mime_content_type($filePath);
|
||||
$mimeType = $mimeType !== false ? $mimeType : 'application/octet-stream';
|
||||
|
||||
$this->send();
|
||||
$this->setRealHeader('Content-Description: File Transfer');
|
||||
$this->setRealHeader('Content-Type: ' . $mimeType);
|
||||
$this->setRealHeader('Content-Disposition: attachment; filename="' . basename($filePath) . '"');
|
||||
$this->setRealHeader('Expires: 0');
|
||||
$this->setRealHeader('Cache-Control: must-revalidate');
|
||||
$this->setRealHeader('Pragma: public');
|
||||
$this->setRealHeader('Content-Length: ' . $fileSize);
|
||||
|
||||
// // Clear the output buffer
|
||||
ob_clean();
|
||||
flush();
|
||||
|
||||
// // Read the file and send it to the output buffer
|
||||
readfile($filePath);
|
||||
if (empty(getenv('PHPUNIT_TEST'))) {
|
||||
exit; // @codeCoverageIgnore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* Flight: An extensible micro-framework.
|
||||
*
|
||||
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
|
||||
* @license MIT, http://flightphp.com/license
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace flight\net;
|
||||
|
||||
@ -12,133 +8,259 @@ namespace flight\net;
|
||||
* The Route class is responsible for routing an HTTP request to
|
||||
* an assigned callback function. The Router tries to match the
|
||||
* requested URL against a series of URL patterns.
|
||||
*
|
||||
* @license MIT, http://flightphp.com/license
|
||||
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
|
||||
*/
|
||||
class Route {
|
||||
class Route
|
||||
{
|
||||
/**
|
||||
* @var string URL pattern
|
||||
* URL pattern
|
||||
*/
|
||||
public $pattern;
|
||||
public string $pattern;
|
||||
|
||||
/**
|
||||
* @var mixed Callback function
|
||||
* Callback function
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $callback;
|
||||
|
||||
/**
|
||||
* @var array HTTP methods
|
||||
* HTTP methods
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
public $methods = array();
|
||||
public array $methods = [];
|
||||
|
||||
/**
|
||||
* @var array Route parameters
|
||||
* Route parameters
|
||||
*
|
||||
* @var array<int, ?string>
|
||||
*/
|
||||
public $params = array();
|
||||
public array $params = [];
|
||||
|
||||
/**
|
||||
* @var string Matching regular expression
|
||||
* Matching regular expression
|
||||
*/
|
||||
public $regex;
|
||||
public ?string $regex = null;
|
||||
|
||||
/**
|
||||
* @var string URL splat content
|
||||
* URL splat content
|
||||
*/
|
||||
public $splat = '';
|
||||
public string $splat = '';
|
||||
|
||||
/**
|
||||
* @var boolean Pass self in callback parameters
|
||||
* Pass self in callback parameters
|
||||
*/
|
||||
public $pass = false;
|
||||
public bool $pass = false;
|
||||
|
||||
/**
|
||||
* The alias is a way to identify the route using a simple name ex: 'login' instead of /admin/login
|
||||
*/
|
||||
public string $alias = '';
|
||||
|
||||
/**
|
||||
* The middleware to be applied to the route
|
||||
*
|
||||
* @var array<int, callable|object|string>
|
||||
*/
|
||||
public array $middleware = [];
|
||||
|
||||
/** Whether the response for this route should be streamed. */
|
||||
public bool $is_streamed = false;
|
||||
|
||||
/**
|
||||
* If this route is streamed, the headers to be sent before the response.
|
||||
*
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
public array $streamed_headers = [];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $pattern URL pattern
|
||||
* @param mixed $callback Callback function
|
||||
* @param array $methods HTTP methods
|
||||
* @param boolean $pass Pass self in callback parameters
|
||||
* @param string $pattern URL pattern
|
||||
* @param callable|string $callback Callback function
|
||||
* @param array<int, string> $methods HTTP methods
|
||||
* @param bool $pass Pass self in callback parameters
|
||||
*/
|
||||
public function __construct($pattern, $callback, $methods, $pass) {
|
||||
public function __construct(string $pattern, $callback, array $methods, bool $pass, string $alias = '')
|
||||
{
|
||||
$this->pattern = $pattern;
|
||||
$this->callback = $callback;
|
||||
$this->methods = $methods;
|
||||
$this->pass = $pass;
|
||||
$this->alias = $alias;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a URL matches the route pattern. Also parses named parameters in the URL.
|
||||
*
|
||||
* @param string $url Requested URL
|
||||
* @param boolean $case_sensitive Case sensitive matching
|
||||
* @return boolean Match status
|
||||
* @param string $url Requested URL (original format, not URL decoded)
|
||||
* @param bool $case_sensitive Case sensitive matching
|
||||
*
|
||||
* @return bool Match status
|
||||
*/
|
||||
public function matchUrl($url, $case_sensitive = false) {
|
||||
public function matchUrl(string $url, bool $case_sensitive = false): bool
|
||||
{
|
||||
// Wildcard or exact match
|
||||
if ($this->pattern === '*' || $this->pattern === $url) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$ids = array();
|
||||
$ids = [];
|
||||
$last_char = substr($this->pattern, -1);
|
||||
|
||||
// Get splat
|
||||
if ($last_char === '*') {
|
||||
$n = 0;
|
||||
$len = strlen($url);
|
||||
$len = \strlen($url);
|
||||
$count = substr_count($this->pattern, '/');
|
||||
|
||||
for ($i = 0; $i < $len; $i++) {
|
||||
if ($url[$i] == '/') $n++;
|
||||
if ($n == $count) break;
|
||||
if ($url[$i] === '/') {
|
||||
++$n;
|
||||
}
|
||||
|
||||
if ($n === $count) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->splat = (string)substr($url, $i+1);
|
||||
$this->splat = urldecode(strval(substr($url, $i + 1)));
|
||||
}
|
||||
|
||||
// Build the regex for matching
|
||||
$regex = str_replace(array(')','/*'), array(')?','(/?|/.*?)'), $this->pattern);
|
||||
$pattern_utf_chars_encoded = preg_replace_callback(
|
||||
'#(\\p{L}+)#u',
|
||||
static function ($matches) {
|
||||
return urlencode($matches[0]);
|
||||
},
|
||||
$this->pattern
|
||||
);
|
||||
$regex = str_replace([')', '/*'], [')?', '(/?|/.*?)'], $pattern_utf_chars_encoded);
|
||||
|
||||
$regex = preg_replace_callback(
|
||||
'#@([\w]+)(:([^/\(\)]*))?#',
|
||||
function($matches) use (&$ids) {
|
||||
static function ($matches) use (&$ids) {
|
||||
$ids[$matches[1]] = null;
|
||||
if (isset($matches[3])) {
|
||||
return '(?P<'.$matches[1].'>'.$matches[3].')';
|
||||
return '(?P<' . $matches[1] . '>' . $matches[3] . ')';
|
||||
}
|
||||
return '(?P<'.$matches[1].'>[^/\?]+)';
|
||||
|
||||
return '(?P<' . $matches[1] . '>[^/\?]+)';
|
||||
},
|
||||
$regex
|
||||
);
|
||||
|
||||
// Fix trailing slash
|
||||
if ($last_char === '/') {
|
||||
$regex .= '?';
|
||||
}
|
||||
// Allow trailing slash
|
||||
else {
|
||||
$regex .= '/?';
|
||||
}
|
||||
$regex .= $last_char === '/' ? '?' : '/?';
|
||||
|
||||
// Attempt to match route and named parameters
|
||||
if (preg_match('#^'.$regex.'(?:\?.*)?$#'.(($case_sensitive) ? '' : 'i'), $url, $matches)) {
|
||||
foreach ($ids as $k => $v) {
|
||||
$this->params[$k] = (array_key_exists($k, $matches)) ? urldecode($matches[$k]) : null;
|
||||
}
|
||||
|
||||
$this->regex = $regex;
|
||||
|
||||
return true;
|
||||
if (!preg_match('#^' . $regex . '(?:\?[\s\S]*)?$#' . (($case_sensitive) ? '' : 'i'), $url, $matches)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
foreach (array_keys($ids) as $k) {
|
||||
$this->params[$k] = (\array_key_exists($k, $matches)) ? urldecode($matches[$k]) : null;
|
||||
}
|
||||
|
||||
$this->regex = $regex;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an HTTP method matches the route methods.
|
||||
*
|
||||
* @param string $method HTTP method
|
||||
*
|
||||
* @return bool Match status
|
||||
*/
|
||||
public function matchMethod($method) {
|
||||
return count(array_intersect(array($method, '*'), $this->methods)) > 0;
|
||||
public function matchMethod(string $method): bool
|
||||
{
|
||||
return \count(array_intersect([$method, '*'], $this->methods)) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an alias matches the route alias.
|
||||
*/
|
||||
public function matchAlias(string $alias): bool
|
||||
{
|
||||
return $this->alias === $alias;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hydrates the route url with the given parameters
|
||||
*
|
||||
* @param array<string, mixed> $params the parameters to pass to the route
|
||||
*/
|
||||
public function hydrateUrl(array $params = []): string
|
||||
{
|
||||
$url = preg_replace_callback("/(?:@([\w]+)(?:\:([^\/]+))?\)*)/i", function ($match) use ($params) {
|
||||
if (isset($match[1]) && isset($params[$match[1]])) {
|
||||
return $params[$match[1]];
|
||||
}
|
||||
}, $this->pattern);
|
||||
|
||||
// catches potential optional parameter
|
||||
$url = str_replace('(/', '/', $url);
|
||||
// trim any trailing slashes
|
||||
if ($url !== '/') {
|
||||
$url = rtrim($url, '/');
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the route alias
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setAlias(string $alias): self
|
||||
{
|
||||
$this->alias = $alias;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the route middleware
|
||||
*
|
||||
* @param array<int, callable|string>|callable|string $middleware
|
||||
*/
|
||||
public function addMiddleware($middleware): self
|
||||
{
|
||||
if (is_array($middleware) === true) {
|
||||
$this->middleware = array_merge($this->middleware, $middleware);
|
||||
} else {
|
||||
$this->middleware[] = $middleware;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the response should be streamed
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function stream(): self
|
||||
{
|
||||
$this->is_streamed = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This will allow the response for this route to be streamed.
|
||||
*
|
||||
* @param array<string, mixed> $headers a key value of headers to set before the stream starts.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function streamWithHeaders(array $headers): self
|
||||
{
|
||||
$this->is_streamed = true;
|
||||
$this->streamed_headers = $headers;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@ -1,87 +1,234 @@
|
||||
<?php
|
||||
/**
|
||||
* Flight: An extensible micro-framework.
|
||||
*
|
||||
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
|
||||
* @license MIT, http://flightphp.com/license
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace flight\net;
|
||||
|
||||
use Exception;
|
||||
use flight\net\Route;
|
||||
|
||||
/**
|
||||
* The Router class is responsible for routing an HTTP request to
|
||||
* an assigned callback function. The Router tries to match the
|
||||
* requested URL against a series of URL patterns.
|
||||
* requested URL against a series of URL patterns.
|
||||
*
|
||||
* @license MIT, http://flightphp.com/license
|
||||
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
|
||||
*/
|
||||
class Router {
|
||||
class Router
|
||||
{
|
||||
/**
|
||||
* Case sensitive matching.
|
||||
*/
|
||||
public bool $case_sensitive = false;
|
||||
|
||||
/**
|
||||
* Mapped routes.
|
||||
*
|
||||
* @var array
|
||||
* @var array<int,Route> $routes
|
||||
*/
|
||||
protected $routes = array();
|
||||
protected array $routes = [];
|
||||
|
||||
/**
|
||||
* The current route that is has been found and executed.
|
||||
*/
|
||||
public ?Route $executedRoute = null;
|
||||
|
||||
/**
|
||||
* Pointer to current route.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $index = 0;
|
||||
protected int $index = 0;
|
||||
|
||||
/**
|
||||
* Case sensitive matching.
|
||||
*
|
||||
* @var boolean
|
||||
* When groups are used, this is mapped against all the routes
|
||||
*/
|
||||
public $case_sensitive = false;
|
||||
protected string $groupPrefix = '';
|
||||
|
||||
/**
|
||||
* Group Middleware
|
||||
*
|
||||
* @var array<int,mixed>
|
||||
*/
|
||||
protected array $groupMiddlewares = [];
|
||||
|
||||
/**
|
||||
* Allowed HTTP methods
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected array $allowedMethods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'];
|
||||
|
||||
/**
|
||||
* Gets mapped routes.
|
||||
*
|
||||
* @return array Array of routes
|
||||
* @return array<int,Route> Array of routes
|
||||
*/
|
||||
public function getRoutes() {
|
||||
public function getRoutes(): array
|
||||
{
|
||||
return $this->routes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all routes in the router.
|
||||
*/
|
||||
public function clear() {
|
||||
$this->routes = array();
|
||||
public function clear(): void
|
||||
{
|
||||
$this->routes = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a URL pattern to a callback function.
|
||||
*
|
||||
* @param string $pattern URL pattern to match
|
||||
* @param callback $callback Callback function
|
||||
* @param boolean $pass_route Pass the matching route object to the callback
|
||||
* @param string $pattern URL pattern to match.
|
||||
* @param callable|string $callback Callback function or string class->method
|
||||
* @param bool $pass_route Pass the matching route object to the callback.
|
||||
* @param string $route_alias Alias for the route.
|
||||
*/
|
||||
public function map($pattern, $callback, $pass_route = false) {
|
||||
$url = $pattern;
|
||||
$methods = array('*');
|
||||
public function map(string $pattern, $callback, bool $pass_route = false, string $route_alias = ''): Route
|
||||
{
|
||||
|
||||
if (strpos($pattern, ' ') !== false) {
|
||||
list($method, $url) = explode(' ', trim($pattern), 2);
|
||||
$url = trim($url);
|
||||
$methods = explode('|', $method);
|
||||
// This means that the route is defined in a group, but the defined route is the base
|
||||
// url path. Note the '' in route()
|
||||
// Ex: Flight::group('/api', function() {
|
||||
// Flight::route('', function() {});
|
||||
// }
|
||||
// Keep the space so that it can execute the below code normally
|
||||
if ($this->groupPrefix !== '') {
|
||||
$url = ltrim($pattern);
|
||||
} else {
|
||||
$url = trim($pattern);
|
||||
}
|
||||
|
||||
$this->routes[] = new Route($url, $callback, $methods, $pass_route);
|
||||
$methods = ['*'];
|
||||
|
||||
if (strpos($url, ' ') !== false) {
|
||||
[$method, $url] = explode(' ', $url, 2);
|
||||
$url = trim($url);
|
||||
$methods = explode('|', $method);
|
||||
|
||||
// Add head requests to get methods, should they come in as a get request
|
||||
if (in_array('GET', $methods, true) === true && in_array('HEAD', $methods, true) === false) {
|
||||
$methods[] = 'HEAD';
|
||||
}
|
||||
}
|
||||
|
||||
// And this finishes it off.
|
||||
if ($this->groupPrefix !== '') {
|
||||
$url = rtrim($this->groupPrefix . $url);
|
||||
}
|
||||
|
||||
$route = new Route($url, $callback, $methods, $pass_route, $route_alias);
|
||||
|
||||
// to handle group middleware
|
||||
foreach ($this->groupMiddlewares as $gm) {
|
||||
$route->addMiddleware($gm);
|
||||
}
|
||||
|
||||
$this->routes[] = $route;
|
||||
|
||||
return $route;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a GET based route
|
||||
*
|
||||
* @param string $pattern URL pattern to match
|
||||
* @param callable|string $callback Callback function or string class->method
|
||||
* @param bool $pass_route Pass the matching route object to the callback
|
||||
* @param string $alias Alias for the route
|
||||
*/
|
||||
public function get(string $pattern, $callback, bool $pass_route = false, string $alias = ''): Route
|
||||
{
|
||||
return $this->map('GET ' . $pattern, $callback, $pass_route, $alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a POST based route
|
||||
*
|
||||
* @param string $pattern URL pattern to match
|
||||
* @param callable|string $callback Callback function or string class->method
|
||||
* @param bool $pass_route Pass the matching route object to the callback
|
||||
* @param string $alias Alias for the route
|
||||
*/
|
||||
public function post(string $pattern, $callback, bool $pass_route = false, string $alias = ''): Route
|
||||
{
|
||||
return $this->map('POST ' . $pattern, $callback, $pass_route, $alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a PUT based route
|
||||
*
|
||||
* @param string $pattern URL pattern to match
|
||||
* @param callable|string $callback Callback function or string class->method
|
||||
* @param bool $pass_route Pass the matching route object to the callback
|
||||
* @param string $alias Alias for the route
|
||||
*/
|
||||
public function put(string $pattern, $callback, bool $pass_route = false, string $alias = ''): Route
|
||||
{
|
||||
return $this->map('PUT ' . $pattern, $callback, $pass_route, $alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a PATCH based route
|
||||
*
|
||||
* @param string $pattern URL pattern to match
|
||||
* @param callable|string $callback Callback function or string class->method
|
||||
* @param bool $pass_route Pass the matching route object to the callback
|
||||
* @param string $alias Alias for the route
|
||||
*/
|
||||
public function patch(string $pattern, $callback, bool $pass_route = false, string $alias = ''): Route
|
||||
{
|
||||
return $this->map('PATCH ' . $pattern, $callback, $pass_route, $alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a DELETE based route
|
||||
*
|
||||
* @param string $pattern URL pattern to match
|
||||
* @param callable|string $callback Callback function or string class->method
|
||||
* @param bool $pass_route Pass the matching route object to the callback
|
||||
* @param string $alias Alias for the route
|
||||
*/
|
||||
public function delete(string $pattern, $callback, bool $pass_route = false, string $alias = ''): Route
|
||||
{
|
||||
return $this->map('DELETE ' . $pattern, $callback, $pass_route, $alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Group together a set of routes
|
||||
*
|
||||
* @param string $groupPrefix group URL prefix (such as /api/v1)
|
||||
* @param callable $callback The necessary calling that holds the Router class
|
||||
* @param array<int, callable|object> $groupMiddlewares
|
||||
* The middlewares to be applied to the group. Example: `[$middleware1, $middleware2]`
|
||||
*/
|
||||
public function group(string $groupPrefix, callable $callback, array $groupMiddlewares = []): void
|
||||
{
|
||||
$oldGroupPrefix = $this->groupPrefix;
|
||||
$oldGroupMiddlewares = $this->groupMiddlewares;
|
||||
$this->groupPrefix .= $groupPrefix;
|
||||
$this->groupMiddlewares = array_merge($this->groupMiddlewares, $groupMiddlewares);
|
||||
$callback($this);
|
||||
$this->groupPrefix = $oldGroupPrefix;
|
||||
$this->groupMiddlewares = $oldGroupMiddlewares;
|
||||
}
|
||||
|
||||
/**
|
||||
* Routes the current request.
|
||||
*
|
||||
* @param Request $request Request object
|
||||
* @return Route|bool Matching route or false if no match
|
||||
* @return false|Route Matching route or false if no match
|
||||
*/
|
||||
public function route(Request $request) {
|
||||
$url_decoded = urldecode( $request->url );
|
||||
public function route(Request $request)
|
||||
{
|
||||
while ($route = $this->current()) {
|
||||
if ($route !== false && $route->matchMethod($request->method) && $route->matchUrl($url_decoded, $this->case_sensitive)) {
|
||||
$urlMatches = $route->matchUrl($request->url, $this->case_sensitive);
|
||||
$methodMatches = $route->matchMethod($request->method);
|
||||
if ($urlMatches === true && $methodMatches === true) {
|
||||
$this->executedRoute = $route;
|
||||
return $route;
|
||||
// capture the route but don't execute it. We'll use this in Engine->start() to throw a 405
|
||||
} elseif ($urlMatches === true && $methodMatches === false) {
|
||||
$this->executedRoute = $route;
|
||||
}
|
||||
$this->next();
|
||||
}
|
||||
@ -89,29 +236,158 @@ class Router {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the URL for a given route alias
|
||||
*
|
||||
* @param string $alias the alias to match
|
||||
* @param array<string,mixed> $params the parameters to pass to the route
|
||||
*/
|
||||
public function getUrlByAlias(string $alias, array $params = []): string
|
||||
{
|
||||
$potential_aliases = [];
|
||||
foreach ($this->routes as $route) {
|
||||
$potential_aliases[] = $route->alias;
|
||||
if ($route->matchAlias($alias)) {
|
||||
// This will make it so the params that already
|
||||
// exist in the url will be passed in.
|
||||
if (!empty($this->executedRoute->params)) {
|
||||
$params = $params + $this->executedRoute->params;
|
||||
}
|
||||
return $route->hydrateUrl($params);
|
||||
}
|
||||
}
|
||||
|
||||
// use a levenshtein to find the closest match and make a recommendation
|
||||
$closest_match = '';
|
||||
$closest_match_distance = 0;
|
||||
foreach ($potential_aliases as $potential_alias) {
|
||||
$levenshtein_distance = levenshtein($alias, $potential_alias);
|
||||
if ($levenshtein_distance > $closest_match_distance) {
|
||||
$closest_match = $potential_alias;
|
||||
$closest_match_distance = $levenshtein_distance;
|
||||
}
|
||||
}
|
||||
|
||||
$exception_message = 'No route found with alias: \'' . $alias . '\'.';
|
||||
if ($closest_match !== '') {
|
||||
$exception_message .= ' Did you mean \'' . $closest_match . '\'?';
|
||||
}
|
||||
|
||||
throw new Exception($exception_message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a resource controller customizing the methods names mapping.
|
||||
*
|
||||
* @param class-string $controllerClass
|
||||
* @param array<string, string|array<string>> $options
|
||||
*/
|
||||
public function mapResource(
|
||||
string $pattern,
|
||||
string $controllerClass,
|
||||
array $options = []
|
||||
): void {
|
||||
|
||||
$defaultMapping = [
|
||||
'index' => 'GET ',
|
||||
'create' => 'GET /create',
|
||||
'store' => 'POST ',
|
||||
'show' => 'GET /@id',
|
||||
'edit' => 'GET /@id/edit',
|
||||
'update' => 'PUT /@id',
|
||||
'destroy' => 'DELETE /@id'
|
||||
];
|
||||
|
||||
// Create a custom alias base
|
||||
$aliasBase = trim(basename($pattern), '/');
|
||||
if (isset($options['alias_base']) === true) {
|
||||
$aliasBase = $options['alias_base'];
|
||||
}
|
||||
|
||||
// Only use these controller methods
|
||||
if (isset($options['only']) === true) {
|
||||
$only = $options['only'];
|
||||
$defaultMapping = array_filter($defaultMapping, function ($key) use ($only) {
|
||||
return in_array($key, $only, true) === true;
|
||||
}, ARRAY_FILTER_USE_KEY);
|
||||
|
||||
// Exclude these controller methods
|
||||
} elseif (isset($options['except']) === true) {
|
||||
$except = $options['except'];
|
||||
$defaultMapping = array_filter($defaultMapping, function ($key) use ($except) {
|
||||
return in_array($key, $except, true) === false;
|
||||
}, ARRAY_FILTER_USE_KEY);
|
||||
}
|
||||
|
||||
// Add group middleware
|
||||
$middleware = [];
|
||||
if (isset($options['middleware']) === true) {
|
||||
$middleware = $options['middleware'];
|
||||
}
|
||||
|
||||
$this->group(
|
||||
$pattern,
|
||||
function (Router $router) use ($controllerClass, $defaultMapping, $aliasBase): void {
|
||||
foreach ($defaultMapping as $controllerMethod => $methodPattern) {
|
||||
$router->map(
|
||||
$methodPattern,
|
||||
[ $controllerClass, $controllerMethod ]
|
||||
)->setAlias($aliasBase . '.' . $controllerMethod);
|
||||
}
|
||||
},
|
||||
$middleware
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewinds the current route index.
|
||||
*/
|
||||
public function rewind(): void
|
||||
{
|
||||
$this->index = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if more routes can be iterated.
|
||||
*
|
||||
* @return bool More routes
|
||||
*/
|
||||
public function valid(): bool
|
||||
{
|
||||
return isset($this->routes[$this->index]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current route.
|
||||
*
|
||||
* @return Route
|
||||
* @return false|Route
|
||||
*/
|
||||
public function current() {
|
||||
return isset($this->routes[$this->index]) ? $this->routes[$this->index] : false;
|
||||
public function current()
|
||||
{
|
||||
return $this->routes[$this->index] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the previous route.
|
||||
*/
|
||||
public function previous(): void
|
||||
{
|
||||
--$this->index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next route.
|
||||
*
|
||||
* @return Route
|
||||
*/
|
||||
public function next() {
|
||||
$this->index++;
|
||||
public function next(): void
|
||||
{
|
||||
++$this->index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset to the first route.
|
||||
*/
|
||||
public function reset() {
|
||||
$this->index = 0;
|
||||
public function reset(): void
|
||||
{
|
||||
$this->rewind();
|
||||
}
|
||||
}
|
||||
|
||||
|
157
libs/flight/net/UploadedFile.php
Normal file
157
libs/flight/net/UploadedFile.php
Normal file
@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace flight\net;
|
||||
|
||||
use Exception;
|
||||
|
||||
class UploadedFile
|
||||
{
|
||||
/**
|
||||
* @var string $name The name of the uploaded file.
|
||||
*/
|
||||
private string $name;
|
||||
|
||||
/**
|
||||
* @var string $mimeType The MIME type of the uploaded file.
|
||||
*/
|
||||
private string $mimeType;
|
||||
|
||||
/**
|
||||
* @var int $size The size of the uploaded file in bytes.
|
||||
*/
|
||||
private int $size;
|
||||
|
||||
/**
|
||||
* @var string $tmpName The temporary name of the uploaded file.
|
||||
*/
|
||||
private string $tmpName;
|
||||
|
||||
/**
|
||||
* @var int $error The error code associated with the uploaded file.
|
||||
*/
|
||||
private int $error;
|
||||
|
||||
/**
|
||||
* Constructs a new UploadedFile object.
|
||||
*
|
||||
* @param string $name The name of the uploaded file.
|
||||
* @param string $mimeType The MIME type of the uploaded file.
|
||||
* @param int $size The size of the uploaded file in bytes.
|
||||
* @param string $tmpName The temporary name of the uploaded file.
|
||||
* @param int $error The error code associated with the uploaded file.
|
||||
*/
|
||||
public function __construct(string $name, string $mimeType, int $size, string $tmpName, int $error)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->mimeType = $mimeType;
|
||||
$this->size = $size;
|
||||
$this->tmpName = $tmpName;
|
||||
$this->error = $error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the client-side filename of the uploaded file.
|
||||
*
|
||||
* @return string The client-side filename.
|
||||
*/
|
||||
public function getClientFilename(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the media type of the uploaded file as provided by the client.
|
||||
*
|
||||
* @return string The media type of the uploaded file.
|
||||
*/
|
||||
public function getClientMediaType(): string
|
||||
{
|
||||
return $this->mimeType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of the uploaded file.
|
||||
*
|
||||
* @return int The size of the uploaded file.
|
||||
*/
|
||||
public function getSize(): int
|
||||
{
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the temporary name of the uploaded file.
|
||||
*
|
||||
* @return string The temporary name of the uploaded file.
|
||||
*/
|
||||
public function getTempName(): string
|
||||
{
|
||||
return $this->tmpName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error code associated with the uploaded file.
|
||||
*
|
||||
* @return int The error code.
|
||||
*/
|
||||
public function getError(): int
|
||||
{
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the uploaded file to the specified target path.
|
||||
*
|
||||
* @param string $targetPath The path to move the file to.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function moveTo(string $targetPath): void
|
||||
{
|
||||
if ($this->error !== UPLOAD_ERR_OK) {
|
||||
throw new Exception($this->getUploadErrorMessage($this->error));
|
||||
}
|
||||
|
||||
$isUploadedFile = is_uploaded_file($this->tmpName) === true;
|
||||
if (
|
||||
$isUploadedFile === true
|
||||
&&
|
||||
move_uploaded_file($this->tmpName, $targetPath) === false
|
||||
) {
|
||||
throw new Exception('Cannot move uploaded file'); // @codeCoverageIgnore
|
||||
} elseif ($isUploadedFile === false && getenv('PHPUNIT_TEST')) {
|
||||
rename($this->tmpName, $targetPath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the error message for a given upload error code.
|
||||
*
|
||||
* @param int $error The upload error code.
|
||||
*
|
||||
* @return string The error message.
|
||||
*/
|
||||
protected function getUploadErrorMessage(int $error): string
|
||||
{
|
||||
switch ($error) {
|
||||
case UPLOAD_ERR_INI_SIZE:
|
||||
return 'The uploaded file exceeds the upload_max_filesize directive in php.ini.';
|
||||
case UPLOAD_ERR_FORM_SIZE:
|
||||
return 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.';
|
||||
case UPLOAD_ERR_PARTIAL:
|
||||
return 'The uploaded file was only partially uploaded.';
|
||||
case UPLOAD_ERR_NO_FILE:
|
||||
return 'No file was uploaded.';
|
||||
case UPLOAD_ERR_NO_TMP_DIR:
|
||||
return 'Missing a temporary folder.';
|
||||
case UPLOAD_ERR_CANT_WRITE:
|
||||
return 'Failed to write file to disk.';
|
||||
case UPLOAD_ERR_EXTENSION:
|
||||
return 'A PHP extension stopped the file upload.';
|
||||
default:
|
||||
return 'An unknown error occurred. Error code: ' . $error;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* Flight: An extensible micro-framework.
|
||||
*
|
||||
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
|
||||
* @license MIT, http://flightphp.com/license
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace flight\template;
|
||||
|
||||
@ -12,115 +8,123 @@ namespace flight\template;
|
||||
* The View class represents output to be displayed. It provides
|
||||
* methods for managing view data and inserts the data into
|
||||
* view templates upon rendering.
|
||||
*
|
||||
* @license MIT, http://flightphp.com/license
|
||||
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
|
||||
*/
|
||||
class View {
|
||||
/**
|
||||
* Location of view templates.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $path;
|
||||
class View
|
||||
{
|
||||
/** Location of view templates. */
|
||||
public string $path;
|
||||
|
||||
/**
|
||||
* File extension.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $extension = '.php';
|
||||
/** File extension. */
|
||||
public string $extension = '.php';
|
||||
|
||||
public bool $preserveVars = true;
|
||||
|
||||
/**
|
||||
* View variables.
|
||||
*
|
||||
* @var array
|
||||
* @var array<string, mixed> $vars
|
||||
*/
|
||||
protected $vars = array();
|
||||
protected array $vars = [];
|
||||
|
||||
/**
|
||||
* Template file.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $template;
|
||||
/** Template file. */
|
||||
private string $template;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $path Path to templates directory
|
||||
*/
|
||||
public function __construct($path = '.') {
|
||||
public function __construct(string $path = '.')
|
||||
{
|
||||
$this->path = $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a template variable.
|
||||
*
|
||||
* @param string $key Key
|
||||
* @return mixed Value
|
||||
* @return mixed Variable value or `null` if doesn't exists
|
||||
*/
|
||||
public function get($key) {
|
||||
return isset($this->vars[$key]) ? $this->vars[$key] : null;
|
||||
public function get(string $key)
|
||||
{
|
||||
return $this->vars[$key] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a template variable.
|
||||
*
|
||||
* @param mixed $key Key
|
||||
* @param string $value Value
|
||||
* @param string|iterable<string, mixed> $key
|
||||
* @param mixed $value Value
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function set($key, $value = null) {
|
||||
if (is_array($key) || is_object($key)) {
|
||||
public function set($key, $value = null): self
|
||||
{
|
||||
if (\is_iterable($key)) {
|
||||
foreach ($key as $k => $v) {
|
||||
$this->vars[$k] = $v;
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
$this->vars[$key] = $value;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a template variable is set.
|
||||
*
|
||||
* @param string $key Key
|
||||
* @return boolean If key exists
|
||||
* @return bool If key exists
|
||||
*/
|
||||
public function has($key) {
|
||||
public function has(string $key): bool
|
||||
{
|
||||
return isset($this->vars[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsets a template variable. If no key is passed in, clear all variables.
|
||||
*
|
||||
* @param string $key Key
|
||||
* @return $this
|
||||
*/
|
||||
public function clear($key = null) {
|
||||
if (is_null($key)) {
|
||||
$this->vars = array();
|
||||
}
|
||||
else {
|
||||
public function clear(?string $key = null): self
|
||||
{
|
||||
if ($key === null) {
|
||||
$this->vars = [];
|
||||
} else {
|
||||
unset($this->vars[$key]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a template.
|
||||
*
|
||||
* @param string $file Template file
|
||||
* @param array $data Template data
|
||||
* @param ?array<string, mixed> $data Template data
|
||||
*
|
||||
* @throws \Exception If template not found
|
||||
*/
|
||||
public function render($file, $data = null) {
|
||||
public function render(string $file, ?array $data = null): void
|
||||
{
|
||||
$this->template = $this->getTemplate($file);
|
||||
|
||||
if (!file_exists($this->template)) {
|
||||
throw new \Exception("Template file not found: {$this->template}.");
|
||||
if (!\file_exists($this->template)) {
|
||||
$normalized_path = self::normalizePath($this->template);
|
||||
throw new \Exception("Template file not found: {$normalized_path}.");
|
||||
}
|
||||
|
||||
if (is_array($data)) {
|
||||
$this->vars = array_merge($this->vars, $data);
|
||||
}
|
||||
\extract($this->vars);
|
||||
|
||||
extract($this->vars);
|
||||
if (\is_array($data) === true) {
|
||||
\extract($data);
|
||||
|
||||
if ($this->preserveVars === true) {
|
||||
$this->vars = \array_merge($this->vars, $data);
|
||||
}
|
||||
}
|
||||
|
||||
include $this->template;
|
||||
}
|
||||
@ -129,56 +133,71 @@ class View {
|
||||
* Gets the output of a template.
|
||||
*
|
||||
* @param string $file Template file
|
||||
* @param array $data Template data
|
||||
* @param ?array<string, mixed> $data Template data
|
||||
*
|
||||
* @return string Output of template
|
||||
*/
|
||||
public function fetch($file, $data = null) {
|
||||
ob_start();
|
||||
public function fetch(string $file, ?array $data = null): string
|
||||
{
|
||||
\ob_start();
|
||||
|
||||
$this->render($file, $data);
|
||||
$output = ob_get_clean();
|
||||
|
||||
return $output;
|
||||
return \ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a template file exists.
|
||||
*
|
||||
* @param string $file Template file
|
||||
*
|
||||
* @return bool Template file exists
|
||||
*/
|
||||
public function exists($file) {
|
||||
return file_exists($this->getTemplate($file));
|
||||
public function exists(string $file): bool
|
||||
{
|
||||
return \file_exists($this->getTemplate($file));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the full path to a template file.
|
||||
*
|
||||
* @param string $file Template file
|
||||
*
|
||||
* @return string Template file location
|
||||
*/
|
||||
public function getTemplate($file) {
|
||||
public function getTemplate(string $file): string
|
||||
{
|
||||
$ext = $this->extension;
|
||||
|
||||
if (!empty($ext) && (substr($file, -1 * strlen($ext)) != $ext)) {
|
||||
if (!empty($ext) && (\substr($file, -1 * \strlen($ext)) != $ext)) {
|
||||
$file .= $ext;
|
||||
}
|
||||
|
||||
if ((substr($file, 0, 1) == '/')) {
|
||||
$is_windows = \strtoupper(\substr(PHP_OS, 0, 3)) === 'WIN';
|
||||
|
||||
if ((\substr($file, 0, 1) === '/') || ($is_windows && \substr($file, 1, 1) === ':')) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
return $this->path.'/'.$file;
|
||||
|
||||
return $this->path . DIRECTORY_SEPARATOR . $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays escaped output.
|
||||
*
|
||||
* @param string $str String to escape
|
||||
*
|
||||
* @return string Escaped string
|
||||
*/
|
||||
public function e($str) {
|
||||
echo htmlentities($str);
|
||||
public function e(string $str): string
|
||||
{
|
||||
$value = \htmlentities($str);
|
||||
echo $value;
|
||||
return $value;
|
||||
}
|
||||
|
||||
protected static function normalizePath(string $path, string $separator = DIRECTORY_SEPARATOR): string
|
||||
{
|
||||
return \str_replace(['\\', '/'], $separator, $path);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,74 +1,75 @@
|
||||
<?php
|
||||
/**
|
||||
* Flight: An extensible micro-framework.
|
||||
*
|
||||
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
|
||||
* @license MIT, http://flightphp.com/license
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace flight\util;
|
||||
|
||||
if (!interface_exists('JsonSerializable')) {
|
||||
require_once dirname(__FILE__) . '/LegacyJsonSerializable.php';
|
||||
}
|
||||
use ArrayAccess;
|
||||
use Countable;
|
||||
use Iterator;
|
||||
use JsonSerializable;
|
||||
|
||||
/**
|
||||
* The Collection class allows you to access a set of data
|
||||
* using both array and object notation.
|
||||
*
|
||||
* @license MIT, http://flightphp.com/license
|
||||
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
|
||||
* @implements ArrayAccess<string, mixed>
|
||||
* @implements Iterator<string, mixed>
|
||||
*/
|
||||
class Collection implements \ArrayAccess, \Iterator, \Countable, \JsonSerializable {
|
||||
class Collection implements ArrayAccess, Iterator, Countable, JsonSerializable
|
||||
{
|
||||
/**
|
||||
* Collection data.
|
||||
*
|
||||
* @var array
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
private $data;
|
||||
private array $data;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $data Initial data
|
||||
* @param array<string, mixed> $data Initial data
|
||||
*/
|
||||
public function __construct(array $data = array()) {
|
||||
public function __construct(array $data = [])
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an item.
|
||||
*
|
||||
* @param string $key Key
|
||||
* @return mixed Value
|
||||
* @return mixed Value if `$key` exists in collection data, otherwise returns `NULL`
|
||||
*/
|
||||
public function __get($key) {
|
||||
return isset($this->data[$key]) ? $this->data[$key] : null;
|
||||
public function __get(string $key)
|
||||
{
|
||||
return $this->data[$key] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an item.
|
||||
*
|
||||
* @param string $key Key
|
||||
* @param mixed $value Value
|
||||
* @param mixed $value Value
|
||||
*/
|
||||
public function __set($key, $value) {
|
||||
public function __set(string $key, $value): void
|
||||
{
|
||||
$this->data[$key] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an item exists.
|
||||
*
|
||||
* @param string $key Key
|
||||
* @return bool Item status
|
||||
*/
|
||||
public function __isset($key) {
|
||||
public function __isset(string $key): bool
|
||||
{
|
||||
return isset($this->data[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an item.
|
||||
*
|
||||
* @param string $key Key
|
||||
*/
|
||||
public function __unset($key) {
|
||||
public function __unset(string $key): void
|
||||
{
|
||||
unset($this->data[$key]);
|
||||
}
|
||||
|
||||
@ -76,23 +77,27 @@ class Collection implements \ArrayAccess, \Iterator, \Countable, \JsonSerializab
|
||||
* Gets an item at the offset.
|
||||
*
|
||||
* @param string $offset Offset
|
||||
*
|
||||
* @return mixed Value
|
||||
*/
|
||||
public function offsetGet($offset) {
|
||||
return isset($this->data[$offset]) ? $this->data[$offset] : null;
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
return $this->data[$offset] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an item at the offset.
|
||||
*
|
||||
* @param string $offset Offset
|
||||
* @param mixed $value Value
|
||||
* @param ?string $offset Offset
|
||||
* @param mixed $value Value
|
||||
*/
|
||||
public function offsetSet($offset, $value) {
|
||||
if (is_null($offset)) {
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetSet($offset, $value): void
|
||||
{
|
||||
if ($offset === null) {
|
||||
$this->data[] = $value;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
$this->data[$offset] = $value;
|
||||
}
|
||||
}
|
||||
@ -100,117 +105,119 @@ class Collection implements \ArrayAccess, \Iterator, \Countable, \JsonSerializab
|
||||
/**
|
||||
* Checks if an item exists at the offset.
|
||||
*
|
||||
* @param string $offset Offset
|
||||
* @return bool Item status
|
||||
* @param string $offset
|
||||
*/
|
||||
public function offsetExists($offset) {
|
||||
public function offsetExists($offset): bool
|
||||
{
|
||||
return isset($this->data[$offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an item at the offset.
|
||||
*
|
||||
* @param string $offset Offset
|
||||
* @param string $offset
|
||||
*/
|
||||
public function offsetUnset($offset) {
|
||||
public function offsetUnset($offset): void
|
||||
{
|
||||
unset($this->data[$offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the collection.
|
||||
*/
|
||||
public function rewind() {
|
||||
public function rewind(): void
|
||||
{
|
||||
reset($this->data);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets current collection item.
|
||||
*
|
||||
* @return mixed Value
|
||||
*/
|
||||
public function current() {
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function current()
|
||||
{
|
||||
return current($this->data);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets current collection key.
|
||||
*
|
||||
* @return mixed Value
|
||||
*/
|
||||
public function key() {
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function key()
|
||||
{
|
||||
return key($this->data);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the next collection value.
|
||||
*
|
||||
* @return mixed Value
|
||||
*/
|
||||
public function next()
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function next(): void
|
||||
{
|
||||
return next($this->data);
|
||||
next($this->data);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the current collection key is valid.
|
||||
*
|
||||
* @return bool Key status
|
||||
*/
|
||||
public function valid()
|
||||
*/
|
||||
public function valid(): bool
|
||||
{
|
||||
$key = key($this->data);
|
||||
return ($key !== NULL && $key !== FALSE);
|
||||
return key($this->data) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the size of the collection.
|
||||
*
|
||||
* @return int Collection size
|
||||
*/
|
||||
public function count() {
|
||||
return sizeof($this->data);
|
||||
public function count(): int
|
||||
{
|
||||
return \count($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the item keys.
|
||||
*
|
||||
* @return array Collection keys
|
||||
* @return array<int, string> Collection keys
|
||||
*/
|
||||
public function keys() {
|
||||
public function keys(): array
|
||||
{
|
||||
return array_keys($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the collection data.
|
||||
*
|
||||
* @return array Collection data
|
||||
* @return array<string, mixed> Collection data
|
||||
*/
|
||||
public function getData() {
|
||||
public function getData(): array
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the collection data.
|
||||
*
|
||||
* @param array $data New collection data
|
||||
* @param array<string, mixed> $data New collection data
|
||||
*/
|
||||
public function setData(array $data) {
|
||||
public function setData(array $data): void
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the collection data which can be serialized to JSON
|
||||
*
|
||||
* @return array Collection data which can be serialized by <b>json_encode</b>
|
||||
*/
|
||||
public function jsonSerialize() {
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all items from the collection.
|
||||
*/
|
||||
public function clear() {
|
||||
$this->data = array();
|
||||
public function clear(): void
|
||||
{
|
||||
$this->data = [];
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Flight: An extensible micro-framework.
|
||||
*
|
||||
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
|
||||
* @license MIT, http://flightphp.com/license
|
||||
*/
|
||||
|
||||
interface JsonSerializable {
|
||||
public function jsonSerialize();
|
||||
}
|
8
libs/flight/util/ReturnTypeWillChange.php
Normal file
8
libs/flight/util/ReturnTypeWillChange.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
// This file is only here so that the PHP8 attribute for doesn't throw an error in files
|
||||
class ReturnTypeWillChange
|
||||
{
|
||||
}
|
Reference in New Issue
Block a user