对Laravel身份认证的分析

功能初始化

php artisan make:auth 使用laravel提供的命令行功能,创建路由、控制器、视图。

路由:

Auth::routes();

Route::get('/home', 'HomeController@index')->name('home');

HomeController:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class HomeController extends Controller
{
    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth');
    }

    /**
     * Show the application dashboard.
     *
     * @return \Illuminate\Contracts\Support\Renderable
     */
    public function index()
    {
        return view('home');
    }
}

视图:

resources\views\auth\

路由分析

Auth::routes();

很明显,这是用facade的形式调用了容器中Auth实例的routes方法。
config/app.php的facade别名配置中,找到了:

'Auth' => Illuminate\Support\Facades\Auth::class,

然后查看该Auth门面类,发现其中有routes方法:

class Auth extends Facade
{
    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor()
    {
        return 'auth';
    }

    /**
     * Register the typical authentication routes for an application.
     *
     * @param  array  $options
     * @return void
     */
    public static function routes(array $options = [])
    {
        static::$app->make('router')->auth($options);
    }
}

可见其使用了router实例的auth方法,那么从哪找这个router呢,我们都知道门面的特点只是替代容器进行访问,所以门面这里还是调用的容器里的实例,所以这个router就是绑定到容器里的服务名称,那么肯定就是在路由模块里的服务提供者里找。
Illuminate\Routing\RoutingServiceProvider
然后通过这里找到了注册进容器时的服务名称就是router,绑定的类是Illuminate\Routing\Router

然后从该Router类中找到了auth方法:

/**
 * Register the typical authentication routes for an application.
 *
 * @param  array  $options
 * @return void
 */
public function auth(array $options = [])
{
    // Authentication Routes...
    $this->get('login', 'Auth\LoginController@showLoginForm')->name('login');
    $this->post('login', 'Auth\LoginController@login');
    $this->post('logout', 'Auth\LoginController@logout')->name('logout');

    // Registration Routes...
    if ($options['register'] ?? true) {
        $this->get('register', 'Auth\RegisterController@showRegistrationForm')->name('register');
        $this->post('register', 'Auth\RegisterController@register');
    }

    // Password Reset Routes...
    if ($options['reset'] ?? true) {
        $this->resetPassword();
    }

    // Email Verification Routes...
    if ($options['verify'] ?? false) {
        $this->emailVerification();
    }
}

由此可见是从这里增加的Auth相关的路由。

中间件底层运作分析

HomeController的构造方法中定义了该控制器的中间件使用auth,让我们分析一下底层是如何运作的。

public function __construct()
{
    $this->middleware('auth');
}

HomeController继承的App\Http\Controllers\Controller,而该Controller又继承的Illuminate\Routing\Controller这个抽象类。
从最终的这个抽象类中看到调用的这个middleware方法:

    /**
     * Register middleware on the controller.
     *
     * @param  array|string|\Closure  $middleware
     * @param  array   $options
     * @return \Illuminate\Routing\ControllerMiddlewareOptions
     */
    public function middleware($middleware, array $options = [])
    {
        foreach ((array) $middleware as $m) {
            $this->middleware[] = [
                'middleware' => $m,
                'options' => &$options,
            ];
        }

        return new ControllerMiddlewareOptions($options);
    }

    /**
     * Get the middleware assigned to the controller.
     *
     * @return array
     */
    public function getMiddleware()
    {
        return $this->middleware;
    }

可以理解为,给当前使用的这个控制器,也就是HomeController,设定了要使用的中间件。
上边这里不仅有一个middleware方法用来设置控制器的中间件,还有一个getMiddleware方法来获取控制器的中间件,请注意这个getMiddleware方法最后会调用到。

那么这个中间件从什么时候触发的呢?这里简单列一下一个请求的生命流程在Route时经历的方法:

