Yaf 项目骨架

在 yaf 的源码目录,附带了一个工具 tools 目录, 下面有个 cg/yaf_cg 工具,是通过模版生成 yaf 项目骨架的工具。

Usage:
./yaf_cg ApplicationName [ApplicationPath]

项目目录

├── application
│   ├── Bootstrap.php
│   ├── controllers
│   ├── library
│   ├── models
│   ├── plugins
│   └── views
├── conf
│   └── application.ini
└── index.php
  1. index.php 程序的入口文件
  2. application 主要的应用程序代码目录。 2.1 可以通过application.directory这个配置来修改。 2.2 application 目录下的目录(controllers、models、plugins、views)是默认的代码结构目录,yaf 会根据相关的类名称,去相关目录下加载相关的文件,例如 IndexController extends Yaf\Controller_Abstract 这个文件,yaf 就会到 controllers 目录下查找 Index.php、类UserModel就会到 models 目录下查找 User.php(可以定制文件后缀或前缀模式,如 ModelUser 就是前缀模式)。 2.3 除了 library 目录以外,其它目录都是在框架编译的时候确定的,library 目录主要是存放一些本地库文件例如我有个Helper_Html (名称空间模式 Helper\Html)类,通过注册本地类名称空间Yaf_Loader::registerLocalNamespace("Helper")来加载,文件的存储结构是 library/helper/Html.php 需要注意的就是要开启名称空间,使用namespace的类名称和不使用namespace的 classname 有些差异。 2.4 Bootstrap.php 是框架的引导类 (可以没有),继承自 Yaf\Bootstrap_Abstract,在 Application 对象创建的时候可以调用 $application->bootstrap()->run() 这样会执行Bootstrap类里所有_init开头的方法,一般用于初始化一些配置或者数据或者初始化plugin等资源。

  3. conf 是配置文件目录, 也是可以修改的。

Yaf 完成一次请求的流程图

yaf流程图

流程解读

index.php 入口

<?php
define('APPLICATION_PATH', dirname(__FILE__));

$application = new Yaf\Application(APPLICATION_PATH . "/conf/application.ini");
$application->bootstrap() // 执行引导类Bootstrap.php 相关的引导方法
->run();

Bootstrap.php 引导类

<?php
/**
* 所有的 _init 开头的的方法,都会被执行
*/
class Bootstrap extends Yaf\Bootstrap_Abstract
{
/**
* 初始化本地类库的名称空间 Biz Ns
* 例如本地类库 Biz_Test, Ns\Test 放在library目录下
* library/biz/Test.php
* library/ns/Test.php
*/
public function _initRegisterLocalClass(Yaf\Dispatcher $dispatcher)
{
$loader = Yaf\Loader::getInstance();
$loader->registerLocalNamespace(array("Biz", "Ns"));
}

/**
* 初始化一些配置信息
*/
public function _initConfig() {
$arrConfig = Yaf\Application::app()->getConfig();
Yaf\Registry::set('config', $arrConfig);
}

public function _initPlugin(Yaf\Dispatcher $dispatcher) {
// 初始化一些插件, 插件文件存放在 plugins 目录,
// 类名字规则是 XxxxPlugin 放在 plugins 下的 Xxxx.php 文件
}

public function _initRoute(Yaf\Dispatcher $dispatcher) {
// 增加一些路由规则
// 默认是 Yaf_Route_Static
// 支持以下方式
// Yaf_Route_Simple
// Yaf_Route_Supervar
// Yaf_Route_Static
// Yaf_Route_Map
// Yaf_Route_Rewrite
// Yaf_Route_Regex

}
…… 可以做更多的事情
}

插件 Plugins

创建一个插件 Sample

// Sample.php
class SamplePlugin extends Yaf\Plugin_Abstract {

插件定义了6个 hook

routerStartup
routerShutdown
dispatchLoopStartup
preDispatch
postDispatch
dispatchLoopShutdown

插件的执行顺序是先进先调用。

路由规则 routes

yaf 支持多种路由规则,默认采用了Yaf_Route_Static方式,支持以下方式

Yaf_Route_Simple
Yaf_Route_Supervar
Yaf_Route_Static
Yaf_Route_Map
Yaf_Route_Rewrite
Yaf_Route_Regex

路由规则可以配置在 application.ini 配置文件内,也可以在程序初始化的时候动态生成增加,用户也可以通过实现Route_Interface接口,自定义路由规则。具体各种路由协议的含义和使用方式见路由协议详解

视图 Views

yaf 的视图文件默认放在 views 目录下,默认文件后缀名称 .phtml,view 文件就是 php 文件。

模块 modules 支持

