最近使用laravel比較多,所以也鉆研了部分的源代碼。本文作為筆記已備日后查詢冀续。
首先從路由開始,laravel自帶認(rèn)證的密碼重置控制器路由在Illuminate\Routing\Router.php
中。
/**
* Register the typical authentication routes for an application.
*
* @return void
*/
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');
$this->post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail');
$this->get('password/reset/{token}', 'Auth\ResetPasswordController@showResetForm');
$this->post('password/reset', 'Auth\ResetPasswordController@reset');
}
get('password/reset','Auth\ForgotPasswordController@showLinkRequestForm');
這個(gè)路由是用來(lái)返回密碼重置申請(qǐng)頁(yè)面的。這個(gè)很簡(jiǎn)單魂毁,不再多說(shuō)。
我們看到這個(gè)路由
post('password/email','Auth\ForgotPasswordController@sendResetLinkEmail');
這是用來(lái)發(fā)送郵件的出嘹,我們順著這一條追蹤下去席楚。
app\Http\Controllers\Auth\ForgotPasswordController.php
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
class ForgotPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset emails and
| includes a trait which assists in sending these notifications from
| your application to your users. Feel free to explore this trait.
|
*/
use SendsPasswordResetEmails;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
}
}
可以看到控制器里代碼很少,具體實(shí)現(xiàn)被封裝在了SendsPasswordResetEmails
trait里税稼。從代碼上面的引用可以輕松地通過(guò)命名空間找到
Illuminate\Foundation\Auth\SendsPasswordResetEmails.php
/**
* Send a reset link to the given user.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*/
public function sendResetLinkEmail(Request $request)
{
$this->validate($request, ['email' => 'required|email']);
// We will send the password reset link to this user. Once we have attempted
// to send the link, we will examine the response then see the message we
// need to show to the user. Finally, we'll send out a proper response.
$response = $this->broker()->sendResetLink(
$request->only('email')
);
return $response == Password::RESET_LINK_SENT
? $this->sendResetLinkResponse($response)
: $this->sendResetLinkFailedResponse($request, $response);
}
看這個(gè)方法烦秩,首先驗(yàn)證請(qǐng)求的email是否合法,然后
$response = $this->broker()->sendResetLink(
$request->only('email')
);
首先調(diào)用了自身的broker()
方法郎仆,我們來(lái)看一下
/**
* Get the broker to be used during password reset.
*
* @return \Illuminate\Contracts\Auth\PasswordBroker
*/
public function broker()
{
return Password::broker();
}
看注釋只祠,發(fā)現(xiàn)返回的實(shí)例類型是 Contracts
下面的,Contracts
下面定義的全部是接口扰肌,我們看不到內(nèi)部的實(shí)現(xiàn)抛寝,這下線索就斷掉了。沒(méi)關(guān)系曙旭,我們靈活一點(diǎn)盗舰,這里只要知道返回的是一個(gè)PasswordBroker
接口的對(duì)象就行了。然后我們走捷徑桂躏,直接用編輯器的搜索功能搜sendResetLink
這個(gè)方法就行了钻趋。因?yàn)槲覀兡繕?biāo)是看這封郵件是怎么發(fā)出去的。很容易就找到了這個(gè)方法的實(shí)現(xiàn)在
Illuminate\Auth\Passwords\PasswordBroker.php
/**
* Send a password reset link to a user.
*
* @param array $credentials
* @return string
*/
public function sendResetLink(array $credentials)
{
// First we will check to see if we found a user at the given credentials and
// if we did not we will redirect back to this current URI with a piece of
// "flash" data in the session to indicate to the developers the errors.
$user = $this->getUser($credentials);
if (is_null($user)) {
return static::INVALID_USER;
}
// Once we have the reset token, we are ready to send the message out to this
// user with a link to reset their password. We will then redirect back to
// the current URI having nothing set in the session to indicate errors.
$user->sendPasswordResetNotification(
$this->tokens->create($user)
);
return static::RESET_LINK_SENT;
}
首先從傳入的參數(shù)中獲得user
剂习,驗(yàn)證user
是否存在蛮位,然后重點(diǎn)來(lái)了
$user->sendPasswordResetNotification(
$this->tokens->create($user)
);
我們先不急著找sendPasswordResetNotification
,我們先看一下$this->tokens->create($user)
较沪,在類的構(gòu)造方法中
public function __construct(TokenRepositoryInterface $tokens,
UserProvider $users)
{
$this->users = $users;
$this->tokens = $tokens;
}
我們看到傳入了一個(gè)TokenRepositoryInterface
接口的實(shí)例,從字面上我們能判斷出這個(gè)接口是和token
生成有關(guān)的土至。我們直接搜索這個(gè)接口的實(shí)現(xiàn)购对。
Illuminate\Auth\Passwords\DatabaseTokenRepository.php
這個(gè)類實(shí)現(xiàn)了TokenRepositoryInterface
接口。我們看看create
方法是怎樣定義的陶因。
public function create(CanResetPasswordContract $user)
{
$email = $user->getEmailForPasswordReset();
$this->deleteExisting($user);
// We will create a new, random token for the user so that we can e-mail them
// a safe link to the password reset form. Then we will insert a record in
// the database so that we can verify the token within the actual reset.
$token = $this->createNewToken();
$this->getTable()->insert($this->getPayload($email, $token));
return $token;
}
果然這里面是生成了用于重置密碼的token
骡苞,可以我們發(fā)現(xiàn)兩個(gè)奇怪的地方
1.參數(shù)傳入的是一個(gè)CanResetPasswordContract
接口實(shí)例,這個(gè)實(shí)例竟然就是user
,那么我們要看看這個(gè)接口和User
模型之間有什么關(guān)系楷扬。從該文件的引用我們知道CanResetPasswordContract
是一個(gè)別名解幽,真名叫Illuminate\Contracts\Auth\CanResetPassword
但是我們不去找這個(gè)接口,因?yàn)槊髦朗墙涌诤嫫唬疫^(guò)去一定撲個(gè)空躲株。我們要找實(shí)現(xiàn)這個(gè)接口的位置,于是再次搜索镣衡。這下我們發(fā)現(xiàn)了Illuminate\Foundation\Auth\User.php
<?php
namespace Illuminate\Foundation\Auth;
use Illuminate\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Foundation\Auth\Access\Authorizable;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
class User extends Model implements
AuthenticatableContract,
AuthorizableContract,
CanResetPasswordContract
{
use Authenticatable, Authorizable, CanResetPassword;
}
原來(lái)user
模型實(shí)現(xiàn)了這個(gè)接口霜定。第一個(gè)疑點(diǎn)解決。下面我們來(lái)看create
方法中的第二個(gè)疑點(diǎn)廊鸥。
2.這句話
$this->getTable()->insert($this->getPayload($email, $token));
我們很容易可以知道是用來(lái)向password_reset
表中寫入數(shù)據(jù)的望浩。但是我們沒(méi)有看到它指定任何模型或者表名,那么它是在哪里找到這個(gè)表的呢惰说。我們先看getTable()
方法磨德,這個(gè)方法就定義在下方,
/**
* Begin a new database query against the table.
*
* @return \Illuminate\Database\Query\Builder
*/
protected function getTable()
{
return $this->connection->table($this->table);
}
/**
* Get the database connection instance.
*
* @return \Illuminate\Database\ConnectionInterface
*/
public function getConnection()
{
return $this->connection;
}
我特意把getConnection()
方法一起粘過(guò)來(lái)吆视。我們發(fā)現(xiàn)這兩個(gè)方法都返回了該類的屬性典挑,那么我們就去構(gòu)造方法中找傳入的依賴。
/**
* Create a new token repository instance.
*
* @param \Illuminate\Database\ConnectionInterface $connection
* @param string $table
* @param string $hashKey
* @param int $expires
* @return void
*/
public function __construct(ConnectionInterface $connection, $table, $hashKey, $expires = 60)
{
$this->table = $table;
$this->hashKey = $hashKey;
$this->expires = $expires * 60;
$this->connection = $connection;
}
構(gòu)造方法中果然看到傳入了一個(gè)table
和一個(gè)connection
啦吧,connection
我們不管您觉,是數(shù)據(jù)庫(kù)連接實(shí)例,我們找table
授滓,可是我們發(fā)現(xiàn)這里table
就是一個(gè)簡(jiǎn)單的string
類型琳水,那么我們必須找到這個(gè)DatabaseTokenRepository
類實(shí)例化的地方,繼續(xù)搜索褒墨。我們找到了一個(gè)PasswordBrokerManager
類里面createTokenRepository
方法返回了DatabaseTokenRepository
的實(shí)例對(duì)象炫刷。
/**
* Create a token repository instance based on the given configuration.
*
* @param array $config
* @return \Illuminate\Auth\Passwords\TokenRepositoryInterface
*/
protected function createTokenRepository(array $config)
{
$key = $this->app['config']['app.key'];
if (Str::startsWith($key, 'base64:')) {
$key = base64_decode(substr($key, 7));
}
$connection = isset($config['connection']) ? $config['connection'] : null;
return new DatabaseTokenRepository(
$this->app['db']->connection($connection),
$config['table'],
$key,
$config['expire']
);
}
我們發(fā)現(xiàn)這里傳入了config
數(shù)組里的table
擎宝,
我們?cè)赾onfig\auth.php中找到了配置項(xiàng)
'passwords' => [
'users' => [
'provider' => 'users',
'table' => 'password_resets',
'expire' => 60,
],
],
為什么實(shí)例對(duì)象要在這里返回呢郁妈,到這里我推測(cè)再搜索PasswordBrokerManager
被引用的地方一定是一個(gè)服務(wù)提供者了。果然我們找到了Illuminate\Auth\Passwords\PasswordResetServiceProvider.php
服務(wù)容器通過(guò)這個(gè)服務(wù)提供者調(diào)用PasswordBrokerManager
從而解析出DatabaseTokenRepository
實(shí)例绍申,提供依賴注入噩咪。
好現(xiàn)在我們?cè)倩氐?/p>
$user->sendPasswordResetNotification(
$this->tokens->create($user)
);
我們要來(lái)看sendPasswordResetNotification
顾彰,直接搜索到定義處Illuminate\Auth\Passwords\CanResetPassword.php
/**
* Send the password reset notification.
*
* @param string $token
* @return void
*/
public function sendPasswordResetNotification($token)
{
$this->notify(new ResetPasswordNotification($token));
}
我們追蹤notify
方法,notify
在Illuminate\Notifications\RoutesNotifications.php
的traitRoutesNotifications
中定義胃碾。然后Illuminate\Notifications\Notifiable.php
trait中引用了RoutesNotifications
涨享,最后在app\User.php
中引用了Notifiable
。上面說(shuō)到過(guò)Illuminate\Foundation\Auth\User.php
實(shí)現(xiàn)了CanResetPassword
接口仆百,app\User.php
中的User
其實(shí)就是繼承了Illuminate\Foundation\Auth\User.php
<?php
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
... ...
}
所以上面的
$this->notify(new ResetPasswordNotification($token));
中的this
就是User
實(shí)例厕隧,所以能夠調(diào)用notify
方法,這里繼承實(shí)現(xiàn)關(guān)系比較復(fù)雜俄周,需要自己多鉆研吁讨。剩下就不再挖掘了,這里是通過(guò)laravel框架的通知來(lái)發(fā)送重置密碼的郵件峦朗。
篇幅原因只能寫到這里建丧,繼續(xù)深挖下去是無(wú)窮無(wú)盡的。