最近使用laravel比较多,所以也钻研了部分的源代码。本文作为笔记已备日后查询。
首先从路由开始,laravel自带认证的密码重置控制器路由在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');
这个路由是用来返回密码重置申请页面的。这个很简单,不再多说。
我们看到这个路由
post('password/email','Auth\ForgotPasswordController@sendResetLinkEmail');
这是用来发送邮件的,我们顺着这一条追踪下去。
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');
}
}
可以看到控制器里代码很少,具体实现被封装在了SendsPasswordResetEmails
trait里。从代码上面的引用可以轻松地通过命名空间找到
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);
}
看这个方法,首先验证请求的email是否合法,然后
$response = $this->broker()->sendResetLink(
$request->only('email')
);
首先调用了自身的broker()
方法,我们来看一下
/**
* Get the broker to be used during password reset.
*
* @return \Illuminate\Contracts\Auth\PasswordBroker
*/
public function broker()
{
return Password::broker();
}
看注释,发现返回的实例类型是 Contracts
下面的,Contracts
下面定义的全部是接口,我们看不到内部的实现,这下线索就断掉了。没关系,我们灵活一点,这里只要知道返回的是一个PasswordBroker
接口的对象就行了。然后我们走捷径,直接用编辑器的搜索功能搜sendResetLink
这个方法就行了。因为我们目标是看这封邮件是怎么发出去的。很容易就找到了这个方法的实现在
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;
}
首先从传入的参数中获得user
,验证user
是否存在,然后重点来了
$user->sendPasswordResetNotification(
$this->tokens->create($user)
);
我们先不急着找sendPasswordResetNotification
,我们先看一下$this->tokens->create($user)
,在类的构造方法中
public function __construct(TokenRepositoryInterface $tokens,
UserProvider $users)
{
$this->users = $users;
$this->tokens = $tokens;
}
我们看到传入了一个TokenRepositoryInterface
接口的实例,从字面上我们能判断出这个接口是和token
生成有关的。我们直接搜索这个接口的实现。
Illuminate\Auth\Passwords\DatabaseTokenRepository.php
这个类实现了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
,可以我们发现两个奇怪的地方
1.参数传入的是一个CanResetPasswordContract
接口实例,这个实例竟然就是user
,那么我们要看看这个接口和User
模型之间有什么关系。从该文件的引用我们知道CanResetPasswordContract
是一个别名,真名叫Illuminate\Contracts\Auth\CanResetPassword
但是我们不去找这个接口,因为明知道是接口,找过去一定扑个空。我们要找实现这个接口的位置,于是再次搜索。这下我们发现了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;
}
原来user
模型实现了这个接口。第一个疑点解决。下面我们来看create
方法中的第二个疑点。
2.这句话
$this->getTable()->insert($this->getPayload($email, $token));
我们很容易可以知道是用来向password_reset
表中写入数据的。但是我们没有看到它指定任何模型或者表名,那么它是在哪里找到这个表的呢。我们先看getTable()
方法,这个方法就定义在下方,
/**
* 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()
方法一起粘过来。我们发现这两个方法都返回了该类的属性,那么我们就去构造方法中找传入的依赖。
/**
* 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;
}
构造方法中果然看到传入了一个table
和一个connection
,connection
我们不管,是数据库连接实例,我们找table
,可是我们发现这里table
就是一个简单的string
类型,那么我们必须找到这个DatabaseTokenRepository
类实例化的地方,继续搜索。我们找到了一个PasswordBrokerManager
类里面createTokenRepository
方法返回了DatabaseTokenRepository
的实例对象。
/**
* 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']
);
}
我们发现这里传入了config
数组里的table
,
我们在config\auth.php中找到了配置项
'passwords' => [
'users' => [
'provider' => 'users',
'table' => 'password_resets',
'expire' => 60,
],
],
为什么实例对象要在这里返回呢,到这里我推测再搜索PasswordBrokerManager
被引用的地方一定是一个服务提供者了。果然我们找到了Illuminate\Auth\Passwords\PasswordResetServiceProvider.php
服务容器通过这个服务提供者调用PasswordBrokerManager
从而解析出DatabaseTokenRepository
实例,提供依赖注入。
好现在我们再回到
$user->sendPasswordResetNotification(
$this->tokens->create($user)
);
我们要来看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
。上面说到过Illuminate\Foundation\Auth\User.php
实现了CanResetPassword
接口,app\User.php
中的User
其实就是继承了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
实例,所以能够调用notify
方法,这里继承实现关系比较复杂,需要自己多钻研。剩下就不再挖掘了,这里是通过laravel框架的通知来发送重置密码的邮件。
篇幅原因只能写到这里,继续深挖下去是无穷无尽的。