目前的项目中选择了Phalcon Framework作为未来一段时间的核心框架。技术选型的原因会单开一篇Blog另说,本次优先对Phalcon的MVC架构与启动流程进行分析说明,如有遗漏还望指出。

Phalcon本身支持创建多种形式的Web应用项目以应对不同场景,包括迷你应用单模块标准应用、以及较复杂的多模块应用

本次以最复杂的多模块应用为例,Phalcon版本为1.3.2,用一个Phalcon所创建的标准项目来分析。

创建项目

Phalcon环境配置安装后,可以通过命令行生成一个标准的Phalcon多模块应用:

 phalcon project eva --type modules

入口文件为public/index.php,简化后一共5行,包含了整个Phalcon的启动流程,以下将按顺序说明。

require __DIR__ . '/../config/services.php';
$application = new Phalcon\Mvc\Application();
$application->setDI($di);
require __DIR__ . '/../config/modules.php';
echo $application->handle()->getContent();

DI注册阶段

Phalcon的所有组件服务都是通过DI(依赖注入)进行组织的,这也是目前大部分主流框架所使用的方法。通过DI,可以灵活的控制框架中的服务:哪些需要启用,哪些不启用,组件的内部细节等等。因此Phalcon是一个松耦合可替换的框架,完全可以通过DI替换MVC中任何一个组件。

require __DIR__ . '/../config/services.php';

这个文件中默认注册了Phalcon\Mvc\Router(路由)、Phalcon\Mvc\Url(Url)、Phalcon\Session\Adapter\Files(Session)三个最基本的组件。同时当MVC启动后,DI中默认注册的服务还有很多,可以通过DI得到所有当前已经注册的服务:

$services = $application->getDI()->getServices();
foreach($services as $key => $service) {
var_dump($key);
var_dump(get_class($application->getDI()->get($key)));
}

打印看到Phalcon还注册了以下服务:

  • dispatcher : Phalcon\Mvc\Dispatcher 分发服务,将路由命中的结果分发到对应的Controller
  • modelsManager : Phalcon\Mvc\Model\Manager Model管理
  • 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 密码Hash、防止CSRF等
  • crypt : Phalcon\Crypt 加密算法
  • annotations : Phalcon\Annotations\Adapter\Memory 注解分析
  • flash : Phalcon\Flash\Direct 提示信息输出
  • flashSession : Phalcon\Flash\Session 提示信息通过Session延迟输出
  • tag : Phalcon\Tag View的常用Helper

而每一个服务都可以通过DI进行替换。接下来实例化一个标准的MVC应用,然后将我们定义好的DI注入进去。

$application = new Phalcon\Mvc\Application();
$application->setDI($di);

模块注册阶段

与DI一样,Phalcon建议通过引入一个独立文件的方式注册所有需要的模块:

require __DIR__ . '/../config/modules.php';

这个文件的内容如下:

$application->registerModules(array(
'frontend' => array(
'className' => 'Eva\Frontend\Module',
'path' => __DIR__ . '/../apps/frontend/Module.php'
)
));

可以看到Phalcon所谓的模块注册,其实只是告诉框架MVC模块的引导文件Module.php所在位置及类名是什么。

MVC阶段

$application->handle()是整个MVC的核心,这个函数中处理了路由、模块、分发等MVC的全部流程,处理过程中在关键位置会通过事件驱动触发一系列application:事件,方便外部注入逻辑,最终返回一个Phalcon\Http\Response。整个handle方法的过程并不复杂,下面按顺序介绍:

基础检查

首先检查DI,如果没有任何DI注入,会抛出错误:

A dependency injection object is required to access internal services

然后从DI启动EventsManager,并且通过EventsManager触发事件application:boot

路由阶段

接下来进入路由阶段,从DI中获得路由服务router,将uri传入路由并调用路由的handle()方法

路由的handle方法负责将一个uri根据路由配置,转换为相应的Module、Controller、Action等,这一阶段接下来会检查路由是否命中了某个模块,并通过Router->getModuleName()获得模块名。

