Laravel核心解读--控制器

控制器

控制器能够将相关的请求处理逻辑组成一个单独的类, 通过前面的路由和中间件两个章节我们多次强调Laravel应用的请求在进入应用后首现会通过Http Kernel里定义的基本中间件

protected $middleware = [\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,\App\Http\Middleware\TrimStrings::class,\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,\App\Http\Middleware\TrustProxies::class,
];

然后Http Kernel会通过dispatchToRoute将请求对象移交给路由对象进行处理,路由对象会收集路由上绑定的中间件然后还是像上面Http Kernel里一样用一个Pipeline管道对象将请求传送通过这些路由上绑定的这些中间键,到达目的地后会执行路由绑定的控制器方法然后把执行结果封装成响应对象,响应对象一次通过后置中间件最后返回给客户端。

下面是刚才说的这些步骤对应的核心代码:

namespace Illuminate\Foundation\Http;
class Kernel implements KernelContract
{protected function dispatchToRouter(){return function ($request) {$this->app->instance('request', $request);return $this->router->dispatch($request);};}
}namespace Illuminate\Routing;
class Router implements RegistrarContract, BindingRegistrar
{    public function dispatch(Request $request){$this->currentRequest = $request;return $this->dispatchToRoute($request);}public function dispatchToRoute(Request $request){return $this->runRoute($request, $this->findRoute($request));}protected function runRoute(Request $request, Route $route){$request->setRouteResolver(function () use ($route) {return $route;});$this->events->dispatch(new Events\RouteMatched($route, $request));return $this->prepareResponse($request,$this->runRouteWithinStack($route, $request));}protected function runRouteWithinStack(Route $route, Request $request){$shouldSkipMiddleware = $this->container->bound('middleware.disable') &&$this->container->make('middleware.disable') === true;//收集路由和控制器里应用的中间件$middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);return (new Pipeline($this->container))->send($request)->through($middleware)->then(function ($request) use ($route) {return $this->prepareResponse($request, $route->run());});}
}namespace Illuminate\Routing;
class Route
{public function run(){$this->container = $this->container ?: new Container;try {if ($this->isControllerAction()) {return $this->runController();}return $this->runCallable();} catch (HttpResponseException $e) {return $e->getResponse();}}}

我们在前面的文章里已经详细的解释过Pipeline、中间件和路由的原理了,接下来就看看当请求最终找到了路由对应的控制器方法后Laravel是如何为控制器方法注入正确的参数并调用控制器方法的。

解析控制器和方法名

路由运行控制器方法的操作runController首现会解析出路由中对应的控制器名称和方法名称。我们在讲路由那一章里说过路由对象的action属性都是类似下面这样的:

['uses' => 'App\Http\Controllers\SomeController@someAction','controller' => 'App\Http\Controllers\SomeController@someAction','middleware' => ...
]
class Route
{protected function isControllerAction(){return is_string($this->action['uses']);}protected function runController(){return (new ControllerDispatcher($this->container))->dispatch($this, $this->getController(), $this->getControllerMethod());}public function getController(){if (! $this->controller) {$class = $this->parseControllerCallback()[0];$this->controller = $this->container->make(ltrim($class, '\\'));}return $this->controller;}protected function getControllerMethod(){return $this->parseControllerCallback()[1];}protected function parseControllerCallback(){return Str::parseCallback($this->action['uses']);}
}class Str
{//解析路由里绑定的控制器方法字符串,返回控制器和方法名称字符串构成的数组public static function parseCallback($callback, $default = null){return static::contains($callback, '@') ? explode('@', $callback, 2) : [$callback, $default];}
}

所以路由通过parseCallback方法将uses配置项里的控制器字符串解析成数组返回, 数组第一项为控制器名称、第二项为方法名称。在拿到控制器和方法的名称字符串后,路由对象将自身、控制器和方法名传递给了Illuminate\Routing\ControllerDispatcher类,由ControllerDispatcher来完成最终的控制器方法的调用。下面我们详细看看ControllerDispatcher是怎么来调用控制器方法的。

class ControllerDispatcher
{use RouteDependencyResolverTrait;public function dispatch(Route $route, $controller, $method){$parameters = $this->resolveClassMethodDependencies($route->parametersWithoutNulls(), $controller, $method);if (method_exists($controller, 'callAction')) {return $controller->callAction($method, $parameters);}return $controller->{$method}(...array_values($parameters));}
}

上面可以很清晰地看出,ControllerDispatcher里控制器的运行分为两步:解决method的参数依赖resolveClassMethodDependencies、调用控制器方法。

解决method参数依赖

解决方法的参数依赖通过RouteDependencyResolverTrait这一trait负责:

trait RouteDependencyResolverTrait
{protected function resolveClassMethodDependencies(array $parameters, $instance, $method){if (! method_exists($instance, $method)) {return $parameters;}return $this->resolveMethodDependencies($parameters, new ReflectionMethod($instance, $method));}//参数为路由参数数组$parameters(可为空array)和控制器方法的反射对象public function resolveMethodDependencies(array $parameters, ReflectionFunctionAbstract $reflector){$instanceCount = 0;$values = array_values($parameters);foreach ($reflector->getParameters() as $key => $parameter) {$instance = $this->transformDependency($parameter, $parameters);if (! is_null($instance)) {$instanceCount++;$this->spliceIntoParameters($parameters, $key, $instance);} elseif (! isset($values[$key - $instanceCount]) &&$parameter->isDefaultValueAvailable()) {$this->spliceIntoParameters($parameters, $key, $parameter->getDefaultValue());}}return $parameters;}}

在解决方法的参数依赖时会应用到PHP反射的ReflectionMethod类来对控制器方法进行方向工程, 通过反射对象获取到参数后会判断现有参数的类型提示(type hint)是否是一个类对象参数,如果是类对象参数并且在现有参数中没有相同类的对象那么就会通过服务容器来make出类对象。

