Newb question: populating table based on query
Hi all - kinda new to Filament, been hacking around with Lara/Livewire for a while, now starting to migrate my app over to Filament bit by bit learning as I go. I'm calling Filament parts as Livewire components.
My app is multi-tenanted. Objects in various databases belong to 'organisations' or other groupings. In Lara/Livewire, I'd pass the collection into the Livewire component based off a query $users=User::where('organisation',Session::get('currentOrg'))->get();
The Filament code I'm working off looks like this:
return $table
->query(User::query())
->columns([
TextColumn::make('name')
->searchable(isIndividual: true, isGlobal: false)
->sortable()
->label('Full Name'),
TextColumn::make('email')
->label('Email Address')
->searchable(isIndividual: true, isGlobal: false)
->sortable(),
Clearly, it's returning a table with all the users in the database. How do I filter that to just those records where the organisation field = whatever?
Everything I google for this seems to give me quite complex answers, but my spidey sense tells me there's a simpler way.. TIA..13 Replies
Just adjust this part as you want:
->query(User::query())
e.g:
->query(fn() => static::getEloquentQuery()->where($hasAdminPrivileges ? [] : ['user_id' => $userId])->orderBy('created_at', 'desc'))
Alternatively you may just add a table filter with a default value if you want just to use the filter condition on the first load
This is for the table in general, if you want a query to be used by filament on all the resource model queries then you may override this resource class method. e.g if you want to include trashed records or loading a relation:
If you want to know more about possible customizations in the future just inspect the filament classes, you will find many usefull methodsThanks - admittedly, I'm scratching my head here. Looking at your example:
->query(fn() => static::getEloquentQuery()
.......
I replace my ->query(User::query())
with:
->query(fn() => static::getEloquentQuery()->where('organisation_id', 2)
but it borks saying it can't find method getEloquentQuery.
I see some other code examples suggesting to add
public static function getEloquentQuery(): Builder
{
return parent::getEloquentQuery();
}
which doesn't work and also doesn't make much sense to me, not least because I don't see where I reference the User:: model now?
Sorry if I'm being dumb here... I'm just not used to this way of doing things... once I get a working example though, I'll be off to the races!getEloquentQuery
doesn't apply if you are not using panel builder with resources.
You associate your resource with a model, which is where it gets it from.
If you are just using using the table in a livewire component. Nothing special about the query though, you just need to chain on the where method:
->query(User::query()->where('organisation',Session::get('currentOrg'))
I think the ->query() modifier should be the base query, ie User::query() and any modifications to that should be through ->modifyQueryUsing()
GitHub
filament/packages/tables/src/Table/Concerns/HasQuery.php at 3.x · f...
A collection of beautiful full-stack components for Laravel. The perfect starting point for your next app. Using Livewire, Alpine.js and Tailwind CSS. - filamentphp/filament
Suspect both approaches will work, but switching the logic as @awcodes suggests makes for cleaner code
->query(User::query()->where('organisation',Session::get('currentOrg'))
Thanks! That's the incantation I was looking for! I think I must have tried every other possible incantation of that but somehow missing the correct one!
I'll definitely look into awcodes suggestion if that's the better way to do this. THANKS!BTW, opinion varies...but assuming these are authenticated sessions...I would always insist on global scopes at the highest and any parentless models for this, rather than applying the limitations on the resource\table directly.
Yes, authenticated. May I ask for a bit of an explanation of, or pointers to, what you're talking about here?
You're suggesting that I don't put the filter on the table - why?
And where should I put it? I'm not sure what you mean by global scopes and parentless models (again, apols for being dumb, learning here!)
Global scopes apply to models, and they apply to all queries. Though they can be conditionally applied.
It is a powerful Laravel tool, you can find out what they are and how to apply them here: https://laravel.com/docs/11.x/eloquent#global-scopes
Laravel - The PHP Framework For Web Artisans
Laravel is a PHP web application framework with expressive, elegant syntax. We’ve already laid the foundation — freeing you to create without sweating the small things.
I found some time at the weekend to read through the above. I'd read it before but didn't connect it with what you were saying. So, yes I get what global scopes are, and how and where they're applied. In the context of Filament tables, I'm confused as to the why though?
Is it a form of protection? In other words, if I define a scope such as:
class currentOrgScope implements Scope
{
public function apply(Builder $builder, Model $model): void
{
$builder->where('organisation', '=', Session::get('currentOrg');
}
}
And then
#[ScopedBy([currentOrgScope::class])]
class Widget extends Model
{
on the model, that will ensure 100% that any results on the widgets table will belong to that organisation (provided of course it has such a column).
The desired effect being that I can't erroneously code something that leaks the widgets from all organisations. This should probably be best practice anyway right? Not just Filament table stuff?Protection yes, but also clean code. You only have to define it once, and if it changes, you only have to change it in one place.
You can then scope any child models with a
whereHas
using the relationship, and now you've automatically protected access to child model data as well.
Filament can exclude global scopes on request, for instances where you have something like a global administrator login. (Although for that, I would insist on separate guards and condition the scopes accordingly, but that's a whole other topic!)Got it. Thanks for your input 🤗.