有点疑问,也是分享,希望大家进来讨论一下,关于使用 Auth::attempt ($credentials) 验证报错的问题!

我使用 Auth::attempt($credentials) 掩着那个登录时 ,当我的 User模型为下面这种写法时,
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{

在点击登录提交数据后会报 以下

Type error: Argument 1 passed to EloquentUserProvider::validateCredentials() must be an instance of Illuminate\Contracts\Auth\Authenticatable, instance of App\Models\User given, called in /home/vagrant/Code/Laravel/vendor/laravel/framework/src/Illuminate/Auth/SessionGuard.php on line 379
这样的错误!
当我将User模型文件修改成以下,这样就能够正常登录并跳转到个人页面
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
}

那么继承Model 和 继承 Authenticatable 有什么区别呢,继承Authenticatable的作用是什么呢?为什么继承Model会报错呢?现在问题已经解决但是还是不清楚!

《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 3

Laravel的封装非常非常精细,代码可重用性非常高,用起来很爽,但这也导致了解它的运作原理变得很困难。从Auth::Attempt()展开分析,绕了好远的路。

我们来看看Authenticatable是什么,然后顺着Auth::attempt()的调用过程,看看它依赖了Authenticatable的什么东东。
User.php代码片段:

namespace App\Models;

use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
use App\Notifications\ResetPassword;
use Auth;

class User extends Authenticatable
{
.
.
.

Authenticatable,即Illuminate\Foundation\Auth\User,其代码如下:

<?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类继承Model,并实现了三个接口,而三个接口分别由三个trait一一实现,而其中 AuthenticatableAuth::attempt() 调用过程中到的,有关这个报错的。

接下来,我们分析一下Auth::attempt()调用流程:
attempt() 位于Illuminate\Auth\SessionGuard(Auth怎么调用到attempt()还可以分析一长篇,这里先看attemp()的实现),attempt()代码片段:

public function attempt(array $credentials = [], $remember = false)
{
    $this->fireAttemptEvent($credentials, $remember);
    $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);
    if ($this->hasValidCredentials($user, $credentials)) {
        $this->login($user, $remember);
        return true;
    }
    $this->fireFailedEvent($user, $credentials);
    return false;
}

调用到的方法:

protected function hasValidCredentials($user, $credentials)
    {
        return ! is_null($user) && $this->provider->validateCredentials($user, $credentials);
    }

$this->provider是从配置文件读取进来的驱动,这里为Eloquent, 所以validateCredentials是属于 Illuminate\Auth\EloquentUserProvider下的,代码:

public function validateCredentials(UserContract $user, array $credentials)
    {
        $plain = $credentials['password'];

        return $this->hasher->check($plain, $user->getAuthPassword());
    }

这里重点看一下注入的类 UserContractUserContract是 Illuminate\Contracts\Auth\Authenticatable的别名,仅仅是一个接口,其实现位于Authenticatable这个trait。前面这个trait在Illuminate\Foundation\Auth\User引入,然后User模型继承了这个类,所以这里就和attempt()方法搭上关系了, UserContract 的代码片段如下:

namespace Illuminate\Contracts\Auth;

interface Authenticatable
{

attempt() 中有这一行: $user = $this->provider->retrieveByCredentials($credentials);validateCredentials的$user变量是从这里产生的。
retrieveByCredentials方法代码如下:

public function retrieveByCredentials(array $credentials)
    {
        if (empty($credentials) ||
           (count($credentials) === 1 &&
            array_key_exists('password', $credentials))) {
            return;
        }
        $query = $this->createModel()->newQuery();
        foreach ($credentials as $key => $value) {
            if (Str::contains($key, 'password')) {
                continue;
            }

            if (is_array($value) || $value instanceof Arrayable) {
                $query->whereIn($key, $value);
            } else {
                $query->where($key, $value);
            }
        }

        return $query->first();
    }

重点看$query = $this->createModel()->newQuery();这一行。