Illuminate\Foundation\Http\Kernel->handle // http请求处理
Illuminate\Foundation\Http\Kernel->sendRequestThroughRouter // 基础服务的注册、使用管道经过基础中间件,然后返回管道中then方法的响应结果。
Illuminate\Foundation\Http\Kernel->dispatchToRouter // 交给路由模块处理,并返回路由模块处理结果
Illuminate\Routing\Router->dispatch // 对Router类的当前要处理的请求的初始化,并返回dispatchToRoute方法处理结果
Illuminate\Routing\Router->dispatchToRoute // 匹配到对应路由,并返回runRoute方法执行对该路由的处理结果。
Illuminate\Routing\Router->runRoute // 使用prepareResponse方法处理runRouteWithinStack方法的返回结果,并返回处理结果。
Illuminate\Routing\Router->runRouteWithinStack // 真正运行对该路由的处理

从下面的runRouteWithinStack方法可以看出,在这里又使用了管道。
这里首先判断系统是否关闭了中间件,如果没有关闭就获取处理这个请求的这个路由的所有中间件。
然后使用管道进行处理,经过一层层中间件,最后执行$route->run()方法,该run方法会判断路由类型是控制器还是闭包,然后获取处理结果并return。

/**
 * Run the given route within a Stack "onion" instance.
 *
 * @param  \Illuminate\Routing\Route  $route
 * @param  \Illuminate\Http\Request  $request
 * @return mixed
 */
protected function runRouteWithinStack(Route $route, Request $request)
{
    $shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
                            $this->container->make('middleware.disable') === true;

    $middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);

    return (new Pipeline($this->container))
                    ->send($request)
                    ->through($middleware)
                    ->then(function ($request) use ($route) {
                        return $this->prepareResponse(
                            $request, $route->run()
                        );
                    });
}

然后我们看gatherMiddleware这个方法,这个方法就是获取本次请求所匹配的那个路由的所有中间件。
从下面代码可见,首先调用gatherMiddleware方法获取本次要使用的中间件,由于获取的中间件是别名,所以还要从所有的中间件和组中进行匹配,得到别名所对应的类。

/**
 * Gather the middleware for the given route with resolved class names.
 *
 * @param  \Illuminate\Routing\Route  $route
 * @return array
 */
public function gatherRouteMiddleware(Route $route)
{
    $middleware = collect($route->gatherMiddleware())->map(function ($name) {
        return (array) MiddlewareNameResolver::resolve($name, $this->middleware, $this->middlewareGroups);
    })->flatten();

    return $this->sortMiddleware($middleware);
}

那么就分析一下gatherMiddleware的代码,其中$this->controllerMiddleware()这个方法就是获取了在路由里面设置的中间件,也就是我们从HomeController中设置的那个auth中间件。

/**
 * Get all middleware, including the ones from the controller.
 *
 * @return array
 */
public function gatherMiddleware()
{
    if (! is_null($this->computedMiddleware)) {
        return $this->computedMiddleware;
    }

    $this->computedMiddleware = [];

    return $this->computedMiddleware = array_unique(array_merge(
        $this->middleware(), $this->controllerMiddleware()
    ), SORT_REGULAR);
}

再看controllerMiddleware方法,这块用了controller调度器的一个类,调用了该调度器类的getMiddleware方法。

    /**
     * Get the middleware for the route's controller.
     *
     * @return array
     */
    public function controllerMiddleware()
    {
        if (! $this->isControllerAction()) {
            return [];
        }

        return $this->controllerDispatcher()->getMiddleware(
            $this->getController(), $this->getControllerMethod()
        );
    }

然后再看上面调度器类中的getMiddleware方法,这就很明显了,这里用处理该路由的控制器,也就是HomeController去调用其父类Illuminate\Routing\Controller这个抽象类的getMiddleware方法。

    /**
     * Get the middleware for the controller instance.
     *
     * @param  \Illuminate\Routing\Controller  $controller
     * @param  string  $method
     * @return array
     */
    public function getMiddleware($controller, $method)
    {
        if (! method_exists($controller, 'getMiddleware')) {
            return [];
        }

        return collect($controller->getMiddleware())->reject(function ($data) use ($method) {
            return static::methodExcludedByOptions($method, $data['options']);
        })->pluck('middleware')->all();
    }

截止这里,一个匹配该路由的HTTP请求,就获取到了控制器中设置的中间件,然后一层一层回到上面的runRouteWithinStack方法处,然后利用管道执行使中间件生效。

