Flight-一个简洁快速的php框架

Flight是一款非常小巧的框架,相比传统的框架,飞行?飞机?(作者这名字有意思,滑稽)框架就像它的名字一样,同时重写功能是这个框架的特色,通过重写部分可以很好的自定义自己需要的功能,达到快速上手的目的,本文翻译值Flight在github上的介绍,如果有错误的地方,可以在下面留言。

What is Flight?

Flight是一个快速,简单,可扩展的PHP框架。帮助你快速轻松地构建RESTful Web应用程序。

1
2
3
4
5
6
7
require 'flight/Flight.php';
Flight::route('/', function(){
echo 'hello world!';
});
Flight::start();

官方手册(英文)

要求

PHP 5.3或者以上

协议

Flight使用MIT协议开源。

安装

1. 下载下面文件.

如果你使用Composer,可以执行下面命令安装:

1
composer require mikecao/flight

或者点击下载,然后解压到你的程序目录.

2. 服务器配置.

使用 Apache, 在.htaccess文件里添加下面内容:

1
2
3
4
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]

注意: 如果你在子目录中安装Flight,请在 RewriteEngine On 后面添加 RewriteBase /subdir/

使用 Nginx, 添加下面内容到配置文件中:

1
2
3
4
5
server {
location / {
try_files $uri $uri/ /index.php;
}
}

3. 新建index.php

首先,加载Flight。

1
require 'flight/Flight.php';

如果你使用Composer, 则使用自动加载器。

1
require 'vendor/autoload.php';

定义一个路由,并分配路由处理函数。

1
2
3
Flight::route('/', function(){
echo 'hello world!';
});

最后,开始使用Flight

1
Flight::start();

路由

当一个URL规则被匹配到时,便会调取回调函数。

1
2
3
Flight::route('/', function(){
echo 'hello world!';
});

回调可以是任何可调用的对象,可以是一个常规函数:

1
2
3
4
5
function hello(){
echo 'hello world!';
}
Flight::route('/', 'hello');

或者是一个类:

1
2
3
4
5
6
7
class Greeting {
public static function hello() {
echo 'hello world!';
}
}
Flight::route('/', array('Greeting', 'hello'));

或者一个对象的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Greeting
{
public function __construct() {
$this->name = 'John Doe';
}
public function hello() {
echo "Hello, {$this->name}!";
}
}
$greeting = new Greeting();
Flight::route('/', array($greeting, 'hello'));

路由根据注册的先后顺序匹配,先匹配到的会先被调用。

请求方式

默认情况下,路由模式与所有请求方式匹配,可以通过在URL之前放置标识符来响应特定请求方式。

1
2
3
4
5
6
7
Flight::route('GET /', function(){
echo 'I received a GET request.';
});
Flight::route('POST /', function(){
echo 'I received a POST request.';
});

你也可以使用|分隔符将不同的请求方式映射到同一个回调上:

1
2
3
Flight::route('GET|POST /', function(){
echo 'I received either a GET or a POST request.';
});

正则表达式

可以在路由上使用正则表达式来匹配:

1
2
3
Flight::route('/user/[0-9]+', function(){
// This will match /user/1234
});

命名参数

您可以在路由中指定命名参数,这些参数将被传递到你的回调函数(译注:注意顺序)。

1
2
3
Flight::route('/@name/@id', function($name, $id){
echo "hello, $name ($id)!";
});

你也可以在命名参数后面添加正则表达式,并用:分割开:

1
2
3
4
Flight::route('/@name/@id:[0-9]{3}', function($name, $id){
// This will match /bob/123
// But will not match /bob/12345
});

可选参数

你也可以使用括号包裹起来,表示可选参数:

1
2
3
4
5
6
7
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
});

没有匹配到的参数值为NULL

通配符

上面的例子只能匹配到单个网段,如果你想匹配到多个网段,你可以使用*通配符:

1
2
3
Flight::route('/blog/*', function(){
// This will match /blog/2000/02/01
});

使用一个回调函数响应所有请求:

1
2
3
Flight::route('*', function(){
// Do something
});

冒泡

你可以在回调函数返回true来执行下一个匹配到的路由:

1
2
3
4
5
6
7
8
9
10
11
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方法的第三个参数为true,这样路由对象会作为 最后一个参数 传入回调函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
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);

扩展

Flight被设计成一个可扩展的框架。框架提供了一组默认方法和组件,但它允许你添加自己的方法和类,甚至覆盖原有方法和类。

映射方法

使用map方法映射自定义方法:

1
2
3
4
5
6
7
// Map your method
Flight::map('hello', function($name){
echo "hello $name!";
});
// 调用自定义方法
Flight::hello('Bob');

注册类

使用register方法注册自定义类:

1
2
3
4
5
// Register your class
Flight::register('user', 'User');
// Get an instance of your class
$user = Flight::user();