    protected function transformDependency(ReflectionParameter $parameter, $parameters){$class = $parameter->getClass();if ($class && ! $this->alreadyInParameters($class->name, $parameters)) {return $parameter->isDefaultValueAvailable()? $parameter->getDefaultValue(): $this->container->make($class->name);}}protected function alreadyInParameters($class, array $parameters){return ! is_null(Arr::first($parameters, function ($value) use ($class) {return $value instanceof $class;}));}

解析出类对象后需要将类对象插入到参数列表中去

    protected function spliceIntoParameters(array &$parameters, $offset, $value){array_splice($parameters, $offset, 0, [$value]);}

我们之前讲服务容器时,里面讲的服务解析解决是类构造方法的参数依赖,而这里resolveClassMethodDependencies里解决的是具体某个方法的参数依赖,它Laravel对method dependency injection概念的实现。

当路由的参数数组与服务容器构造的类对象数量之和不足以覆盖控制器方法参数个数时,就要去判断该参数是否具有默认参数,也就是会执行resolveMethodDependencies方法foreach块里的else if分支将参数的默认参数插入到方法的参数列表$parameters中去。

} elseif (! isset($values[$key - $instanceCount]) &&$parameter->isDefaultValueAvailable()) {$this->spliceIntoParameters($parameters, $key, $parameter->getDefaultValue());
}

调用控制器方法

解决完method的参数依赖后就该调用方法了,这个很简单, 如果控制器有callAction方法就会调用callAction方法,否则的话就直接调用方法。

