Yaf framework 相关项目骨架

在 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;
}
以上就是几个主要的类和相关代码流程。

评论

此博客中的热门博文

如何防止Cloudflare CDN背后的图片被盗连(Hotlink Protection)?

J4125 低功耗 2.5G 迷你软路由详细折腾心得

保持你的口腔健康的技巧

nginx中location的顺序(优先级)及rewrite规则写法

也谈SpringCloud:假如 《复仇者联盟4》是一个微服务

一个新手机号新户福利大汇总

面试时的HR压低薪资时的沟通技巧

项目开发文档编写规范

远离你身边的煤气灯人

kubernetes中部署mysql高可用集群