Laravel 獲取當前 Guard 分析 —源自電商購物車的實際需求


iBrand 產品中關於購物車的需求比較復雜,我們基於 overture/laravel-shopping-cart 擴展出了更加符合電商需求的購物車包,之前有文章進行過簡單的介紹: Laravel shopping cart : 電商購物車包,線上完美運行中

源碼地址:  ibrand/laravel-shopping-cart

原需求

最開始擴展這個包時是因為以下需求:

  • 用戶登錄后的購物車數據需要存儲在數據庫中。因為客戶希望能夠直觀的看到目前購物車中商品信息,以便推送優惠信息來促使轉化。雖然我們按照 GA 的標准把數據傳送過去了,但是我們發現 GA中數據並不是非常准確。
  • 用戶在商城中的購物車數據
  • 導購使用導購小程序代用戶下單或結賬時加入的購物車數據,不和用戶購物車數據同步。

原解決方案

最初需求出來的時候,我們通過不同的 Guard 來作為用戶購物車數據的區分,因為商城和導購是兩種不同的用戶系統,所以當時在購物車 ServiceProvider 中的代碼如下:

            $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 值和用戶對象,本來在新需求未增加時,一切都運行的挺正常。

新需求

18年新增的需求:

  • 用戶門店掃碼(二維碼或條形碼)自助下單的購物車數據要和商城的購物車數據區分

也就是現在存在三種購物車數據類型

  • 用戶在商城的購物車數據
  • 用戶在線下門店中自助下單的購物車數據
  • 導購的購物車數據

新需求解決方案

新需求出現的時候,為了區分購物車數據,肯定是直接新建一個 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.

           foreach ($guards as $guard) { if ($user = auth($guard)->user()) { $currentGuard = $guard; break; } }

解決方案

之前工程師未發現合適的方法,所以采用了循環遍歷 guards 來判斷當前請求已認證 guard,當新需求產生后,這個方法不再適用,但是又不能對當前的購物車包進行大改,所以只有再找解決辦法,

思路

在 Laravel 中 request()->user() 都會獲取到正確已認證的 guard user 數據,所以准備決定從這里的源碼入手。

源碼分析

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

討論交流

聯系我們




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM