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联系咱们

相关文章
相关标签/搜索