Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

zaster's avatar

Eager Loading - Roles and Permissions without a package approach

I am getting lot of duplicated queries becuase of this approach. How could i minimize the number of queries

User Class

    public function roles()
    {
        return $this->belongsToMany('App\Models\Role', 'role_user', 'user_id', 'role_id')
        ->withPivot('id', 'role_id', 'user_id')
        ->withTimestamps();
    }


    public function hasAnyRole(String $role)
    {
        return null !== $this->roles()->where('name', $role)->first();
    }


    public function hasAnyRoles(array $role)
    {
        return null !== $this->roles()->whereIn('name', $role)->first();
    }

Role Class

    public function users()
    {
        return $this->belongsToMany(User::class);
    }

**AuthServiceProvider **

<?php

namespace App\Providers;

use App\Models\User;
use App\Policies\UserPolicy;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * The policy mappings for the application.
     *
     * @var array
     */
    // protected $policies = [
    //      'App\Models\User' => 'App\Policies\UserPolicy',
    // ];
    /**
     * Register any authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        
        //Item Gates
        Gate::define('item-create', function($item) {
            return $item->hasAnyRoles(['Item Manager', 'Super Item Manager']);
        });

        Gate::define('item-read', function($item) {
            return $item->hasAnyRoles(['Item Manager', 'Super Item Manager']);
        });

        Gate::define('item-update', function($item) {
            return $item->hasAnyRoles(['Item Manager', 'Super Item Manager']);
        });

        Gate::define('item-delete', function($item) {
            return $item->hasAnyRoles(['Super Item Manager']);
        });

    // There are around 40+ gates like this

Livewire Component

<?php

namespace App\Http\Livewire;

use App\Models\Item;
use Livewire\Component;
use App\Models\Category;
use Livewire\WithPagination;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;

class ItemTracker extends Component
{
    use WithPagination;
    use AuthorizesRequests;

    public $sortBy = 'id';
    public $sortAsc = false;
    public $item;
    public $type = '';
    public $categories;
    public $search;
    public $searchCategory = '';
    public $perPage = 1000;
    public $confirmingItemDeletion = false;


    // Table Sort
    public function sortBy($field)
    {
        if($field == $this->sortBy){
            $this->sortAsc = !$this->sortAsc;
        }
         $this->sortBy = $field;
    }

    // Delete - Show Modal
    public function confirmItemDeletion($id)
    {
        $this->confirmingItemDeletion = $id;
    }

    // Delete Item
    public function deleteItem(Item $item)
    {
        $this->authorize('item-delete', $item);

        $item->delete();
        $this->confirmingItemDeletion = false;
        return redirect(request()->header('Referer')); //To refresh the page
    }

    public function cancelDeleteItem()
    {
        $this->confirmingUserDeletion = false;
        return redirect(request()->header('Referer')); //To refresh the page
    }

    public function updatingSearch()
    {
        $this->resetPage();
    }

    public function mount()
    {
        $this->categories = Category::orderBy('name', 'asc')->get();
    }


    public function render()
    {
        $items = Item::with('category')
        ->when($this->searchCategory != '', function($query){
                $query->whereHas('category', function($query) {
                    $query->where('category_id', $this->searchCategory);
            });
        })
        ->where(function ($query) {
            $query->where('name', 'LIKE', '%' . $this->search . '%');
        })
        ->orderBy($this->sortBy, $this->sortAsc ? 'ASC' : 'DESC')
        ->paginate($this->perPage);

        // dd($items);

        return view('livewire.item-tracker', [
            'items' => $items,
        ]);
    }
}

<div class="flex flex-col">

    <div class="xl:flex xl:items-center xl:justify-between xl:mx-4 xl:mb-2">
        <p class="text-3xl ...">Item Tracker</p>
        <div class="xl:w-2/6">
            <div class="flex flex-col space-y-2 xl:space-x-2 xl:flex xl:flex-row xl:space-y-0">

                <select wire:model="perPage"
                    class="block border-gray-300 rounded-md shadow-sm 1/6 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
                    <option>10</option>
                    <option>100</option>
                    <option>1000</option>
                </select>

                <select wire:model="searchCategory"
                    class="block w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
                    <option value="">Select a Category</option>
                    @foreach ($categories as $category)
                        <option value="{{ $category->id }}">{{ $category->name }}</option>
                    @endforeach
                </select>

                <x-jet-input wire:model="search" id="search" type="search" class="block w-full" placeholder="Search"  autocomplete="off" />
            </div>
        </div>
    </div>

    <div class="overflow-x-auto xl:-my-2 sm:-mx-6 lg:-mx-8">
        <div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
        <div class="overflow-hidden border-b border-gray-200 shadow sm:rounded-lg">
            <table id="myTable" class="min-w-full text-center divide-y divide-gray-200">
            <thead class="font-bold text-gray-700 bg-gray-50">
                <tr>
                <th scope="col" class="w-1/12 px-6 py-3 text-xs tracking-wider uppercase">
                    <div class="flex items-center justify-center">
                        <button wire:click="sortBy('id')" class="text-xs font-bold tracking-wider uppercase">id</button>
                        <x-sort-icon icon sort-field="id" :sort-by="$sortBy" :sort-asc="$sortAsc" />
                    </div>
                </th>
                <th scope="col" class="w-2/12 px-6 py-3 text-xs tracking-wider uppercase">
                    <div class="flex items-center justify-center">
                        <button wire:click="sortBy('name')" class="text-xs font-bold tracking-wider uppercase">name</button>
                        <x-sort-icon icon sort-field="name" :sort-by="$sortBy" :sort-asc="$sortAsc" />
                    </div>
                </th>
                <th scope="col" class="w-2/12 px-6 py-3 text-xs tracking-wider uppercase">
                    <div class="flex items-center justify-center">
                        <button wire:click="sortBy('category')" class="text-xs font-bold tracking-wider uppercase">category</button>
                        <x-sort-icon icon sort-field="category" :sort-by="$sortBy" :sort-asc="$sortAsc" />
                    </div>
                </th>
                <th scope="col" class="w-2/12 px-6 py-3 text-xs tracking-wider uppercase">
                    <div class="flex items-center justify-center">
                        <button class="text-xs font-bold tracking-wider uppercase"></button>
                    </div>
                </th>
                </tr>
            </thead>
            <tbody class="bg-white divide-y divide-gray-200">
                @foreach ($items as $item)
                    <tr class="h-5 overflow-y-auto">
                        <td class="px-6 py-2 text-sm text-gray-500 border-2 border-gray-200 whitespace-nowrap">
                            {{ $item->id }}
                        </td>
                        <td class="px-6 py-2 text-sm text-left text-gray-500 border-2 border-gray-200 whitespace-nowrap">
                            {{ $item->name }}
                        </td>
                        <td class="px-6 py-2 text-sm text-left text-gray-500 border-2 border-gray-200 whitespace-nowrap">
                            {{ $item->category->name }}
                        </td>
                        <td class="px-6 py-2 text-sm text-left text-gray-500 border-2 border-gray-200 whitespace-nowrap">
                        @can('item-update')
                            <a href="{{ route('employees.items.edit', $item) }}"><x-warning-button>Edit</x-warning-button></a>
                        @endcan
                        @can('item-delete')
                            <x-jet-danger-button wire:click="confirmItemDeletion({{ $item->id }})">Delete</x-jet-danger-button>
                        @endcan
                        </td>
                    </tr>
                @endforeach

                <!-- More people... -->
            </tbody>
            </table>
        </div>
        {{-- Pagination --}}
         <div class="mx-4 mt-4">
            {{ $items->links() }}
        </div>
        </div>
    </div>


     <!-- Delete Item Confirmation Modal -->
    @can('item-delete')
        <x-jet-dialog-modal wire:model="confirmingItemDeletion">
            <x-slot name="title">
                {{ __('Delete Item') }}
            </x-slot>

            <x-slot name="content">
                {{ __('Are you sure you want to delete this Item?') }}
            </x-slot>

            <x-slot name="footer">
                <x-jet-secondary-button wire:click="cancelDeleteItem()" wire:loading.attr="disabled">
                    {{ __('Cancel') }}
                </x-jet-secondary-button>

                <x-jet-danger-button class="ml-2" wire:click="deleteItem({{ $confirmingItemDeletion }})" wire:loading.attr="disabled">
                    {{ __('Delete') }}
                </x-jet-danger-button>
            </x-slot>
        </x-jet-dialog-modal>
     @endcan


</div>
0 likes
3 replies
zaster's avatar
                        @can('item-update')
                            <a href="{{ route('employees.items.edit', $item) }}"><x-warning-button>Edit</x-warning-button></a>
                        @endcan
                        @can('item-delete')
                            <x-jet-danger-button wire:click="confirmItemDeletion({{ $item->id }})">Delete</x-jet-danger-button>
                        @endcan

Is there a way to do something for the above mentioned ?

I have realized that these @can('item-update') and @can('item-delete') are the reasons for the majority of duplicated queries. Is there a way to kind of eager load all the items with those gates ?

jlrdw's avatar

I do not remember which video, but @jeffreyway demonstrates how to deal with that exact question in one of the from scratch series, either 6 or 8.

Please or to participate in this conversation.