register方法允许你在注册的时候添加要传递的参数,注册的类初始化时将传入设定的参数。
这是一个加载数据库连接的例子:

1
2
3
4
5
6
7
8
9
// 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();

register方法允许你添加一个回调函数,回调函数将在注册类实例化后立即执行:

1
2
3
4
// 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);
});

默认情况下,每次加载类,都将得到一个共享的实例。要获得一个类的新实例,只需传入false作为参数:

1
2
3
4
5
// Shared instance of the class
$shared = Flight::db();
// New instance of the class
$new = Flight::db(false);

注意:映射方法优先于注册类。 如果你使用相同的名称声明,则只会调用映射方法。

覆盖

Flight允许你覆盖其默认功能以满足需要,而无需修改任何代码。

栗子:当Flight没有匹配到一个URL时,它将会调用notFound方法,并返回一个HTTP 404代码,可以通过map方法重写原来的方法:

1
2
3
4
Flight::map('notFound', function(){
// Display custom 404 page
include 'errors/404.html';
});

Flight允许你替换框架的核心组件。
栗子:使用你自定义类替换默认的路由类:

1
2
3
4
5
// Register your custom class
Flight::register('router', 'MyRouter');
// When Flight loads the Router instance, it will load your class
$myrouter = Flight::router();

框架自带类似mapregister的方法不能被重写,你就不要挣扎了。

过滤

Flight允许在调用之前和调用之后执行过滤器。没有预先定义的钩子你需要记住(原文:There are no predefined hooks you need to memorize.)。你可以过滤任何默认框架方法以及你映射的任何自定义方法。

一个过滤函数应该长这样子:

1
2
3
function(&$params, &$output) {
// Filter code
}

通过传入的参数,控制过滤器。

调用函数之前运行过滤器:

1
2
3
Flight::before('start', function(&$params, &$output){
// Do something
});

调用函数之后运行过滤器:

1
2
3
Flight::after('start', function(&$params, &$output){
// Do something
});

可以根据需要,向任何方法添加任意数量的过滤器,这些过滤器将按照注册的顺序被执行。

这是一个过滤过程的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 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');

这个栗子会输出以下内容:

Hello Fred! Have a nice day!

如果定义多个过滤器,你可以在任意过滤器中返回false,跳过其他过滤器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Flight::before('start', function(&$params, &$output){
echo 'one';
});
Flight::before('start', function(&$params, &$output){
echo 'two';
// This will end the chain
return false;
});
// 这个过滤器不会被执行
Flight::before('start', function(&$params, &$output){
echo 'three';
});

注意, 核心方法例如 mapregister 不能被过滤,因为它们被直接调用而不是动态调用。

变量

Flight允许你保存变量,以便它们在应用程序中的任何位置使用。

1
2
3
4
5
// Save your variable
Flight::set('id', 123);
// Elsewhere in your application
$id = Flight::get('id');

判断变量是否已经存在:

1
2
3
if (Flight::has('id')) {
// Do something
}

你也可以清除设置的变量:

1
2
3
4
5
// Clears the id variable
Flight::clear('id');
// Clears all variables
Flight::clear();

Flight还使用变量进行配置:

1
Flight::set('flight.log_errors', true);

视图(模板)

Flight默认提供一些基本的模板功能。通过向render方法传入模板名和数据调用模板,数据参数可选:

1
Flight::render('hello.php', array('name' => 'Bob'));

传入的数据会自动注入模板,并且可以像局部变量一样引用。模板是一个简单的PHP文件。模板hello.php内容如下:

1
Hello, '<?php echo $name; ?>'!

The output would be:

Hello, Bob!

你也可以使用set方法手动设置视图变量:

1
Flight::view()->set('name', 'Bob');

这样你就可以在所有模板里面使用name变量:

1
Flight::render('hello');

注意:在render方法中指定模板的名称时,可以省略.php扩展名。

默认情况下,Flight会在views目录寻找模板文件,可以通过下面代码设置模板存放路径:

1
Flight::set('flight.views.path', '/path/to/views');

布局

通常现在的网站都有布局文件,可以在render方法传入第三个参数,例如酱紫:

1
2
Flight::render('header', array('heading' => 'Hello'), 'header_content');
Flight::render('body', array('body' => 'World'), 'body_content');

这些视图将会保存到header_contentbody_content变量中.这样子就可以渲染你的布局模板:

1
Flight::render('layout', array('title' => 'Home Page'));

模板文件大概长下面酱紫:

header.php:

1
<h1><?php echo $heading; ?></h1>

body.php:

1
<div><?php echo $body; ?></div>

layout.php:

1
2
3
4
5
6
7
8
9
<html>
<head>
<title><?php echo $title; ?></title>
</head>
<body>
<?php echo $header_content; ?>
<?php echo $body_content; ?>
</body>
</html>

以上将会输出:

