)
27 |
28 | [许式伟的架构课](http://gk.link/a/102UP)
29 |
30 |
31 |
32 | 当然还有日新月异的前端知识也是需要会基础的用法的,最起码了解一下团队内部使用的前端框架的基础知识,这样对咱们做系统设计也会有帮助。
33 |
--------------------------------------------------------------------------------
/articles/HttpKernel.md:
--------------------------------------------------------------------------------
1 | # Http Kernel
2 |
3 | Http Kernel是Laravel中用来串联框架的各个核心组件来网络请求的,简单的说只要是通过`public/index.php`来启动框架的都会用到Http Kernel,而另外的类似通过`artisan`命令、计划任务、队列启动框架进行处理的都会用到Console Kernel, 今天我们先梳理一下Http Kernel做的事情。
4 |
5 |
6 |
7 | ### 内核绑定
8 |
9 | 既然Http Kernel是Laravel中用来串联框架的各个部分处理网络请求的,我们来看一下内核是怎么加载到Laravel中应用实例中来的,在`public/index.php`中我们就会看见首先就会通过`bootstrap/app.php`这个脚手架文件来初始化应用程序:
10 |
11 |
12 |
13 | 下面是 `bootstrap/app.php` 的代码,包含两个主要部分**创建应用实例**和**绑定内核至 APP 服务容器**
14 |
15 | ```
16 | singleton(
24 | Illuminate\Contracts\Http\Kernel::class,
25 | App\Http\Kernel::class
26 | );
27 |
28 | $app->singleton(
29 | Illuminate\Contracts\Console\Kernel::class,
30 | App\Console\Kernel::class
31 | );
32 |
33 | $app->singleton(
34 | Illuminate\Contracts\Debug\ExceptionHandler::class,
35 | App\Exceptions\Handler::class
36 | );
37 |
38 | return $app;
39 | ```
40 |
41 | HTTP 内核继承自 Illuminate\Foundation\Http\Kernel类,在 HTTP 内核中 内它定义了中间件相关数组, 中间件提供了一种方便的机制来过滤进入应用的 HTTP 请求和加工流出应用的HTTP响应。
42 |
43 | ```
44 | [
70 | \App\Http\Middleware\EncryptCookies::class,
71 | \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
72 | \Illuminate\Session\Middleware\StartSession::class,
73 | // \Illuminate\Session\Middleware\AuthenticateSession::class,
74 | \Illuminate\View\Middleware\ShareErrorsFromSession::class,
75 | \App\Http\Middleware\VerifyCsrfToken::class,
76 | \Illuminate\Routing\Middleware\SubstituteBindings::class,
77 | ],
78 | 'api' => [
79 | 'throttle:60,1',
80 | 'bindings',
81 | ],
82 | ];
83 | /**
84 | * The application's route middleware.
85 | *
86 | * These middleware may be assigned to groups or used individually.
87 | *
88 | * @var array
89 | */
90 | protected $routeMiddleware = [
91 | 'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
92 | 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
93 | 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
94 | 'can' => \Illuminate\Auth\Middleware\Authorize::class,
95 | 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
96 | 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
97 | ];
98 | }
99 | ```
100 |
101 |
102 |
103 | 在其父类 「Illuminate\Foundation\Http\Kernel」 内部定义了属性名为 「bootstrappers」 的 引导程序 数组:
104 |
105 | ```
106 | protected $bootstrappers = [
107 | \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
108 | \Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
109 | \Illuminate\Foundation\Bootstrap\HandleExceptions::class,
110 | \Illuminate\Foundation\Bootstrap\RegisterFacades::class,
111 | \Illuminate\Foundation\Bootstrap\RegisterProviders::class,
112 | \Illuminate\Foundation\Bootstrap\BootProviders::class,
113 | ];
114 | ```
115 |
116 |
117 |
118 | 引导程序组中 包括完成环境检测、配置加载、异常处理、Facades 注册、服务提供者注册、启动服务这六个引导程序。
119 |
120 | 有关中间件和引导程序相关内容的讲解可以浏览我们之前相关章节的内容。
121 |
122 | ### 应用解析内核
123 |
124 | 在将应用初始化阶段将Http内核绑定至应用的服务容器后,紧接着在`public/index.php`中我们可以看到使用了服务容器的`make`方法将Http内核实例解析了出来:
125 |
126 | ```
127 | $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
128 | ```
129 |
130 | 在实例化内核时,将在 HTTP 内核中定义的中间件注册到了 [路由器](https://github.com/laravel/framework/blob/5.6/src/Illuminate/Routing/Router.php),注册完后就可以在实际处理 HTTP 请求前调用路由上应用的中间件实现过滤请求的目的:
131 |
132 | ```
133 | namespace Illuminate\Foundation\Http;
134 | ...
135 | class Kernel implements KernelContract
136 | {
137 | /**
138 | * Create a new HTTP kernel instance.
139 | *
140 | * @param \Illuminate\Contracts\Foundation\Application $app
141 | * @param \Illuminate\Routing\Router $router
142 | * @return void
143 | */
144 | public function __construct(Application $app, Router $router)
145 | {
146 | $this->app = $app;
147 | $this->router = $router;
148 |
149 | $router->middlewarePriority = $this->middlewarePriority;
150 |
151 | foreach ($this->middlewareGroups as $key => $middleware) {
152 | $router->middlewareGroup($key, $middleware);
153 | }
154 |
155 | foreach ($this->routeMiddleware as $key => $middleware) {
156 | $router->aliasMiddleware($key, $middleware);
157 | }
158 | }
159 | }
160 |
161 | namespace Illuminate/Routing;
162 | class Router implements RegistrarContract, BindingRegistrar
163 | {
164 | /**
165 | * Register a group of middleware.
166 | *
167 | * @param string $name
168 | * @param array $middleware
169 | * @return $this
170 | */
171 | public function middlewareGroup($name, array $middleware)
172 | {
173 | $this->middlewareGroups[$name] = $middleware;
174 |
175 | return $this;
176 | }
177 |
178 | /**
179 | * Register a short-hand name for a middleware.
180 | *
181 | * @param string $name
182 | * @param string $class
183 | * @return $this
184 | */
185 | public function aliasMiddleware($name, $class)
186 | {
187 | $this->middleware[$name] = $class;
188 |
189 | return $this;
190 | }
191 | }
192 | ```
193 |
194 | ### 处理HTTP请求
195 |
196 | 通过服务解析完成Http内核实例的创建后就可以用HTTP内核实例来处理HTTP请求了
197 |
198 | ```
199 | //public/index.php
200 | $response = $kernel->handle(
201 | $request = Illuminate\Http\Request::capture()
202 | );
203 | ```
204 |
205 | 在处理请求之前会先通过`Illuminate\Http\Request`的 `capture()` 方法以进入应用的HTTP请求的信息为基础创建出一个 Laravel Request请求实例,在后续应用剩余的生命周期中`Request`请求实例就是对本次HTTP请求的抽象,关于[Laravel Request请求实例](https://github.com/kevinyan815/Learning_Laravel_Kernel/blob/master/articles/Request.md)的讲解可以参考以前的章节。
206 |
207 | 将HTTP请求抽象成`Laravel Request请求实例`后,请求实例会被传导进入到HTTP内核的`handle`方法内部,请求的处理就是由`handle`方法来完成的。
208 |
209 | ```
210 | namespace Illuminate\Foundation\Http;
211 |
212 | class Kernel implements KernelContract
213 | {
214 | /**
215 | * Handle an incoming HTTP request.
216 | *
217 | * @param \Illuminate\Http\Request $request
218 | * @return \Illuminate\Http\Response
219 | */
220 | public function handle($request)
221 | {
222 | try {
223 | $request->enableHttpMethodParameterOverride();
224 |
225 | $response = $this->sendRequestThroughRouter($request);
226 | } catch (Exception $e) {
227 | $this->reportException($e);
228 |
229 | $response = $this->renderException($request, $e);
230 | } catch (Throwable $e) {
231 | $this->reportException($e = new FatalThrowableError($e));
232 |
233 | $response = $this->renderException($request, $e);
234 | }
235 |
236 | $this->app['events']->dispatch(
237 | new Events\RequestHandled($request, $response)
238 | );
239 |
240 | return $response;
241 | }
242 | }
243 | ```
244 |
245 | `handle` 方法接收一个请求对象,并最终生成一个响应对象。其实`handle`方法我们已经很熟悉了在讲解很多模块的时候都是以它为出发点逐步深入到模块的内部去讲解模块内的逻辑的,其中`sendRequestThroughRouter`方法在服务提供者和中间件都提到过,它会加载在内核中定义的引导程序来引导启动应用然后会将使用`Pipeline`对象传输HTTP请求对象流经框架中定义的HTTP中间件们和路由中间件们来完成过滤请求最终将请求传递给处理程序(控制器方法或者路由中的闭包)由处理程序返回相应的响应。关于`handle`方法的注解我直接引用以前章节的讲解放在这里,具体更详细的分析具体是如何引导启动应用以及如何将传输流经各个中间件并到达处理程序的内容请查看[服务提供器](https://github.com/kevinyan815/Learning_Laravel_Kernel/blob/master/articles/ServiceProvider.md)、[中间件](https://github.com/kevinyan815/Learning_Laravel_Kernel/blob/master/articles/Middleware.md)还有[路由](https://github.com/kevinyan815/Learning_Laravel_Kernel/blob/master/articles/Route.md)这三个章节。
246 |
247 | ```
248 | protected function sendRequestThroughRouter($request)
249 | {
250 | $this->app->instance('request', $request);
251 |
252 | Facade::clearResolvedInstance('request');
253 |
254 | $this->bootstrap();
255 |
256 | return (new Pipeline($this->app))
257 | ->send($request)
258 | ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
259 | ->then($this->dispatchToRouter());
260 | }
261 |
262 | /*引导启动Laravel应用程序
263 | 1. DetectEnvironment 检查环境
264 | 2. LoadConfiguration 加载应用配置
265 | 3. ConfigureLogging 配置日至
266 | 4. HandleException 注册异常处理的Handler
267 | 5. RegisterFacades 注册Facades
268 | 6. RegisterProviders 注册Providers
269 | 7. BootProviders 启动Providers
270 | */
271 | public function bootstrap()
272 | {
273 | if (! $this->app->hasBeenBootstrapped()) {
274 | /**依次执行$bootstrappers中每一个bootstrapper的bootstrap()函数
275 | $bootstrappers = [
276 | 'Illuminate\Foundation\Bootstrap\DetectEnvironment',
277 | 'Illuminate\Foundation\Bootstrap\LoadConfiguration',
278 | 'Illuminate\Foundation\Bootstrap\ConfigureLogging',
279 | 'Illuminate\Foundation\Bootstrap\HandleExceptions',
280 | 'Illuminate\Foundation\Bootstrap\RegisterFacades',
281 | 'Illuminate\Foundation\Bootstrap\RegisterProviders',
282 | 'Illuminate\Foundation\Bootstrap\BootProviders',
283 | ];*/
284 | $this->app->bootstrapWith($this->bootstrappers());
285 | }
286 | }
287 | ```
288 |
289 | ### 发送响应
290 |
291 | 经过上面的几个阶段后我们最终拿到了要返回的响应,接下来就是发送响应了。
292 |
293 | ```
294 | //public/index.php
295 | $response = $kernel->handle(
296 | $request = Illuminate\Http\Request::capture()
297 | );
298 |
299 | // 发送响应
300 | $response->send();
301 | ```
302 |
303 | 发送响应由 `Illuminate\Http\Response`的`send()`方法完成父类其定义在父类`Symfony\Component\HttpFoundation\Response`中。
304 |
305 | ```
306 | public function send()
307 | {
308 | $this->sendHeaders();// 发送响应头部信息
309 | $this->sendContent();// 发送报文主题
310 |
311 | if (function_exists('fastcgi_finish_request')) {
312 | fastcgi_finish_request();
313 | } elseif (!\in_array(PHP_SAPI, array('cli', 'phpdbg'), true)) {
314 | static::closeOutputBuffers(0, true);
315 | }
316 | return $this;
317 | }
318 | ```
319 |
320 | 关于Response对象的详细分析可以参看我们之前讲解Laravel Response对象的章节。
321 |
322 | ### 终止应用程序
323 |
324 | 响应发送后,HTTP内核会调用`terminable`中间件做一些后续的处理工作。比如,Laravel 内置的「session」中间件会在响应发送到浏览器之后将会话数据写入存储器中。
325 |
326 | ```
327 | // public/index.php
328 | // 终止程序
329 | $kernel->terminate($request, $response);
330 | ```
331 |
332 |
333 |
334 | ```
335 | //Illuminate\Foundation\Http\Kernel
336 | public function terminate($request, $response)
337 | {
338 | $this->terminateMiddleware($request, $response);
339 | $this->app->terminate();
340 | }
341 |
342 | // 终止中间件
343 | protected function terminateMiddleware($request, $response)
344 | {
345 | $middlewares = $this->app->shouldSkipMiddleware() ? [] : array_merge(
346 | $this->gatherRouteMiddleware($request),
347 | $this->middleware
348 | );
349 | foreach ($middlewares as $middleware) {
350 | if (! is_string($middleware)) {
351 | continue;
352 | }
353 | list($name, $parameters) = $this->parseMiddleware($middleware);
354 | $instance = $this->app->make($name);
355 | if (method_exists($instance, 'terminate')) {
356 | $instance->terminate($request, $response);
357 | }
358 | }
359 | }
360 | ```
361 |
362 | Http内核的`terminate`方法会调用`teminable`中间件的`terminate`方法,调用完成后从HTTP请求进来到返回响应整个应用程序的生命周期就结束了。
363 |
364 | ### 总结
365 |
366 | 本节介绍的HTTP内核起到的主要是串联作用,其中设计到的初始化应用、引导应用、将HTTP请求抽象成Request对象、传递Request对象通过中间件到达处理程序生成响应以及响应发送给客户端。这些东西在之前的章节里都有讲过,并没有什么新的东西,希望通过这篇文章能让大家把之前文章里讲到的每个点串成一条线,这样对Laravel整体是怎么工作的会有更清晰的概念。
367 |
368 | 上一篇: [加载和读取ENV配置](https://github.com/kevinyan815/Learning_Laravel_Kernel/blob/master/articles/ENV.md)
369 |
370 | 下一篇: [Console内核](https://github.com/kevinyan815/Learning_Laravel_Kernel/blob/master/articles/ConsoleKernel.md)
371 |
372 |
--------------------------------------------------------------------------------
/articles/IocContainer.md:
--------------------------------------------------------------------------------
1 | # 服务容器(IocContainer)
2 |
3 | Laravel的核心是IocContainer, 文档中称其为“服务容器”,服务容器是一个用于管理类依赖和执行依赖注入的强大工具,Laravel中的功能模块比如 Route、Eloquent ORM、Request、Response等等等等,实际上都是与核心无关的类模块提供的,这些类从注册到实例化,最终被我们所使用,其实都是 laravel 的服务容器负责的。
4 |
5 | 如果对服务容器是什么没有清晰概念的话推荐一篇博文来了解一下服务容器的来龙去脉:[laravel神奇的服务容器](https://www.insp.top/learn-laravel-container)
6 |
7 | 服务容器中有两个概念[控制反转(IOC)和依赖注入(DI)](http://blog.csdn.net/doris_crazy/article/details/18353197):
8 |
9 | >依赖注入和控制反转是对同一件事情的不同描述,它们描述的角度不同。依赖注入是从应用程序的角度在描述,应用程序依赖容器创建并注入它所需要的外部资源。而控制反转是从容器的角度在描述,容器控制应用程序,由容器反向的向应用程序注入应用程序所需要的外部资源。
10 |
11 | 在Laravel中框架把自带的各种服务绑定到服务容器,我们也可以绑定自定义服务到容器。当应用程序需要使用某一个服务时,服务容器会讲服务解析出来同时自动解决服务之间的依赖然后交给应用程序使用。
12 |
13 | 本篇就来探讨一下Laravel中的服务绑定和解析是如何实现的
14 |
15 | ## 服务绑定
16 | 常用的绑定服务到容器的方法有instance, bind, singleton, alias。下面我们分别来看一下。
17 |
18 | ### instance
19 | 将一个已存在的对象绑定到服务容器里,随后通过名称解析该服务时,容器将总返回这个绑定的实例。
20 |
21 | $api = new HelpSpot\API(new HttpClient);
22 | $this->app->instance('HelpSpot\Api', $api);
23 |
24 | 会把对象注册到服务容器的$instances属性里
25 |
26 | [
27 | 'HelpSpot\Api' => $api//$api是API类的对象,这里简写了
28 | ]
29 |
30 | ### bind
31 | 绑定服务到服务容器
32 |
33 | 有三种绑定方式:
34 |
35 | 1.绑定自身
36 | $this->app->bind('HelpSpot\API', null);
37 |
38 | 2.绑定闭包
39 | $this->app->bind('HelpSpot\API', function () {
40 | return new HelpSpot\API();
41 | });//闭包直接提供类实现方式
42 | $this->app->bind('HelpSpot\API', function ($app) {
43 | return new HelpSpot\API($app->make('HttpClient'));
44 | });//闭包返回需要依赖注入的类
45 | 3. 绑定接口和实现
46 | $this->app->bind('Illuminate\Tests\Container\IContainerContractStub', 'Illuminate\Tests\Container\ContainerImplementationStub');
47 |
48 |
49 | 针对第一种情况,其实在bind方法内部会在绑定服务之前通过`getClosure()`为服务生成闭包,我们来看一下bind方法源码。
50 |
51 | public function bind($abstract, $concrete = null, $shared = false)
52 | {
53 | $abstract = $this->normalize($abstract);
54 |
55 | $concrete = $this->normalize($concrete);
56 | //如果$abstract为数组类似['Illuminate/ServiceName' => 'service_alias']
57 | //抽取别名"service_alias"并且注册到$aliases[]中
58 | //注意:数组绑定别名的方式在5.4中被移除,别名绑定请使用下面的alias方法
59 | if (is_array($abstract)) {
60 | list($abstract, $alias) = $this->extractAlias($abstract);
61 |
62 | $this->alias($abstract, $alias);
63 | }
64 |
65 | $this->dropStaleInstances($abstract);
66 |
67 | if (is_null($concrete)) {
68 | $concrete = $abstract;
69 | }
70 | //如果只提供$abstract,则在这里为其生成concrete闭包
71 | if (! $concrete instanceof Closure) {
72 | $concrete = $this->getClosure($abstract, $concrete);
73 | }
74 |
75 | $this->bindings[$abstract] = compact('concrete', 'shared');
76 |
77 | if ($this->resolved($abstract)) {
78 | $this->rebound($abstract);
79 | }
80 | }
81 |
82 |
83 | protected function getClosure($abstract, $concrete)
84 | {
85 | // $c 就是$container,即服务容器,会在回调时传递给这个变量
86 | return function ($c, $parameters = []) use ($abstract, $concrete) {
87 | $method = ($abstract == $concrete) ? 'build' : 'make';
88 |
89 | return $c->$method($concrete, $parameters);
90 | };
91 | }
92 |
93 |
94 |
95 |
96 | bind把服务注册到服务容器的$bindings属性里类似这样:
97 |
98 | $bindings = [
99 | 'HelpSpot\API' => [//闭包绑定
100 | 'concrete' => function ($app, $paramters = []) {
101 | return $app->build('HelpSpot\API');
102 | },
103 | 'shared' => false//如果是singleton绑定,这个值为true
104 | ]
105 | 'Illuminate\Tests\Container\IContainerContractStub' => [//接口实现绑定
106 | 'concrete' => 'Illuminate\Tests\Container\ContainerImplementationStub',
107 | 'shared' => false
108 | ]
109 | ]
110 |
111 |
112 | ### singleton
113 |
114 | public function singleton($abstract, $concrete = null)
115 | {
116 | $this->bind($abstract, $concrete, true);
117 | }
118 |
119 | singleton 方法是bind方法的变种,绑定一个只需要解析一次的类或接口到容器,然后接下来对于容器的调用该服务将会返回同一个实例
120 |
121 | ### alias
122 | 把服务和服务别名注册到容器:
123 |
124 | public function alias($abstract, $alias)
125 | {
126 | $this->aliases[$alias] = $this->normalize($abstract);
127 | }
128 | alias 方法在上面讲bind方法里有用到过,它会把把服务别名和服务类的对应关系注册到服务容器的$aliases属性里。
129 | 例如:
130 | $this->app->alias('\Illuminate\ServiceName', 'service_alias');
131 | 绑定完服务后在使用时就可以通过
132 | $this->app->make('service_alias');
133 | 将服务对象解析出来,这样make的时候就不用写那些比较长的类名称了,对make方法的使用体验上有很大提升。
134 |
135 |
136 | ## 服务解析
137 | make: 从服务容器中解析出服务对象,该方法接收你想要解析的类名或接口名作为参数
138 |
139 | /**
140 | * Resolve the given type from the container.
141 | *
142 | * @param string $abstract
143 | * @param array $parameters
144 | * @return mixed
145 | */
146 | public function make($abstract, array $parameters = [])
147 | {
148 | // getAlias方法会假定$abstract是绑定的别名,从$aliases找到映射的真实类型名
149 | // 如果没有映射则$abstract即为真实类型名,将$abstract原样返回
150 | $abstract = $this->getAlias($this->normalize($abstract));
151 |
152 | // 如果服务是通过instance()方式绑定的,就直接解析返回绑定的service
153 | if (isset($this->instances[$abstract])) {
154 | return $this->instances[$abstract];
155 | }
156 |
157 | // 获取$abstract接口对应的$concrete(接口的实现)
158 | $concrete = $this->getConcrete($abstract);
159 |
160 | if ($this->isBuildable($concrete, $abstract)) {
161 | $object = $this->build($concrete, $parameters);
162 | } else {
163 | // 如果时接口实现这种绑定方式,通过接口拿到实现后需要再make一次才能
164 | // 满足isBuildable的条件 ($abstract === $concrete)
165 | $object = $this->make($concrete, $parameters);
166 | }
167 |
168 | foreach ($this->getExtenders($abstract) as $extender) {
169 | $object = $extender($object, $this);
170 | }
171 |
172 | // 如果服务是以singleton方式注册进来的则,把构建好的服务对象放到$instances里,
173 | // 避免下次使用时重新构建
174 | if ($this->isShared($abstract)) {
175 | $this->instances[$abstract] = $object;
176 | }
177 |
178 | $this->fireResolvingCallbacks($abstract, $object);
179 |
180 | $this->resolved[$abstract] = true;
181 |
182 | return $object;
183 | }
184 |
185 | protected function getConcrete($abstract)
186 | {
187 | if (! is_null($concrete = $this->getContextualConcrete($abstract))) {
188 | return $concrete;
189 | }
190 |
191 | // 如果是$abstract之前没有注册类实现到服务容器里,则服务容器会认为$abstract本身就是接口的类实现
192 | if (! isset($this->bindings[$abstract])) {
193 | return $abstract;
194 | }
195 |
196 | return $this->bindings[$abstract]['concrete'];
197 | }
198 |
199 | protected function isBuildable($concrete, $abstract)
200 | {
201 | return $concrete === $abstract || $concrete instanceof Closure;
202 | }
203 |
204 |
205 |
206 | 通过对make方法的梳理我们发现,build方法的职能是构建解析出来的服务的对象的,下面看一下构建对象的具体流程。(构建过程中用到了[PHP类的反射][1]来实现服务的依赖注入)
207 |
208 |
209 | public function build($concrete, array $parameters = [])
210 | {
211 | // 如果是闭包直接执行闭包并返回(对应闭包绑定)
212 | if ($concrete instanceof Closure) {
213 | return $concrete($this, $parameters);
214 | }
215 |
216 | // 使用反射ReflectionClass来对实现类进行反向工程
217 | $reflector = new ReflectionClass($concrete);
218 |
219 | // 如果不能实例化,这应该是接口或抽象类,再或者就是构造函数是private的
220 | if (! $reflector->isInstantiable()) {
221 | if (! empty($this->buildStack)) {
222 | $previous = implode(', ', $this->buildStack);
223 |
224 | $message = "Target [$concrete] is not instantiable while building [$previous].";
225 | } else {
226 | $message = "Target [$concrete] is not instantiable.";
227 | }
228 |
229 | throw new BindingResolutionException($message);
230 | }
231 |
232 | $this->buildStack[] = $concrete;
233 |
234 | // 获取构造函数
235 | $constructor = $reflector->getConstructor();
236 |
237 | // 如果构造函数是空,说明没有任何依赖,直接new返回
238 | if (is_null($constructor)) {
239 | array_pop($this->buildStack);
240 |
241 | return new $concrete;
242 | }
243 |
244 | // 获取构造函数的依赖(形参),返回一组ReflectionParameter对象组成的数组表示每一个参数
245 | $dependencies = $constructor->getParameters();
246 |
247 | $parameters = $this->keyParametersByArgument(
248 | $dependencies, $parameters
249 | );
250 |
251 | // 构建构造函数需要的依赖
252 | $instances = $this->getDependencies(
253 | $dependencies, $parameters
254 | );
255 |
256 | array_pop($this->buildStack);
257 |
258 | return $reflector->newInstanceArgs($instances);
259 | }
260 |
261 | // 获取依赖
262 | protected function getDependencies(array $parameters, array $primitives = [])
263 | {
264 | $dependencies = [];
265 |
266 | foreach ($parameters as $parameter) {
267 | $dependency = $parameter->getClass();
268 |
269 | // 某一依赖值在$primitives中(如:app()->make(ApiService::class, ['clientId' => 'id'])调用时$primitives里会包含ApiService类构造方法中参数$clientId的参数值)已提供
270 | // $parameter->name返回参数名
271 | if (array_key_exists($parameter->name, $primitives)) {
272 | $dependencies[] = $primitives[$parameter->name];
273 | }
274 | elseif (is_null($dependency)) {
275 | // 参数的ReflectionClass为null,说明是基本类型,如'int','string'
276 | $dependencies[] = $this->resolveNonClass($parameter);
277 | } else {
278 | // 参数是一个类的对象, 则用resolveClass去把对象解析出来
279 | $dependencies[] = $this->resolveClass($parameter);
280 | }
281 | }
282 |
283 | return $dependencies;
284 | }
285 |
286 | // 解析出依赖类的对象
287 | protected function resolveClass(ReflectionParameter $parameter)
288 | {
289 | try {
290 | // $parameter->getClass()->name返回的是类名(参数在typehint里声明的类型)
291 | // 然后递归继续make(在make时发现依赖类还有其他依赖,那么会继续make依赖的依赖
292 | // 直到所有依赖都被解决了build才结束)
293 | return $this->make($parameter->getClass()->name);
294 | } catch (BindingResolutionException $e) {
295 | if ($parameter->isOptional()) {
296 | return $parameter->getDefaultValue();
297 | }
298 |
299 | throw $e;
300 | }
301 | }
302 |
303 |
304 | 服务容器就是laravel的核心, 它通过依赖注入很好的替我们解决对象之间的相互依赖关系,而又通过控制反转让外部来来定义具体的行为(Route, Eloquent这些都是外部模块,它们自己定义了行为规范,这些类从注册到实例化给你使用才是服务容器负责的)。
305 |
306 | 一个类要被容器所能够提取,必须要先注册至这个容器。既然 laravel 称这个容器叫做服务容器,那么我们需要某个服务,就得先注册、绑定这个服务到容器,那么提供服务并绑定服务至容器的东西就是服务提供器(ServiceProvider)。服务提供者主要分为两个部分:register(注册) 和 boot(引导、初始化)这就引出了我们后面要学习的内容。
307 |
308 | 上一篇: [类的反射和依赖注入][1]
309 |
310 | 下一篇: [服务提供器][3]
311 |
312 |
313 | [1]: https://github.com/kevinyan815/Learning_Laravel_Kernel/blob/master/articles/reflection.md
314 | [3]: https://github.com/kevinyan815/Learning_Laravel_Kernel/blob/master/articles/ServiceProvider.md
315 |
--------------------------------------------------------------------------------
/articles/Middleware.md:
--------------------------------------------------------------------------------
1 | # 中间件
2 |
3 | 中间件(Middleware)在Laravel中起着过滤进入应用的HTTP请求对象(Request)和完善离开应用的HTTP响应对象(Reponse)的作用, 而且可以通过应用多个中间件来层层过滤请求、逐步完善响应。这样就做到了程序的解耦,如果没有中间件那么我们必须在控制器中来完成这些步骤,这无疑会造成控制器的臃肿。
4 |
5 | 举一个简单的例子,在一个电商平台上用户既可以是一个普通用户在平台上购物也可以在开店后是一个卖家用户,这两种用户的用户体系往往都是一套,那么在只有卖家用户才能访问的控制器里我们只需要应用两个中间件来完成卖家用户的身份认证:
6 |
7 | ```
8 | class MerchantController extends Controller
9 | {
10 | public function __construct()
11 | {
12 | $this->middleware('auth');
13 | $this->middleware('mechatnt_auth');
14 | }
15 | }
16 | ```
17 | 在auth中间件里做了通用的用户认证,成功后HTTP Request会走到merchant_auth中间件里进行商家用户信息的认证,两个中间件都通过后HTTP Request就能进入到要去的控制器方法中了。利用中间件,我们就能把这些认证代码抽离到对应的中间件中了,而且可以根据需求自由组合多个中间件来对HTTP Request进行过滤。
18 |
19 | 再比如Laravel自动给所有路由应用的`VerifyCsrfToken`中间件,在HTTP Requst进入应用走过`VerifyCsrfToken`中间件时会验证Token防止跨站请求伪造,在Http Response 离开应用前会给响应添加合适的Cookie。(laravel5.5开始CSRF中间件只自动应用到web路由上)
20 |
21 | 上面例子中过滤请求的叫前置中间件,完善响应的叫做后置中间件。用一张图可以标示整个流程:
22 | 
23 |
24 |
25 | 上面概述了下中间件在laravel中的角色,以及什么类型的代码应该从控制器挪到中间件里,至于如何定义和使用自己的laravel 中间件请参考[官方文档](https://d.laravel-china.org/docs/5.5/middleware)。
26 |
27 | 下面我们主要来看一下Laravel中是怎么实现中间件的,中间件的设计应用了一种叫做装饰器的设计模式,如果你还不知道什么是装饰器模式可以查阅设计模式相关的书,也可以翻看我之前的文章[装饰模式(DecoratorPattern)](https://github.com/kevinyan815/Learning_Laravel_Kernel/blob/master/articles/DecoratorPattern.md)。
28 |
29 | Laravel实例化Application后,会从服务容器里解析出Http Kernel对象,通过类的名字也能看出来Http Kernel就是Laravel里负责HTTP请求和响应的核心。
30 |
31 | ```
32 | /**
33 | * @var \App\Http\Kernel $kernel
34 | */
35 | $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
36 |
37 | $response = $kernel->handle(
38 | $request = Illuminate\Http\Request::capture()
39 | );
40 |
41 | $response->send();
42 |
43 | $kernel->terminate($request, $response);
44 | ```
45 | 在`index.php`里可以看到,从服务容器里解析出Http Kernel,因为在`bootstrap/app.php`里绑定了`Illuminate\Contracts\Http\Kernel`接口的实现类`App\Http\Kernel`所以$kernel实际上是`App\Http\Kernel`类的对象。
46 | 解析出Http Kernel后Laravel将进入应用的请求对象传递给Http Kernel的handle方法,在handle方法负责处理流入应用的请求对象并返回响应对象。
47 |
48 | /**
49 | * Handle an incoming HTTP request.
50 | *
51 | * @param \Illuminate\Http\Request $request
52 | * @return \Illuminate\Http\Response
53 | */
54 | public function handle($request)
55 | {
56 | try {
57 | $request->enableHttpMethodParameterOverride();
58 |
59 | $response = $this->sendRequestThroughRouter($request);
60 | } catch (Exception $e) {
61 | $this->reportException($e);
62 |
63 | $response = $this->renderException($request, $e);
64 | } catch (Throwable $e) {
65 | $this->reportException($e = new FatalThrowableError($e));
66 |
67 | $response = $this->renderException($request, $e);
68 | }
69 |
70 | $this->app['events']->dispatch(
71 | new Events\RequestHandled($request, $response)
72 | );
73 |
74 | return $response;
75 | }
76 |
77 | 中间件过滤应用的过程就发生在`$this->sendRequestThroughRouter($request)`里:
78 |
79 | /**
80 | * Send the given request through the middleware / router.
81 | *
82 | * @param \Illuminate\Http\Request $request
83 | * @return \Illuminate\Http\Response
84 | */
85 | protected function sendRequestThroughRouter($request)
86 | {
87 | $this->app->instance('request', $request);
88 |
89 | Facade::clearResolvedInstance('request');
90 |
91 | $this->bootstrap();
92 |
93 | return (new Pipeline($this->app))
94 | ->send($request)
95 | ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
96 | ->then($this->dispatchToRouter());
97 | }
98 |
99 | 这个方法的前半部分是对Application进行了初始化,在上一面讲解服务提供器的文章里有对这一部分的详细讲解。Laravel通过Pipeline(管道)对象来传输请求对象,在Pipeline中请求对象依次通过Http Kernel里定义的中间件的前置操作到达控制器的某个action或者直接闭包处理得到响应对象。
100 |
101 | 看下Pipeline里这几个方法:
102 |
103 | public function send($passable)
104 | {
105 | $this->passable = $passable;
106 |
107 | return $this;
108 | }
109 |
110 | public function through($pipes)
111 | {
112 | $this->pipes = is_array($pipes) ? $pipes : func_get_args();
113 |
114 | return $this;
115 | }
116 |
117 | public function then(Closure $destination)
118 | {
119 | $firstSlice = $this->getInitialSlice($destination);
120 |
121 | //pipes 就是要通过的中间件
122 | $pipes = array_reverse($this->pipes);
123 |
124 | //$this->passable就是Request对象
125 | return call_user_func(
126 | array_reduce($pipes, $this->getSlice(), $firstSlice), $this->passable
127 | );
128 | }
129 |
130 |
131 | protected function getInitialSlice(Closure $destination)
132 | {
133 | return function ($passable) use ($destination) {
134 | return call_user_func($destination, $passable);
135 | };
136 | }
137 |
138 | //Http Kernel的dispatchToRouter是Piple管道的终点或者叫目的地
139 | protected function dispatchToRouter()
140 | {
141 | return function ($request) {
142 | $this->app->instance('request', $request);
143 |
144 | return $this->router->dispatch($request);
145 | };
146 | }
147 |
148 | 上面的函数看起来比较晕,我们先来看下array_reduce里对它的callback函数参数的解释:
149 |
150 | >mixed array_reduce ( array $array , callable $callback [, mixed $initial = NULL ] )
151 |
152 | >array_reduce() 将回调函数 callback 迭代地作用到 array 数组中的每一个单元中,从而将数组简化为单一的值。
153 |
154 | >callback ( mixed $carry , mixed $item )
155 | carry
156 | 携带上次迭代里的值; 如果本次迭代是第一次,那么这个值是 initial。item 携带了本次迭代的值。
157 |
158 | getInitialSlice方法,他的返回值是作为传递给callbakc函数的$carry参数的初始值,这个值现在是一个闭包,我把getInitialSlice和Http Kernel的dispatchToRouter这两个方法合并一下,现在$firstSlice的值为:
159 |
160 | ```
161 | $destination = function ($request) {
162 | $this->app->instance('request', $request);
163 | return $this->router->dispatch($request);
164 | };
165 |
166 | $firstSlice = function ($passable) use ($destination) {
167 | return call_user_func($destination, $passable);
168 | };
169 | ```
170 |
171 | 接下来我们看看array_reduce的callback:
172 |
173 | //Pipeline
174 | protected function getSlice()
175 | {
176 | return function ($stack, $pipe) {
177 | return function ($passable) use ($stack, $pipe) {
178 | try {
179 | $slice = parent::getSlice();
180 |
181 | return call_user_func($slice($stack, $pipe), $passable);
182 | } catch (Exception $e) {
183 | return $this->handleException($passable, $e);
184 | } catch (Throwable $e) {
185 | return $this->handleException($passable, new FatalThrowableError($e));
186 | }
187 | };
188 | };
189 | }
190 |
191 | //Pipleline的父类BasePipeline的getSlice方法
192 | protected function getSlice()
193 | {
194 | return function ($stack, $pipe) {
195 | return function ($passable) use ($stack, $pipe) {
196 | if ($pipe instanceof Closure) {
197 | return call_user_func($pipe, $passable, $stack);
198 | } elseif (! is_object($pipe)) {
199 | //解析中间件名称和参数 ('throttle:60,1')
200 | list($name, $parameters) = $this->parsePipeString($pipe);
201 | $pipe = $this->container->make($name);
202 | $parameters = array_merge([$passable, $stack], $parameters);
203 | } else{
204 | $parameters = [$passable, $stack];
205 | }
206 | //$this->method = handle
207 | return call_user_func_array([$pipe, $this->method], $parameters);
208 | };
209 | };
210 | }
211 |
212 | **注:在Laravel5.5版本里 getSlice这个方法的名称换成了carry, 两者在逻辑上没有区别,所以依然可以参照着5.5版本里中间件的代码来看本文。**
213 |
214 | getSlice会返回一个闭包函数, $stack在第一次调用getSlice时它的值是$firstSlice, 之后的调用中就它的值就是这里返回的值个闭包了:
215 |
216 | $stack = function ($passable) use ($stack, $pipe) {
217 | try {
218 | $slice = parent::getSlice();
219 |
220 | return call_user_func($slice($stack, $pipe), $passable);
221 | } catch (Exception $e) {
222 | return $this->handleException($passable, $e);
223 | } catch (Throwable $e) {
224 | return $this->handleException($passable, new FatalThrowableError($e));
225 | }
226 | };
227 |
228 | getSlice返回的闭包里又会去调用父类的getSlice方法,他返回的也是一个闭包,在闭包会里解析出中间件对象、中间件参数(无则为空数组), 然后把$passable(请求对象), $stack和中间件参数作为中间件handle方法的参数进行调用。
229 |
230 | 上面封装的有点复杂,我们简化一下,其实getSlice的返回值就是:
231 |
232 | $stack = function ($passable) use ($stack, $pipe) {
233 | //解析中间件和中间件参数,中间件参数用$parameter代表,无参数时为空数组
234 | $parameters = array_merge([$passable, $stack], $parameters)
235 | return $pipe->handle($parameters)
236 | };
237 |
238 |
239 | array_reduce每次调用callback返回的闭包都会作为参数$stack传递给下一次对callback的调用,array_reduce执行完成后就会返回一个嵌套了多层闭包的闭包,每层闭包用到的外部变量$stack都是上一次之前执行reduce返回的闭包,相当于把中间件通过闭包层层包裹包成了一个洋葱。
240 |
241 | 在then方法里,等到array_reduce执行完返回最终结果后就会对这个洋葱闭包进行调用:
242 |
243 | return call_user_func( array_reduce($pipes, $this->getSlice(), $firstSlice), $this->passable);
244 |
245 |
246 | 这样就能依次执行中间件handle方法,在handle方法里又会去再次调用之前说的reduce包装的洋葱闭包剩余的部分,这样一层层的把洋葱剥开直到最后。通过这种方式让请求对象依次流过了要通过的中间件,达到目的地Http Kernel 的`dispatchToRouter`方法。
247 |
248 | 通过剥洋葱的过程我们就能知道为什么在array_reduce之前要先对middleware数组进行反转, 因为包装是一个反向的过程, 数组$pipes中的第一个中间件会作为第一次reduce执行的结果被包装在洋葱闭包的最内层,所以只有反转后才能保证初始定义的中间件数组中第一个中间件的handle方法会被最先调用。
249 |
250 | 上面说了Pipeline传送请求对象的目的地是Http Kernel 的`dispatchToRouter`方法,其实到远没有到达最终的目的地,现在请求对象了只是刚通过了`\App\Http\Kernel`类里`$middleware`属性里罗列出的几个中间件:
251 |
252 | protected $middleware = [
253 | \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
254 | \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
255 | \App\Http\Middleware\TrimStrings::class,
256 | \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
257 | \App\Http\Middleware\TrustProxies::class,
258 | ];
259 |
260 | 当请求对象进入Http Kernel的`dispatchToRouter`方法后,请求对象在被Router dispatch派发给路由时会进行收集路由上应用的中间件和控制器里应用的中间件。
261 |
262 | ```
263 | namespace Illuminate\Foundation\Http;
264 | class Kernel implements KernelContract
265 | {
266 | protected function dispatchToRouter()
267 | {
268 | return function ($request) {
269 | $this->app->instance('request', $request);
270 |
271 | return $this->router->dispatch($request);
272 | };
273 | }
274 | }
275 |
276 |
277 | namespace Illuminate\Routing;
278 | class Router implements RegistrarContract, BindingRegistrar
279 | {
280 | public function dispatch(Request $request)
281 | {
282 | $this->currentRequest = $request;
283 |
284 | return $this->dispatchToRoute($request);
285 | }
286 |
287 | public function dispatchToRoute(Request $request)
288 | {
289 | return $this->runRoute($request, $this->findRoute($request));
290 | }
291 |
292 | protected function runRoute(Request $request, Route $route)
293 | {
294 | $request->setRouteResolver(function () use ($route) {
295 | return $route;
296 | });
297 |
298 | $this->events->dispatch(new Events\RouteMatched($route, $request));
299 |
300 | return $this->prepareResponse($request,
301 | $this->runRouteWithinStack($route, $request)
302 | );
303 | }
304 |
305 | protected function runRouteWithinStack(Route $route, Request $request)
306 | {
307 | $shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
308 | $this->container->make('middleware.disable') === true;
309 | //收集路由和控制器里应用的中间件
310 | $middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);
311 |
312 | return (new Pipeline($this->container))
313 | ->send($request)
314 | ->through($middleware)
315 | ->then(function ($request) use ($route) {
316 | return $this->prepareResponse(
317 | $request, $route->run()
318 | );
319 | });
320 |
321 | }
322 | }
323 |
324 | namespace Illuminate\Routing;
325 | class Route
326 | {
327 | public function run()
328 | {
329 | $this->container = $this->container ?: new Container;
330 | try {
331 | if ($this->isControllerAction()) {
332 | return $this->runController();
333 | }
334 | return $this->runCallable();
335 | } catch (HttpResponseException $e) {
336 | return $e->getResponse();
337 | }
338 | }
339 |
340 | }
341 | ```
342 |
343 |
344 | 收集完路由和控制器里应用的中间件后,依然是利用Pipeline对象来传送请求对象通过收集上来的这些中间件然后到达最终的目的地,在那里会执行目的路由的run方法,run方法里面会判断路由对应的是一个控制器方法还是闭包然后进行相应地调用,最后把执行结果包装成Response对象。Response对象会依次通过上面应用的所有中间件的后置操作,最终离开应用被发送给客户端。
345 |
346 | 限于篇幅和为了文章的可读性,收集路由和控制器中间件然后执行路由对应的处理方法的过程我就不在这里详述了,感兴趣的同学可以自己去看Router的源码,本文的目的还是主要为了梳理laravel是如何设计中间件的以及如何执行它们的,希望能对感兴趣的朋友有帮助。
347 |
348 | 上一篇: [装饰模式](https://github.com/kevinyan815/Learning_Laravel_Kernel/blob/master/articles/DecoratorPattern.md)
349 |
350 | 下一篇: [控制器](https://github.com/kevinyan815/Learning_Laravel_Kernel/blob/master/articles/Controller.md)
351 |
--------------------------------------------------------------------------------
/articles/Observer.md:
--------------------------------------------------------------------------------
1 | # 观察者模式
2 |
3 | Laravel的Event事件系统提供了一个简单的观察者模式实现,能够订阅和监听应用中发生的各种事件,在PHP的标准库(SPL)里甚至提供了三个接口`SplSubject`, `SplObserver`, `SplObjectStorage`来让开发者更容易地实现观察者模式,不过我还是想脱离SPL提供的接口和特定编程语言来说一下如何通过面向对象程序设计来实现观察者模式,示例是PHP代码不过用其他面向对象语言实现起来也是一样的。
4 |
5 | ### 模式定义
6 |
7 | 观察者模式(Observer Pattern):定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式又叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。
8 |
9 | 观察者模式的核心在于Subject和Observer接口,Subject(主题目标)包含一个给定的状态,观察者“订阅”这个主题,将主题的当前状态通知观察者,每次给定状态改变时所有观察者都会得到通知。
10 |
11 | 发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以对应多个观察者,而且这些观察者之间没有相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展。
12 |
13 |
14 |
15 | ### 模式结构说明
16 |
17 | 
18 |
19 | - Subject 目标抽象类
20 | - ConcreteSubject 具体目标
21 | - Observer 观察者抽象类
22 | - ConcreteObserver 具体观察者
23 |
24 |
25 |
26 | ### 应用举例
27 |
28 | 比如在设置用户(主题)的状态后要分别发送当前的状态描述信息给用户的邮箱和手机,我们可以使用两个观察者订阅用户的状态,一旦设置状态后主题就会通知的订阅了状态改变的观察者,在两个观察者里面我们可以分别来实现发送邮件信息和短信信息的功能。
29 |
30 | 1. 抽象目标类
31 |
32 | ```
33 | abstract class Subject
34 | {
35 | protected $stateNow;
36 | protected $observers = [];
37 |
38 | public function attach(Observer $observer)
39 | {
40 | array_push($this->observers, $observer);
41 | }
42 |
43 | public function detach(Observer $ob)
44 | {
45 | $pos = 0;
46 | foreach ($this->observers as $viewer) {
47 | if ($viewer == $ob) {
48 | array_splice($this->observers, $pos, 1);
49 | }
50 | $pos++;
51 | }
52 | }
53 |
54 | public function notify()
55 | {
56 | foreach ($this->observers as $viewer) {
57 | $viewer->update($this);
58 | }
59 | }
60 | }
61 | ```
62 |
63 | 在抽象类中`attach` `detach` 和`notify`都是具体方法,这些是继承才能使用的方法,将由`Subject`的子类使用。
64 |
65 | 2. 具体目标类
66 |
67 | ```
68 | class ConcreteSubject extends Subject
69 | {
70 | public function setState($state)
71 | {
72 | $this->stateNow = $state;
73 | $this->notify();
74 | }
75 |
76 | public function getState()
77 | {
78 | return $this->stateNow;
79 | }
80 | }
81 | ```
82 |
83 | 3. 抽象观察者
84 |
85 | ```
86 | abstract class Observer
87 | {
88 | abstract public function update(Subject $subject);
89 | }
90 | ```
91 |
92 | 在抽象观察者中,抽象方法`update`等待子类为它提供一个特定的实现。
93 |
94 | 4. 具体观察者
95 |
96 | ```
97 | class ConcreteObserverDT extends Observer
98 | {
99 | private $currentState;
100 |
101 | public function update(Subject $subject)
102 | {
103 | $this->currentState = $subject->getState();
104 |
105 | echo ''. $this->currentState .'
';
106 | }
107 | }
108 |
109 | class ConcreteObserverPhone extends Observer
110 | {
111 | private $currentState;
112 |
113 | public function update(Subject $subject)
114 | {
115 | $this->currentState = $subject->getState();
116 |
117 | echo ''. $this->currentState .'
';
118 | }
119 | }
120 | ```
121 |
122 | 在例子中为了理解起来简单,我们只是根据不同的客户端设置了不同的内容样式,实际应用中可以真正的调用邮件和短信服务来发送信息。
123 |
124 | 5. 使用观察者模式
125 |
126 | ```
127 | class Client
128 | {
129 | public function __construct()
130 | {
131 | $sub = new ConcreteSubject();
132 |
133 | $obDT = new ConcreteObserverDT();
134 | $obPhone = new ConcreteObserverPhone();
135 |
136 | $sub->attach($obDT);
137 | $sub->attach($obPhone);
138 | $sub->setState('Hello World');
139 | }
140 | }
141 |
142 | $worker = new Client();
143 | ```
144 |
145 |
146 |
147 | ### 何时使用观察者模式
148 |
149 | - 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
150 | - 一个对象必须通知其他对象,而并不知道这些对象是谁。
151 | - 基于事件触发机制来解耦复杂逻辑时,从整个逻辑的不同关键点抽象出不同的事件,主流程只需要关心最核心的逻辑并能正确地触发事件(Subject),其余相关功能实现由观察者或者叫订阅者来完成。
152 |
153 |
154 |
155 | ### 总结
156 |
157 | - 观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象,当这个目标对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新。
158 | - 模式包含四个角色:目标又称为主题,它是指被观察的对象;具体目标是目标类的子类,通常它包含有经常发生改变的数据,当它的状态发生改变时,向它的各个观察者发出通知;观察者将对观察目标的改变做出反应;在具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要和具体目标的状态保持一致。
159 |
160 | - 观察者模式的主要优点在于可以实现表示层和数据逻辑层的分离,并在观察目标和观察者之间建立一个抽象的耦合,支持广播通信;其主要缺点在于如果一个观察目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间,而且如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
161 |
162 | 上一篇: [Database 模型关联](https://github.com/kevinyan815/Learning_Laravel_Kernel/blob/master/articles/Database4.md)
163 |
164 | 下一篇: [事件系统](https://github.com/kevinyan815/Learning_Laravel_Kernel/blob/master/articles/Event.md)
165 |
--------------------------------------------------------------------------------
/articles/Request.md:
--------------------------------------------------------------------------------
1 | # Request
2 | 很多框架都会将来自客户端的请求抽象成类方便应用程序使用,在Laravel中也不例外。`Illuminate\Http\Request`类在Laravel框架中就是对客户端请求的抽象,它是构建在`Symfony`框架提供的Request组件基础之上的。今天这篇文章就简单来看看Laravel是怎么创建请求Request对象的,而关于Request对象为应用提供的能力我并不会过多去说,在我讲完创建过程后你也就知道去源码哪里找Request对象提供的方法了,网上有些速查表列举了一些Request提供的方法不过不够全并且有的也没有解释,所以我还是推荐在开发中如果好奇Request是否已经实现了你想要的能力时去Request的源码里看下有没有提供对应的方法,方法注释里都清楚地标明了每个方法的执行结果。下面让我们进入正题吧。
3 |
4 | ### 创建Request对象
5 | 我们可以在Laravel应用程序的`index.php`文件中看到,在Laravel应用程序正式启动完成前Request对象就已经被创建好了:
6 |
7 | ```
8 | //public/index.php
9 | $app = require_once __DIR__.'/../bootstrap/app.php';
10 |
11 | $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
12 |
13 | $response = $kernel->handle(
14 | //创建request对象
15 | $request = Illuminate\Http\Request::capture()
16 | );
17 | ```
18 |
19 | 客户端的HTTP请求是`Illuminate\Http\Request`类的对象
20 |
21 | ```
22 | class Request extends SymfonyRequest implements Arrayable, ArrayAccess
23 | {
24 | //新建Request实例
25 | public static function capture()
26 | {
27 | static::enableHttpMethodParameterOverride();
28 |
29 | return static::createFromBase(SymfonyRequest::createFromGlobals());
30 | }
31 | }
32 |
33 | ```
34 | 通过`Illuminate\Http\Request`类的源码可以看到它是继承自`Symfony Request`类的,所以`Illuminate\Http\Request`类中实现的很多功能都是以`Symfony Reques`提供的功能为基础来实现的。上面的代码就可以看到`capture`方法新建Request对象时也是依赖于`Symfony Request`类的实例的。
35 |
36 | ```
37 | namespace Symfony\Component\HttpFoundation;
38 | class Request
39 | {
40 | /**
41 | * 根据PHP提供的超级全局数组来创建Smyfony Request实例
42 | *
43 | * @return static
44 | */
45 | public static function createFromGlobals()
46 | {
47 | // With the php's bug #66606, the php's built-in web server
48 | // stores the Content-Type and Content-Length header values in
49 | // HTTP_CONTENT_TYPE and HTTP_CONTENT_LENGTH fields.
50 | $server = $_SERVER;
51 | if ('cli-server' === PHP_SAPI) {
52 | if (array_key_exists('HTTP_CONTENT_LENGTH', $_SERVER)) {
53 | $server['CONTENT_LENGTH'] = $_SERVER['HTTP_CONTENT_LENGTH'];
54 | }
55 | if (array_key_exists('HTTP_CONTENT_TYPE', $_SERVER)) {
56 | $server['CONTENT_TYPE'] = $_SERVER['HTTP_CONTENT_TYPE'];
57 | }
58 | }
59 |
60 | $request = self::createRequestFromFactory($_GET, $_POST, array(), $_COOKIE, $_FILES, $server);
61 |
62 | if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded')
63 | && in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), array('PUT', 'DELETE', 'PATCH'))
64 | ) {
65 | parse_str($request->getContent(), $data);
66 | $request->request = new ParameterBag($data);
67 | }
68 |
69 | return $request;
70 | }
71 |
72 | }
73 | ```
74 |
75 | 上面的代码有一处需要额外解释一下,自PHP5.4开始PHP内建的builtin web server可以通过命令行解释器来启动,例如:
76 |
77 | > php -S localhost:8000 -t htdocs
78 | >
79 | >
80 | >
81 | > ```
82 | > -S : Run with built-in web server.
83 | > -t Specify document root for built-in web server.
84 | > ```
85 | >
86 | >
87 |
88 | 但是内建web server有一个bug是将`CONTENT_LENGTH`和`CONTENT_TYPE`这两个请求首部存储到了`HTTP_CONTENT_LENGTH`和`HTTP_CONTENT_TYPE`中,为了统一内建服务器和真正的server中的请求首部字段所以在这里做了特殊处理。
89 |
90 |
91 |
92 | Symfony Request 实例的创建是通过PHP中的超级全局数组来创建的,这些超级全局数组有`$_GET`,`$_POST`,`$_COOKIE`,`$_FILES`,`$_SERVER`涵盖了PHP中所有与HTTP请求相关的超级全局数组,创建Symfony Request实例时会根据这些全局数组创建Symfony Package里提供的`ParamterBag` `ServerBag` `FileBag` `HeaderBag`实例,这些Bag都是Symfony提供地针对不同HTTP组成部分的访问和设置API, 关于Symfony提供的`ParamterBag`这些实例有兴趣的读者自己去源码里看看吧,这里就不多说了。
93 |
94 | ```
95 | class Request
96 | {
97 |
98 | /**
99 | * @param array $query The GET parameters
100 | * @param array $request The POST parameters
101 | * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
102 | * @param array $cookies The COOKIE parameters
103 | * @param array $files The FILES parameters
104 | * @param array $server The SERVER parameters
105 | * @param string|resource|null $content The raw body data
106 | */
107 | public function __construct(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null)
108 | {
109 | $this->initialize($query, $request, $attributes, $cookies, $files, $server, $content);
110 | }
111 |
112 | public function initialize(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null)
113 | {
114 | $this->request = new ParameterBag($request);
115 | $this->query = new ParameterBag($query);
116 | $this->attributes = new ParameterBag($attributes);
117 | $this->cookies = new ParameterBag($cookies);
118 | $this->files = new FileBag($files);
119 | $this->server = new ServerBag($server);
120 | $this->headers = new HeaderBag($this->server->getHeaders());
121 |
122 | $this->content = $content;
123 | $this->languages = null;
124 | $this->charsets = null;
125 | $this->encodings = null;
126 | $this->acceptableContentTypes = null;
127 | $this->pathInfo = null;
128 | $this->requestUri = null;
129 | $this->baseUrl = null;
130 | $this->basePath = null;
131 | $this->method = null;
132 | $this->format = null;
133 | }
134 |
135 | }
136 | ```
137 |
138 | 可以看到Symfony Request类除了上边说到的那几个,还有很多属性,这些属性在一起构成了对HTTP请求完整的抽象,我们可以通过实例属性方便地访问`Method`,`Charset`等这些HTTP请求的属性。
139 |
140 | 拿到Symfony Request实例后, Laravel会克隆这个实例并重设其中的一些属性:
141 |
142 | ```
143 | namespace Illuminate\Http;
144 | class Request extends ....
145 | {
146 | //在Symfony request instance的基础上创建Request实例
147 | public static function createFromBase(SymfonyRequest $request)
148 | {
149 | if ($request instanceof static) {
150 | return $request;
151 | }
152 |
153 | $content = $request->content;
154 |
155 | $request = (new static)->duplicate(
156 | $request->query->all(), $request->request->all(), $request->attributes->all(),
157 | $request->cookies->all(), $request->files->all(), $request->server->all()
158 | );
159 |
160 | $request->content = $content;
161 |
162 | $request->request = $request->getInputSource();
163 |
164 | return $request;
165 | }
166 |
167 | public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null)
168 | {
169 | return parent::duplicate($query, $request, $attributes, $cookies, $this->filterFiles($files), $server);
170 | }
171 | }
172 | //Symfony Request中的 duplicate方法
173 | public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null)
174 | {
175 | $dup = clone $this;
176 | if (null !== $query) {
177 | $dup->query = new ParameterBag($query);
178 | }
179 | if (null !== $request) {
180 | $dup->request = new ParameterBag($request);
181 | }
182 | if (null !== $attributes) {
183 | $dup->attributes = new ParameterBag($attributes);
184 | }
185 | if (null !== $cookies) {
186 | $dup->cookies = new ParameterBag($cookies);
187 | }
188 | if (null !== $files) {
189 | $dup->files = new FileBag($files);
190 | }
191 | if (null !== $server) {
192 | $dup->server = new ServerBag($server);
193 | $dup->headers = new HeaderBag($dup->server->getHeaders());
194 | }
195 | $dup->languages = null;
196 | $dup->charsets = null;
197 | $dup->encodings = null;
198 | $dup->acceptableContentTypes = null;
199 | $dup->pathInfo = null;
200 | $dup->requestUri = null;
201 | $dup->baseUrl = null;
202 | $dup->basePath = null;
203 | $dup->method = null;
204 | $dup->format = null;
205 |
206 | if (!$dup->get('_format') && $this->get('_format')) {
207 | $dup->attributes->set('_format', $this->get('_format'));
208 | }
209 |
210 | if (!$dup->getRequestFormat(null)) {
211 | $dup->setRequestFormat($this->getRequestFormat(null));
212 | }
213 |
214 | return $dup;
215 | }
216 | ```
217 |
218 |
219 |
220 | Request对象创建好后在Laravel应用中我们就能方便的应用它提供的能力了,在使用Request对象时如果你不知道它是否实现了你想要的功能,很简单直接去`Illuminate\Http\Request`的源码文件里查看就好了,所有方法都列在了这个源码文件里,比如:
221 |
222 |
223 |
224 | ```
225 | /**
226 | * Get the full URL for the request.
227 | * 获取请求的URL(包含host, 不包括query string)
228 | *
229 | * @return string
230 | */
231 | public function fullUrl()
232 | {
233 | $query = $this->getQueryString();
234 |
235 | $question = $this->getBaseUrl().$this->getPathInfo() == '/' ? '/?' : '?';
236 |
237 | return $query ? $this->url().$question.$query : $this->url();
238 | }
239 |
240 | /**
241 | * Get the full URL for the request with the added query string parameters.
242 | * 获取包括了query string 的完整URL
243 | *
244 | * @param array $query
245 | * @return string
246 | */
247 | public function fullUrlWithQuery(array $query)
248 | {
249 | $question = $this->getBaseUrl().$this->getPathInfo() == '/' ? '/?' : '?';
250 |
251 | return count($this->query()) > 0
252 | ? $this->url().$question.http_build_query(array_merge($this->query(), $query))
253 | : $this->fullUrl().$question.http_build_query($query);
254 | }
255 | ```
256 |
257 |
258 |
259 | ### Request经过的驿站
260 |
261 | 创建完Request对象后, Laravel的Http Kernel会接着往下执行:加载服务提供器引导Laravel应用、启动应用、让Request经过基础的中间件、通过Router匹配查找Request对应的路由、执行匹配到的路由、Request经过路由上到中间件到达控制器方法。
262 |
263 | ### 总结
264 |
265 | 随着Request最终到达对应的控制器方法后它的使命基本上也就完成了, 在控制器方法里从Request中获取输入参数然后执行应用的某一业务逻辑获得结果,结果会被转化成Response响应对象返回给发起请求的客户端。
266 |
267 | 这篇文章主要梳理了Laravel中Request对象,主要是想让大家知道如何去查找Laravel中Request现有提供了哪些能力供我们使用避免我们在业务代码里重新造轮子去实现Request已经提供的方法。
268 |
269 | 上一篇: [控制器](https://github.com/kevinyan815/Learning_Laravel_Kernel/blob/master/articles/Controller.md)
270 |
271 | 下一篇: [Response](https://github.com/kevinyan815/Learning_Laravel_Kernel/blob/master/articles/Response.md)
272 |
--------------------------------------------------------------------------------
/articles/Response.md:
--------------------------------------------------------------------------------
1 | # Response
2 |
3 | 前面两节我们分别讲了Laravel的控制器和Request对象,在讲Request对象的那一节我们看了Request对象是如何被创建出来的以及它支持的方法都定义在哪里,讲控制器时我们详细地描述了如何找到Request对应的控制器方法然后执行处理程序的,本节我们就来说剩下的那一部分,控制器方法的执行结果是如何被转换成响应对象Response然后返回给客户端的。
4 |
5 |
6 |
7 | ### 创建Response
8 |
9 | 让我们回到Laravel执行路由处理程序返回响应的代码块:
10 |
11 | ```
12 | namespace Illuminate\Routing;
13 | class Router implements RegistrarContract, BindingRegistrar
14 | {
15 | protected function runRoute(Request $request, Route $route)
16 | {
17 | $request->setRouteResolver(function () use ($route) {
18 | return $route;
19 | });
20 |
21 | $this->events->dispatch(new Events\RouteMatched($route, $request));
22 |
23 | return $this->prepareResponse($request,
24 | $this->runRouteWithinStack($route, $request)
25 | );
26 | }
27 |
28 | protected function runRouteWithinStack(Route $route, Request $request)
29 | {
30 | $shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
31 | $this->container->make('middleware.disable') === true;
32 | //收集路由和控制器里应用的中间件
33 | $middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);
34 |
35 | return (new Pipeline($this->container))
36 | ->send($request)
37 | ->through($middleware)
38 | ->then(function ($request) use ($route) {
39 | return $this->prepareResponse(
40 | $request, $route->run()
41 | );
42 | });
43 |
44 | }
45 | }
46 | ```
47 |
48 |
49 |
50 | 在讲控制器的那一节里我们已经提到过`runRouteWithinStack`方法里是最终执行路由处理程序(控制器方法或者闭包处理程序)的地方,通过上面的代码我们也可以看到执行的结果会传递给`Router`的`prepareResponse`方法,当程序流返回到`runRoute`里后又执行了一次`prepareResponse`方法得到了要返回给客户端的Response对象, 下面我们就来详细看一下`prepareResponse`方法。
51 |
52 | ```
53 | class Router implements RegistrarContract, BindingRegistrar
54 | {
55 | /**
56 | * 通过给定值创建Response对象
57 | *
58 | * @param \Symfony\Component\HttpFoundation\Request $request
59 | * @param mixed $response
60 | * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse
61 | */
62 | public function prepareResponse($request, $response)
63 | {
64 | return static::toResponse($request, $response);
65 | }
66 |
67 | public static function toResponse($request, $response)
68 | {
69 | if ($response instanceof Responsable) {
70 | $response = $response->toResponse($request);
71 | }
72 |
73 | if ($response instanceof PsrResponseInterface) {
74 | $response = (new HttpFoundationFactory)->createResponse($response);
75 | } elseif (! $response instanceof SymfonyResponse &&
76 | ($response instanceof Arrayable ||
77 | $response instanceof Jsonable ||
78 | $response instanceof ArrayObject ||
79 | $response instanceof JsonSerializable ||
80 | is_array($response))) {
81 | $response = new JsonResponse($response);
82 | } elseif (! $response instanceof SymfonyResponse) {
83 | $response = new Response($response);
84 | }
85 |
86 | if ($response->getStatusCode() === Response::HTTP_NOT_MODIFIED) {
87 | $response->setNotModified();
88 | }
89 |
90 | return $response->prepare($request);
91 | }
92 | }
93 | ```
94 |
95 | 在上面的代码中我们看到有三种Response:
96 |
97 |
98 |
99 | | Class Name | Representation |
100 | | ------------------------------------------------------------ | --------------------------------- |
101 | | PsrResponseInterface(Psr\Http\Message\ResponseInterface的别名) | Psr规范中对服务端响应的定义 |
102 | | Illuminate\Http\JsonResponse (Symfony\Component\HttpFoundation\Response的子类) | Laravel中对服务端JSON响应的定义 |
103 | | Illuminate\Http\Response (Symfony\Component\HttpFoundation\Response的子类) | Laravel中对普通的非JSON响应的定义 |
104 |
105 | 通过`prepareResponse`中的逻辑可以看到,无论路由执行结果返回的是什么值最终都会被Laravel转换为成一个Response对象,而这些对象都是Symfony\Component\HttpFoundation\Response类或者其子类的对象。从这里也就能看出来跟Request一样Laravel的Response也是依赖Symfony框架的`HttpFoundation`组件来实现的。
106 |
107 | 我们来看一下Symfony\Component\HttpFoundation\Response的构造方法:
108 |
109 | ```
110 | namespace Symfony\Component\HttpFoundation;
111 | class Response
112 | {
113 | public function __construct($content = '', $status = 200, $headers = array())
114 | {
115 | $this->headers = new ResponseHeaderBag($headers);
116 | $this->setContent($content);
117 | $this->setStatusCode($status);
118 | $this->setProtocolVersion('1.0');
119 | }
120 | //设置响应的Content
121 | public function setContent($content)
122 | {
123 | if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable(array($content, '__toString'))) {
124 | throw new \UnexpectedValueException(sprintf('The Response content must be a string or object implementing __toString(), "%s" given.', gettype($content)));
125 | }
126 |
127 | $this->content = (string) $content;
128 |
129 | return $this;
130 | }
131 | }
132 | ```
133 |
134 | 所以路由处理程序的返回值在创业Response对象时会设置到对象的content属性里,该属性的值就是返回给客户端的响应的响应内容。
135 |
136 | ### 设置Response headers
137 |
138 | 生成Response对象后就要执行对象的`prepare`方法了,该方法定义在`Symfony\Component\HttpFoundation\Resposne`类中,其主要目的是对Response进行微调使其能够遵从HTTP/1.1协议(RFC 2616)。
139 |
140 | ```
141 | namespace Symfony\Component\HttpFoundation;
142 | class Response
143 | {
144 | //在响应被发送给客户端之前对其进行修订使其能遵从HTTP/1.1协议
145 | public function prepare(Request $request)
146 | {
147 | $headers = $this->headers;
148 |
149 | if ($this->isInformational() || $this->isEmpty()) {
150 | $this->setContent(null);
151 | $headers->remove('Content-Type');
152 | $headers->remove('Content-Length');
153 | } else {
154 | // Content-type based on the Request
155 | if (!$headers->has('Content-Type')) {
156 | $format = $request->getRequestFormat();
157 | if (null !== $format && $mimeType = $request->getMimeType($format)) {
158 | $headers->set('Content-Type', $mimeType);
159 | }
160 | }
161 |
162 | // Fix Content-Type
163 | $charset = $this->charset ?: 'UTF-8';
164 | if (!$headers->has('Content-Type')) {
165 | $headers->set('Content-Type', 'text/html; charset='.$charset);
166 | } elseif (0 === stripos($headers->get('Content-Type'), 'text/') && false === stripos($headers->get('Content-Type'), 'charset')) {
167 | // add the charset
168 | $headers->set('Content-Type', $headers->get('Content-Type').'; charset='.$charset);
169 | }
170 |
171 | // Fix Content-Length
172 | if ($headers->has('Transfer-Encoding')) {
173 | $headers->remove('Content-Length');
174 | }
175 |
176 | if ($request->isMethod('HEAD')) {
177 | // cf. RFC2616 14.13
178 | $length = $headers->get('Content-Length');
179 | $this->setContent(null);
180 | if ($length) {
181 | $headers->set('Content-Length', $length);
182 | }
183 | }
184 | }
185 |
186 | // Fix protocol
187 | if ('HTTP/1.0' != $request->server->get('SERVER_PROTOCOL')) {
188 | $this->setProtocolVersion('1.1');
189 | }
190 |
191 | // Check if we need to send extra expire info headers
192 | if ('1.0' == $this->getProtocolVersion() && false !== strpos($this->headers->get('Cache-Control'), 'no-cache')) {
193 | $this->headers->set('pragma', 'no-cache');
194 | $this->headers->set('expires', -1);
195 | }
196 |
197 | $this->ensureIEOverSSLCompatibility($request);
198 |
199 | return $this;
200 | }
201 | }
202 | ```
203 |
204 | `prepare`里针对各种情况设置了相应的`response header` 比如`Content-Type`、`Content-Length`等等这些我们常见的首部字段。
205 |
206 |
207 |
208 | ### 发送Response
209 |
210 | 创建并设置完Response后它会流经路由和框架中间件的后置操作,在中间件的后置操作里一般都是对Response进行进一步加工,最后程序流回到Http Kernel那里, Http Kernel会把Response发送给客户端,我们来看一下这部分的代码。
211 |
212 | ```
213 | //入口文件public/index.php
214 | $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
215 |
216 | $response = $kernel->handle(
217 | $request = Illuminate\Http\Request::capture()
218 | );
219 |
220 | $response->send();
221 |
222 | $kernel->terminate($request, $response);
223 | ```
224 |
225 | ```
226 | namespace Symfony\Component\HttpFoundation;
227 | class Response
228 | {
229 | public function send()
230 | {
231 | $this->sendHeaders();
232 | $this->sendContent();
233 |
234 | if (function_exists('fastcgi_finish_request')) {
235 | fastcgi_finish_request();
236 | } elseif ('cli' !== PHP_SAPI) {
237 | static::closeOutputBuffers(0, true);
238 | }
239 |
240 | return $this;
241 | }
242 |
243 | //发送headers到客户端
244 | public function sendHeaders()
245 | {
246 | // headers have already been sent by the developer
247 | if (headers_sent()) {
248 | return $this;
249 | }
250 |
251 | // headers
252 | foreach ($this->headers->allPreserveCaseWithoutCookies() as $name => $values) {
253 | foreach ($values as $value) {
254 | header($name.': '.$value, false, $this->statusCode);
255 | }
256 | }
257 |
258 | // status
259 | header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode);
260 |
261 | // cookies
262 | foreach ($this->headers->getCookies() as $cookie) {
263 | if ($cookie->isRaw()) {
264 | setrawcookie($cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly());
265 | } else {
266 | setcookie($cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly());
267 | }
268 | }
269 |
270 | return $this;
271 | }
272 |
273 | //发送响应内容到客户端
274 | public function sendContent()
275 | {
276 | echo $this->content;
277 |
278 | return $this;
279 | }
280 | }
281 | ```
282 |
283 | `send`的逻辑就非常好理解了,把之前设置好的那些headers设置到HTTP响应的首部字段里,Content会echo后被设置到HTTP响应的主体实体中。最后PHP会把完整的HTTP响应发送给客户端。
284 |
285 | send响应后Http Kernel会执行`terminate`方法调用terminate中间件里的`terminate`方法,最后执行应用的`termiate`方法来结束整个应用生命周期(从接收请求开始到返回响应结束)。
286 |
287 |
288 | 上一篇: [Request](https://github.com/kevinyan815/Learning_Laravel_Kernel/blob/master/articles/Request.md)
289 |
290 | 下一篇: [Database 基础介绍](https://github.com/kevinyan815/Learning_Laravel_Kernel/blob/master/articles/Database1.md)
291 |
--------------------------------------------------------------------------------
/articles/reflection.md:
--------------------------------------------------------------------------------
1 | # 类的反射和依赖注入
2 |
3 | 在讲服务容器之前我想先梳理下PHP反射相关的知识,PHP反射是程序实现依赖注入的基础,也是Laravel的服务容器实现服务解析的基础,如果你已经掌握了这方面基础知识,那么可以跳过本文直接看服务容器部分的内容。
4 |
5 | PHP具有完整的反射 API,提供了对类、接口、函数、方法和扩展进行逆向工程的能力。通过类的反射提供的能力我们能够知道类是如何被定义的,它有什么属性、什么方法、方法都有哪些参数,类文件的路径是什么等很重要的信息。也正式因为类的反射很多PHP框架才能实现依赖注入自动解决类与类之间的依赖关系,这给我们平时的开发带来了很大的方便。 本文主要是讲解如何利用类的反射来实现依赖注入(Dependency Injection),并不会去逐条讲述PHP Reflection里的每一个API,详细的API参考信息请查阅[官方文档][1]
6 |
7 | **再次声明这里实现的依赖注入非常简单,并不能应用到实际开发中去,可以参考后面的文章[服务容器(IocContainer)][2], 了解Laravel的服务容器是如何实现依赖注入的。**
8 |
9 | 为了更好地理解,我们通过一个例子来看类的反射,以及如何实现依赖注入。
10 | 下面这个类代表了坐标系里的一个点,有两个属性横坐标x和纵坐标y。
11 | ```php
12 | /**
13 | * Class Point
14 | */
15 | class Point
16 | {
17 | public $x;
18 | public $y;
19 |
20 | /**
21 | * Point constructor.
22 | * @param int $x horizontal value of point's coordinate
23 | * @param int $y vertical value of point's coordinate
24 | */
25 | public function __construct($x = 0, $y = 0)
26 | {
27 | $this->x = $x;
28 | $this->y = $y;
29 | }
30 | }
31 | ```
32 | 接下来这个类代表圆形,可以看到在它的构造函数里有一个参数是`Point`类的,即`Circle`类是依赖与`Point`类的。
33 |
34 | ```php
35 | class Circle
36 | {
37 | /**
38 | * @var int
39 | */
40 | public $radius;//半径
41 |
42 | /**
43 | * @var Point
44 | */
45 | public $center;//圆心点
46 |
47 | const PI = 3.14;
48 |
49 | public function __construct(Point $point, $radius = 1)
50 | {
51 | $this->center = $point;
52 | $this->radius = $radius;
53 | }
54 |
55 | //打印圆点的坐标
56 | public function printCenter()
57 | {
58 | printf('center coordinate is (%d, %d)', $this->center->x, $this->center->y);
59 | }
60 |
61 | //计算圆形的面积
62 | public function area()
63 | {
64 | return 3.14 * pow($this->radius, 2);
65 | }
66 | }
67 | ```
68 | ReflectionClass
69 | --
70 |
71 | 下面我们通过反射来对`Circle`这个类进行反向工程。
72 | 把`Circle`类的名字传递给`reflectionClass`来实例化一个`ReflectionClass`类的对象。
73 |
74 | ```php
75 | $reflectionClass = new reflectionClass(Circle::class);
76 | //返回值如下
77 | object(ReflectionClass)#1 (1) {
78 | ["name"]=>
79 | string(6) "Circle"
80 | }
81 | ```
82 | 反射出类的常量
83 | --
84 | ```php
85 | $reflectionClass->getConstants();
86 | ```
87 | 返回一个由常量名称和值构成的关联数组
88 | ```php
89 | array(1) {
90 | ["PI"]=>
91 | float(3.14)
92 | }
93 | ```
94 |
95 | 通过反射获取属性
96 | --
97 | ```php
98 | $reflectionClass->getProperties();
99 | ```
100 | 返回一个由ReflectionProperty对象构成的数组
101 | ```php
102 | array(2) {
103 | [0]=>
104 | object(ReflectionProperty)#2 (2) {
105 | ["name"]=>
106 | string(6) "radius"
107 | ["class"]=>
108 | string(6) "Circle"
109 | }
110 | [1]=>
111 | object(ReflectionProperty)#3 (2) {
112 | ["name"]=>
113 | string(6) "center"
114 | ["class"]=>
115 | string(6) "Circle"
116 | }
117 | }
118 | ```
119 | 反射出类中定义的方法
120 | --
121 | ```php
122 | $reflectionClass->getMethods();
123 | ```
124 | 返回ReflectionMethod对象构成的数组
125 | ```php
126 | array(3) {
127 | [0]=>
128 | object(ReflectionMethod)#2 (2) {
129 | ["name"]=>
130 | string(11) "__construct"
131 | ["class"]=>
132 | string(6) "Circle"
133 | }
134 | [1]=>
135 | object(ReflectionMethod)#3 (2) {
136 | ["name"]=>
137 | string(11) "printCenter"
138 | ["class"]=>
139 | string(6) "Circle"
140 | }
141 | [2]=>
142 | object(ReflectionMethod)#4 (2) {
143 | ["name"]=>
144 | string(4) "area"
145 | ["class"]=>
146 | string(6) "Circle"
147 | }
148 | }
149 | ```
150 | 我们还可以通过`getConstructor()`来单独获取类的构造方法,其返回值为一个`ReflectionMethod`对象。
151 | ```php
152 | $constructor = $reflectionClass->getConstructor();
153 | ```
154 | 反射出方法的参数
155 | --
156 | ```php
157 | $parameters = $constructor->getParameters();
158 | ```
159 | 其返回值为ReflectionParameter对象构成的数组。
160 | ```php
161 | array(2) {
162 | [0]=>
163 | object(ReflectionParameter)#3 (1) {
164 | ["name"]=>
165 | string(5) "point"
166 | }
167 | [1]=>
168 | object(ReflectionParameter)#4 (1) {
169 | ["name"]=>
170 | string(6) "radius"
171 | }
172 | }
173 | ```
174 |
175 | 依赖注入
176 | --
177 | 好了接下来我们编写一个名为`make`的函数,传递类名称给`make`函数返回类的对象,在`make`里它会帮我们注入类的依赖,即在本例中帮我们注入`Point`对象给`Circle`类的构造方法。
178 | ```php
179 | //构建类的对象
180 | function make($className)
181 | {
182 | $reflectionClass = new ReflectionClass($className);
183 | $constructor = $reflectionClass->getConstructor();
184 | $parameters = $constructor->getParameters();
185 | $dependencies = getDependencies($parameters);
186 |
187 | return $reflectionClass->newInstanceArgs($dependencies);
188 | }
189 |
190 | //依赖解析
191 | function getDependencies($parameters)
192 | {
193 | $dependencies = [];
194 | foreach($parameters as $parameter) {
195 | $dependency = $parameter->getClass();
196 | if (is_null($dependency)) {
197 | if($parameter->isDefaultValueAvailable()) {
198 | $dependencies[] = $parameter->getDefaultValue();
199 | } else {
200 | //不是可选参数的为了简单直接赋值为字符串0
201 | //针对构造方法的必须参数这个情况
202 | //laravel是通过service provider注册closure到IocContainer,
203 | //在closure里可以通过return new Class($param1, $param2)来返回类的实例
204 | //然后在make时回调这个closure即可解析出对象
205 | //具体细节我会在另一篇文章里面描述
206 | $dependencies[] = '0';
207 | }
208 | } else {
209 | //递归解析出依赖类的对象
210 | $dependencies[] = make($parameter->getClass()->name);
211 | }
212 | }
213 |
214 | return $dependencies;
215 | }
216 | ```
217 | 定义好`make`方法后我们通过它来帮我们实例化Circle类的对象:
218 | ```php
219 | $circle = make('Circle');
220 | $area = $circle->area();
221 | /*var_dump($circle, $area);
222 | object(Circle)#6 (2) {
223 | ["radius"]=>
224 | int(1)
225 | ["center"]=>
226 | object(Point)#11 (2) {
227 | ["x"]=>
228 | int(0)
229 | ["y"]=>
230 | int(0)
231 | }
232 | }
233 | float(3.14)*/
234 | ```
235 |
236 | 通过上面这个实例我简单描述了一下如何利用PHP类的反射来实现依赖注入,Laravel的依赖注入也是通过这个思路来实现的,只不过设计的更精密大量地利用了闭包回调来应对各种复杂的依赖注入。
237 |
238 | 本文的[示例代码的下载链接][4]
239 |
240 |
241 | 下一篇:[Laravel服务容器][3]
242 |
243 | [1]: http://php.net/manual/zh/intro.reflection.php
244 | [2]: https://github.com/kevinyan815/Learning_Laravel_Kernel/blob/master/articles/IocContainer.md
245 | [3]: https://github.com/kevinyan815/Learning_Laravel_Kernel/blob/master/articles/IocContainer.md
246 | [4]: https://github.com/kevinyan815/php_reflection_dependency_injection_demo/blob/master/reflection.php
247 |
--------------------------------------------------------------------------------
/codes/reflection_dependency_injection_demo.php:
--------------------------------------------------------------------------------
1 | center = $point;
19 | $this->radius = $radius;
20 | }
21 |
22 | public function printCenter()
23 | {
24 | printf('center coordinate is (%d, %d)', $this->center->x, $this->center->y);
25 | }
26 |
27 | public function area()
28 | {
29 | return 3.14 * pow($this->radius, 2);
30 | }
31 | }
32 |
33 | /**
34 | * Class Point
35 | */
36 | class Point
37 | {
38 | public $x;
39 | public $y;
40 |
41 | /**
42 | * Point constructor.
43 | * @param int $x horizontal value of point's coordinate
44 | * @param int $y vertical value of point's coordinate
45 | */
46 | public function __construct($x = 0, $y = 0)
47 | {
48 | $this->x = $x;
49 | $this->y = $y;
50 | }
51 | }
52 |
53 |
54 | $reflectionClass = new reflectionClass(Circle::class);
55 |
56 |
57 | function make($className)
58 | {
59 | $reflectionClass = new ReflectionClass($className);
60 | $constructor = $reflectionClass->getConstructor();
61 | $parameters = $constructor->getParameters();
62 | $dependencies = getDependencies($parameters);
63 |
64 | return $reflectionClass->newInstanceArgs($dependencies);
65 | }
66 |
67 | function getDependencies($parameters)
68 | {
69 | $dependencies = [];
70 | foreach($parameters as $parameter) {
71 | $dependency = $parameter->getClass();
72 | if (is_null($dependency)) {
73 | if($parameter->isDefaultValueAvailable()) {
74 | $dependencies[] = $parameter->getDefaultValue();
75 | } else {
76 | //to easily implement this function, I just assume 0 to built-in type parameters
77 | $dependencies[] = '0';
78 | }
79 | } else {
80 | $dependencies[] = make($parameter->getClass()->name);
81 | }
82 | }
83 |
84 | return $dependencies;
85 | }
86 |
87 | $circle = make('Circle');
88 | $area = $circle->area();
89 |
--------------------------------------------------------------------------------
/images/.gitkeep:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/images/WX20200119-143845@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kevinyan815/Learning_Laravel_Kernel/77048f9676738898cdf3201f823d4df4b7f7ff77/images/WX20200119-143845@2x.png
--------------------------------------------------------------------------------
/images/WechatDonation.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kevinyan815/Learning_Laravel_Kernel/77048f9676738898cdf3201f823d4df4b7f7ff77/images/WechatDonation.jpeg
--------------------------------------------------------------------------------
/images/tWbHIMFsM3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kevinyan815/Learning_Laravel_Kernel/77048f9676738898cdf3201f823d4df4b7f7ff77/images/tWbHIMFsM3.png
--------------------------------------------------------------------------------