Cache Eloquent Relationship query - php

How can I cache this Eloquent query:
dd($user->roles);
Because above will somehow trigger the $user->roles() query I assume.
I have tried with this:
public function roles() {
return \Cache::remember('user_' . $this->id . '_roles', 10, function() {
return $this->hasMany('App\Role');
});
}
But it does not work, because it has to return a array, not eloquent query.
Any suggestions?

Here is my approach:
public function bookmarks(): HasMany
{
return $this->hasMany(Bookmark::class);
}
protected function getBookmarksCacheKey(): string
{
return sprintf('user-%d-bookmarks', $this->id);
}
public function clearBookmarksCache(): bool
{
return Cache::forget($this->getBookmarksCacheKey());
}
public function getBookmarksAttribute(): Collection
{
if ($this->relationLoaded('bookmarks')) {
return $this->getRelationValue('bookmarks');
}
$bookmarks = Cache::rememberForever($this->getBookmarksCacheKey(), function () {
return $this->getRelationValue('bookmarks');
});
$this->setRelation('bookmarks', $bookmarks);
return $bookmarks;
}

You can't store a relationship in the cache. You need to cache the actual data retrieved from the database. So you'll have something like this:
public function roles()
{
return \Cache::remember('user_' . $this->id . '_roles', 10, function()
{
return $this->hasMany('App\Role')->get()->toArray();
});
}
And now you have to access it as a method, not a property, because it's not returning a relation anymore (and Eloquent would throw an exception):
$user->roles();
Now you should get an array as you want.

If you want to cache user together with its roles you can do it this way:
$user = User::find(1);
$user->load('roles');
Cache::put('users_'.$user->id, $user, 10);
I don't know why, but you need to use load here instead of with. If you used with you would get error that you cannot cache PDO instance.

Related

Laravel - relationship

I'm trying to figure out how I can get a nested relationship but without any success.
Modal: Workout
public static function getWorkout($workout_id = null)
{
if ($workout_id) {
return Workout::whereId($workout_id)->with('exercises.sets')->first();
}
return [];
}
public function exercises()
{
return $this->belongsToMany('App\Models\Exercise');
)
Modal: Exercise
public function sets()
{
return $this->hasMany('App\Models\Set');
}
This solution gives me all sets based on "exercise_id". I need to get only the sets within the workout.
If I do this it works, the problem is now how I should get the ID of the workout to pass. I've tried to put the relation in the Workout Model as well but then the response of sets will get outside the exercise array.
public function sets()
{
return $this->hasMany('App\Models\Set')->where('workout_id', 5);
}
Try the following snippet where I specify a condition for the eager load.
public static function getWorkout($workout_id = null)
{
if ($workout_id) {
return Workout::whereId($workout_id)->with(['exercises.sets' => function($query) use($workout_id)
{
$query->where('workout_id', $workout_id);
}])->first();
}
return [];
}

Fetch data nested relations eloquent with() in Laravel

I have a model file "Student" that contains the relation :
public function implementations()
{
return $this->hasMany(Implementation::class);
}
In my Implementation model, I have theses relations :
public function score()
{
return $this->belongsTo(Score::class, 'id', 'implementation_id');
}
public function project()
{
return $this->belongsTo(Project::class);
}
I would like to retrieve a table with all its data.
I tried this
public function getStudents($id)
{
$event = Event::where('id', $id)->first();
$data = $event->students()->with('implementations')->get();
return response()->json($data);
}
It works. But I do not have the result I would like. I would also like to recover the implementations data with theproject and score relationships
I tried that but it does not work
public function getStudents($id)
{
$event = Event::where('id', $id)->first();
$data = $event->students()->with('implementations')->with('project', 'score')->get();
return response()->json($data);
}
Thank you very much for your help!
Have you tried Event::with('students', 'students.implementations') yet? You can eager load relations of relations using the dot notation.

Eloquent all records relationship

I'm trying to build an alternative relationship that returns all records instead of only related records. I have tried returning a query builder, but that doesn't work, it must be a relationship. What should I return to make this work?
public function devices()
{
if ($this->admin) {
// return all devices relationship instead
} else {
return $this->belongsToMany('Device', 'permissions');
}
}
Fiddle: https://implode.io/XXLGG8
Edit: I'd like to continue building the query in most cases, not just get the devices.
The devices() function in your model is expected to return a relation, you shouldn't add the if statement there. Make your devices() function like this:
public function devices()
{
return $this->belongsToMany('Device', 'permissions');
}
In your User model add a new function:
public function getDevices() {
if($this->admin === true) {
return Device::all();
}
return $this->devices();
}
Now you can do:
$admin->getDevices(); // will return all devices
$user->getDevices(); // will return only relations
I actually went a slightly different way and used a scope:
protected function scopeHasAccess($query, User $user)
{
if ($user->admin) {
return $query;
}
return $query->join('permissions', 'permissions.device_id', "devices.id")
->where('permissions.user_id', $user->user_id);
}
Add devices accessor method to the User model and implement your logic there.
public function getDevicesAttribute() {
if ($this->admin) {
return Device::all();
}
return $this->getRelationValue('devices');
}
See updated "fiddle".

Laravel how to create empty relationship?

I have a condition inside a relationship where if the user is logged in it will return the relation and if not i want it to return empty relationship.
here is what i want :
public function dummy()
{
return (auth()->user()) ? $this->hasOne(blah::class) : emptyrelationship();
}
You should check with DD() what is being returned as you like.
If there's no data for the relationship to show, it will just return no data.
To return an empty relationship instead of null, you can try this:
public function item()
{
return $this->belongsTo(Item::class)
->withDefault(function () {
return new Item();
});
}
Try This example
public function shop(){
if(true) {
return $this->newQuery(); // or newQueryWithoutScopes()
}
return $this->belongsTo('App\Models\Shop');
}
If there is no user in the user table collection will return relationship as "relationship: user: []" as blank only if you do dd($var) on your query,then you can check though conditions in your code;
Eloquent has a method for that newModelInstance
Best to keep the eloquent model standard relationship and move logic elsewhere
public function dummy()
{
return $this->hasOne(blah::class);
}
$dummy = $model->dummy;
if (!$dummy) {
$dummy = $model->dummy()->newModelInstance();
}

Eager loading on polymorphic relations

class Admin {
public function user()
{
return $this->morphOne('App\Models\User', 'humanable');
}
public function master()
{
return $this->hasOne('App\Models\Master');
}
}
class Master {
public function admin()
{
return $this->hasOne('App\Models\Admin');
}
}
class User {
public function humanable()
{
return $this->morphTo();
}
public function images()
{
return $this->hasOne('\App\Models\Image');
}
}
class Image {
public function user()
{
return $this->belongsTo('\App\Models\User');
}
}
Now if I dump this:
return \App\Models\Admin::where('id',1)->with(array('user.images','master'))->first();
I get the perfect result one master, one user and one image record.
But if I do this
return $user = \App\Models\User::where('id',1)->with(array('humanable','humanable.master'))->first();
I only get one Admin record, the query get * from masters doesn't even run.
Any idea what I'm doing wrong, I'm sure this is possible.
If I remember correctly Laravel has lots of pitfall. You can try to use the protected $with var in Admin model instead of query builder with function.
class Admin {
protected $with = ['master'];
public function user() {
return $this->morphOne('App\Models\User', 'humanable');
}
public function master() {
return $this->hasOne('App\Models\Master');
}
}
In query builder, only need to include humanable. Now you should see master inside the humanable object.
return $user = \App\Models\User::where('id',1)->with('humanable')->first();
Hope this help.

Categories