目录

Phalcon 框架之启动流程

稀里糊涂的使用 Phalcon 一年多了,对于其运行的的流程也知道个大概,本来计划尽早梳理一下,整理下学习心得,但是,由于种种原因还是没好好整理记录。好记性不如烂笔头,再经过来来回回翻文档过程中愈加痛恨自己的记忆力了,文档上找起来也不见得容易,还是老老实实记录一下常用的一些东西吧,这样在一个地方找总胜过在一个庞大的手册跳来跳去吧。顺便提一下,Phalcon 的官方文档越来越好用了。

创建项目

执行以下的命令创建一个标准的 Phalcon Micro Project

1
$ phalcon project --name example --enable-webtools --directory ./ --type micro --use-config-ini --trace

入口文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?php

use Phalcon\Di\FactoryDefault;
use Phalcon\Mvc\Micro;

error_reporting(E_ALL);

define('BASE_PATH', dirname(__DIR__));
define('APP_PATH', BASE_PATH . '/app');

try {

    /**
     * The FactoryDefault Dependency Injector automatically registers the services that
     * provide a full stack framework. These default services can be overidden with custom ones.
     */
    $di = new FactoryDefault();

    /**
     * Include Services
     */
    include APP_PATH . '/config/services.php';

    /**
     * Get config service for use in inline setup below
     */
    $config = $di->getConfig();

    /**
     * Include Autoloader
     */
    include APP_PATH . '/config/loader.php';

    /**
     * Starting the application
     * Assign service locator to the application
     */
    $app = new Micro($di);

    /**
     * Include Application
     */
    include APP_PATH . '/app.php';

    /**
     * Handle the request
     */
    $app->handle();

} catch (\Exception $e) {
      echo $e->getMessage() . '<br>';
      echo '<pre>' . $e->getTraceAsString() . '</pre>';
}

简单来说,就是

  • 创建依赖注入容器
  • 引入服务,将依赖及服务收集到容器中
  • 自动加载
  • 将容器注入给应用程序
  • 请求处理并响应

依赖注册

系统默认注册

1
2
3
4
/**
 * Include Services
 */
include APP_PATH . '/config/services.php';

services.php 默认注册了 配置 - \Phalcon\Config\Adapter\Ini(ConfigFile), 视图 - Phalcon\Mvc\View\Simple, URL - Phalcon\Mvc\UrlPhalcon\Db\Adapter\Pdo\XxxAdapter 这四个组件,同时当应用程序启动时,DI 中默认注册了其它服务

1
2
3
4
5
$services = $app->getDI()->getServices();
foreach ($services as $key => $service) {
    var_dump($key);
    var_dump(get_class($app->getDI()->get($key)));
}

输出后的 Phalcon 注册服务如下:

其中 urlconfigviewdbapplication 是没有对应的依赖服务的

  • router - Phalcon\Mvc\Router: 路由
  • dispatcher - Phalcon\Mvc\Dispatcher: 调度,将路由命中的结果分发到对应的处理单元上
  • url - Phalcon\Mvc\Url: 解析生成 URL
  • modelsManager - Phalcon\Mvc\Model\Manager: 模型管理器
  • modelsMetadata - Phalcon\Mvc\Model\MetaData\Memory: ORM 映射
  • response - Phalcon\Http\Response: 响应
  • cookies - Phalcon\Http\Response\Cookies: Cookies
  • request - Phalcon\Http\Request: 请求
  • filter - Phalcon\Filter: 过滤器
  • escaper - Phalcon\Escaper: 转义工具
  • security - Phalcon\Security: 安全工具(密码 HashCRSF 保护)
  • crypt - Phalcon\Crypt: 密码工具
  • annotations - Phalcon\Annotations\Adapter\Memory: 注释分析
  • flash - Phalcon\Flash\Direct: 提示信息输出
  • flashSession - Phalcon\Flash\Session: 提示信息通过 Session 延迟输出
  • tag - Phalcon\Tag: 视图助手
  • session - Phalcon\Session\Adapter\Files: Session
  • sessionBag - Phalcon\Session\Bag: Session
  • eventsManager - Phalcon\Events\Manager: 事件
  • transactionManager - Phalcon\Mvc\Model\Transaction\Manager: 事务
  • assets - Phalcon\Assets\Manager: 资产
  • config - \Phalcon\Config\Adapter\Ini(ConfigFile): 配置
  • view - Phalcon\Mvc\View\Simple: 视图
  • db - Phalcon\Db\Adapter\Pdo\Mysql: 数据库,可选(MySQLPostgresql 等)
  • application - Phalcon\Mvc\Micro: 应用