auth中间件执行流程分析

中间件的添加和删除是在App\Http\Kernel类中定义的,这些中间件在框架初始化时就配置到了Router类中,具体可见Illuminate\Foundation\Http\Kernel类的构造方法。
从中间件的别名对应关系可以发现,auth中间件对应的类是

'auth' => \App\Http\Middleware\Authenticate::class,

然后从第三步分析中,在runRouteWithinStack方法这里,使用管道让请求经过了每个中间件,这里打印一下$middleware的值:

/**
 * Run the given route within a Stack "onion" instance.
 *
 * @param  \Illuminate\Routing\Route  $route
 * @param  \Illuminate\Http\Request  $request
 * @return mixed
 */
protected function runRouteWithinStack(Route $route, Request $request)
{
    $shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
                            $this->container->make('middleware.disable') === true;

    $middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);
    dd($middleware);
    return (new Pipeline($this->container))
                    ->send($request)
                    ->through($middleware)
                    ->then(function ($request) use ($route) {
                        return $this->prepareResponse(
                            $request, $route->run()
                        );
                    });
}

dd调试输出内容为:

array:7 [0 => "App\Http\Middleware\EncryptCookies"
  1 => "Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse"
  2 => "Illuminate\Session\Middleware\StartSession"
  3 => "Illuminate\View\Middleware\ShareErrorsFromSession"
  4 => "App\Http\Middleware\VerifyCsrfToken"
  5 => "App\Http\Middleware\Authenticate"
  6 => "Illuminate\Routing\Middleware\SubstituteBindings"
]

然后看管道中then方法,执行这个管道时其中$middleware的运行情况:

    /**
     * Run the pipeline with a final destination callback.
     *
     * @param  \Closure  $destination
     * @return mixed
     */
    public function then(Closure $destination)
    {
        dump(array_reverse($this->pipes));
        $pipeline = array_reduce(
            array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)
        );

        return $pipeline($this->passable);
    }

其中第一个参数就是以上打印的数组,第二个参数$this->carry()是一个闭包,是要迭代处理数组值的方法。最后一个参数是作为第一次迭代的初始值。
也就是说利用了array_reduce函数,就开始了对中间件的处理。
请注意,这个$this->carry()方法返回的也是闭包,也没有真正执行,而是最后一行这里:return $pipeline($this->passable);才执行了所有闭包。

下边贴上了最终执行管道闭包时的代码:

    $response = method_exists($pipe, $this->method)
                    ? $pipe->{$this->method}(...$parameters)
                    : $pipe(...$parameters);

这段代码也很好理解,pipe是每一节管道,也就是每个中间件,$this->method是要执行的中间件中的方法,默认是handle。
执行auth中间件的时候,在这里就可以理解为:

$response = App\Http\Middleware\Authenticate->handle(...); 

那么我们看看Authenticate类的handle方法的实现逻辑,在Authenticate父类中:

handle方法没什么好说的,直接看authenticate方法,可见,如果不通过if ($this->auth->guard($guard)->check()) {的判断,就会判定该请求未授权。

public function handle($request, Closure $next, ...$guards)
{
    $this->authenticate($request, $guards);

    return $next($request);
}

/**
 * Determine if the user is logged in to any of the given guards.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  array  $guards
 * @return void
 *
 * @throws \Illuminate\Auth\AuthenticationException
 */
protected function authenticate($request, array $guards)
{
    if (empty($guards)) {
        $guards = [null];
    }

    foreach ($guards as $guard) {
        if ($this->auth->guard($guard)->check()) {
            return $this->auth->shouldUse($guard);
        }
    }

    throw new AuthenticationException(
        'Unauthenticated.', $guards, $this->redirectTo($request)
    );
}

这个$this->authIlluminate\Auth\AuthManager类的门面,那我们看AuthManager类中的guard方法:

该方法的作用是返回一个守卫,首先判断如果传递的守卫如果是null,就使用getDefaultDriver方法获取默认守卫。
然后调用resolve方法把守卫实例解析出来,在resolve中会根据配置的驱动调用对应处理方法,根据配置文件的默认项,最终调用createSessionDriver方法。

    /**
     * Attempt to get the guard from the local cache.
     *
     * @param  string|null  $name
     * @return \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard
     */
    public function guard($name = null)
    {
        $name = $name ?: $this->getDefaultDriver();

        return $this->guards[$name] ?? $this->guards[$name] = $this->resolve($name);
    }

    /**
     * Get the default authentication driver name.
     *
     * @return string
     */
    public function getDefaultDriver()
    {
        return $this->app['config']['auth.defaults.guard'];
    }

    /**
     * Resolve the given guard.
     *
     * @param  string  $name
     * @return \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard
     *
     * @throws \InvalidArgumentException
     */
    protected function resolve($name)
    {
        $config = $this->getConfig($name);

        if (is_null($config)) {
            throw new InvalidArgumentException("Auth guard [{$name}] is not defined.");
        }

        if (isset($this->customCreators[$config['driver']])) {
            return $this->callCustomCreator($name, $config);
        }

        $driverMethod = 'create'.ucfirst($config['driver']).'Driver';

        if (method_exists($this, $driverMethod)) {
            return $this->{$driverMethod}($name, $config);
        }

        throw new InvalidArgumentException(
            "Auth driver [{$config['driver']}] for guard [{$name}] is not defined."
        );
    }


    /**
     * Create a session based authentication guard.
     *
     * @param  string  $name
     * @param  array  $config
     * @return \Illuminate\Auth\SessionGuard
     */
    public function createSessionDriver($name, $config)
    {
        $provider = $this->createUserProvider($config['provider'] ?? null);

        $guard = new SessionGuard($name, $provider, $this->app['session.store']);

        // When using the remember me functionality of the authentication services we
        // will need to be set the encryption instance of the guard, which allows
        // secure, encrypted cookie values to get generated for those cookies.
        if (method_exists($guard, 'setCookieJar')) {
            $guard->setCookieJar($this->app['cookie']);
        }

        if (method_exists($guard, 'setDispatcher')) {
            $guard->setDispatcher($this->app['events']);
        }

        if (method_exists($guard, 'setRequest')) {
            $guard->setRequest($this->app->refresh('request', $guard, 'setRequest'));
        }

        return $guard;
    }

createSessionDriver方法中,根据配置文件创建了provider和SessionGuard守卫实例,并对守卫实例进行一些初始化设置,并返回一个SessionGuard实例。

为了后面对照查看,这里贴上config/auth.php的部分配置:

	config/auth.php的部分配置:
    'defaults' => [
        'guard' => 'web',
        'passwords' => 'users',
    ],

    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],

        'api' => [
            'driver' => 'token',
            'provider' => 'users',
            'hash' => false,
        ],
    ],
    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\User::class,
        ],
    ],

然后再回到上面中间件的authenticate方法中,使用得到的SessionGuard实例调用check方法,该check方法位于SessionGuard类内use的trait类GuardHelpers中。
通过check方法检测身份是否通过,如果通过则调用shouldUse方法设定通过审核的守卫以及对应的用户实例(用户实例就是上面配置文件中providers中的model,也就是App\User::class)。

foreach ($guards as $guard) {
            if ($this->auth->guard($guard)->check()) {
                return $this->auth->shouldUse($guard);

截止这里,这个auth中间件的流程就走完了。

另外顺带一提,登录成功后通过门面方式比较常用的比如:\Auth::user();就是调用的SessionGuard方法的user方法。

登录流程分析

先看看未登录访问处理流程

首先,guard防护类型在config/auth.php中默认是web,另外默认防护类型还支持api。
驱动也是支持session和token这两种,在auth组件中的Illuminate\Auth\AuthManager类中都可以看到对应的处理方法。
本次使用的就是默认的配置。

    'defaults' => [
        'guard' => 'web',
        'passwords' => 'users',
    ],
    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],

        'api' => [
            'driver' => 'token',
            'provider' => 'users',
            'hash' => false,
        ],
    ],
    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\User::class,
        ],
    ],
    'passwords' => [
        'users' => [
            'provider' => 'users',
            'table' => 'password_resets',
            'expire' => 60,
        ],
    ],