  1. yaf 默认支持模块的,在默认的路由模式下,一般请求的路径为 /index.php/Module/Controller/Action/p1/v1/p2/v2 (通过 rewrite 规则去除 index.php)就是 /Module/Controller/Action/p1/v1/p2/v2

  2. 如果不指定默认的模块控制器和action,那么这三个值默认为Index,请求 /index.php 执行的路由规则就是 /Index/Index/Index

  3. 增加一个模块 Test, 需要配置 application.modules="Index,Test" ,且在 applications 下增加 modules/Test/controllers 文件夹,这样 /test/index/index 就会执行 modules/Test/controllers/Index.php 文件的 indexAction 方法

  4. 如果只有一个默认 module ,那么不需要 modules 文件夹。

引入外部库

composer 方式

通过 composer 安装的第三方库都会带有autoload.php 文件,那么根据具体的情况可以在 index.php 入口文件或者 Bootstrap 引导类内,加载这个 autoload.php 文件来使用外部类,例如

<?php
// index.php
define('APPLICATION_PATH',
dirname(__FILE__));

// 引入第三方类库
require(APPLICATION_PATH . '/vendor/autoload.php');

$application = new Yaf\Application(APPLICATION_PATH . "/conf/application.ini");
$application->bootstrap()
->run();

 Controller 使用第三方库
<?php
use GuzzleHttp\Client;

class IndexController extends Yaf\Controller_Abstract
{
public function indexAction()
{

$httpClient = new Client();
var_dump($httpClient);
}

全局库 yaf.library

这个配置选项是配置在 php.ini 内的,例如多个项目需要公用一些全局的库文件,那么通过指定该路径,来让不同的项目之间共享同一个库文件。

自定义 class loader

如果你的库文件命名方式等不符合 yaf 的自动加载风格,那么可以自定义 auto 方式,可以在 Bootstrap 引导类加载的时候进行注册。

主要类库代码解析

Yaf_Loader::autoload

这个是 Yaf 自动加载类的函数, 整个加载行为会受到 yaf.use_spl_autoload 这个配置的影响,开启的情况下, Yaf在加载不成功的情况下, 会继续让PHP的自动加载函数加载,否则会触发 E_WARNING 或者 E_STRICT 下面是相关部分源码:

if (yaf_internal_autoload(file_name, file_name_len, &directory TSRMLS_CC)) {
char *lc_classname = zend_str_tolower_dup(origin_classname, class_name_len);
if (zend_hash_exists(EG(class_table), lc_classname, class_name_len + 1)) {
……
} else {
efree(lc_classname);
php_error_docref(NULL TSRMLS_CC, E_STRICT, "Could not find class %s in %s", class_name, directory);
}
}  else {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed opening script %s: %s", directory, strerror(errno));
}

Yaf_Loader::autoload 流程图

Yaf_Loader::autoload 流程图

  1. 将 \ 处理为 _ 主要是为了让类名无论是名称空间模式,还是 _ 模式,都兼容。
  2. 确定文件目录位置: 主要是根据系统的几个固定目录来判断文件的位置 controllers、models、plugins、library 等。

Yaf_Application::bootstrap

除了 autoload 是一个比较重要的方法以外,bootstrap 也是一个比较重要的方法,因为经常会有很多数据初始化等代码需要在这个阶段来做,bootstrap()方法的主要工作就是循环遍历 Bootstrap extends Yaf_Bootstrap 这个类的所有以 _init 开头的方法,相关代码如下

methods = &((*ce)->function_table);
for(zend_hash_internal_pointer_reset(methods);
zend_hash_has_more_elements(methods) == SUCCESS;
zend_hash_move_forward(methods)) {
char *func;
uint len;
ulong idx;
zend_hash_get_current_key_ex(methods, &func, &len, &idx, 0, NULL);
/* cann't use ZEND_STRL in strncasecmp, it cause a compile failed in VS2009 */
/* YAF_BOOTSTRAP_INITFUNC_PREFIX 就是 _init */
if (strncasecmp(func, YAF_BOOTSTRAP_INITFUNC_PREFIX, sizeof(YAF_BOOTSTRAP_INITFUNC_PREFIX)-1)) {
continue;
}
zend_call_method(&bootstrap, *ce, NULL, func, len - 1, NULL, 1, dispatcher, NULL TSRMLS_CC);
/** an uncaught exception threw in function call */
if (EG(exception)) {
zval_ptr_dtor(&bootstrap);
RETURN_FALSE;
}
}

Yaf_Dispatcher

Yaf_Dispatcher 是个非常重要的的类,基本贯穿了整个开发过程,包括 plugin 的注册和调用,都和 Yaf_Dispatcher 这个类密切相关。注册类的代码非常简单

PHP_METHOD(yaf_dispatcher, registerPlugin) {
zval *plugin, *plugins;
yaf_dispatcher_t *self = getThis();

if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &plugin) == FAILURE) {
return;
}

if (Z_TYPE_P(plugin) != IS_OBJECT || !instanceof_function(Z_OBJCE_P(plugin), yaf_plugin_ce TSRMLS_CC)) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Expect a %s instance", yaf_plugin_ce->name);
RETURN_FALSE;
}

