├── book.json ├── GLOSSARY.md ├── references.md ├── assets └── 01-workflow.png ├── a_contrastive_analysis_of_classic_mvc_and_symfony.md ├── diy_event_driven_framework.md ├── play_with_event_system.md ├── SUMMARY.md ├── README.md ├── core_components.md └── big_picture.md /book.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } -------------------------------------------------------------------------------- /GLOSSARY.md: -------------------------------------------------------------------------------- 1 | # Glossary 2 | 3 | -------------------------------------------------------------------------------- /references.md: -------------------------------------------------------------------------------- 1 | # 参考资料 2 | 文档中参考了一些博客、文档、资料,整理在这章供大家查阅。 -------------------------------------------------------------------------------- /assets/01-workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rswork/symfony-event-driven-kernel/HEAD/assets/01-workflow.png -------------------------------------------------------------------------------- /a_contrastive_analysis_of_classic_mvc_and_symfony.md: -------------------------------------------------------------------------------- 1 | # 与经典MVC框架的对比 2 | 与经典MVC框架的粗略对比,主要指初始化、加载具体逻辑处理代码、发送响应完成请求过程中的差异。 3 | 4 | ## Event Driven Structure 5 | 6 | ## MVC Structure 7 | 8 | ## Good And Bad -------------------------------------------------------------------------------- /diy_event_driven_framework.md: -------------------------------------------------------------------------------- 1 | # DIY事件驱动框架 2 | 如何DIY一个事件驱动的小框架,以Silex为例,使用Symfony的标准库编写一个微型框架。 3 | 4 | ## Silex's way 5 | 6 | ## DIY a command util 7 | 8 | ## Make classic MVC Eventable(Just For Fun) -------------------------------------------------------------------------------- /play_with_event_system.md: -------------------------------------------------------------------------------- 1 | # 应用事件系统 2 | 通过代码执行时的调用逻辑,讲解Kernel的处理流程以及相关事件,应用这些事件和定义自己的事件。 3 | 4 | ## Dispatcher 5 | 6 | ## Listener And Subscriber 7 | 8 | ## Kernel Events 9 | 10 | ## Custom Events 11 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | * [简介](README.md) 4 | * [核心组件](core_components.md) 5 | * [Big Picture](big_picture.md) 6 | * [应用事件系统](play_with_event_system.md) 7 | * [与经典MVC框架的对比](a_contrastive_analysis_of_classic_mvc_and_symfony.md) 8 | * [DIY事件驱动框架](diy_event_driven_framework.md) 9 | * [参考资料](references.md) 10 | 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Symfony's Event Driven Kernel 2 | 3 | 总结Symfony的事件驱动核心,如何在很短的代码行数上实现更多的扩展性。文档中如无意外,Symfony特指2.x和3.x版本,内部API可能有些不同,但是大体结构一致,这里的示例代码将从3.x版本提取。 4 | 5 | ## 核心组件 6 | 7 | 了解核心的构成组件实现的功能,学习如何单独使用它们,为了解Symfony的事件核心做准备。 8 | 9 | ## Big Picture 10 | 11 | 通过代码执行时的调用分支,抽取顶层的运行逻辑,了解Symfony的最顶层事件。 12 | 13 | ## 应用事件系统 14 | 15 | 了解如何运用Symfony标准的事件系统,做到对Request-Response控制自如。了解如何实现自己的事件,用自定义事件解耦业务逻辑中的复杂流程。 16 | 17 | ## 与经典MVC框架的对比 18 | 19 | 与经典MVC框架的粗略对比,主要指初始化、加载具体逻辑处理代码、发送响应完成请求过程中的差异。这里是与Zend Framework 1.x进行对比。 20 | 21 | ## DIY事件驱动框架 22 | 23 | 如何DIY一个事件驱动的小框架,以Silex为例,使用Symfony的标准库编写一个小的玩具框架。 24 | 25 | ## 参考资料 26 | 27 | 文档中参考了一些博客、文档、资料,整理在这章供大家查阅。 28 | 29 | -------------------------------------------------------------------------------- /core_components.md: -------------------------------------------------------------------------------- 1 | # 核心组件 2 | 了解核心的构成组件实现的功能,学习如何单独使用它们,为了解Symfony的事件核心做准备。这一章只是简短介绍各个组件的功能,你需要自己引入这些组件进行相应的学习和练。 3 | 4 | Symfony的核心由几个组件组成:HttpFoundation、HttpKernel、EventDispatcher、DependencyInjection等。这些组件实现了Symfony的事件驱动核心的大部分功能。 5 | 6 | 你可以通过[组件的官方文档](http://symfony.com/doc/current/components/index.html)来了解和使用它们,也可以根据文档编写一些测试脚本来熟悉组件的用法和用途,帮助自己更好的了解组件的功能。 7 | 8 | ## HttpFoundation 9 | Symfony的[`HttpFoundation`](http://symfony.com/doc/current/components/http_foundation/index.html)组件对PHP默认的一些全局参数以及生成响应数据的一些函数做了一层面向对象方式的封装,替代默认的PHP对`Request`和`Response`的操作方式。本段大部分内容摘抄自[官方文档](http://symfony.com/doc/current/components/http_foundation/introduction.html)。 10 | 11 | 针对`Request`中的`HTTP请求头`、`URL数据`、`POST数据`、`COOKIE数据`、`SESSION据`、`文件上传`和`SERVER参数`进行了封装,可以通过`Symfony\Component\HttpFoundation\Request`类的实例化对象取得这些参数。对于[`Session`](http://symfony.com/doc/current/components/http_foundation/sessions.html)管理也同样进行了封装,统一处理。 12 | 13 | 针对`Response`进行了统一的封装处理,使原本PHP散乱的无法管理各处响应的问题得以解决,通过构建一个`Symfony\Component\HttpFoundation\Response`对象来统一管理响应数据。可以控制`响应的HTTP头`、`响应内容`、`COOKIE`等等。可以响应`html内容`、`JSON数据`、`托管静态文件`、`数据流形式响应`等等常见的给客户端响应的数据形式。 14 | 15 | ## HttpKernel 16 | [`HttpKernel`](http://symfony.com/doc/current/components/http_kernel/index.html)组件提供了一个php框架所必须的基础,它是Symfony框架的基石,而且你还可以独立使用它来构建自己的框架。这个组件基于一套HTTP生命周期流程化的概念构建而成,流程主要关注如何将`Request`转换成`Response`,借助`EventDispatcher`组件,实现这个基于事件驱动的流程的核心,即整个HTTP生命周期流程化抽象的实现。 17 | 18 | 它基于的核心概念是在一个HTTP请求的生命周期中从`Request`到`Response`的流程化,即[`The Workflow of a Request`](http://symfony.com/doc/current/components/http_kernel/introduction.html#the-workflow-of-a-request)。另外它具有很高的可定制性,不但可以实现全栈型的重型框架\(Symfony\),又可以实现微型框架\(Silex\),或者是一个专业的CMS\(Drupal、eZPlatform\)。 19 | 20 | 它定义了一个HTTP请求的几个[`标准事件`](http://symfony.com/doc/current/components/http_kernel/introduction.html#component-http-kernel-event-table),使框架结构清晰明了,并且给开发者提供了完善的扩展度很高的流程控制框架,带来了比MVC更自由的开发体验。 21 | 22 | ## EventDispatcher 23 | [`EventDispatcher`](http://symfony.com/doc/current/components/event_dispatcher/introduction.html)组件是一个让项目具有更好的扩展性但不用修改原逻辑代码的工具,降低每个组件之间的耦合度,减少硬编码调用,使组件之间的调用只需要监听相应的事件和监听事件。Symfony的核心就是定义了一系列事件,触发,其余的框架组件只需要监听这些事件,多一个组件,只是多了一个监听,Kernel的代码没修改一行。 24 | 25 | 你可以在其它框架引入此组件,定义自己的流程并使用事件来组织各个模块的调用,使框架更灵活扩展,而且使流程控制代码和具体的处理逻辑分离,随时可进行模块的添加和升级。 26 | 27 | ## DependencyInjection 28 | [`DependencyInjection`](http://symfony.com/doc/current/components/dependency_injection/introduction.html)是Symfony框架的基础,标准化和集中对象在应用程序中构造的方式。脱离框架它也能独立使用,因为它本身没有太多强制性的依赖。它提供了一个服务容器,将定义的各种对象注入到这个容器中,每次需要一个对象不用再手动实例化它,只需要从容器中取出即可使用。Symfony中大部分功能都会注入到服务容器中,事件系统也是基于服务容器构建,它是Symfony可灵活扩展的基础,为企业系统开发提供了更可靠的基础架构。 29 | 30 | -------------------------------------------------------------------------------- /big_picture.md: -------------------------------------------------------------------------------- 1 | # Big Picture 2 | 通过分析Symfony的源代码,梳理顶层的运行逻辑,了解Symfony的最顶层事件。 3 | 4 | ## The Front Controller 5 | ```php 6 | // web/app.php 7 | use Symfony\Component\HttpFoundation\Request; 8 | ... 9 | 10 | $kernel = new AppKernel('prod', false); 11 | ... 12 | 13 | $request = Request::createFromGlobals(); 14 | $response = $kernel->handle($request); 15 | $response->send(); 16 | $kernel->terminate($request, $response); 17 | ``` 18 | 19 | [这段代码](https://github.com/symfony/symfony-standard/blob/v3.2.1/web/app.php)精简的构建了Symfony框架的生命周期,所有框架内的功能都是由这段代码调用和执行的。 20 | 21 | ``` 22 | 1. 实例化一个框架内核 23 | 2. 从php全局环境中创建Request 24 | 3. 将Request交给框架内核接管 25 | 4. 发送内核返回的Response内容 26 | 5. 内核结束一次“请求-响应” 27 | ``` 28 | 29 | ## Boot And Handle Request 30 | Symfony是从一个内核实例接管一个请求来启动的,通过创建一个继承了 `Symfony\Component\HttpKernel\Kernel` 的实例对象初始化一个Symfony应用。一个Symfony应用内核包含了一些当前运行的环境参数和注册的bundle参数以及一些其它的和应用相关的属性和方法构成,它本身不是运行具体处理请求逻辑的对象,而是一个构建运行环境的对象:bundle系统的初始化,服务容器的初始化等等。 31 | 32 | [默认的应用内核构造方法](https://github.com/symfony/symfony/blob/v3.2.1/src/Symfony/Component/HttpKernel/Kernel.php#L71): 33 | 34 | ```php 35 | // Symfony/Component/HttpKernel/Kernel.php 36 | 37 | abstract class Kernel implements KernelInterface, TerminableInterface 38 | { 39 | ... 40 | public function __construct($environment, $debug) 41 | { 42 | $this->environment = $environment; 43 | $this->debug = (bool) $debug; 44 | $this->rootDir = $this->getRootDir(); 45 | $this->name = $this->getName(); 46 | 47 | if ($this->debug) { 48 | $this->startTime = microtime(true); 49 | } 50 | } 51 | ... 52 | } 53 | ``` 54 | 55 | 当实例化一个应用内核时(new AppKernel\('prod', false\)): 56 | 57 | ```php 58 | ... 59 | public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) 60 | { 61 | if (false === $this->booted) { 62 | $this->boot(); 63 | } 64 | 65 | return $this->getHttpKernel()->handle($request, $type, $catch); 66 | } 67 | ... 68 | public function boot() 69 | { 70 | if (true === $this->booted) { 71 | return; 72 | } 73 | 74 | if ($this->loadClassCache) { 75 | $this->doLoadClassCache($this->loadClassCache[0], $this->loadClassCache[1]); 76 | } 77 | 78 | // init bundles 79 | $this->initializeBundles(); 80 | 81 | // init container 82 | $this->initializeContainer(); 83 | 84 | foreach ($this->getBundles() as $bundle) { 85 | $bundle->setContainer($this->container); 86 | $bundle->boot(); 87 | } 88 | 89 | $this->booted = true; 90 | } 91 | ... 92 | protected function getHttpKernel() 93 | { 94 | return $this->container->get('http_kernel'); 95 | } 96 | ... 97 | } 98 | ``` 99 | 应用内核的handle方法只有寥寥几行,它只做了两件事,调用自己的启动方法,然后从服务容器中取出 `http_kernel` 将Request交由其接管。从这里我们看出,其实Symfony的应用内核只是一个外壳,真正接管和处理请求的另有其人。这样做有一个很明显的优势:`http_kernel`服务可以被替换,可以扩展,可以自定义应用内核使用不同的服务,而所有的所有都是从容器开始,而应用内核在这里只是起到了一个初始化和引导启动的功能。那么我们就来看看默认的`http_kernel`是如何实现的,首先需要找到这个服务的配置文件: 100 | 101 | ```xml 102 | // framework-bundle/Resources/config/services.xml 103 | 104 | ... 105 | 106 | 107 | 108 | 109 | 110 | 111 | ... 112 | 113 | ``` 114 | 它来自Symfony的HttpKernel组件里的HttpKernel类,这个是官方实现的一个标准内核,当我们从服务容器中取出它时,是已经实例化的一个对象,在应用内核的handle方法中,初始化之后就调用了它的handle方法,让我们来一探handle方法: 115 | 116 | ```php 117 | // Symfony/Component/HttpKernel/HttpKernel.php 118 | class HttpKernel implements HttpKernelInterface, TerminableInterface 119 | { 120 | ... 121 | public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) 122 | { 123 | $request->headers->set('X-Php-Ob-Level', ob_get_level()); 124 | try { 125 | return $this->handleRaw($request, $type); 126 | } catch (\Exception $e) { 127 | if ($e instanceof ConflictingHeadersException) { 128 | $e = new BadRequestHttpException('The request headers contain conflicting information regarding the origin of this request.', $e); 129 | } 130 | if (false === $catch) { 131 | $this->finishRequest($request, $type); 132 | throw $e; 133 | } 134 | return $this->handleException($e, $request, $type); 135 | } 136 | } 137 | ... 138 | } 139 | ``` 140 | 141 | 这个方法里是真正处理请求和框架内部抛出的异常的地方,直接调用handleRaw方法,如果它抛出异常,那么对异常进行处理并返回一个特殊的Response,这段代码你会发现它的解说其实就在文档。我们一点点来看吧: 142 | 143 | ```php 144 | // Symfony/Component/HttpKernel/HttpKernel.php 145 | class HttpKernel implements HttpKernelInterface, TerminableInterface 146 | { 147 | ... 148 | private function handleRaw(Request $request, $type = self::MASTER_REQUEST) 149 | { 150 | $this->requestStack->push($request); 151 | // request 152 | $event = new GetResponseEvent($this, $request, $type); 153 | $this->dispatcher->dispatch(KernelEvents::REQUEST, $event); 154 | if ($event->hasResponse()) { 155 | return $this->filterResponse($event->getResponse(), $request, $type); 156 | } 157 | // load controller 158 | if (false === $controller = $this->resolver->getController($request)) { 159 | throw new NotFoundHttpException(sprintf('Unable to find the controller for path "%s". The route is wrongly configured.', $request->getPathInfo())); 160 | } 161 | $event = new FilterControllerEvent($this, $controller, $request, $type); 162 | $this->dispatcher->dispatch(KernelEvents::CONTROLLER, $event); 163 | $controller = $event->getController(); 164 | // controller arguments 165 | $arguments = $this->argumentResolver->getArguments($request, $controller); 166 | $event = new FilterControllerArgumentsEvent($this, $controller, $arguments, $request, $type); 167 | $this->dispatcher->dispatch(KernelEvents::CONTROLLER_ARGUMENTS, $event); 168 | $controller = $event->getController(); 169 | $arguments = $event->getArguments(); 170 | // call controller 171 | $response = call_user_func_array($controller, $arguments); 172 | // view 173 | if (!$response instanceof Response) { 174 | $event = new GetResponseForControllerResultEvent($this, $request, $type, $response); 175 | $this->dispatcher->dispatch(KernelEvents::VIEW, $event); 176 | if ($event->hasResponse()) { 177 | $response = $event->getResponse(); 178 | } 179 | if (!$response instanceof Response) { 180 | $msg = sprintf('The controller must return a response (%s given).', $this->varToString($response)); 181 | // the user may have forgotten to return something 182 | if (null === $response) { 183 | $msg .= ' Did you forget to add a return statement somewhere in your controller?'; 184 | } 185 | throw new \LogicException($msg); 186 | } 187 | } 188 | return $this->filterResponse($response, $request, $type); 189 | } 190 | ... 191 | private function filterResponse(Response $response, Request $request, $type) 192 | { 193 | $event = new FilterResponseEvent($this, $request, $type, $response); 194 | $this->dispatcher->dispatch(KernelEvents::RESPONSE, $event); 195 | $this->finishRequest($request, $type); 196 | return $event->getResponse(); 197 | } 198 | ... 199 | private function finishRequest(Request $request, $type) 200 | { 201 | $this->dispatcher->dispatch(KernelEvents::FINISH_REQUEST, new FinishRequestEvent($this, $request, $type)); 202 | $this->requestStack->pop(); 203 | } 204 | ... 205 | } 206 | ``` 207 | 208 | 从代码中不难看出,这里的实现和文档中的图例讲解一模一样: 209 | 210 | ![](/assets/01-workflow.png) 211 | 212 | 图中的Workflow便是此段代码的图示,其中的每个方块是一个事件 213 | 214 | 1. KernelEvents::REQUEST 215 | 2. KernelEvents::CONTROLLER 216 | 3. KernelEvents::CONTROLLER_ARGUMENTS 217 | 4. KernelEvents::VIEW 218 | 5. KernelEvents::RESPONSE 219 | 6. KernelEvents::FINISH_REQUEST 220 | 221 | 最后这些流程中一旦出现异常,会被外层捕获,并触发 `KernelEvents::FINISH_REQUEST`和`KernelEvents::EXCEPTION`,最后在`Front Controller`里调用应用内核的terminate方法触发`KernelEvents::TERMINATE`。 222 | 223 | ## Terminate 224 | ```php 225 | // web/app.php 226 | use Symfony\Component\HttpFoundation\Request; 227 | ... 228 | $response = $kernel->handle($request); 229 | $response->send(); 230 | $kernel->terminate($request, $response); 231 | ``` 232 | 最终,当我们从应用内核获得一个响应之后,直接发送它,然后结束整个应用。 233 | 234 | ## 总结 235 | 这些事件点中除了必要的逻辑分支之外,只用了两行代码,如此简短的代码便是Symfony的核心流程,你可能这时会想到,其实Symfony的文档[从一开始](http://symfony.com/doc/current/introduction/http_fundamentals.html)就在灌输这种Request-Response生命周期的概念,因为它自始至终确实就是这么架构的。 236 | 237 | Symfony用事件机制代替了硬编码的回调调用,而基于事件化整个框架,为Web开发添加了更灵活的架构方式,让程序更容易扩展,而且它提供了一个很棒的思路:**业务逻辑为何不以同样的思想来组织呢?** --------------------------------------------------------------------------------