在laravel5.5中,可以根据控制器方法的参数类型,自动注入一个实例化对象,极大提升了编程的效率,但是相比较与Java的SpringMVC框架,功能还是有所欠缺,使用起来还是不太方便,主要体现在方法参数的注入不完全是按照参数名称进行的,如果改变了传入参数的顺序会导致类型不匹配的错误。

一、控制器方法参数注入步骤设计

1、在/routes/web.php中添加路由

  1. Route::get('/diary/show/{diary}/{page?}','Diary\DiaryController@list');

2、编写控制器文件DiaryController.php放到/app/Http/Controllers/Diary/路径下面

  1. <?php
  2. namespace App\Http\Controllers\Diary;
  3. use App\Http\Controllers\Controller;
  4. class DiaryController extends Controller
    {
  5. public function show(\App\Diary $diary,$page=11){
  6. var_dump($diary->title,$page);
      }
    }

3、构建模型\App\Diary并安装到数据库(略)

  1. <?php
  2. namespace App;
  3. use Illuminate\Database\Eloquent\Model;
  4. class Diary extends Model
  5. {
  6. protected $table='diary';
  7. public $timestamps = false;
  8. }

4、访问控制器方法

打开浏览器输入:“http://127.0.0.1//diary/show/4/12”

此时输出数据表diary中id=4的title字段值和12

二、注入参数类型说明

说明:laravel会根据请求路由中匹配的{diary}和{page}变量和控制器方法中需要的方法参数类型,生成实例对象并注入到控制器方法中,

针对不同的参数类型分三种情况:

1、如果参数类型实现了UrlRoutable接口(即继承自Illuminate\Database\Eloquent\Model),则在模型对象对应的表中查找id值为路由中匹配参数值的那条记录,并构建模型对象;

2、如果参数类型为自定义类型(没有实现UrlRoutable接口),则laravel会构建一个对象后注入;

3、如果参数类型为基础数据类型,并且名称为路由参数中定义的名称,则从路由参数中获取值;

4、如果参数类型为基础数据类型,但名称未在路由参数中定义,如果有默认值则使用默认值,否则系统提示错误。

三、目前注入参数存在的问题分析

参考java的Spring MVC框架,laravel的参数类型注入还存在缺陷,主要体现在不完全是按照参数名称进行注入。

1、如果改变控制器参数的顺序,会出现参数类型传递错误,如将DiaryController控制的show方法的参数改变顺序,则会导致错误发生:

  1. <?php
  2. namespace App\Http\Controllers\Diary;
  3. use App\Http\Controllers\Controller;
  4. class DiaryController extends Controller
  5. {
  6. public function show($page,\App\Diary $diary){
  7. var_dump($diary->title,$page);
  8.   }
  9. }

2、由于参数类型为基础数据类型(参见二(3)),并不是按照名称来注入的参数,因此将代码改为如下,同样会运行正常

  1. <?php
  2. namespace App\Http\Controllers\Diary;
  3. use App\Http\Controllers\Controller;
  4. class DiaryController extends Controller
  5. {
  6. public function show(\App\Diary $diary,$pag){
  7. var_dump($diary->title,$pag);
  8.   }
  9. }

四、laravel5.5控制器方法参数注入源码剖析

1、实现了UrlRoutable接口的参数类型由路由中间件Illuminate\Routing\Middleware\SubstituteBinding实现构建

  1. public function handle($request, Closure $next)
  2. {
  3. $this->router->substituteBindings($route = $request->route());
  4. $this->router->substituteImplicitBindings($route);
  5.  
  6. return $next($request);
  7. }

Illuminate\Routing\Router的substituteImplicitBindings方法

  1. public function substituteImplicitBindings($route)
  2. {
  3. ImplicitRouteBinding::resolveForRoute($this->container, $route);
  4. }

在Illuminate\Routing\ImplicitRouteBinding的resolveForRoute方法中实现

  1. public static function resolveForRoute($container, $route)
  2. {
  3. //从路由参数中获取参数值,$parameters为 ['diary':'4','page':'12']
  4. $parameters = $route->parameters();
  5. //获取控制器的函数参数列表,此处传入UrlRoutable::class,只返回实现UrlRoutable接口的参数
  6. $signatureParameters = $route->signatureParameters(UrlRoutable::class);
  7. foreach ($signatureParameters as $parameter) {
  8. if (! $parameterName = static::getParameterName($parameter->name, $parameters)) {
  9. continue;
  10. }
  11.  
  12. $parameterValue = $parameters[$parameterName];
  13. if ($parameterValue instanceof UrlRoutable) {
  14. continue;
  15. }
  16. //构建模型的实例(基础自Illuminate\Database\Eloquent\Model),此处为App\Diary
  17. $instance = $container->make($parameter->getClass()->name);
  18. //将参数值绑定到模型,参加Illuminate\Database\Eloquent\Model的resolveRouteBinding方法
  19. if (! $model = $instance->resolveRouteBinding($parameterValue)) {
  20. throw (new ModelNotFoundException)->setModel(get_class($instance));
  21. }
           //根据参数名称注入模型实例
  22. $route->setParameter($parameterName, $model);
  23. }
  24. }