依赖注册属性的改变

1
2
3
4
5
/**
 * Set routing capabilities
 */
$router = $app->getDI()->get('router');
$router->setUriSource(\Phalcon\Mvc\Router::URI_SOURCE_SERVER_REQUEST_URI);

模块注册

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
/**
 * Composer autoloader
 */
require BASE_PATH . '/vendor/autoload.php';

/**
 * Registering an autoloader
 */
$loader = new \Phalcon\Loader();

$loader->registerNamespaces([
    'App\\Model'     => '../app/models',
    'App\\Component' => '../app/components',
    'App\\Util'      => '../app/utils',
])->register();

$loader->registerDirs([
    $config->application->modelsDir,
])->register();

模块注册,其实就是告诉应用程序需要引导的模块路径和类名

MVC 分层

  • 此处的 $app 指的就是创建的 Micro 对象,一开始我们创建的应用类型为 Phalcon Micro Project
  • $app->handle(); 是整个 MVC 的核心,这个方法处理了 MVC 的全部流程,它获得所有请求后,在处理过程中通过事件驱动触发一系列的 app 事件,最终返回一个完整的 Phalcon\Http\Response 对象

自检

这个阶段,主要检查 DI,确保必要的服务注册进来

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 如果将 `db` 服务注释掉,会抛出下面的异常

/**
 * Database connection is created based in the parameters defined in the configuration file
 */
// $di->setShared('db', function () {
//     $config = $this->getConfig();
//
//     $class = 'Phalcon\Db\Adapter\Pdo\\' . $config->database->adapter;
//     $params = [
//         'host'     => $config->database->host,
//         'username' => $config->database->username,
//         'password' => $config->database->password,
//         'dbname'   => $config->database->dbname,
//         'charset'  => $config->database->charset,
//     ];
//
//     if ($config->database->adapter == 'Postgresql') {
//         unset($params['charset']);
//     }
//
//     $connection = new $class($params);
//
//     return $connection;
// });

Service 'db' wasn't found in the dependency injection container

接下来,app 可以把事件绑定到 Phalcon\Events\Manager

事件名称触发点备注
boot当应用处理它首个请求时被执行
beforeStartModule初始化模块之前,仅当模块被注册时
afterStartModule初始化模块之后,仅当模块被注册时
beforeHandleRequest调度分发循环之前
afterHandleRequest调度分发循环之后

将一个事件绑定到事件管理器上

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<?php

use Phalcon\Events\Event;
use Phalcon\Events\Manager as EventsManager;

$eventsManager = new EventsManager();

$application->setEventsManager($eventsManager);

$eventsManager->attach(
    "application",
    function (Event $event, $application) {
        // ...
    }
);

做完这些自检后,就顺得地进入了路由阶段

路由

DI 中容器中通过键名 router 获取路由服务,将 uri 传入路由并调用路由的 handle() 方法

路由的 handle() 方法也是好脾气,负责把将请求中的原始 uri 检查(路由是否命中)解析后,转换为对应的 ModuleControllerAction 等,并通过 $router->getModuleName() 获取模块名,判断模块是否存在,如果存在就加载相应的模块启动模块,否则,就直接进入到路由的分发阶段

模块

之前绑定到事件管理器上事件就会因为指定的路由而触发,事件触发后检查模块的正确性,根据模块文件中定义的 classNamepath 等,将模块引导文件加载进来,并调用模块引导文件中必须存在的方法. 也就是说,自定义的模块必须实现 ModuleDefinitionInterface 定义的 registerAutoloaders(\Phalcon\DiInterface $dependencyInjector = null)registerServices(\Phalcon\DiInterface $dependencyInjector) 方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php

namespace Phalcon\Mvc;

/**
 * Phalcon\Mvc\ModuleDefinitionInterface
 *
 * This interface must be implemented by class module definitions
 */
