前言
Laravel
有一個神器:
php artisan make:auth
能夠快速的幫我們完成一套注冊和登錄的認證機制,但是這套機制具體的是怎么跑起來的呢?我們不妨來一起看看他的源碼。不過在這篇文章中,我只會闡述大致的流程,至於一些具體的細節,比如他的登錄次數限制是怎么完成的之類的不妨自己去尋找答案。
源碼解讀系列,有興趣的小伙伴可以點個star,我會持續更新各個部分的解讀,也是和大家一起進步的一個過程,如有寫的不對的地方還望指出。
過程
路由
當我們執行完命令之后,我們會發現,在routes/web.php
中多了這樣一行代碼:
Auth::routes();
結合我們在前面講到的Facades,我們會執行Facades/Auth.php
的routes
方法:
public static function routes()
{
static::$app->make('router')->auth();
}
而$app->make('router')
會返回一個Routing/Router.php
對象的實例,而他的auth
方法是:
public function auth()
{
// 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...
$this->get('register', 'Auth\RegisterController@showRegistrationForm')->name('register');
$this->post('register', 'Auth\RegisterController@register');
// Password Reset Routes...
$this->get('password/reset', 'Auth\ForgotPasswordController@showLinkRequestForm')->name('password.request');
$this->post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail')->name('password.email');
$this->get('password/reset/{token}', 'Auth\ResetPasswordController@showResetForm')->name('password.reset');
$this->post('password/reset', 'Auth\ResetPasswordController@reset');
}
而這里的get
和post
方法,其實也就是我們通過Route::get
等最終會調用的方法。
注冊
我們直接看表單提交的方法:
$this->post('register', 'Auth\RegisterController@register');
//RegisterController.php
class RegisterController extends Controller
{
/*
|--------------------------------------------------------------------------
| Register Controller
|--------------------------------------------------------------------------
|
| This controller handles the registration of new users as well as their
| validation and creation. By default this controller uses a trait to
| provide this functionality without requiring any additional code.
|
*/
use RegistersUsers;
/**
* Where to redirect users after registration.
*
* @var string
*/
protected $redirectTo = '/home';
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
}
/**
* Get a validator for an incoming registration request.
*
* @param array $data
* @return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:6|confirmed',
]);
}
/**
* Create a new user instance after a valid registration.
*
* @param array $data
* @return User
*/
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
]);
}
}
里面使用到了RegistersUsers
這個trait
,里面就有我們需要的register
方法:
public function register(Request $request)
{
$this->validator($request->all())->validate();
event(new Registered($user = $this->create($request->all())));
// 默認情況下,$this->guard()會返回一個`SessionGuard`的對象
$this->guard()->login($user);
return $this->registered($request, $user)
?: redirect($this->redirectPath());
}
protected function guard()
{
return Auth::guard();
}
其中最核心的部分就是:
$this->guard()->login($user);
而$this->guard()
在默認配置下會返回一個SessionGuard
的對象,具體是這樣實現的:
前面我們有講過,當我們執行Facades
下一個不存在的方法時,我們會調用Facade.php
的__callStatic
方法,這個方法會獲取當前對象的getFacadeAccessor
方法而返回一個對應的對象並調用他所需要調用的方法,這個詳細過程在我的第三篇中都有所闡述。
而auth
的別名綁定,是在我們初始化的過程中綁定的,具體可以看我寫的第一篇,所以,這里我們會調用AuthManager
這個對象:
public function guard($name = null)
{
$name = $name ?: $this->getDefaultDriver();
return isset($this->guards[$name])
? $this->guards[$name]
: $this->guards[$name] = $this->resolve($name);
}
public function getDefaultDriver()
{
return $this->app['config']['auth.defaults.guard'];
}
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 guard driver [{$name}] is not defined.");
}
//config/auth.php
'defaults' => [
'guard' => 'web',
'passwords' => 'users',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
],
],
通過調用AuthManager
可以拼接得出,最終他會調用一個createSessionDriver
方法:
public function createSessionDriver($name, $config)
{
$provider = $this->createUserProvider($config['provider']);
$guard = new SessionGuard($name, $provider, $this->app['session.store']);
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;
}
所以最終是調用了SessionGuard
的login
方法來完成session
部分的功能。
登錄
展示表單的部分就不贅述了,這里我們直接看:
$this->post('login', 'Auth\LoginController@login');
//LoginController.php
class LoginController extends Controller
{
use AuthenticatesUsers;
/**
* Where to redirect users after login.
*
* @var string
*/
protected $redirectTo = '/home';
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest')->except('logout');
}
}
他使用到了一個AuthenticatesUsers
trait,里面有一個login
方法,也就是我們要使用到的login
了:
public function login(Request $request)
{
$this->validateLogin($request);
if ($this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
if ($this->attemptLogin($request)) {
return $this->sendLoginResponse($request);
}
$this->incrementLoginAttempts($request);
return $this->sendFailedLoginResponse($request);
}
其實大部分和register
的部分差不多,核心部分還是在於SessionGuard
對象的獲取,這里就不過多贅述了,不過值得一提的是,像Auth::check()
等很多方法的使用,其實也會通過SessionGuard
來完成,主要是通過:
public function __call($method, $parameters)
{
return $this->guard()->{$method}(...$parameters);
}
public function check()
{
return ! is_null($this->user());
}
來得以完成調度。