 public function createModel()
    {
        $class = '\\'.ltrim($this->model, '\\');

        return new $class;
    }

这里生产了类的实例:

 public function createModel()
    {
        $class = '\\'.ltrim($this->model, '\\');

        return new $class;
    }

这里返回的$user为什么是UserContract类,我也还没搞清楚。有空再慢慢分析。

5年前 评论
leo

你看一下 Authenticatable 这个类的代码不就知道了吗?

5年前 评论

Laravel的封装非常非常精细,代码可重用性非常高,用起来很爽,但这也导致了解它的运作原理变得很困难。从Auth::Attempt()展开分析,绕了好远的路。

我们来看看Authenticatable是什么,然后顺着Auth::attempt()的调用过程,看看它依赖了Authenticatable的什么东东。
User.php代码片段:

namespace App\Models;

use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
use App\Notifications\ResetPassword;
use Auth;

class User extends Authenticatable
{
.
.
.

Authenticatable,即Illuminate\Foundation\Auth\User,其代码如下:

<?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类继承Model,并实现了三个接口,而三个接口分别由三个trait一一实现,而其中 AuthenticatableAuth::attempt() 调用过程中到的,有关这个报错的。

接下来,我们分析一下Auth::attempt()调用流程:
attempt() 位于Illuminate\Auth\SessionGuard(Auth怎么调用到attempt()还可以分析一长篇,这里先看attemp()的实现),attempt()代码片段:

public function attempt(array $credentials = [], $remember = false)
{
    $this->fireAttemptEvent($credentials, $remember);
    $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);
    if ($this->hasValidCredentials($user, $credentials)) {
        $this->login($user, $remember);
        return true;
    }
    $this->fireFailedEvent($user, $credentials);
    return false;
}

调用到的方法:

protected function hasValidCredentials($user, $credentials)
    {
        return ! is_null($user) && $this->provider->validateCredentials($user, $credentials);
    }

$this->provider是从配置文件读取进来的驱动,这里为Eloquent, 所以validateCredentials是属于 Illuminate\Auth\EloquentUserProvider下的,代码:

public function validateCredentials(UserContract $user, array $credentials)
    {
        $plain = $credentials['password'];

        return $this->hasher->check($plain, $user->getAuthPassword());
    }

这里重点看一下注入的类 UserContractUserContract是 Illuminate\Contracts\Auth\Authenticatable的别名,仅仅是一个接口,其实现位于Authenticatable这个trait。前面这个trait在Illuminate\Foundation\Auth\User引入,然后User模型继承了这个类,所以这里就和attempt()方法搭上关系了, UserContract 的代码片段如下:

namespace Illuminate\Contracts\Auth;

interface Authenticatable
{

attempt() 中有这一行: $user = $this->provider->retrieveByCredentials($credentials);validateCredentials的$user变量是从这里产生的。
retrieveByCredentials方法代码如下:

public function retrieveByCredentials(array $credentials)
    {
        if (empty($credentials) ||
           (count($credentials) === 1 &&
            array_key_exists('password', $credentials))) {
            return;
        }
        $query = $this->createModel()->newQuery();
        foreach ($credentials as $key => $value) {
            if (Str::contains($key, 'password')) {
                continue;
            }

            if (is_array($value) || $value instanceof Arrayable) {
                $query->whereIn($key, $value);
            } else {
                $query->where($key, $value);
            }
        }

        return $query->first();
    }

重点看$query = $this->createModel()->newQuery();这一行。

 public function createModel()
    {
        $class = '\\'.ltrim($this->model, '\\');

        return new $class;
    }

这里生产了类的实例:

 public function createModel()
    {
        $class = '\\'.ltrim($this->model, '\\');

        return new $class;
    }

这里返回的$user为什么是UserContract类,我也还没搞清楚。有空再慢慢分析。

5年前 评论

感谢大佬分享,最起码错在哪是知道了!

5年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!