接下来访问/home路由,由于HomeController设定了auth中间件,route模块在执行管道时,会经过Illuminate\Auth\Middleware\Authenticate这个负责认证的中间件处理。

	Authenticate:
	
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @param  string[]  ...$guards
     * @return mixed
     *
     * @throws \Illuminate\Auth\AuthenticationException
     */
    public function handle($request, Closure $next, ...$guards)
    {
        $this->authenticate($request, $guards);

        return $next($request);
    }

    /**
     * Determine if the user is logged in to any of the given guards.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  array  $guards
     * @return void
     *
     * @throws \Illuminate\Auth\AuthenticationException
     */
    protected function authenticate($request, array $guards)
    {
        if (empty($guards)) {
            $guards = [null];
        }

        foreach ($guards as $guard) {
            if ($this->auth->guard($guard)->check()) {
                return $this->auth->shouldUse($guard);
            }
        }

        throw new AuthenticationException(
            'Unauthenticated.', $guards, $this->redirectTo($request)
        );
    }

guard防护类型,默认的话就会取设定的config值,也就是web,驱动就是session,服务提供者为users,提供者的驱动是orm,model使用的User。

上面这个Authenticate中间件类中,$this->auth是AuthManager类。
1.首先调用grand方法设置好要使用的防护类型。
2.然后调用check方法检测用户是否已登录,如果未登录则返回null。
3.如果第二步返回null,则抛出AuthenticationException类,其中redirectTo()就是获取我们在子类中设定的未授权跳转地址。

如果走到了第三步,route组件的的pipeline会catch到中间件抛出的异常,处理后return,浏览器收到response后,根据redirectTo()返回的URL,跳转到/login页面,整个请求结束。

再看看处理登录请求的流程

填写账号密码提交表单后,根据框架默认的Auth::route(),对应的Controller是Auth\LoginController@login,LoginController use 了trait类AuthenticatesUsers,很多逻辑都是在该trait类中判断的,所以下面的代码基本都是以这个trait类展开。

分析处理登录请求的方法,该方法说明非常明确,处理一个向本应用发起的登录请求。

AuthenticatesUsers:

    /**
     * Handle a login request to the application.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|\Illuminate\Http\JsonResponse
     *
     * @throws \Illuminate\Validation\ValidationException
     */
    public function login(Request $request)
    {
    	// 1.第一步调用validateLogin方法验证表单必填项和是否为字符串。
    	// 注:这里底层查了一下request类并没有validate方法,
    	// 而是在服务提供者Illuminate\Foundation\Providers\FoundationServiceProvider中使用macro方法注册的,
    	// 该macro方法属于Macroable这个trait类,该方法用来给已定义无法修改的对象提供一种额外方式(增加方法)进行功能增强。
        $this->validateLogin($request);
        
        // If the class is using the ThrottlesLogins trait, we can automatically throttle
        // the login attempts for this application. We'll key this by the username and
        // the IP address of the client making these requests into this application.
        
        // 2.第二步是登录时的特征检测,比如可以验证是否登录太多次,或者根据IP禁用登录等规则。这个实现是在ThrottlesLogins(风控)类中。
        // 但是可以在LoginController中根据自己的业务重写hasTooManyLoginAttempts方法,对登录的用户进行验证。
        // 当检测方法通过时,会调用fireLockoutEvent方法对该用户进行锁定或者其他业务操作,这个方法也可以在LoginController中重写。sendLockoutResponse方法也是一样。
        if (method_exists($this, 'hasTooManyLoginAttempts') &&
            $this->hasTooManyLoginAttempts($request)) {
            $this->fireLockoutEvent($request);

            return $this->sendLockoutResponse($request);
        }

        // 3.第三步,调用attemptLogin方法登录用户,$this->guard()是根据auth配置初始化得到的 SessionGuard (session防护类)。
        // 由于attemptLogin内调用的是SessionGuard防护类的方法,所以下面增加一个代码块用于分析SessionGuard防护类的逻辑。
        if ($this->attemptLogin($request)) {
        	// 通过检测,这时SessionGuard里的$user变量已经存储了当前登录用户的model 对象。并将用户导向登录成功后的URL。
        	// 也就是说这个http响应给客户端之后,客户端再访问本应用,即为已登录身份。
        	// 因为验证的时候,会在SessionGuard中寻找$user变量,该变量值已被设置,所以中间件就会放行。
            return $this->sendLoginResponse($request);
        }

        // 4.记录一次登录失败,用于风控检测用户尝试登录次数。
        // If the login attempt was unsuccessful we will increment the number of attempts
        // to login and redirect the user back to the login form. Of course, when this
        // user surpasses their maximum number of attempts they will get locked out.
        $this->incrementLoginAttempts($request);

        // 5.若第三步得到false,登录失败,则在这里调用sendFailedLoginResponse方法抛出登录失败消息。
        return $this->sendFailedLoginResponse($request);
    }

    /**
     * Attempt to log the user into the application.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return bool
     */
    protected function attemptLogin(Request $request)
    {
        return $this->guard()->attempt(
            $this->credentials($request), $request->filled('remember')
        );
    }

