iBrand 产品中关于购物车的需求比较复杂,咱们基于 overture/laravel-shopping-cart 扩展出了更加符合电商需求的购物车包,以前有文章进行过简单的介绍: Laravel shopping cart : 电商购物车包,线上完美运行中php
源码地址: ibrand/laravel-shopping-cart
最开始扩展这个包时是由于如下需求:laravel
最初需求出来的时候,咱们经过不一样的 Guard 来做为用户购物车数据的区分,由于商城和导购是两种不一样的用户系统,因此当时在购物车 ServiceProvider 中的代码以下:git
$currentGuard = null; $user = null; $guards = array_keys(config('auth.guards')); foreach ($guards as $guard) { if ($user = auth($guard)->user()) { $currentGuard = $guard; break; } } if ($user) { //The cart name like `cart.{guard}.{user_id}`: cart.api.1 $cart->name($currentGuard.'.'.$user->id); }else{ throw new Exception('Invalid auth.'); }
经过循环遍历目前全部的 Guards
来获取目前请求中用户所属的 guard 值和用户对象,原本在新需求未增长时,一切都运行的挺正常。github
18年新增的需求:web
也就是如今存在三种购物车数据类型数据库
新需求出现的时候,为了区分购物车数据,确定是直接新建一个 guard,因此在 config/auth.php
中添加了 shop guard
代码以下。小程序
'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'admin' => [ 'driver' => 'session', 'provider' => 'admins', ], 'api' => [ 'driver' => 'passport', 'provider' => 'users', ], 'shop' => [ 'driver' => 'passport', 'provider' => 'users', ], 'clerk' => [ 'driver' => 'passport', 'provider' => 'clerk', ], ],
本觉得会运行良好,可是咱们忽略了一个细节,api
和 shop
两个 guard 的 provider 是同样的,由于都是属于用户的,而 api
又定义在 shop
前面,因此当执行以下代码时会出现问题,由于循环到 auth('api')->user()
的时候就退出了,致使了 shop guard
的数据也会存储成 api guard
.segmentfault
foreach ($guards as $guard) { if ($user = auth($guard)->user()) { $currentGuard = $guard; break; } }
以前工程师未发现合适的方法,因此采用了循环遍历 guards 来判断当前请求已认证 guard,当新需求产生后,这个方法再也不适用,可是又不能对当前的购物车包进行大改,因此只有再找解决办法,api
在 Laravel 中 request()->user()
都会获取到正确已认证的 guard user
数据,因此准备决定从这里的源码入手。session
request()->user()
实际调用的代码是 Illuminate\Http\Request
class 中 user()
方法
public function user($guard = null) { return call_user_func($this->getUserResolver(), $guard); }
追踪源码到 Illuminate\Auth\AuthServiceProvider
class,具体执行的代码为:return call_user_func($app['auth']->userResolver(), $guard);
protected function registerRequestRebindHandler() { $this->app->rebinding('request', function ($app, $request) { $request->setUserResolver(function ($guard = null) use ($app) { return call_user_func($app['auth']->userResolver(), $guard); }); }); }
继续追踪源码到 Illuminate\Auth\AuthManager
class
public function shouldUse($name) { $name = $name ?: $this->getDefaultDriver(); $this->setDefaultDriver($name); $this->userResolver = function ($name = null) { return $this->guard($name)->user(); }; }
到这里其实就结束了,咱们发现 request()->user()
最终执行的代码是 $this->guard($name)->user()
。因此咱们须要查看下 shouldUser
方法是在哪里调用的。
仍然看源码 Illuminate\Auth\Middleware\Authenticate
:
protected function authenticate(array $guards) { if (empty($guards)) { return $this->auth->authenticate(); } foreach ($guards as $guard) { if ($this->auth->guard($guard)->check()) { return $this->auth->shouldUse($guard); } } throw new AuthenticationException('Unauthenticated.', $guards); }
代码最终到这里基本比较清楚了,已认证用户的请求会经过 Authenticate
middleware ,而且把系统默认的 guard 设置为当前请求的 guard.
//获取到已认证用户的 guard foreach ($guards as $guard) { if ($this->auth->guard($guard)->check()) { return $this->auth->shouldUse($guard); } }
设置已认证 guard 为默认 guard,代替 config('auth.defaults.guard')
中的值
public function shouldUse($name) { $name = $name ?: $this->getDefaultDriver(); $this->setDefaultDriver($name); $this->userResolver = function ($name = null) { return $this->guard($name)->user(); }; }
因此获取到当前请求的 Guard 值,能够直接经过 AuthManager
class 中的 getDefaultDriver()
便可。
if ($defaultGuard = $app['auth']->getDefaultDriver()) { $currentGuard = $defaultGuard; $user = auth($currentGuard)->user(); }