plugins = zend_read_property(yaf_dispatcher_ce, self, ZEND_STRL(YAF_DISPATCHER_PROPERTY_NAME_PLUGINS), 1 TSRMLS_CC);

Z_ADDREF_P(plugin);
add_next_index_zval(plugins, plugin);

RETVAL_ZVAL(self, 1, 0);
}

调用插件以及插件中的 6 个 hook 的代码主要集中在 yaf_response_t * yaf_dispatcher_dispatch(yaf_dispatcher_t *dispatcher TSRMLS_DC) 方法内,主要是 Yaf_Application::run() 的时候,调用该方法。

/** 代表了 hook 的 6 个阶段
YAF_PLUGIN_HOOK_ROUTESTARTUP
YAF_PLUGIN_HOOK_ROUTESHUTDOWN
YAF_PLUGIN_HOOK_LOOPSTARTUP
YAF_PLUGIN_HOOK_PREDISPATCH
YAF_PLUGIN_HOOK_POSTDISPATCH
YAF_PLUGIN_HOOK_LOOPSHUTDOWN
*/

if (!yaf_request_is_routed(request TSRMLS_CC)) {
YAF_PLUGIN_HANDLE(plugins, YAF_PLUGIN_HOOK_ROUTESTARTUP, request, response);
YAF_EXCEPTION_HANDLE(dispatcher, request, response);
if (!yaf_dispatcher_route(dispatcher, request TSRMLS_CC)) {
yaf_trigger_error(YAF_ERR_ROUTE_FAILED TSRMLS_CC, "Routing request failed");
YAF_EXCEPTION_HANDLE_NORET(dispatcher, request, response);
zval_ptr_dtor(&response);
return NULL;
}
yaf_dispatcher_fix_default(dispatcher, request TSRMLS_CC);
YAF_PLUGIN_HANDLE(plugins, YAF_PLUGIN_HOOK_ROUTESHUTDOWN, request, response);
YAF_EXCEPTION_HANDLE(dispatcher, request, response);
(void)yaf_request_set_routed(request, 1 TSRMLS_CC);
} else {
yaf_dispatcher_fix_default(dispatcher, request TSRMLS_CC);
}

YAF_PLUGIN_HANDLE(plugins, YAF_PLUGIN_HOOK_LOOPSTARTUP, request, response);
YAF_EXCEPTION_HANDLE(dispatcher, request, response);

view = yaf_dispatcher_init_view(dispatcher, NULL, NULL TSRMLS_CC);
if (!view) {
return NULL;
}

do {
YAF_PLUGIN_HANDLE(plugins, YAF_PLUGIN_HOOK_PREDISPATCH, request, response);
YAF_EXCEPTION_HANDLE(dispatcher, request, response);
if (!yaf_dispatcher_handle(dispatcher, request, response, view TSRMLS_CC)) {
YAF_EXCEPTION_HANDLE(dispatcher, request, response);
zval_ptr_dtor(&response);
return NULL;
}
yaf_dispatcher_fix_default(dispatcher, request TSRMLS_CC);
YAF_PLUGIN_HANDLE(plugins, YAF_PLUGIN_HOOK_POSTDISPATCH, request, response);
YAF_EXCEPTION_HANDLE(dispatcher, request, response);
} while (--nesting > 0 && !yaf_request_is_dispatched(request TSRMLS_CC));

YAF_PLUGIN_HANDLE(plugins, YAF_PLUGIN_HOOK_LOOPSHUTDOWN, request, response);
YAF_EXCEPTION_HANDLE(dispatcher, request, response);

if (0 == nesting && !yaf_request_is_dispatched(request TSRMLS_CC)) {
yaf_trigger_error(YAF_ERR_DISPATCH_FAILED TSRMLS_CC, "The max dispatch nesting %ld was reached", YAF_G(forward_limit));
YAF_EXCEPTION_HANDLE_NORET(dispatcher, request, response);
zval_ptr_dtor(&response);
return NULL;
}

以上就是几个主要的类和相关代码流程。