在laravel5.5中,可以根据控制器方法的参数类型,自动注入一个实例化对象,极大提升了编程的效率,但是相比较与Java的SpringMVC框架,功能还是有所欠缺,使用起来还是不太方便,主要体现在方法参数的注入不完全是按照参数名称进行的,如果改变了传入参数的顺序会导致类型不匹配的错误。
1、在/routes/web.php中添加路由
- Route: :get('/diary/show/{diary}/{page?}', 'Diary\DiaryController@list');
2、编写控制器文件DiaryController.php放到/app/Http/Controllers/Diary/路径下面
- <?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);
- }
- }
3、构建模型\App\Diary并安装到数据库(略)
- <?php
- namespace App;
- use Illuminate\Database\Eloquent\Model;
- class Diary extends Model
- {
- protected $table='diary';
- public $timestamps = false;
- }
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方法的参数改变顺序,则会导致错误发生:
- <?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);
- }
- }
2、由于参数类型为基础数据类型(参见二(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、模型参数(见二(1))和名称在路由参数中定义的基础类型(见二(3))必须按照在路由中定义的顺序首先传入控制器方法,否则会出现类型不匹配的错误;
2、自定义类型(见二(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注入。
来源: http://www.cnblogs.com/springwind2006/p/7780524.html