跟踪laravel自带密码重置的源代码

最近使用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

middleware('guest');
    }
}

可以看到控制器里代码很少,具体实现被封装在了SendsPasswordResetEmailstrait里。从代码上面的引用可以轻松地通过命名空间找到
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

原来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和一个connectionconnection我们不管,是数据库连接实例,我们找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方法,notifyIlluminate\Notifications\RoutesNotifications.php的traitRoutesNotifications中定义。然后Illuminate\Notifications\Notifiable.phptrait中引用了RoutesNotifications,最后在app\User.php中引用了Notifiable。上面说到过Illuminate\Foundation\Auth\User.php实现了CanResetPassword接口,app\User.php中的User其实就是继承了Illuminate\Foundation\Auth\User.php

所以上面的

$this->notify(new ResetPasswordNotification($token));

中的this就是User实例,所以能够调用notify方法,这里继承实现关系比较复杂,需要自己多钻研。剩下就不再挖掘了,这里是通过laravel框架的通知来发送重置密码的邮件。

篇幅原因只能写到这里,继续深挖下去是无穷无尽的。

你可能感兴趣的:(跟踪laravel自带密码重置的源代码)