附加说明:

此处调用$route对象(Illuminate\Routing\Route类型)setParameter方法,说明模型参数类型(见二(1))正是通过参数类型和参数名称同时匹配才注入模型实例

2、其它类型的控制器参数在运行路由控制器时绑定

关键部分在Illuminate\Routing\ControllerDispatcher的dispatch方法中实现:

  1. public function dispatch(Route $route, $controller, $method)
  2. {
  3. //解析控制器方法的参数
  4. $parameters = $this->resolveClassMethodDependencies(
  5. $route->parametersWithoutNulls(), $controller, $method
  6. );
  7. if (method_exists($controller, 'callAction')) {
  8. //通过Illuminate\Routing\Controller的callAction调用控制器方法
  9. return $controller->callAction($method, $parameters);
  10. }
  11. //直接调用控制器方法
  12. return $controller->{$method}(...array_values($parameters));
  13. }

调用resolveClassMethodDependencies方法

  1. public function resolveMethodDependencies(array $parameters, ReflectionFunctionAbstract $reflector)
  2. {
  3. $instanceCount = 0;
  4. $values = array_values($parameters);
  5. //通过方法反射获取方法参数
  6. foreach ($reflector->getParameters() as $key => $parameter) {
  7. //如果有默认值则返回默认值,如果是自定义方法则构建实例返回
  8. $instance = $this->transformDependency(
  9. $parameter, $parameters
  10. );
  11.  
  12. if (! is_null($instance)) {
  13. $instanceCount++;
  14. //自定义类型(未实现UrlRoutable接口)的实例注入
  15. $this->spliceIntoParameters($parameters, $key, $instance);
  16. } elseif (! isset($values[$key - $instanceCount]) &&
  17. $parameter->isDefaultValueAvailable()) {
  18. //未在路由参数中定义,但有默认值的参数注入
  19. $this->spliceIntoParameters($parameters, $key, $parameter->getDefaultValue());
  20. }
  21. }
  22. return $parameters;
  23. }

问题总结说明:

1、模型参数(见二(1))和名称在路由参数中定义的基础类型(见二(3))必须按照在路由中定义的顺序首先传入控制器方法,否则会出现类型不匹配的错误;

2、自定义类型(见二(2))和名称未在路由参数中定义的基础类型参数(见二(4)),在控制器方法中按出现的顺序依次传入。

五、问题修复

打开/vendor/laravel/framework/src/Illuminate/Routing/RouteDependencyResolverTrait.php文件,

将resolveMethodDependencies方法修改为如下代码

  1. public function resolveMethodDependencies(array $parameters,ReflectionFunctionAbstract $reflector){
  2. $methodParameters=[];
  3. foreach($reflector->getParameters() as $key=>$parameter){
  4. $name=$parameter->getName();
  5. $instance=$this->transformDependency($parameter, $parameters);
  6. if(!is_null($instance)){
  7. $methodParameters[]=$instance;
  8. }elseif(!isset($parameters[$name]) && $parameter->isDefaultValueAvailable()){
  9. $methodParameters[]=$parameter->getDefaultValue();
  10. }else{
  11. $methodParameters[]=isset($parameters[$name]) ? $parameters[$name] : null;
  12. }
  13. }
  14. return $methodParameters;
  15. }

修改之后完全按照名称和类型注入控制器方法参数,代码变得更简洁,功能也更强大了!

如果参数没有在路由中定义并且未提供默认值,那么将以null注入。

本文为原创文章,版权归作者所有,转载请注明来源




