I'm trying to configure my Laravel app to store/retrieve user emails from a separate, related table. I want Laravel's built-in User
model to retrieve its email record from a related Person
model, which is linked by a person_id
field on User
.
I followed the steps in this answer, which is very similar to my scenario.
However, I'm encountering an issue:
I create a new user, and inspecting the records shows that everything has been set up properly: a Person
model is created with extra information not included in User
, and User
properly references the Person
model via a relation titled person
. I can log in using the new user, which makes me think the service provider is properly linked as well.
When I send a password reset link, however, I get the error:
App\Models\User : 65
getEmailAttribute
'Call to a member function getAttribute() on null'
.
public function person()
{
return $this->belongsTo(Person::class);
}
public function getEmailAttribute()
{
// Error occurs here!
return $this->person->getAttribute('email');
}
public function getFirstNameAttribute()
{
return $this->person->getAttribute('firstName');
}
public function getLastNameAttribute()
{
return $this->person->getAttribute('lastName');
}
It seems like the code in Password::sendResetLink
thinks that the person
relation is null. I've checked which User
id it's trying to reference, and a manual inspection shows that person
is defined, I can even use the accessor normally, e.g. User::find({id})->email
. I can't think of any reason why person would be null, as it's set up as a foreign key constraint on the database level...
Trying a password reset on another user account in my app - this one created by the database seeder - and it works fine...
Additionally, a nonsense email (not stored in DB) produces the same error... although I've confirmed that my first encounter with this error was using a proper email that is stored in the DB...
EDIT:
public function retrieveByCredentials(array $credentials)
{
if (
empty($credentials) ||
(count($credentials) === 1 &&
str_contains($this->firstCredentialKey($credentials), 'password'))
) {
return;
}
// First we will add each credential element to the query as a where clause.
// Then we can execute the query and, if we found a user, return it in a
// Eloquent User "model" that will be utilized by the Guard instances.
$query = $this->newModelQuery();
foreach ($credentials as $key => $value) {
if (str_contains($key, 'password')) {
continue;
}
if (is_array($value) || $value instanceof Arrayable) {
$query->with([$this->foreign_model => function ($q) use ($key, $value) {
$q->whereIn($key, $value);
}]);
} elseif ($value instanceof Closure) {
$value($query);
} else {
//This is not working
$query->with([$this->foreign_model => function ($q) use ($key, $value) {
$q->where($key, $value);
}]);
}
}
return $query->first();
}
.
'users' => [
'driver' => 'person_user_provider',
'model' => App\Models\User::class,
'foreign_model' => 'person'
],