Flight是什么?
Flight是一個快速,簡易,可擴展的PHP框架。Flight能使你快速和輕松地創(chuàng)建RESTful Web應(yīng)用。
require 'flight/Flight.php';
Flight::route('/', function(){
echo 'hello world!';
});
Flight::start();
需求
Flight需要PHP 5.3或更高版本。
License
Flight is released under the MIT license.
安裝
1.框架下載
如果你在使用Composer,你可以運行如下命令:
composer require mikecao/flight
或者你可以直接下載,之后將Flight框架文件放入你的web目錄中。
2. 配置你的web服務(wù)器
對于Apache服務(wù)器,編輯你的.htaccess文件添加如下內(nèi)容:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]
對于Nginx服務(wù)器,添加如下內(nèi)容到你的server聲明中:
server {
location / {
try_files $uri $uri/ /index.php;
}
}
3. 創(chuàng)建你的index.php文件(示例)
首先引入這個框架。
require 'flight/Flight.php';
接著定義一個路由并且注冊一個函數(shù)去處理對應(yīng)請求。
Flight::route('/', function(){
echo 'hello world!';
});
最后,啟動框架。
Flight::start();
路由
Flight中的路由是指將一個URL模式(pattern)匹配到一個回調(diào)函數(shù)中。
Flight::route('/', function(){
echo 'hello world!';
});
只要能被調(diào)用,都可以當做回調(diào)函數(shù)。所以可以使用一個普通的函數(shù)當做回調(diào):
function hello(){
echo 'hello world!';
}
Flight::route('/', 'hello');
也可以是某一個類的方法:
class Greeting {
public static function hello() {
echo 'hello world!';
}
}
Flight::route('/', array('Greeting','hello'));
如果定義了多個路由,路由依照定義它們的順序進行匹配。第一個匹配到該請求的路由將被調(diào)用。
HTTP METHOD路由
在默認不指定的情況下,路由會對相應(yīng)請求的所有Method(例如:GET POST PUT DELETE...)進行匹配。你可以通過在URL前面加一個方法標識符的方式來響應(yīng)指定的Method。
Flight::route('GET /', function(){
echo 'I received a GET request.';
});
Flight::route('POST /', function(){
echo 'I received a POST request.';
});
你還可以使用|分隔符來映射多個Method到同一個回調(diào)中。
Flight::route('GET|POST /', function(){
echo 'I received either a GET or a POST request.';
});
正則表達式
在路由中你可以使用正則表達式:
Flight::route('/user/[0-9]+', function(){
// 這個將匹配到 /user/1234
});
命名參數(shù)
你可以在路由中指定命名參數(shù),它們會被傳遞到你的回調(diào)函數(shù)里。
Flight::route('/@name/@id', function($name, $id){
echo "hello, $name ($id)!";
});
你也可以通過使用:分隔符在命名變量中引入正則表達式
Flight::route('/@name/@id:[0-9]{3}', function($name, $id){
// 這個將匹配到 /bob/123
// 但是不會匹配到 /bob/12345
});
可選參數(shù)
你可以通過將URL段(segments)包在括號里的方式來指定哪些命名參數(shù)是可選的。
Flight::route('/blog(/@year(/@month(/@day)))', function($year, $month, $day){
// 它將匹配如下URLS:
// /blog/2012/12/10
// /blog/2012/12
// /blog/2012
// /blog
});
任何沒有被匹配到的可選參數(shù)將以NULL值傳入。
通配符
匹配只發(fā)生在單獨的URL段(segments)。如果你想匹配多段,可以使用*通配符。
Flight::route('/blog/*', function(){
// 這個將匹配到 /blog/2000/02/01
});
要將所有的請求路由到單一的回調(diào)上,你可以這么做:
Flight::route('*', function(){
// Do something
});
路由的傳遞
當從一個被匹配到的回調(diào)函數(shù)中返回true時,路由功能將繼續(xù)執(zhí)行,傳遞到下一個能匹配的路由中。
Flight::route('/user/@name', function($name){
// 檢查某些條件
if ($name != "Bob") {
// 延續(xù)到下一個路由
return true;
}
});
Flight::route('/user/*', function(){
// 這里會被調(diào)用到
});
路由信息
如果你想檢視匹配到的路由信息,可以請求將路由對象傳遞到你的回調(diào)函數(shù)中:你需要把
route方法的第三個參數(shù)設(shè)置成true。這個路由對象總是會作為最后一個參數(shù)傳入你的回調(diào)函數(shù)。
Flight::route('/', function($route){
// 匹配到的HTTP方法的數(shù)組
$route->methods;
// 命名參數(shù)數(shù)組
$route->params;
// 匹配的正則表達式
$route->regex;
// Contains the contents of any '*' used in the URL pattern
$route->splat;
}, true);
擴展
Fligth被設(shè)計成一個可擴展的框架。這個框架帶來了一系列的默認方法和組件,但是它允許你
映射你自己的方法,注冊你自己的類,甚至可以重寫已有的類和方法。
方法的映射
你可以使用map函數(shù)去映射你自定義的方法:
// 映射你自己的方法
Flight::map('hello', function($name){
echo "hello $name!";
});
// 調(diào)用你的自定義方法
Flight::hello('Bob');
類的注冊
你可以使用register函數(shù)去注冊你自己的類:
// 注冊你定義的類
Flight::register('user', 'User');
// 得到你定義的類的一個實例
$user = Flight::user();
register方法允許你向類的構(gòu)造函數(shù)傳遞參數(shù)。所以當你加載自定義類的時候,它將會
預(yù)初始化(pre-initialized)。你可以通過一個追加的數(shù)組來傳遞定義的構(gòu)造函數(shù)參數(shù)。
這是一個加載數(shù)據(jù)庫連接的例子:
// 注冊一個帶有構(gòu)造函數(shù)參數(shù)的類
Flight::register('db', 'PDO', array('mysql:host=localhost;dbname=test','user','pass'));
// 得到你定義的類的一個實例
// 這里將創(chuàng)建一個帶有你定義的參數(shù)的對象
//
// new PDO('mysql:host=localhost;dbname=test','user','pass');
//
$db = Flight::db();
如果你傳遞了額外的回調(diào)函數(shù)參數(shù),它將會在類構(gòu)造完之后立即執(zhí)行。這就允許你為這個新對象去
執(zhí)行任何的安裝過程(set up procedures)。這個回調(diào)函數(shù)會被傳遞一個參數(shù),就是這個新對象的實例。
// 這個回調(diào)函數(shù)將會傳遞到這個被構(gòu)造的對象中
Flight::register('db', 'PDO', array('mysql:host=localhost;dbname=test','user','pass'), function($db){
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
});
默認情況下,每次你加載一個類,你會得到一個共享的實例。如果要得到一個類的新實例,
簡單的傳遞一個false參數(shù)就行了。
// 類的共享實例
$shared = Flight::db();
// 類的新實例
$new = Flight::db(false);
需要記住的是,被映射的方法優(yōu)先于被注冊的類。如果你用相同的名字將它們都聲明了,那么只有
映射的方法會被調(diào)用。
重寫(Overriding)
Flight允許你按照自己的需要去重寫它的默認功能,而不用修改任何框架的代碼。
例如,當Flight的路由功能沒有匹配到一個URL時,它會調(diào)用notFound方法,發(fā)出一個通用的
HTTP 404響應(yīng)。你可以使用map方法去重寫這個行為。
Flight::map('notFound', function(){
// 顯示自定義的404頁面
include 'errors/404.html';
});
Flight也允許你替換這個框架的核心組件。例如你可以將默認的Router類替換成你自定義的類:
// 注冊成你自定義的類
Flight::register('router', 'MyRouter');
// When Flight loads the Router instance, it will load your class
// 當Flight加載Router實例時,將會加載到你自定義的類
$myrouter = Flight::router();
然而框架的方法諸如map和register是不能夠被重寫的。如果你嘗試這么做的話你會得到一個error。
過濾
Flight允許你在方法調(diào)用之前和之后去過濾它??蚣芾餂]有任何你需要記住的預(yù)定義的鉤子。你可以
過濾任何被映射的自定義方法和框架中的方法。
一個過濾器函數(shù)是像這樣的:
function(&$params, &$output) {
// Filter code
}
通過傳入的變量,你可以操作輸入?yún)?shù)和/或輸出參數(shù)。
這樣做就可以在一個方法運行之前運行一個過濾器:
Flight::before('start', function(&$params, &$output){
// Do something
});
這樣做就可以在一個方法運行之后運行一個過濾器:
Flight::after('start', function(&$params, &$output){
// Do something
});
你可以給任何函數(shù)添加任意數(shù)量的過濾器。它們將按照聲明的順序依次被調(diào)用。
這里是一個過濾器處理的例子:
// 映射一個自定義的方法
Flight::map('hello', function($name){
return "Hello, $name!";
});
// 添加一個前置的過濾器
Flight::before('hello', function(&$params, &$output){
// 操作這里的params
$params[0] = 'Fred';
});
// 添加一個后置的過濾器
Flight::after('hello', function(&$params, &$output){
// 操作這里的output
$output .= " Have a nice day!";
});
// 調(diào)用這個自定義方法
echo Flight::hello('Bob');
這個將會輸出:
Hello Fred! Have a nice day!
如果你定義了多個過濾器,你可以通過在任意一個過濾器函數(shù)中返回false來終結(jié)這個過濾器鏈。
Flight::before('start', function(&$params, &$output){
echo 'one';
});
Flight::before('start', function(&$params, &$output){
echo 'two';
// 如下將會終止這個過濾器鏈
return false;
});
// 這里將不會得到調(diào)用
Flight::before('start', function(&$params, &$output){
echo 'three';
});
記住,核心函數(shù)諸如map和register是不能夠被過濾的,因為它們是被直接調(diào)用而非動態(tài)調(diào)用的。
變量
Flight允許你定義變量,使得它能在應(yīng)用內(nèi)的任何地方被使用。
// 保存你定義的變量
Flight::set('id', 123);
// 在應(yīng)用的其他地方使用
$id = Flight::get('id');
去檢測一個變量是否被設(shè)置了可以這么做:
if (Flight::has('id')) {
// Do something
}
去清除一個變量你可以這么做:
// 清除這個id變量
Flight::clear('id');
// 清除所有的變量
Flight::clear();
Flight框架使用變量的目的還包括了配置。
Flight::set('flight.log_errors', true);
視圖
Flight默認提供了一些基礎(chǔ)的模板功能。調(diào)用帶有模板文件和
可選的模板數(shù)據(jù)的render函數(shù),去顯示一個視圖模板。
Flight::render('hello.php', array('name' => 'Bob'));
你傳進來的模板數(shù)據(jù),會被自動的注入到模板當中,并且可以像一個本地變量一樣去引用。
模板文件就是簡單的PHP文件。如果一個文件名為hello.php的模板文件的內(nèi)容是這樣的:
Hello, '<?php echo $name; ?>'!
輸出會是:
Hello, Bob!
你可以使用set函數(shù)來手動的設(shè)置視圖中的變量:
Flight::view()->set('name', 'Bob');
這個name 變量現(xiàn)在在你所有的視圖中都是可用的了。所以就可以簡化成這樣了:
Flight::render('hello');
注意當你在render函數(shù)中指定模板名時,你可以去掉這個.php的擴展名。
默認情況下Flight會在views目錄下尋找模板文件。你可以通過如下配置的設(shè)定來為你的模板
設(shè)置另外一個路徑。
Flight::set('flight.views.path', '/path/to/views');
布局(Layouts)
對網(wǎng)站來說,擁有一個單獨的可交換內(nèi)容的布局(layout)模板文件是很常見的。要在布局中使用渲染的內(nèi)容,
你可以給render函數(shù)傳遞一個可選的參數(shù)。
Flight::render('header', array('heading' => 'Hello'), 'header_content');
Flight::render('body', array('body' => 'World'), 'body_content');
緊接著你的視圖就有了命名為header_content和body_content的已保存的變量。接下來你就可以
這樣渲染你的布局了:
Flight::render('layout', array('title' => 'Home Page'));
如果你的模板文件是這樣的:
header.php:
<h1><?php echo $heading; ?></h1>
body.php:
<div><?php echo $body; ?></div>
layout.php:
<html>
<head>
<title><?php echo $title; ?></title>
</head>
<body>
<?php echo $header_content; ?>
<?php echo $body_content; ?>
</body>
</html>
輸出會是:
<html>
<head>
<title>Home Page</title>
</head>
<body>
<h1>Hello</h1>
<div>World</div>
</body>
</html>
自定義視圖
Flight允許你替換默認的視圖引擎,只需簡單的注冊你自己的視圖類即可。這里展示的是在視圖中
如何使用Smarty模板引擎:
// 加載Smarty類庫
require './Smarty/libs/Smarty.class.php';
// 將Smarty注冊成視圖類
// 同時傳遞一個回調(diào)函數(shù),在加載過程中配置Smarty
Flight::register('view', 'Smarty', array(), function($smarty){
$smarty->template_dir = './templates/';
$smarty->compile_dir = './templates_c/';
$smarty->config_dir = './config/';
$smarty->cache_dir = './cache/';
});
// 模板中數(shù)據(jù)的賦值
Flight::view()->assign('name', 'Bob');
// 顯示這個模板
Flight::view()->display('hello.tpl');
出于完備性,你還應(yīng)該重寫Flight的默認render方法:
Flight::map('render', function($template, $data){
Flight::view()->assign($data);
Flight::view()->display($template);
});
錯誤(Error)處理
錯誤(Errors)和異常(Exceptions)
所有的errors和exceptions都會被Flight捕獲,然后傳到error方法。該方法默認的行為是
發(fā)出一個帶有錯誤信息的通用的HTTP 500 Internal Server Error響應(yīng)。
出于你自己的需要,你可以重寫這個行為:
Flight::map('error', function(Exception $ex){
// 錯誤處理
echo $ex->getTraceAsString();
});
默認情況下,錯誤(errors)是不會被記錄日志到web服務(wù)器的。你可以通過改變配置來允許記錄。
Flight::set('flight.log_errors', true);
Not Found
當一個URL沒有被找到時,F(xiàn)light將會調(diào)用notFound方法。該方法默認的行為是
發(fā)出一個通用的HTTP 404 Not Found響應(yīng)并帶有簡單的說明信息。
出于你自己的需要,你可以重寫這個行為:
Flight::map('notFound', function(){
// 處理not found
});
重定向(Redirects)
你可以使用redirect方法將當前請求重定向到傳入的新URL中。
Flight::redirect('/new/location');
默認情況下Flight會發(fā)出一個HTTP 303狀態(tài)碼。你可以選擇設(shè)置一個自定義的狀態(tài)碼。
Flight::redirect('/new/location', 401);
請求
Flight將HTTP請求封裝到一個單獨的對象中,你可以這樣獲取到它:
$request = Flight::request();
request對象提供了如下的屬性:
url - 被請求的url
base - The parent subdirectory of the URL
method - 請求的Method (GET, POST, PUT, DELETE)
referrer - 引用(referrer)的 URL
ip - 客戶點的IP地址
ajax - 是否是一個ajax請求
scheme - 服務(wù)器scheme (http, https)
user_agent - 瀏覽器信息
type - Content-type
length - Content-length
query - 查詢字符串參數(shù)(Query string parameters)
data - Post數(shù)據(jù)或者JSON數(shù)據(jù)
cookies - Cookies數(shù)據(jù)
files - 上傳的文件
secure - Whether the connection is secure
accept - HTTP accept parameters
proxy_ip - 客戶端代理ip地址
你可以通過數(shù)組或?qū)ο蟮姆绞絹慝@取query,data,cookies和 files屬性。
也就是說,你可以這樣獲取到查詢字符串參數(shù)(query string parameter):
$id = Flight::request()->query['id'];
或者你可以這樣做:
$id = Flight::request()->query->id;
請求體原始數(shù)據(jù)(RAW Request Body)
要獲取原始的HTTP請求體數(shù)據(jù),舉例來說當你正在處理PUT方法的請求時,你可以這么做:
$body = Flight::request()->getBody();
JSON 輸入
如果你發(fā)送application/json類型的請求并帶有數(shù)據(jù){"id": 123}時,它將被從data屬性中獲取到。
$id = Flight::request()->data->id;
HTTP緩存
Flight為HTTP級別的緩存提供了內(nèi)建的支持。如果滿足緩存的條件,F(xiàn)light將會返回一個
HTTP304 Not Modified響應(yīng)。當下一次客戶端請求相同的資源時,它們會被提示去使用它們
本地的緩存版本。
Last-Modified
你可以使用lastModified方法并傳遞一個UNIX時間戳去設(shè)置一個頁面最后被修改的日期和時間。
客戶端將繼續(xù)使用它們的緩存直到last modified的值被改變了。
Flight::route('/news', function(){
Flight::lastModified(1234567890);
echo 'This content will be cached.';
});
ETag
ETag緩存與Last-Modified類似,但你可以對資源指定任意的id。
Flight::route('/news', function(){
Flight::etag('my-unique-id');
echo 'This content will be cached.';
});
需要記住的是,不論調(diào)用了lastModified或是etag,都會設(shè)置并且檢查緩存的值。如果緩存中的值
跟請求的相同,F(xiàn)light會立即發(fā)送一個HTTP 304響應(yīng)并且停止處理。
停止
你可以通過調(diào)用halt方法在任何地方停止這個框架:
Flight::halt();
你也可以指定可選的HTTP狀態(tài)碼和信息:
Flight::halt(200, 'Be right back...');
調(diào)用halt將會丟棄在那個點之前的任何的響應(yīng)內(nèi)容。如果你想停止這個框架并輸出當前的響應(yīng),使用stop方法:
Flight::stop();
JSON
Flight對發(fā)送JSON和JSONP響應(yīng)提供了支持。發(fā)送一個JSON響應(yīng)時,你傳遞的數(shù)據(jù)將被JSON編碼。
Flight::json(array('id' => 123));
對于JSONP請求,你可以選擇傳遞查詢參數(shù)名(query parameter name)用于定義你的回調(diào)函數(shù):
Flight::jsonp(array('id' => 123), 'q');
所以,當使用?q=my_func構(gòu)造一個GET請求時,你應(yīng)該會收到這樣的輸出:
my_func({"id":123});
如果你沒有傳遞查詢參數(shù)名(query parameter name)的話,它會有一個默認名jsonp。
配置
你可以使用set方法去設(shè)置配置的值,來自定義Flight的某些行為。
Flight::set('flight.log_errors', true);
下面是所有的可進行設(shè)置的配置列表:
flight.base_url - 覆蓋該請求的base url。(默認值:null)
flight.handle_errors - 允許Flight處理所有的內(nèi)部錯誤。 (默認值:true)
flight.log_errors - 向web服務(wù)器的錯誤日志文件里記錄錯誤日志。 (默認值:false)
flight.views.path - 包含視圖模板文件的目錄路徑。 (默認值:./views)
框架的方法
Flight框架被設(shè)計成易于使用和易于理解的。下面就是這個框架完整的方法集合。它由 是常規(guī)靜態(tài)函數(shù)
的核心方法,和被映射的可以被過濾和重寫的擴展方法組成。
核心方法
Flight::map($name, $callback) // 創(chuàng)建一個自定的框架方法
Flight::register($name, $class, [$params], [$callback]) //將一個類注冊成框架方法
Flight::before($name, $callback) // 添加框架方法的前置過濾器
Flight::after($name, $callback) // 添加框架方法的后置過濾器
Flight::path($path) // 添加類自動加載(autoloading)的路徑
Flight::get($key) // 獲取某個變量的值
Flight::set($key, $value) // 設(shè)置變量的值
Flight::has($key) // 某個變量是否被設(shè)值
Flight::clear([$key]) // 清除一個變量
Flight::init() // 初始化框架到默認的設(shè)定
Flight::app() // 獲取整個應(yīng)用對象的實例
擴展方法
Flight::start() // 開啟框架(接收響應(yīng)開始工作)
Flight::stop() // 框架停止并且發(fā)送返回響應(yīng)
Flight::halt([$code], [$message]) // 停止框架并返回一個可選的http狀態(tài)碼和信息
Flight::route($pattern, $callback) // 將一個URL匹配到一個回調(diào)中
Flight::redirect($url, [$code]) // 重定向到另一個URL
Flight::render($file, [$data], [$key]) // 渲染模板文件
Flight::error($exception) // 發(fā)送HTTP 500響應(yīng)
Flight::notFound() // 發(fā)送HTTP 404響應(yīng)
Flight::etag($id, [$type]) // 運行HTTP Etag緩存
Flight::lastModified($time) // 運行HTTP last modified緩存
Flight::json($data, [$code], [$encode]) // 發(fā)送JSON響應(yīng)
Flight::jsonp($data, [$param], [$code], [$encode]) // 發(fā)送JSONP響應(yīng)
任何通過map和register添加的自定義方法都可以被過濾。
框架的實例
替代將Flight運行成一個全局的靜態(tài)類,你可以選擇將它運行成一個對象的實例。
require 'flight/autoload.php';
use flight\Engine;
$app = new Engine();
$app->route('/', function(){
echo 'hello world!';
});
$app->start();
也就是取代調(diào)用靜態(tài)方法,你可以調(diào)用Engine對象實例里同名的方法。