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

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

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

原需求

最開始擴展這個包時是由於如下需求:laravel

  • 用戶登陸後的購物車數據須要存儲在數據庫中。由於客戶但願可以直觀的看到目前購物車中商品信息,以便推送優惠信息來促使轉化。雖然咱們按照 GA 的標準把數據傳送過去了,可是咱們發現 GA中數據並非很是準確。
  • 用戶在商城中的購物車數據
  • 導購使用導購小程序代用戶下單或結帳時加入的購物車數據,不和用戶購物車數據同步。

原解決方案

最初需求出來的時候,咱們經過不一樣的 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',
        ],
    ],

問題產生

本覺得會運行良好,可是咱們忽略了一個細節,apishop 兩個 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();
}

討論交流

iBrand聯繫咱們

相關文章
相關標籤/搜索