如果模块存在,则进入模块启动阶段,否则直接进入分发阶段。

注意到了么,在Phalcon中,模块启动是后于路由的,这意味着Phalcon的模块功能比较弱,我们无法在某个未启动的模块中注册全局服务,甚至无法简单的在当前模块中调用另一个未启动模块。这可能是Phalcon模块功能设计中最大的问题,解决方法暂时不在本文的讨论范围内,以后会另开文章介绍。

模块启动

模块启动时首先会触发application:beforeStartModule事件。事件触发后检查模块的正确性,根据modules.php中定义的classNamepath等,将模块引导文件加载进来,并调用模块引导文件中必须存在的方法。

  • Phalcon\Mvc\ModuleDefinitionInterface->registerAutoloaders ()
  • Phalcon\Mvc\ModuleDefinitionInterface->registerServices (Phalcon\DiInterface $dependencyInjector)

registerAutoloaders()用于注册模块内的命名空间实现自动加载。registerServices ()用于注册模块内服务,在官方示例中registerServices ()注册并定义了view服务以及模板的路径,并且注册了数据库连接服务db并设置数据库的连接信息。

模块启动完成后触发 application:afterStartModule事件,进入分发阶段。

分发阶段(Dispatch)

分发过程由Phalcon\Mvc\Dispatcher(分发器)来完成,所谓分发,在Phalcon里本质上是分发器根据路由命中的结果,调用对应的Controller/Action,最终获得Action返回的结果。

分发开始前首先会准备View,虽然View理论上位于MVC的最后一环,但是如果在分发过程中出现任何问题,通常都需要将问题显示出来,因此View必须在这个环节就提前启动。Phalcon没有准备默认的View服务,需要从外部注入,在多模块demo中,View的注入官方推荐在模块启动阶段完成的。如果是单模块应用,则可以在最开始的DI阶段注入。

如果始终没有View注入,会抛出错误:

Service 'view' was not found in the dependency injection container

导致分发过程直接中断。

分发需要Dispatcher,Dispatcher同样从DI中取得。然后将router中得到的参数(NamespaceName / ModuleName / ControllerName / ActionName / Params),全部复制到Dispatcher中。

分发开始前,会调用View的start()方法。具体可以参考View相关文档,其实Phalcon\Mvc\View->start()就是PHP的输出缓冲函数ob_start的一个简单封装,分发过程中所有输出都会被暂存到缓冲区。

分发开始前还会触发事件application:beforeHandleRequest

正式开始分发会调用Phalcon\Mvc\Dispatcher->dispatch()

Dispatcher内的分发处理

进入Dispatcher后会发现Dispatcher对整个分发过程进行了进一步细分,并且在分发的过程中会按顺序触发非常多的分发事件,可以通过这些分发事件进行更加细致的流程控制。部分事件提供了可中断的机制,只要返回false就可以跳过Dispatcher的分发过程。

由于分发中可以使用Phalcon\Mvc\Dispatcher->forward()来实现Action的复用,因此分发在内部会通过循环实现,通过检测一个全局的finished标记来决定是否继续分发。当以下几种情况时,分发才会结束:

  • Controller抛出异常
  • forward层数达到最大(256次)
  • 所有的Action调用完毕

渲染阶段 View Render

分发结束后会触发application:afterHandleRequest,接下来通过Phalcon\Mvc\Dispatcher->getReturnedValue()取得分发过程返回的结果并进行处理。

由于Action的逻辑在框架外,Action的返回值是无法预期的,因此这里根据返回值是否实现Phalcon\Http\ResponseInterface接口进行区分处理。

当Action返回一个非Phalcon\Http\ResponseInterface类型

此时认为返回值无效,由View自己重新调度Render过程,会触发application:viewRender事件,同时从Dispatcher中取得ControllerName / ActionName / Params作为Phalcon\Mvc\View->render()的入口参数。

Render完毕后调用Phalcon\Mvc\View->finish()结束缓冲区的接收。

接下来从DI获得resonse服务,将Phalcon\Mvc\View->getContent()获得的内容置入response。

当Action返回一个Phalcon\Http\ResponseInterface类型

此时会将Action返回的Response作为最终的响应,不会重新构建新的Response。

返回响应

通过前面的流程,无论中间经历了多少分支,最终都会汇总为唯一的响应。此时会触发application:beforeSendResponse,并调用

  • Phalcon\Http\Response->sendHeaders()
  • Phalcon\Http\Response->sendCookies()

将http的头部信息先行发送。至此,Application->handle()对于请求的处理过程全部结束,对外返回一个Phalcon\Http\Response响应。

发送响应

HTTP头部发送后一般把响应的内容也发送出去:

echo $application->handle()->getContent();

这就是Phalcon Framework的完整MVC流程。

流程控制

分析MVC的启动流程,无疑是希望对流程有更好的把握和控制,方法有两种:

自定义启动

按照上面的流程,我们其实完全可以自己实现$application->handle()->getContent()这一流程,下面就是一个简单的替代方案,代码中暂时没有考虑事件的触发。

//Roter
$router = $di['router'];
$router->handle(); //Module handle
$modules = $application->getModules();
$routeModule = $router->getModuleName();
if (isset($modules[$routeModule])) {
$moduleClass = new $modules[$routeModule]['className']();
$moduleClass->registerAutoloaders();
$moduleClass->registerServices($di);
} //dispatch
$dispatcher = $di['dispatcher'];
$dispatcher->setModuleName($router->getModuleName());
$dispatcher->setControllerName($router->getControllerName());
$dispatcher->setActionName($router->getActionName());
$dispatcher->setParams($router->getParams()); //view
$view = $di['view'];
$view->start();
$controller = $dispatcher->dispatch();
//Not able to call render in controller or else will repeat output
$view->render(
$dispatcher->getControllerName(),
$dispatcher->getActionName(),
$dispatcher->getParams()
);
$view->finish(); $response = $di['response'];
$response->setContent($view->getContent());
$response->sendHeaders();
echo $response->getContent();

MVC事件

Phalcon作为C扩展型的框架,其优势就在于高性能,虽然我们可以通过上一种方法自己实现整个启动,但更好的方式仍然是避免替换框架本身的内容,而使用事件驱动。

下面梳理了整个MVC流程中所涉及的可被监听的事件,可以根据不同需求选择对应事件作为切入点:

  • DI注入

    • application:boot 应用启动
  • 路由阶段
    • 模块启动
    • application:beforeStartModule 模块启动前
    • application:afterStartModule 模块启动后
  • 分发阶段
    • application:beforeHandleRequest进入分发器前
    • 开始分发
      • dispatch:beforeDispatchLoop 分发循环开始前
      • dispatch:beforeDispatch 单次分发开始前
      • dispatch:beforeExecuteRoute Action执行前
      • dispatch:afterExecuteRoute Action执行后
      • dispatch:beforeNotFoundAction 找不到Action
      • dispatch:beforeException 抛出异常前
      • dispatch:afterDispatch 单次分发结束
      • dispatch:afterDispatchLoop 分发循环结束
    • application:afterHandleRequest 分发结束
  • 渲染阶段
    • application:viewRender 渲染开始前
  • 发送响应
    • application:beforeSendResponse 最终响应发送前
 