    public function dispatch(Route $route, $controller, $method){$parameters = $this->resolveClassMethodDependencies($route->parametersWithoutNulls(), $controller, $method);if (method_exists($controller, 'callAction')) {return $controller->callAction($method, $parameters);}return $controller->{$method}(...array_values($parameters));}

执行完拿到结果后,按照上面runRouteWithinStack里的逻辑,结果会被转换成响应对象。然后响应对象会依次经过之前应用过的所有中间件的后置操作,最后返回给客户端。

本文已经收录在系列文章Laravel源码学习里,欢迎访问阅读。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/254617.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

javascript:正则表达式、一个表单验证的例子

阅读目录 本文内容:正则表达式:利用正则表达式进行表单验证的例子:回到顶部本文内容: 正则表达式正则表达式的使用方法正则表达式的特殊匹配字符正则表达式修饰符利用正则表达式进行表单验证的例子首发日期:2018-05-13…

Spring_01 spring容器、控制反转(IOC)、依赖注入(DI)

目录 1 什么是spring框架 2 spring框架的特点 3 spring容器 3.1 什么是spring容器 3.2 spring容器创建对象的编程步骤 3.4 spring容器创建对象的方式 3.5 bean元素的几个重要属性 4 IOC 4.1 什么是IOC 4.2 什么事DI 4.3 DI的三种方式 1 什么是spring框架 是一个开源的用来简化企…

一个传值的问题”*”与”*”

1/********************************************************* 2* Desc:参数传递&#xff1a;使用引用传递指针和直接传递指针地址的区别 3* Author:charley 4* DateTime:2010-12-7 11:00 02***********************************************************/ 03#include <…

spring boot 扩展之AutoConfigurationImportListener

最近阅读spring boot源码时发现&#xff0c;发现当spring使用ConfigurationClassParser加载使用Configuration注解类后&#xff0c;会使用AutoConfigurationImportSelector对加载的 Configuration注解的类进行一次过滤。当AutoConfigurationImportSelector过滤完成后会自动加载…

《高效程序员的45个习惯》-之一

敏捷开发是当下最流行的开发方法&#xff0c;它采用的是一种以人为核心、迭代、循序渐进的开发思想&#xff0c;值得你关注和学习。 最近我就阅读了一本有关敏捷开发的书籍&#xff0c;《高效程序员的45个习惯》。 它以“举反例”的方式来讲述了敏捷开发中程序员应该运用的…

《CLR via C#》之线程处理——线程基础

《CLR via C#》之线程处理——线程基础 《CLR via C#》之线程处理——线程基础windows为什么要支持线程线程开销CPU发展趋势CLR线程和Windows线程使用专用线程执行异步的计算限制操作线程调度和优先级windows为什么要支持线程 早期的操作系统只有一个执行线程&#xff0c;但同时…

spring boot之从零开始开发自己的网站

概述 首先要感谢两位大神&#xff0c;该项目的想法来源自tale和MyBlog。 做了一些改造&#xff0c;增加了一些功能和一些代码的重构&#xff0c;并且更换了博客主题。 关于项目&#xff0c;对于开发的练手项目&#xff0c;能够工程化&#xff0c;严谨一些。 关于文档&#x…

python day5--正则表达式

#----正则表达式 import re elink <a href"(.*)">(.*)</a> info <a href"http://www.baidu.com">baidu</a> cinfo re.findall(elink,info) print (cinfo) import re print(re.search (r^a,abc\neee)) #预期结果 ^匹配字符开…

WCF系列教程之WCF客户端调用服务

1、创建WCF客户端应用程序需要执行下列步骤 (1)、获取服务终结点的服务协定、绑定以及地址信息 (2)、使用该信息创建WCF客户端 (3)、调用操作 (4)、关闭WCF客户端对象 二、操作实例 1、WCF服务层搭建:新建契约层、服务层、和WCF宿主,添加必须的引用(这里不会的参考本人前面的随…

短信认证方案,用手机短信进行上网认证如何实现?

WFilter NGF的“Web认证”模块&#xff0c;提供了一系列的上网认证解决方案。包括如下认证方式&#xff1a;本地用户名密码认证AD域用户名密码认证企业邮箱用户名密码认证Radius用户名密码认证微信WiFi认证Facebook Wifi认证除此&#xff0c;WFilter NGF还有一个“其他”的选项…

Nginx 之一:编译安装nginx 1.8.1 及配置

转http://www.cnblogs.com/zhang-shijie/p/5294162.html 一&#xff1a;基介绍 官网地址www.nginx.org&#xff0c;nginx是由1994年毕业于俄罗斯国立莫斯科鲍曼科技大学的同学为俄罗斯rambler.ru公司开发的&#xff0c;开发工作最早从2002年开始&#xff0c;第一次公开发布时间…

PI校正环节的程序实现推导过程

PI校正环节在经典控制论中非常有用&#xff0c;特别是对负反馈控制系统&#xff0c;基本上都有PI校正环节。1.下面分别说明比例环节和积分环节的作用&#xff0c;以阶跃信号为例。①比例环节单独作用以上分析说明&#xff0c;若只有比例环节的控制系统&#xff0c;阶跃响应也是…

vs里根据json快速创建对应类的方法

有时候,我们在调用别人接口的时候,服务端返回了一个json格式的字符串,我们要获取json里面的数据的话一般有两种方式: 1.通过正则 2.反序列化成一个对象 第一种方式这里不再多说,主要说一下第二种,(为什么呢&#xff0c;你看到后面 你也会喜欢上第二种) 有人肯定会说, json字符串…

人工智能之基于face_recognition的人脸检测与识别

不久乘高铁出行&#xff0c;看见高铁火车站已经实现了“刷脸进站”&#xff0c;而且效率很高&#xff0c;很感兴趣&#xff0c;今天抽时间研究一下&#xff0c;其实没那么复杂。 我基本上是基于https://github.com/ageitgey/face_recognition上的资料和源码做一些尝试和试验。 …

iOS 升级https的方案选择

我的选择是将UIWebView统一替换为WKWebView WKWebView AFN SDWebImage https的支持之前的博客都有涉及转载于:https://www.cnblogs.com/Jusive/p/6867531.html

Python3抓取糗百、不得姐

​点击关注 异步图书&#xff0c;置顶公众号 每天与你分享 IT好书 技术干货 职场知识 重要提示1:本文所列程序均基于Python3.6,低于Python3.6的Python版本可能无法运行.重要提示2:因所抓取的网站可能随时更改展示内容,因此程序也需及时跟进.重要提示3:本程序仅供学习,不能拿去做…

Oracle优化-表设计

前言  绝大多数的Oracle数据库性能问题都是由于数据库设计不合理造成的&#xff0c;只有少部分问题根植于Database Buffer、Share Pool、Redo Log Buffer等内存模块配置不合理&#xff0c;I/O争用&#xff0c;CPU争用等DBA职责范围上。所以除非是面对一个业已完成不可变更的系…

Win10远程桌面 出现 身份验证错误,要求的函数不受支持,这可能是由于CredSSP加密Oracle修正 解决方法...

升级至win10 最新版本10.0.17134&#xff0c;远程桌面连接Window Server时报错信息如下&#xff1a; 出现身份验证错误&#xff0c;要求的函数不正确&#xff0c;这可能是由于CredSSP加密Oracle修正。 解决方法&#xff1a; 运行 gpedit.msc 本地组策略&#xff1a; 计算机配置…

Rsyslog 日志相关内容

[rootserver vusers_home]# rpm -ql rsyslog|more ###.so结尾为模块&#xff0c;模块有分im为输入模块&#xff0c;om 为输出模块/etc/logrotate.d/syslog/etc/pki/rsyslog/etc/rc.d/init.d/rsyslog/etc/rsyslog.conf/etc/rsyslog.d/etc/sysconfig/rsyslog/lib64/rsyslog…

MFC导出对话框类DLL的实现

1.新建基于对话框的应用程序 2.新建MFC DLL工程 3.选择MFC DLL 4.选择扩展Dll选项&#xff08;重要&#xff01;&#xff01;&#xff01;&#xff09; 5.为Dll工程添加一个MFC类&#xff0c;基类为CDialogEx 6.Dll新建的MFC 类中添加resource.h防止编译出错…