路由中间件之 SubstituteBindings
简介
[
'App\Http\Middleware\EncryptCookies',
'Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse',
'Illuminate\Session\Middleware\StartSession',
'Illuminate\View\Middleware\ShareErrorsFromSession',
'App\Http\Middleware\VerifyCsrfToken',
'Illuminate\Routing\Middleware\SubstituteBindings', // 本章内容
'App\Http\Middleware\RedirectIfAuthenticated',
]
SubstituteBindings
作用:显式和隐式地根据请求参数绑定对应数据模型。
这样说,可能大家有点思维模糊。我举一个控制器方法大家就知道了
路由
Route::put('user/{user}', 'UserController@update');
控制器
public function update(User $user, Request $request)
{
//
}
如上,当 PUT
请求 http://localhost/user/1
,实现用户信息修改时。将自动寻找 ID 为 1 的 User 模型,绑定到控制器的 update 方法上。
显式绑定的源码原理
SubstituteBindings 中间件的 handle 方法
public function handle($request, Closure $next)
{
// 显式绑定
$this->router->substituteBindings($route = $request->route());
$this->router->substituteImplicitBindings($route);
return $next($request);
}
我们来看一下路由的 substituteBindings
方法:
public function substituteBindings($route)
{
// $route->parameters() 返回的是请求的参数
foreach ($route->parameters() as $key => $value) {
if (isset($this->binders[$key])) {
$route->setParameter($key, $this->performBinding($key, $value, $route));
}
}
return $route;
}
$this->binders
储存的数据格式是 'string' => '闭包'
。string
就是请求参数中 name,而 闭包 的参数是请求里面的对应 value,返回的是处理后的 value。一般是返回寻找到模型。原因我们来看一下 $this->performBinding
方法
protected function performBinding($key, $value, $route)
{
// PHP 的闭包调用。传入参数值和路由
return call_user_func($this->binders[$key], $value, $route);
}
那么 $this->binders
如何被赋值的呢?
public function bind($key, $binder)
{
$this->binders[str_replace('-', '_', $key)] = RouteBinding::forCallback(
$this->container, $binder
);
}
如何调用,什么时候调用的 bind 方法,我们来看官方文档
Route::bind('user', function ($value) {
return App\User::where('name', $value)->first() ?? abort(404);
});
自定义逻辑解析:就是调用 bind 方法的时刻与位置。
上面这段代码的作用:如下设定
路由
Route::put('user/{user}', 'UserController@update');
控制器
public function update(User $user, Request $request)
{
//...
}
当请求URL:http://localhost/user/yuanshang
时,将在 users 表中寻找 name 字段为 yuanshang 的数据模型,然后重新绑定到 user 参数上,然后传递到控制器上。此时 $user 就是 name 等于 yuanshang 的模型参数数据。
隐式绑定的源码原理
SubstituteBindings 中间件的 handle 方法
public function handle($request, Closure $next)
{
$this->router->substituteBindings($route = $request->route());
// 隐式绑定
$this->router->substituteImplicitBindings($route);
return $next($request);
}
我们来看一下路由的 substituteImplicitBindings
方法:
public function substituteImplicitBindings($route)
{
ImplicitRouteBinding::resolveForRoute($this->container, $route);
}
继续查看 resolveForRoute
方法,传入 Laravel 容器和匹配路由对象
public static function resolveForRoute($container, $route)
{
// 获取原生的请求参数对
$parameters = $route->parameters();
// 获取路由定义的可变参数列表
foreach ($route->signatureParameters(UrlRoutable::class) as $parameter) {
// 如果请求的参数名称没有在路由定义的可变参数列表中,则跳过循环
if (! $parameterName = static::getParameterName($parameter->name, $parameters)) {
continue;
}
// 获取对应值
$parameterValue = $parameters[$parameterName];
// 如果值是对象且属于 UrlRoutable 类,跳过循环
if ($parameterValue instanceof UrlRoutable) {
continue;
}
// 获取模型实例
$instance = $container->make($parameter->getClass()->name);
// 根据定义的字段获取对应数据模型
if (! $model = $instance->resolveRouteBinding($parameterValue)) {
throw (new ModelNotFoundException)->setModel(get_class($instance));
}
// 将模型重新绑定到路由参数上
$route->setParameter($parameterName, $model);
}
}
最后讲一下
Laravel 的资源控制器:
上面 {photo}
就是 photos 数据表的 ID,在控制器中即可以用数据模型绑定的方法,便捷操作数据。
public function update(Photo $photo, Request $request)
{
$photo->name = '修改过的名称';
$photo->save();
return response()->json([
'code' => 200,
'message' => '修改成功',
]);
}