关于laravel5.5控制器方法参数依赖注入原理深度解析及问题修复的更多相关文章

  1. SQL注入原理深度解析

    本文转自:http://www.iii-soft.com/forum.php?mod=viewthread&tid=1613&extra=page%3D1 对于Web应用来说,注射式攻 ...

  2. 第1课:SQL注入原理深度解析

    对于Web应用来说,注射式攻击由来已久,攻击方式也五花八门,常见的攻击方式有SQL注射.命令注射以及新近才出现的XPath注射等等.本文将以SQL注射为例,在源码级对其攻击原理进行深入的讲解. 一.注 ...

  3. @Bean修饰的方法参数的注入方式

    @Bean修饰的方法参数的注入方式: 方法参数默认注入方式为Autowired,即先根据类型匹配,若有多个在根据名称进行匹配. 1:复杂类型可以通过@Qualifier(value=“XXX”)限定; ...

  4. PHP依赖注入原理与用法分析

    https://www.jb51.net/article/146025.htm 本文实例讲述了PHP依赖注入原理与用法.分享给大家供大家参考,具体如下: 引言 依然是来自到喜啦的一道面试题,你知道什么 ...

  5. Spring依赖注入原理分析

    在分析原理之前我们先回顾下依赖注入的概念: 我们常提起的依赖注入(Dependency Injection)和控制反转(Inversion of Control)是同一个概念.具体含义是:当某个角色( ...

  6. 【串线篇】spring泛型依赖注入原理

    spring泛型依赖注入原理 不管三七二十一 servlet :加注解@servlet service:加注解@service dao:加注解@Repository 这相当于在容器中注册这些个类

  7. ASP.NET Core MVC 控制器创建与依赖注入

    本文翻译自<Controller activation and dependency injection in ASP.NET Core MVC>,由于水平有限,故无法保证翻译完全准确,欢 ...

  8. Spring-Context之六:基于Setter方法进行依赖注入

    上文讲了基于构造器进行依赖注入,这里讲解基于Setter方法进行注入.在Java世界中有个约定(Convention),那就是属性的设置和获取的方法名一般是:set+属性名(参数)及get+属性名() ...

  9. laravel5.2总结--服务容器(依赖注入,控制反转)

    1.依赖 我们定义两个类:class Supperman 和 class Power,现在我们要使用Supperman ,而Supperman 依赖了Power class Supperman { p ...

随机推荐

  1. 我的Spring学习记录(二)

    本篇就简单的说一下Bean的装配和AOP 本篇的项目是在上一篇我的Spring学习记录(一) 中项目的基础上进行开发的 1. 使用setter方法和构造方法装配Bean 1.1 前期准备 使用sett ...

  2. [python学习笔记] py2exe 打包

    遇坑 之前经过折腾,pyinstaller打包文件可以在别的windows7上运行.但是,mfk, 客户说是xp系统.崩溃 使用pyinstaller各种折腾,打包出来的依然是不是有效的win32程序 ...

  3. 小知识点-ios跳过app store更新版本

    版本更新实现的思路 获取自身的版本号 获取AppStore的版本号 自身的版本号和AppStore的比较 弹窗提示所需数据的获取的方式 1.获取自身的版本号 2.AppStore的版本号 Wechat ...

  4. InnoDB Undo Log

    简介 Undo Log包含了一系列在一个单独的事务中会产生的所有Undo Log记录.每一个Undo Log记录包含了如何undo事务对某一行修改的必要信息.InnoDB使用Undo Log来进行事务 ...

  5. AF_INET

    AF_INET(又称PF_INET)是 IPv4 网络协议的套接字类型,AF_INET6 则是 IPv6 的:而AF_UNIX 则是Unix系统本地通信. 选择AF_INET 的目的就是使用IPv4 ...

  6. js中的||与&&用法

    &&和||在JQuery源代码内尤为使用广泛,由网上找了些例子作为参考,对其用法研究了一下: &&: function a(){ alert("a" ...

  7. pygame_第一个窗口程序

    ####可以使用python自带的IDLE交互式开发,也可以借助其他的编辑器,我这里采用的pycharm编辑器 1.导入我们所需要的模块 import pygame,sys   --导入我们需要的模块 ...

  8. 探索Java NIO

    什么是NIO? java.nio全称java non-blocking IO,是指jdk1.4 及以上版本里提供的新api(New IO),NIO提供了与标准IO不同的IO工作方式. 核心部分: Ch ...

  9. Ubuntu16笔记本双显卡安装NVIDIA驱动

    blockquote { direction: ltr; color: rgb(0, 0, 0) } blockquote.western { font-family: "Liberatio ...

  10. ZOJ2006 一道很尴尬的string操作题

    ZOJ2006(最小表示法) 题目大意:输出第一个字符串的最小字典序字串的下标! 然后我居然想试一试string的erase的能力,暴力一下,然后20msAC了,尴尬的数据.......... #in ...