内容简介:Laravel HTTP——控制器方法的参数构建与运行
前言
本文 GitBook 地址: https://www.gitbook.com/book/leoyang90/laravel-source-analysis
经过前面一系列中间件的工作,现在请求终于要达到了正确的控制器方法了。本篇文章主要讲 laravel
如何调用控制器方法,并且为控制器方法依赖注入构建参数的过程。
路由控制器的调用
我们前面已经解析过中间件的搜集与 排序 、pipeline 的原理,接下来就要进行路由的 run
运行函数:
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()
);
});
}
路由的 run
函数主要负责路由控制器方法与路由闭包函数的运行:
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();
}
}
路由的运行主要靠 ControllerDispatcher
这个类:
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()
);
}
}
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));
}
}
上面可以很清晰地看出,控制器的运行分为两步:解析函数参数、调用callAction
解析控制器方法参数
解析参数的功能主要由 ControllerDispatcher
类的 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)
);
}
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;
}
}
控制器方法函数参数构造难点在于,参数来源有三种:
- 路由参数赋值
- Ioc 容器自动注入
- 函数自带默认值
在 Ioc 容器自动注入的时候,要保证路由的现有参数中没有相应的类,防止依赖注入覆盖路由绑定的参数:
protected function transformDependency(ReflectionParameter $parameter, $parameters)
{
$class = $parameter->getClass();
if ($class && ! $this->alreadyInParameters($class->name, $parameters)) {
return $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;
}));
}
由 Ioc 容器构造出的参数需要插入到原有的路由参数数组中:
if (! is_null($instance)) {
$instanceCount++;
$this->spliceIntoParameters($parameters, $key, $instance);
}
protected function spliceIntoParameters(array &$parameters, $offset, $value)
{
array_splice(
$parameters, $offset, 0, [$value]
);
}
当路由的参数数组与 Ioc 容器构造的参数数量不足以覆盖控制器参数个数时,就要去判断控制器是否具有默认参数:
elseif (! isset($values[$key - $instanceCount]) &&
$parameter->isDefaultValueAvailable()) {
$this->spliceIntoParameters($parameters, $key, $parameter->getDefaultValue());
}
调用控制器方法 callAction
所有的控制器并非是直接调用相应方法的,而是通过 callAction
函数再分配,如果实在没有相应方法还会调用魔术方法 __call()
:
public function callAction($method, $parameters)
{
return call_user_func_array([$this, $method], $parameters);
}
public function __call($method, $parameters)
{
throw new BadMethodCallException("Method [{$method}] does not exist.");
}
路由闭包函数的调用
路由闭包函数的调用与控制器方法一样,仍然需要依赖注入,参数构造:
protected function runCallable()
{
$callable = $this->action['uses'];
return $callable(...array_values($this->resolveMethodDependencies(
$this->parametersWithoutNulls(), new ReflectionFunction($this->action['uses'])
)));
}
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- [ Laravel从入门到精通 ] 编写 JSON API —— 基于资源控制器和 API 资源类快速构建 API 接口
- iOS之导航返回上上个控制器或指定返回某个控制器
- iOS小技巧·把子视图控制器的视图添加到父视图控制器
- javascript – AngularJS控制器错误 – :$http.get不是控制器部分的函数
- AC敏捷控制器及准入控制技术对比
- angularjs – 使用ui-router时,控制器是否可以从父控制器继承范围
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Java Web入门经典
王国辉、陈英 / 机械工业出版社 / 2013-6 / 69.00元
《Java Web入门经典》以初学者为核心,全面介绍了JavaWeb开发中常用的各种技术。内容排列上由浅入深,让读者循序渐进掌握编程技术;在内容讲解上结合丰富的图解和形象的比喻,帮助读者理解“晦涩难懂”的技术;在内容形式上附有大量的提示、技巧、说明等栏目,夯实读者编程技术,丰富编程经验。全书共分4篇19章,其中,第一篇为“起步篇”,主要包括开启JavaWeb之门、不可不知的客户端应用技术、驾驭Ja......一起来看看 《Java Web入门经典》 这本书的介绍吧!
URL 编码/解码
URL 编码/解码
html转js在线工具
html转js在线工具