4

I am building an application that is using lighthouse-php. Because I have constantly set various policies for different users, I constantly query for user model with a role relationship in different parts applications, and for this reason, would want to store the users in the Redis database and query from there instead. I read couple of articles that I found on the internet such as: laravel-cache-authuser; creating-a-caching-user-provider-for-laravel/; caching-the-laravel-user-provider-with-a-decorator/, reviewed code in here laravel-auth-user, and kind of understood the concept but struggling to understand laravel deep enough to find a suitable solution...

For example, I am struggling to understand how to store User with Role relationship inside event method in UserObserver, it's clear how to do it with one Model but not with a relationship attached.

I had a sense that I should do something like that:

class UserObserver
{
    /**
     * @param User $user
     */
    public function saved(User $user)
    {
        $user->load('role');
        Cache::put("user.$user->id", $user, 60);
    }
}

But this way I make 2 calls to the DB, rather than having the relationship pre-loaded. How could I preload the relationship in the events arguments. I tried to add protected $with = ['role'] so that child model/relationship always loaded. But no matter what I make more calls to DB either to retrieve Role or to retrieve User and Role.

He is some simplified code samples from my project lighthouse-php.

schema.graphql:

type SomeType {
    someMethod(args: [String!]): [Model!] @method @can(ability: "isAdmin",  model: "App\\Models\\User")
}

type User {
    id: ID
    name: String
    role: Role @belongsTo
}

type Role {
    id: ID!
    name: String!
    label: String!
    users: [User!] @hasMany
}

User Model with role relationship:

class User extends Authenticatabl {
    public function role(): BelongsTo
    {
        return $this->belongsTo(Role::class);
    }
}

User Policy that is used on some of the graphql type fields:

class UserPolicy
{
    use HandlesAuthorization;

    public function isAdmin(): Response
    {
        $user = auth()->user();

        return $user->role->name === 'admin'
            ? $this->allow()
            : $this->deny('permission denied');
    }

    public function isManager(): Response
    {
        $user = auth()->user();
        $this->allow();

        return $user->role->name === 'manager' || $user->role->name === 'admin'
            ? $this->allow()
            : $this->deny('Permission Denied');
    }

}

Lighouse custom Query Class for resolving fields via methods.

class SomeType {
    public function someMethod(): string
    {
       // this triggers db call rather than receiving `role->name` from redis along with user
       return auth()->user()->role->name;
    }
}

If I make graphql query that looks something like this (please see below) it causes role relationship to be loaded from db, instead of cache.

query {
   user {
     id
     name
     role {
       id
       name
     }
   }
}
Please help.
Andrius Solopovas
  • 967
  • 11
  • 41

3 Answers3

4

You could cache the relationship by creating a custom accessor for the role attribute on the User model. An example could be :

<?php 

use Illuminate\Support\Facades\Cache; 

class User extends Authenticatabl {

    public function role(): BelongsTo
    {
        return $this->belongsTo(Role::class);
    }

    public static function getRoleCacheKey(User $user): string
    {
        return sprintf('user-%d-role', $user->id);
    }

    // Define accessor for caching purposes
    public function getRoleAttribute(): Collection
    {
        if ($this->relationLoaded('role')) {
            return $this->getRelationValue('role');
        }
        
        // Replace 3600 for the amount of seconds you would like to cache
        $role = Cache::remember(User::getRoleCacheKey($this), 3600, function () {
            return $this->getRelationValue('role');
        });

        $this->setRelation('role', $role);

        return $role;
    }
}

Alternatively, you can use the rememberForever() function to cache it forever. Be aware that you would have to write an implementation to either remove / update the caching manually, as it will hold the value forever. You could create a function that clears the caching like so:

// In User model
public static function forgetRoleCaching(User $user): bool
{
    return Cache::forget(sprintf(User::getRoleCacheKey($user));
}

Your code in the observer can be updated to the following:

class UserObserver
{
    /**
     * @param User $user
     */
    public function saved(User $user)
    {
        // in case user role is cached forever
        User::forgetRoleCaching($user);

        $user->load('role'); // will trigger the accessor an should cache again
        Cache::put("user.$user->id", $user, 60);
    }
}
Eric Landheer
  • 2,033
  • 1
  • 11
  • 25
0

you can store your auth user with your relation using Session.

example :

$auth = Auth::User();
Session::put('user',$auth);

Session::put('relation',$auth->relation);

May it help you

MUH. ISA S
  • 23
  • 4
0

Create a file named "user.php" under "App\Config" path. File should look like this

<?php

return [
    'role' => ''
];

Now you can set a config

config(['user.role' => $example_role]);

Then you can read from any file whenever you need this data

config('user.role');

This is my solution and i set this config value in middleware, works flawless.

AOS
  • 1