Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Laravel Relationship availability after save()

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?

like image 564
Sven Ewers Avatar asked Sep 15 '16 20:09

Sven Ewers


2 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());
like image 153
patricus Avatar answered Sep 24 '22 19:09

patricus


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.

like image 30
Isometriq Avatar answered Sep 26 '22 19:09

Isometriq