interface ModuleDefinitionInterface
{

    /**
     * Registers an autoloader related to the module
     *
     * @param \Phalcon\DiInterface $dependencyInjector
     */
    public function registerAutoloaders(\Phalcon\DiInterface $dependencyInjector = null);

    /**
     * Registers services related to the module
     *
     * @param \Phalcon\DiInterface $dependencyInjector
     */
    public function registerServices(\Phalcon\DiInterface $dependencyInjector);

}

模块启动完成后触发 afterStartModule 事件,标志着正式进入路由的调度分发阶段

分发

Phalcon 的路由调度分发是由 Phalcon\Mvc\Dispatcher 来完成. 所谓分发,就是 Phalcon 根据请求的原始 uri,将其匹配到的地址解析到对应的 ControllerAction,并返回 Action 结果

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
<?php

namespace Phalcon\Mvc;

/**
 * Phalcon\Mvc\Dispatcher
 *
 * Dispatching is the process of taking the request object, extracting the module name,
 * controller name, action name, and optional parameters contained in it, and then
 * instantiating a controller and calling an action of that controller.
 *
 * <code>
 * $di = new \Phalcon\Di();
 *
 * $dispatcher = new \Phalcon\Mvc\Dispatcher();
 *
 * $dispatcher->setDI($di);
 *
 * $dispatcher->setControllerName("posts");
 * $dispatcher->setActionName("index");
 * $dispatcher->setParams([]);
 *
 * $controller = $dispatcher->dispatch();
 * </code>
 */
class Dispatcher extends \Phalcon\Dispatcher implements \Phalcon\Mvc\DispatcherInterface
{
    protected $_handlerSuffix = "Controller";


    protected $_defaultHandler = "index";


    protected $_defaultAction = "index";


    /**
     * Sets the default controller suffix
     *
     * @param string $controllerSuffix
     */
    public function setControllerSuffix($controllerSuffix) {}

    /**
     * Sets the default controller name
     *
     * @param string $controllerName
     */
    public function setDefaultController($controllerName) {}

    /**
     * Sets the controller name to be dispatched
     *
     * @param string $controllerName
     */
    public function setControllerName($controllerName) {}

    /**
     * Gets last dispatched controller name
     *
     * @return string
     */
    public function getControllerName() {}

    /**
     * Gets previous dispatched namespace name
     *
     * @return string
     */
    public function getPreviousNamespaceName() {}

    /**
     * Gets previous dispatched controller name
     *
     * @return string
     */
    public function getPreviousControllerName() {}

    /**
     * Gets previous dispatched action name
     *
     * @return string
     */
    public function getPreviousActionName() {}

    /**
     * Throws an internal exception
     *
     * @param string $message
     * @param int $exceptionCode
     */
    protected function _throwDispatchException($message, $exceptionCode = 0) {}

    /**
     * Handles a user exception
     *
     * @param \Exception $exception
     */
    protected function _handleException(\Exception $exception) {}

    /**
     * Possible controller class name that will be located to dispatch the request
     *
     * @return string
     */
    public function getControllerClass() {}

    /**
     * Returns the latest dispatched controller
     *
     * @return \Phalcon\Mvc\ControllerInterface
     */
    public function getLastController() {}

    /**
     * Returns the active controller in the dispatcher
     *
     * @return \Phalcon\Mvc\ControllerInterface
     */
    public function getActiveController() {}
}

由于这里我创建的是 Phalcon Micro Project,主要用来写接口 API 用的,所以这个过程中就没有提及 View。当然 View 模块并不是在这个阶段启动的,它也是先于调度分发前启动的。如果调度分发阶段出现任何错误,都需要 View 将问题显示出来的

调度器也是从 DI 容器中通过键名 dispatcher 获取的,路由的分发需要调度器分派,因此调度器需要取得路由的详情信息,如:命名空间\模块名\类名\动作名\参数

渲染

响应

走到这里,需要汇总一个唯一的响应一致对外。此时会触发 beforeSendResponse,并调用 Phalcon\Http\Response->sendHeaders()Phalcon\Http\Response->sendCookies(),当然也可以去设置,然后对外返回一个 Phalcon\Http\Response 响应

未完待续……