如何让Laravel的Consultant模型同时支持Projection与认证?
解决Laravel Event Sourcing投影模型的认证多继承问题
针对你遇到的EloquentUserProvider::validateCredentials()类型错误,核心矛盾是PHP不支持多继承,无法同时让Consultant继承Projection和User类。以下是三种可行的解决思路:
方案一:让投影模型实现认证接口并引入Trait
Laravel的User类本质是实现了Authenticatable接口,并通过Trait实现了接口方法。我们可以让Consultant直接实现该接口,同时引入必要的Trait,无需继承User类:
namespace App\Projections; use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Auth\Authenticatable as AuthenticatableTrait; use Illuminate\Auth\Passwords\CanResetPassword; use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract; use Spatie\EventSourcing\Projections\Projection; class Consultant extends Projection implements Authenticatable, CanResetPasswordContract { // 引入认证和密码重置所需的Trait,自动实现接口方法 use AuthenticatableTrait, CanResetPassword; // 投影模型的基础配置 protected $table = 'consultant_projections'; // 其他投影相关代码(如事件处理方法) }
优缺点
- 优点:逻辑集中,无需额外模型,直接用投影模型完成认证
- 缺点:模型承担了投影和认证两个职责,若
Projection类后续更新可能需要同步调整
方案二:分离认证模型与投影模型,建立关联
将认证逻辑和投影逻辑拆分为两个独立模型,通过关联关系打通数据:
- 保留/创建认证专用模型:使用默认的
User模型(或新建ConsultantUser)负责认证:
namespace App\Models; use Illuminate\Foundation\Auth\User as Authenticatable; class User extends Authenticatable { // 标准认证模型代码 public function consultant() { return $this->hasOne(\App\Projections\Consultant::class); } }
- 投影模型添加关联字段:在
Consultant投影模型中添加user_id字段,并定义关联:
namespace App\Projections; use Spatie\EventSourcing\Projections\Projection; use App\Models\User; class Consultant extends Projection { protected $table = 'consultant_projections'; public function user() { return $this->belongsTo(User::class); } // 其他投影相关代码 }
- 登录后关联获取投影数据:登录使用
User模型完成,之后通过auth()->user()->consultant获取投影模型的数据。
优缺点
- 优点:职责分离,符合单一原则,认证和投影逻辑互不干扰
- 缺点:需要维护两个模型,登录后需额外关联查询投影数据
方案三:自定义用户提供者适配投影模型
通过自定义UserProvider,放宽类型检查规则,让投影模型可以通过认证校验:
- 创建自定义用户提供者:
namespace App\Auth; use Illuminate\Auth\EloquentUserProvider; use Illuminate\Contracts\Auth\Authenticatable as UserContract; class ProjectionUserProvider extends EloquentUserProvider { public function validateCredentials(UserContract $user, array $credentials) { // 针对投影模型单独处理密码验证 if ($user instanceof \Spatie\EventSourcing\Projections\Projection) { $plainPassword = $credentials['password']; return $this->hasher->check($plainPassword, $user->getAuthPassword()); } // 其他模型沿用默认逻辑 return parent::validateCredentials($user, $credentials); } }
- 注册自定义提供者:在
AuthServiceProvider的boot方法中注册:
namespace App\Providers; use Illuminate\Support\Facades\Auth; use App\Auth\ProjectionUserProvider; use Illuminate\Support\ServiceProvider; class AuthServiceProvider extends ServiceProvider { public function boot() { $this->registerPolicies(); Auth::provider('projection-eloquent', function ($app, array $config) { return new ProjectionUserProvider($app['hash'], $config['model']); }); } }
- 配置认证守卫使用自定义提供者:修改
config/auth.php:
'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'consultants', ], ], 'providers' => [ 'consultants' => [ 'driver' => 'projection-eloquent', 'model' => App\Projections\Consultant::class, ], ],
- 投影模型实现认证接口:
namespace App\Projections; use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Auth\Authenticatable as AuthenticatableTrait; use Spatie\EventSourcing\Projections\Projection; class Consultant extends Projection implements Authenticatable { use AuthenticatableTrait; protected $table = 'consultant_projections'; }
优缺点
- 优点:无需修改模型继承结构,通过扩展Auth组件适配
- 缺点:需维护自定义Auth类,增加了系统复杂度
内容的提问来源于stack exchange,提问作者keizah7
相关产品推荐
相关产品推荐

