Flight框架(官网)是一个微型的PHP框架,它简单,快速,可扩展。借助Flight可以快捷而轻松的创建你的RESTFul web应用。
虽然是一个微型的框架,而且代码量确实也较少,但我在阅读Flight代码的过程中,感到了它设计和构思独特而精妙的地方,觉得有学习的价值,便决定做一下整理分享出来。
如果你对框架还不熟悉,可以先去官网看下文档,如果需要中文文档的话可以可以点这里。
如果你已经对Flight有一定了解了,接下来就来看看Flight是怎么工作的吧。
<?php
class Flight {
/**
* Framework engine.
* @var object
*/
private static $engine;
// Don't allow object instantiation
private function __construct() {}
private function __destruct() {}
private function __clone() {}
/**
* 之前已经看到了,框架内所有函数都是以Flight类的静态函数形式调用的
* __callStatic()这个魔术方法能处理所有的静态函数
* @param string $name Method name
* @param array $params Method parameters
* @return mixed Callback results
*/
public static function __callStatic($name, $params) {
static $initialized = false;
if (!$initialized) {
//这里定义框架的自动加载机制,实际上是依据PSR-0标准来做的
require_once __DIR__.'/autoload.php';
//Engine类是框架的引擎所在
self::$engine = new \flight\Engine();
$initialized = true;
}
//在这里,Flight对Engine包装了一层而已。对Flight类静态函数的调用,实质上是对Engine类的相应函数的调用
return \flight\core\Dispatcher::invokeMethod(array(self::$engine, $name), $params);
}
}
//那么就直接就来看看Dispatcher::invokeMethod函数吧
namespace flight\core;
class Dispatcher {
/**
* 调用一个方法
* @param mixed $func Class method
* @param array $params Class method parameters
* @return mixed Function results
*/
public static function invokeMethod($func, array &$params = array()) {
list($class, $method) = $func;
$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);
}
}
}
上面注释里提到了,自动加载和PSR-0,我之前写过一篇关于这部分内容的文章。Flight框架的自动加载就是基于namespace和psr-0标准的:
//只列出有关自动加载部分的主要代码
namespace flight\core;
class Loader {
/**
* Starts/stops autoloader.
*
* @param bool $enabled Enable/disable autoloading
* @param mixed $dirs Autoload directories
*/
public static function autoload($enabled = true, $dirs = array()) {
if ($enabled) {
spl_autoload_register(array(__CLASS__, 'loadClass'));
}
else {
spl_autoload_unregister(array(__CLASS__, 'loadClass'));
}
if (!empty($dirs)) {
self::addDirectory($dirs);
}
}
/**
* Autoloads classes.
*
* @param string $class Class name
*/
public static function loadClass($class) {
$class_file = str_replace(array('\\', '_'), '/', $class).'.php';
foreach (self::$dirs as $dir) {
$file = $dir.'/'.$class_file;
if (file_exists($file)) {
require $file;
return;
}
}
}
}
再继续往下看之前,我们不妨先对Flight内主要的类和函数进行一下梳理,以下是Flight框架的内置类:
Flight框架的函数分两部分,一部分是核心函数:
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::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]) // Sends a JSON response.
Flight::jsonp($data, [$param], [$code], [$encode]) // Sends a JSONP response.
Flight框架的使用方式就是对Flight类静态函数调用(Flight::func()),我们在上面提到过,其实质是对Engine对象中函数的调用($engineObj->func())。
而Engine类的函数有两类,一类是核心函数,是直接进行调用(相对于动态调用)的,另外的扩展函数,则是进行动态调用的。
此外,在Flight中加载类和资源,获得某个类的实例,直接调用Flight::className()即可,等同于$engineObj->className()。这个也是采用动态调用的形式。也就是说,除了Engine类的核心函数,其他函数(类)都是动态调用的。这样,框架就可以为此提供一个统一的入口了。
namespace flight;
class Engine {
//....
public function __construct() {
$this->vars = array();
//上面提到过,Flight中,Dispatcher负责处理函数,Loader负责对象的加载
$this->loader = new Loader();
$this->dispatcher = new Dispatcher();
$this->init();
}
/**
* __call是一个魔术方法,当调用一个不存在的函数时,会调用到该函数
* 刚才讲的动态调用就是通过这个函数进行的
* @param string $name Method name
* @param array $params Method parameters
* @return mixed Callback results
*/
public function __call($name, $params) {
//先判断是类还是可直接调用的函数
$callback = $this->dispatcher->get($name);
//如果是函数,通过dispatcher处理
if (is_callable($callback)) {
return $this->dispatcher->run($name, $params);
}
//是否是共享实例
$shared = (!empty($params)) ? (bool)$params[0] : true;
//通过loader加载该类的对象
return $this->loader->load($name, $shared);
}
/**
* 框架初始化
*/
public function init() {
static $initialized = false;
$self = $this;
if ($initialized) {
$this->vars = array();
$this->loader->reset();
$this->dispatcher->reset();
}
// Flight中,类会通过loader的register函数进行注册
// 注册默认组件
$this->loader->register('request', '\flight\net\Request');
$this->loader->register('response', '\flight\net\Response');
$this->loader->register('router', '\flight\net\Router');
$this->loader->register('view', '\flight\template\View', array(), function($view) use ($self) {
$view->path = $self->get('flight.views.path');
});
// 注册框架方法
$methods = array(
'start','stop','route','halt','error','notFound',
'render','redirect','etag','lastModified','json','jsonp'
);
// Flight中,method会通过dispatcher的set函数将对应的回调函数绑定到一个事件中
// 为了可以进行动态调用,Enginge的扩展函数全部是通过 _method 的名字定义的
foreach ($methods as $name) {
$this->dispatcher->set($name, array($this, '_'.$name));
}
// 默认的配置
$this->set('flight.base_url', null);
$this->set('flight.handle_errors', true);
$this->set('flight.log_errors', false);
$this->set('flight.views.path', './views');
$initialized = true;
}
/**
* 将一个类注册到框架方法中,我们就是通过这个函数注册我们自定义的类的
*
* @param string $name Method name
* @param string $class Class name
* @param array $params Class initialization parameters
* @param callback $callback Function to call after object instantiation
* @throws \Exception If trying to map over a framework method
*/
public function register($name, $class, array $params = array(), $callback = null) {
if (method_exists($this, $name)) {
throw new \Exception('Cannot override an existing framework method.');
}
//通过loader的register函数进行注册
$this->loader->register($name, $class, $params, $callback);
}
/**
* 将一个回调函数映射到框架方式中,我们就是通过这个函数映射我们自定义函数的
*
* @param string $name Method name
* @param callback $callback Callback function
* @throws \Exception If trying to map over a framework method
*/
public function map($name, $callback) {
if (method_exists($this, $name)) {
throw new \Exception('Cannot override an existing framework method.');
}
//会通过dispatcher的set函数将对应的回调函数绑定到一个事件中
$this->dispatcher->set($name, $callback);
}
//...
}
Flight中的两个核心函数,map是映射自定义的函数,最后是通过dispathcer的set函数实现的,register是注册自定义的类,最后是通过loader的register函数实现的。而框架自己的核心组件和扩展函数,Engine在初始化过程帮我们完成了这两个过程。接着Flight提供了一个统一的入口,可以动态调用所有非核心的函数,类。这就是Flight的核心加载机制了。
可能你还有疑问,为什么Flight要使用动态调用的形式去访问这些函数或对象?尤其是对于Engine的扩展函数,为什么不直接进行调用呢?因为Flight可以对它们进行过滤或重写。过滤和重写是Flight框架进行扩展的重要功能。框架实现了统一的资源操作方式后,就可以方便的进行重写或者过滤的处理了。需要注意的是,核心函数诸如map和register是不能够进行过滤或重写的,相信你已经清楚为什么了。
框架的重写功能还是使用的map和register这两个函数。这个功能因为框架的设计方式,很轻易的完成了。在Dispatcher和Loader中都动态维护了一个映射表,Dispatcher里是回调到事件的映射,Loader中是class到实例和构造函数等的映射。这样,注册自定义函数或类时,遇到一样名字就会覆盖掉之前的,而使用时只返回最新的。下面是Loader类的部分代码:
namespace flight\core;
class Loader {
//....
/**
* 注册一个类
*
* @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
*/
public function register($name, $class, array $params = array(), $callback = null) {
unset($this->instances[$name]);
$this->classes[$name] = array($class, $params, $callback);
}
/**
* 加载一个已注册的类
*
* @param string $name Method name
* @param bool $shared Shared instance
* @return object Class instance
*/
public function load($name, $shared = true) {
$obj = null;
//$this->classes是注册过的类
//$this->instances是加载过的实例
if (isset($this->classes[$name])) {
list($class, $params, $callback) = $this->classes[$name];
$exists = isset($this->instances[$name]);
//是不是共享实例
if ($shared) {
$obj = ($exists) ?
$this->getInstance($name) :
$this->newInstance($class, $params);
if (!$exists) {
$this->instances[$name] = $obj;
}
}
else {
$obj = $this->newInstance($class, $params);
}
if ($callback && (!$shared || !$exists)) {
$ref = array(&$obj);
call_user_func_array($callback, $ref);
}
}
return $obj;
}
/**
* 得到一个类的单一实例
*
* @param string $name Instance name
* @return object Class instance
*/
public function getInstance($name) {
return isset($this->instances[$name]) ? $this->instances[$name] : null;
}
/**
* 得到一个类的新的实例
*
* @param string|callable $class Class name or callback function to instantiate class
* @param array $params Class initialization parameters
* @return object Class instance
*/
public function newInstance($class, array $params = array()) {
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:
$refClass = new \ReflectionClass($class);
return $refClass->newInstanceArgs($params);
}
}
//....
}
跟过滤器功能有关的函数是before和after,分别是在被过滤函数处理之前或之后进行操作。最终是在Dispatcher类中实现的。
namespace flight;
class Engine {
/**
* Adds a pre-filter to a method.
*
* @param string $name Method name
* @param callback $callback Callback function
*/
public function before($name, $callback) {
$this->dispatcher->hook($name, 'before', $callback);
}
/**
* Adds a post-filter to a method.
*
* @param string $name Method name
* @param callback $callback Callback function
*/
public function after($name, $callback) {
$this->dispatcher->hook($name, 'after', $callback);
}
}
namespace flight\core;
class Dispatcher {
/**
* 将回调注册到一个事件之中
*
* @param string $name Event name
* @param callback $callback Callback function
*/
public function set($name, $callback) {
$this->events[$name] = $callback;
}
/**
* 得到事件关联的回调
*
* @param string $name Event name
* @return callback $callback Callback function
*/
public function get($name) {
return isset($this->events[$name]) ? $this->events[$name] : null;
}
/**
* 在事件上挂一个回调函数
*
* @param string $name Event name
* @param string $type Filter type
* @param callback $callback Callback function
*/
public function hook($name, $type, $callback) {
$this->filters[$name][$type][] = $callback;
}
/**
* 对事件进行分发处理
*
* @param string $name Event name
* @param array $params Callback parameters
* @return string Output of callback
*/
public function run($name, array $params = array()) {
$output = '';
// 运行前置过滤器
if (!empty($this->filters[$name]['before'])) {
$this->filter($this->filters[$name]['before'], $params, $output);
}
// 运行所请求的方法
$output = $this->execute($this->get($name), $params);
// 运行后置过滤器
if (!empty($this->filters[$name]['after'])) {
$this->filter($this->filters[$name]['after'], $params, $output);
}
return $output;
}
}
namespace flight;
class Engine {
/**
* 启动这个框架
*/
public function _start() {
$dispatched = false;
$self = $this;
$request = $this->request();
$response = $this->response();
$router = $this->router();
// 冲刷掉已经存在的输出
if (ob_get_length() > 0) {
$response->write(ob_get_clean());
}
// 启动输出缓冲
ob_start();
// 开启错误处理
$this->handleErrors($this->get('flight.handle_errors'));
// 对AJAX请求关闭缓存
if ($request->ajax) {
$response->cache(false);
}
// 允许后置过滤器的运行
$this->after('start', function() use ($self) {
//start完成之后会调用stop()函数
$self->stop();
});
// 对该请求进行路由
while ($route = $router->route($request)) {
$params = array_values($route->params);
//是否让路由链继续下去
$continue = $this->dispatcher->execute(
$route->callback,
$params
);
$dispatched = true;
if (!$continue) break;
$router->next();
}
//路由没找匹配到
if (!$dispatched) {
$this->notFound();
}
}
/**
* 停止这个框架并且输出当前的响应内容
*
* @param int $code HTTP status code
*/
public function _stop($code = 200) {
$this->response()
->status($code)
->write(ob_get_clean())
->send();
}
}
–EOF–