Phalcon Framework的MVC结构及启动流程分析的更多相关文章

  1. Phalcon Framework的Mvc结构及启动流程(部分源码分析)

    创建项目 Phalcon环境配置安装后,可以通过命令行生成一个标准的Phalcon多模块应用 phalcon project eva --type modules入口文件为public/index.p ...

  2. Phalcon的Mvc结构及启动流程(部分源码分析)

    Phalcon本身有支持创建多种形式的Web应用项目以应对不同场景,包括迷你应用.单模块标准应用.以及较复杂的多模块应用 创建项目 Phalcon环境配置安装后,可以通过命令行生成一个标准的Phalc ...

  3. Spring MVC启动流程分析

    本文是Spring MVC系列博客的第一篇,后续会汇总成贴子. Spring MVC是Spring系列框架中使用频率最高的部分.不管是Spring Boot还是传统的Spring项目,只要是Web项目 ...

  4. net core mvc剖析:启动流程

    net core mvc剖析:启动流程 asp.net core mvc是微软开源的跨平台的mvc框架,首先它跟原有的MVC相比,最大的不同就是跨平台,然后又增加了一些非常实用的新功能,比如taghe ...

  5. u-boot启动流程分析(2)_板级(board)部分

    转自:http://www.wowotech.net/u-boot/boot_flow_2.html 目录: 1. 前言 2. Generic Board 3. _main 4. global dat ...

  6. u-boot启动流程分析(1)_平台相关部分

    转自:http://www.wowotech.net/u-boot/boot_flow_1.html 1. 前言 本文将结合u-boot的“board—>machine—>arch—> ...

  7. Cocos2d-x3.3RC0的Android编译Activity启动流程分析

    本文将从引擎源代码Jni分析Cocos2d-x3.3RC0的Android Activity的启动流程,以下是具体分析. 1.引擎源代码Jni.部分Java层和C++层代码分析 watermark/2 ...

  8. Uboot启动流程分析(转载)

    最近一段时间一直在做uboot移植相关的工作,需要将uboot-2016-7移植到单位设计的ARMv7的处理器上.正好元旦放假三天闲来无事,有段完整的时间来整理下最近的工作成果.之前在学习uboot时 ...

  9. GEF入门实例_总结_04_Eclipse插件启动流程分析

    一.前言 本文承接上一节:GEF入门实例_总结_03_显示菜单和工具栏 注意到app目录下的6个类文件. 这6个文件对RCP应用程序而言非常重要,可能我们现在对这几个文件的理解还是云里雾里,这一节我们 ...

随机推荐

  1. mysql监控以及调优

    mysql 优点: 简单易用,成本低,易扩展,复制功能领先 mysql的生命周期: Mysql服务器监听3306端口>验证访问用户>创建mysql线程>检查内存(Qcache)> ...

  2. 关于json 转换BigDecimal精度丢失问题

    今天在转换一个关于金额字段发现一个关于json转换的bug  目前尚未深入观察 问题: 如果金钱为bigdecimal json转换后不会丢失精度 但是通过@responsebody 返回到前端后发现 ...

  3. 代码: js日期

    日期格式化 (new Date()).Format("yyyy-MM-dd hh:mm:ss.S") ==> 2015-11-16 08:09:04.423 (new Dat ...

  4. 开发者必看|Android 8.0 新特性及开发指南

    背景介绍 谷歌2017 I/O开发者大会今年将于5月17-19日在美国加州举办.大会将跟往年一样发布最新的 Android 系统,今年为 Android 8.0.谷歌在今年3 月21日发布 Andro ...

  5. leetcode949

    public class Solution { public string LargestTimeFromDigits(int[] A) { ); ; ; foreach (var nums in l ...

  6. redis该怎么用

    最近一些人在介绍方案时,经常会出现redis这个词,于是很多小伙伴百度完redis也就觉得它是一个缓存,然后项目里面把数据丢进去完事,甚至有例如将实体属性拆分塞进redis hash里面的奇怪用法等等 ...

  7. windows自带杀毒防火墙

    windows自带杀毒防火墙 Windows Defender FireWall

  8. oracle跟踪sql语句

    oracle跟踪sql语句 select * from v$sql 查询客户端电脑名称的ID select terminal, SID,SERIAL#  from v$session where  ( ...

  9. Delphi XE6打电话

    procedure TPhoneDialerForm.btnMakeCallClick(Sender: TObject); var PhoneDialerService: IFMXPhoneDiale ...

  10. 安装vue.js

    1.  下载node https://nodejs.org/dist/v8.11.2/node-v8.11.2-x64.msi 2. 查看npm版本 在cmd下输入命令:npm -v 如果低于3.0版 ...