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(); }