SessionGuard:

	/**
     * Attempt to authenticate a user using the given credentials.
     *
     * @param  array  $credentials
     * @param  bool   $remember
     * @return bool
     */
    public function attempt(array $credentials = [], $remember = false)
    {
        $this->fireAttemptEvent($credentials, $remember);

        // 根据config,这里的provider是EloquentUserProvider,返回值是用User model对象。
        $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);

        // If an implementation of UserInterface was returned, we'll ask the provider
        // to validate the user against the given credentials, and if they are in
        // fact valid we'll log the users into the application and return true.
        
        // 检测凭证和上面查询出来的对象密码是否吻合,如果吻合则调用login方法,
        // login方法中使用setUser方法,将对象赋值给本类的$user变量。
        if ($this->hasValidCredentials($user, $credentials)) {
            $this->login($user, $remember);

            return true;
        }

        // If the authentication attempt fails we will fire an event so that the user
        // may be notified of any suspicious attempts to access their account from
        // an unrecognized user. A developer may listen to this event as needed.
        $this->fireFailedEvent($user, $credentials);

        return false;
    }

    /**
     * Log a user into the application.
     *
     * @param  \Illuminate\Contracts\Auth\Authenticatable  $user
     * @param  bool  $remember
     * @return void
     */
    public function login(AuthenticatableContract $user, $remember = false)
    {
        $this->updateSession($user->getAuthIdentifier());

        // If the user should be permanently "remembered" by the application we will
        // queue a permanent cookie that contains the encrypted copy of the user
        // identifier. We will then decrypt this later to retrieve the users.
        if ($remember) {
            $this->ensureRememberTokenIsSet($user);

            $this->queueRecallerCookie($user);
        }

        // If we have an event dispatcher instance set we will fire an event so that
        // any listeners will hook into the authentication events and run actions
        // based on the login and logout events fired from the guard instances.
        $this->fireLoginEvent($user, $remember);

        $this->setUser($user);
    }


    /**
     * Set the current user.
     *
     * @param  \Illuminate\Contracts\Auth\Authenticatable  $user
     * @return $this
     */
    public function setUser(AuthenticatableContract $user)
    {
        $this->user = $user;

        $this->loggedOut = false;

        $this->fireAuthenticatedEvent($user);

        return $this;
    }

以上就是对登录流程的分析,在Auth模块的设计中很好的体现了trait类特性的运用(本类中的属性和方法都会覆盖trait类中的属性和方法),可以在子类(LoginController)中重写trait类AuthenticatesUsers内的方法,替换为自己的业务逻辑,非常的方便!

总结

首先就是需要具备一定PHP知识,例如容器、管道、门面、trait等。
要一直学习框架中优秀的东西,不能只做一个应用者,要识其原理,才能化为自己的内功,在实际中运用。

发布了116 篇原创文章 · 获赞 12 · 访问量 99万+

猜你喜欢

转载自blog.csdn.net/u012628581/article/details/102570569