1
2
3
4
5
6
7
8
9
<html>
<head>
<title>Home Page</title>
</head>
<body>
<h1>Hello</h1>
<div>World</div>
</body>
</html>

自定义模板引擎

Flight允许替换原来的模板引擎,这是一个使用Smarty代替默认模板引擎的栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 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');

为了完整性,还应该覆盖Flight的默认渲染方法:

1
2
3
4
Flight::map('render', function($template, $data){
Flight::view()->assign($data);
Flight::view()->display($template);
});

错误处理

错误和异常

所有错误和异常都被Flight捕获并传递给error方法。默认情况下会发送HTTP 500 Internal Server Error和错误信息。

当然如果有需要,可以重写:

1
2
3
4
Flight::map('error', function(Exception $ex){
// Handle error
echo $ex->getTraceAsString();
});

默认情况下,错误不会记录到Web服务器。可以通过更改配置启用它:

1
Flight::set('flight.log_errors', true);

没有找到(Not Found)

如果URL没有被匹配,Flight会调用notFound方法.默认情况下将会返回HTTP 404 Not Found和错误信息.

如果想美化你的404页面,你依旧可以重写:

1
2
3
Flight::map('notFound', function(){
// Handle not found
});

重定向(Redirects)

可以向redirect方法传入一个URL重定向当前请求:

1
Flight::redirect('/new/location');

默认情况下Flight会发送一个303状态码,你可以自定义状态码:

1
Flight::redirect('/new/location', 401);

请求(Requests)

Flight将HTTP请求封装到一个对象中,可以通过执行以下操作来访问:

1
$request = Flight::request();

这个请求对象提供以下属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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

可以把query, data, cookiesfiles当作一个对象或者数组访问:

例如,要获取请求字符串:

1
$id = Flight::request()->query['id'];

或者:

1
$id = Flight::request()->query->id;

原生请求体

To get the raw HTTP request body, for example when dealing with PUT requests, you can do:

1
$body = Flight::request()->getBody();

JSON Input

如果你发送一个类型为application/json的请求,并且带有数据{"id": 123},可以通过data属性访问这些数据:

1
$id = Flight::request()->data->id;

HTTP缓存

如若不理解,请阅读HTTP协议缓存部分
Flight为HTTP级别缓存提供内置支持。 如果满足缓存条件,Flight将返回304 Not Modified的HTTP响应。 下一次客户端请求相同的资源时,将提示他们使用本地的缓存。

Last-Modified

可以通过向lastModified方法传入一个UNIX时间戳来设置页面最后修改的时间和日期。如果lastModified没有更改,客户端将一直使用本地缓存:

1
2
3
4
Flight::route('/news', function(){
Flight::lastModified(1234567890);
echo 'This content will be cached.';
});

ETag

ETag缓存与Last-Modified类似,你可以为你的资源设置一个ID:

1
2
3
4
Flight::route('/news', function(){
Flight::etag('my-unique-id');
echo 'This content will be cached.';
});

注意:调用lastModifiedetag将同时设置和检查缓存值。如果缓存值在请求之间相同,则Flight将立即发送一个HTTP 304响应并停止处理。

停止

可以使用在框架的任何地方使用halt方法停止运行:

1
Flight::halt();

你也可以同时设置HTTP状态码和返回信息:

1
Flight::halt(200, 'Be right back...');

调用halt方法将并不会把当前内容输出,如果想停止框架并输出当前内容请使用stop方法:

1
Flight::stop();

JSON

Flight支持发送json和jsonp响应,要发送json,你可以把数据传入json方法:

1
Flight::json(array('id' => 123));

译注:我并不熟悉JSONP,故下面内容不做翻译。

For JSONP requests you, can optionally pass in the query parameter name you are
using to define your callback function:

1
Flight::jsonp(array('id' => 123), 'q');

So, when making a GET request using ?q=my_func, you should receive the output:

1
my_func({"id":123});

If you don’t pass in a query parameter name it will default to jsonp.

配置

可以使用set方法修改Flight配置:

1
Flight::set('flight.log_errors', true);

所有可以修改的配置:

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)

框架方法

Flight特点是易于使用和理解。 以下是框架完整的方法列表,它由核心方法(不能被重写和过滤)和可扩展方法(可以被重写和过滤)组成。

核心方法

1
2
3
4
5
6
7
8
9
10
11
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

可扩展方法

1
2
3
4
5
6
7
8
9
10
11
12
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.

通过mapregister注册的方法和类也可以使用过滤器。

框架实例化

不一定要将Flight作为全局静态类运行,你也可以选择将其作为对象实例化。

1
2
3
4
5
6
7
8
9
10
11
require 'flight/autoload.php';
use flight\Engine;
$app = new Engine();
$app->route('/', function(){
echo 'hello world!';
});
$app->start();

实例化后,你可以通过相同的名字,调用实例化后对象的方法。