julianatkin
julianatkin

Reputation: 48

Laravel model accessor, 'Call to a member function getAttribute() on null'

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'
    ],

Upvotes: 0

Views: 1768

Answers (1)

mrhn
mrhn

Reputation: 18926

In my experience, somehow you app is calling this logic with faulty data and you are not certain whats causing it. There is ton of exception services, that can give deeper insights to how this happens, context, url called from etc. Since you are here asking the question, i'm guessing you want it fixed and we can help with the first case i described.

Instead why not create a defensive approach that helps this case, which can keep coming up. Laravel has the optional() helper, that makes it possible to call on null objects without the code crashing, the variable will end up null.

return optional($this->person)->getAttribute('email');

PHP 8 has a ?-> nullsafe operator, which does the same. I'm assuming PHP 8 has not been widespread adopted, so optional will works in all cases.

return $this->person?->getAttribute('email');

Upvotes: 2

Related Questions