Home » Php » php – Laravel Relationship availability after save()

php – Laravel Relationship availability after save()

Posted by: admin July 12, 2020 Leave a comment

Questions:

I’m building a simple timetracking App using Laravel 5 where I have two Laravel models, one called “User” and one called “Week”

The relationships are pretty simple:
Week.php:

public function user()
{
    return $this->belongsTo('App\User');
}

User.php:

function weeks()
{
    return $this->hasMany('App\Week');
}

Now, the User.php file also has a simple helper / novelty function called “getCurrentWeek()” that, as the name suggests, returns the current week

function getCurrentWeek()
{
    $possibleWeek = $this->weeks->sortByDesc('starts')->all();
    if(count($possibleWeek) != 0)
    {
        return $possibleWeek[0];
    }
    else
    {
        return null;
    }
}

My problem is: If I create the very first week for a user and attach / relate it to the user like so:

$week = new Week;

//Omitted: Setting so attributes here ...

$week->user_id = $user->id;
$weeks->save()
$user->weeks()->save($week);

And then call the $user->getCurrentWeek(); method, that method returns null, although a new week has been created, is related to the user and has been saved to the database. In my mind, the expected behaviour would be for getCurrentWeek() to return the newly created week.
What am I misunderstanding about Eloquent here / doing just plain wrong?

How to&Answers:

Relationship attributes are lazy loaded the first time they are accessed. Once loaded, they are not automatically refreshed with records that are added or removed from the relationship.

Since your getCurrentWeek() function uses the relationship attribute, this code will work:

$week = new Week;
// setup week ...
$user->weeks()->save($week);

// $user->weeks attribute has not been accessed yet, so it will be loaded
// by the first access inside the getCurrentWeek method
dd($user->getCurrentWeek());

But, this code will not work:

// accessing $user->weeks lazy loads the relationship
echo count($user->weeks);

// relate a new record
$week = new Week;
// setup week ...
$user->weeks()->save($week);

// $user->weeks inside the method will not contain the newly related record,
// as it has already been lazy loaded from the access above.
dd($user->getCurrentWeek());

You can either modify your getCurrentWeek() method to use the relationship method ($this->weeks()) instead of the attribute ($this->weeks), which will always hit the database, or you can reload the relationship (using the load() method) after adding or removing records.

Change the getCurrentWeek() method to use the relationship method weeks() (updated method provided by @Bjorn)

function getCurrentWeek()
{
    return $this->weeks()->orderBy('starts', 'desc')->first();
}

Or, refresh the relationship using the load() method:

// accessing $user->weeks lazy loads the relationship
echo count($user->weeks);

// relate a new record
$week = new Week;
// setup week ...
$user->weeks()->save($week);

// reload the weeks relationship attribute
$user->load('weeks');

// this will now work since $user->weeks was reloaded by the load() method
dd($user->getCurrentWeek());

Answer:

Just to add to @patricus answer…

After save/create/update, you can also empty all the cached relation data like so:

$user->setRelations([]);

Or selectively:

$user->setRelation('weeks',[]);

After that, data will be lazy loaded again only when needed:

$user->weeks

That way you can continue using lazy loading.

Answer:

$user->weeks()->save($week); is not needed because you manually attached the week to the user by using $week->user_id = $user->id; and saving it.

You could actually rewrite the whole function to:

function getCurrentWeek()
{
    return $this->weeks->sortByDesc('starts')->first();
}

Or

function getCurrentWeek()
{
    return $this->weeks()->orderBy('starts', 'desc')->first();
}

Edit:

I made a little proof of concept and it works fine like this:

App\User.php

function weeks()
{
    return $this->hasMany('App\Week');
}

function getCurrentWeek()
{
    return $this->weeks->sortByDesc('starts')->first();
}

App\Week.php

public function user()
{
    return $this->belongsTo('App\User');
}

routes/web.php or App/Http/routes.php

Route::get('poc', function () {
    $user = App\User::find(1);
    $week = new App\Week;
    $week->user_id = $user->id;
    $week->starts = Carbon\Carbon::now();
    $week->save();
    return $user->getCurrentWeek();
});

Result:

{
    id: 1,
    user_id: "1",
    starts: "2016-09-15 21:42:19",
    created_at: "2016-09-15 21:42:19",
    updated_at: "2016-09-15 21:42:19"
}