skip to Main Content

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.

3

Answers


  1. 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

    Login or Signup to reply.
  2. You could cache the relationship by creating a custom accessor for the role attribute on the User model. An example could be :

    <?php 
    
    use IlluminateSupportFacadesCache; 
    
    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);
        }
    }
    
    Login or Signup to reply.
  3. Create a file named "user.php" under "AppConfig" 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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search