在laravel5.5中,能够根据控制器方法的参数类型,自动注入一个实例化对象,极大提高了编程的效率,可是相比较与Java的SpringMVC框架,功能仍是有所欠缺,使用起来仍是不太方便,主要体如今方法参数的注入不彻底是按照参数名称进行的,若是改变了传入参数的顺序会致使类型不匹配的错误。php
一、在/routes/web.php中添加路由java
Route::get('/diary/show/{diary}/{page?}','Diary\DiaryController@list');
二、编写控制器文件DiaryController.php放到/app/Http/Controllers/Diary/路径下面laravel
<?php namespace App\Http\Controllers\Diary; use App\Http\Controllers\Controller; class DiaryController extends Controller
{ public function show(\App\Diary $diary,$page=11){ var_dump($diary->title,$page);
}
}
三、构建模型\App\Diary并安装到数据库(略)web
<?php namespace App; use Illuminate\Database\Eloquent\Model; class Diary extends Model { protected $table='diary'; public $timestamps = false; }
四、访问控制器方法数据库
打开浏览器输入:“http://127.0.0.1//diary/show/4/12”编程
此时输出数据表diary中id=4的title字段值和12浏览器
说明:laravel会根据请求路由中匹配的{diary}和{page}变量和控制器方法中须要的方法参数类型,生成实例对象并注入到控制器方法中,app
针对不一样的参数类型分三种状况:框架
一、若是参数类型实现了UrlRoutable接口(即继承自Illuminate\Database\Eloquent\Model),则在模型对象对应的表中查找id值为路由中匹配参数值的那条记录,并构建模型对象;函数
二、若是参数类型为自定义类型(没有实现UrlRoutable接口),则laravel会构建一个对象后注入;
三、若是参数类型为基础数据类型,而且名称为路由参数中定义的名称,则从路由参数中获取值;
四、若是参数类型为基础数据类型,但名称未在路由参数中定义,若是有默认值则使用默认值,不然系统提示错误。
参考java的Spring MVC框架,laravel的参数类型注入还存在缺陷,主要体如今不彻底是按照参数名称进行注入。
一、若是改变控制器参数的顺序,会出现参数类型传递错误,如将DiaryController控制的show方法的参数改变顺序,则会致使错误发生:
<?php namespace App\Http\Controllers\Diary; use App\Http\Controllers\Controller; class DiaryController extends Controller { public function show($page,\App\Diary $diary){ var_dump($diary->title,$page); } }
二、因为参数类型为基础数据类型(参见二(3)),并非按照名称来注入的参数,所以将代码改成以下,一样会运行正常
<?php namespace App\Http\Controllers\Diary; use App\Http\Controllers\Controller; class DiaryController extends Controller { public function show(\App\Diary $diary,$pag){ var_dump($diary->title,$pag); } }
public function handle($request, Closure $next) { $this->router->substituteBindings($route = $request->route()); $this->router->substituteImplicitBindings($route); return $next($request); }
Illuminate\Routing\Router的substituteImplicitBindings方法
public function substituteImplicitBindings($route) { ImplicitRouteBinding::resolveForRoute($this->container, $route); }
在Illuminate\Routing\ImplicitRouteBinding的resolveForRoute方法中实现
public static function resolveForRoute($container, $route) { //从路由参数中获取参数值,$parameters为 ['diary':'4','page':'12'] $parameters = $route->parameters(); //获取控制器的函数参数列表,此处传入UrlRoutable::class,只返回实现UrlRoutable接口的参数 $signatureParameters = $route->signatureParameters(UrlRoutable::class); foreach ($signatureParameters as $parameter) { if (! $parameterName = static::getParameterName($parameter->name, $parameters)) { continue; } $parameterValue = $parameters[$parameterName]; if ($parameterValue instanceof UrlRoutable) { continue; } //构建模型的实例(基础自Illuminate\Database\Eloquent\Model),此处为App\Diary $instance = $container->make($parameter->getClass()->name); //将参数值绑定到模型,参加Illuminate\Database\Eloquent\Model的resolveRouteBinding方法 if (! $model = $instance->resolveRouteBinding($parameterValue)) { throw (new ModelNotFoundException)->setModel(get_class($instance)); }
//根据参数名称注入模型实例 $route->setParameter($parameterName, $model); } }
附加说明:
此处调用$route对象(Illuminate\Routing\Route类型)setParameter方法,说明模型参数类型(见二(1))正是经过参数类型和参数名称同时匹配才注入模型实例
关键部分在Illuminate\Routing\ControllerDispatcher的dispatch方法中实现:
public function dispatch(Route $route, $controller, $method) { //解析控制器方法的参数 $parameters = $this->resolveClassMethodDependencies( $route->parametersWithoutNulls(), $controller, $method ); if (method_exists($controller, 'callAction')) { //经过Illuminate\Routing\Controller的callAction调用控制器方法 return $controller->callAction($method, $parameters); } //直接调用控制器方法 return $controller->{$method}(...array_values($parameters)); }
调用resolveClassMethodDependencies方法
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++; //自定义类型(未实现UrlRoutable接口)的实例注入 $this->spliceIntoParameters($parameters, $key, $instance); } elseif (! isset($values[$key - $instanceCount]) && $parameter->isDefaultValueAvailable()) { //未在路由参数中定义,但有默认值的参数注入 $this->spliceIntoParameters($parameters, $key, $parameter->getDefaultValue()); } } return $parameters; }
问题总结说明:
一、模型参数(见二(1))和名称在路由参数中定义的基础类型(见二(3))必须按照在路由中定义的顺序首先传入控制器方法,不然会出现类型不匹配的错误;
二、自定义类型(见二(2))和名称未在路由参数中定义的基础类型参数(见二(4)),在控制器方法中按出现的顺序依次传入。
打开/vendor/laravel/framework/src/Illuminate/Routing/RouteDependencyResolverTrait.php文件,
将resolveMethodDependencies方法修改成以下代码
public function resolveMethodDependencies(array $parameters,ReflectionFunctionAbstract $reflector){ $methodParameters=[]; foreach($reflector->getParameters() as $key=>$parameter){ $name=$parameter->getName(); $instance=$this->transformDependency($parameter, $parameters); if(!is_null($instance)){ $methodParameters[]=$instance; }elseif(!isset($parameters[$name]) && $parameter->isDefaultValueAvailable()){ $methodParameters[]=$parameter->getDefaultValue(); }else{ $methodParameters[]=isset($parameters[$name]) ? $parameters[$name] : null; } } return $methodParameters; }
修改以后彻底按照名称和类型注入控制器方法参数,代码变得更简洁,功能也更强大了!
若是参数没有在路由中定义而且未提供默认值,那么将以null注入。
本文为原创文章,版权归做者全部,转载请注明来源