F
Filament15mo ago
John

Spatie multitenancy, resolve resource page to tenant specific class

In my resource:
use App\Filament\Resources\RequestResource\Pages\CreateRequest;
use App\Filament\Resources\RequestResource\Pages\ListRequests;

class RequestResource extends App\Filament\BaseResource
{
// [...]

public static function getPages(): array
{
return [
'index' => ListRequests::route('/'),
'create' => CreateRequest::route('/create'),
// TODO Bind current tenant dynamically
'edit' => \App\Classes\Tenant\BredaVo\RequestWorkflow::route('/{record}/edit'),
];
}
}
use App\Filament\Resources\RequestResource\Pages\CreateRequest;
use App\Filament\Resources\RequestResource\Pages\ListRequests;

class RequestResource extends App\Filament\BaseResource
{
// [...]

public static function getPages(): array
{
return [
'index' => ListRequests::route('/'),
'create' => CreateRequest::route('/create'),
// TODO Bind current tenant dynamically
'edit' => \App\Classes\Tenant\BredaVo\RequestWorkflow::route('/{record}/edit'),
];
}
}
I'd like to switch that BredaVO part for the current tenant. The current tenant is determined with this: https://spatie.be/docs/laravel-multitenancy/v3/basic-usage/automatically-determining-the-current-tenant But... Filament getPages() runs before Multitenancy DomainTenantFinder, so at this point I don't have access to the current tenant yet. If this proves too difficult to figure out or fix, I might use a generic RequestWorkflow class, and redirect certain method calls to a tenant specific class. But there are methods specifically created for a tenant, so I'd need some __call() magic to redirect those. I'm afraid it will get quite messy. I've tried this approach, but it got very messy indeed. So I'm still looking for a solution or workaround.
15 Replies
John
JohnOP15mo ago
I guess the problem is that Filaments getPages() runs when Laravel is building routes, and Spatie determines the current tenant in Middleware. I wouldn't know how to work around this. bump Anyone please?
Dennis Koch
Dennis Koch15mo ago
Pages are LW components therefore you can set a middleware via properties I think. No need to customize this during registration
John
JohnOP15mo ago
I don't understand. I want the 'edit' page to be dynamic. getPages is parsed for routes, before middleware.
Dennis Koch
Dennis Koch15mo ago
I have never worked with spatie multitenancy and I don't really understand how that docs page related to the issue. Why do you need to make changes to the route registration if the tenant is identified in a middleware? As a far as I see you need to add a tenant middleware to your edit page. That's it
class CheckTasks extends EditRecord
{
protected static string | array $middlewares = [
'tenant'
];
class CheckTasks extends EditRecord
{
protected static string | array $middlewares = [
'tenant'
];
John
JohnOP15mo ago
Each tenant has its own edit page. So I'm too late for page middleware.
Dennis Koch
Dennis Koch15mo ago
So what's the difference? Different form schema?
John
JohnOP15mo ago
Between the edit pages? They have a complex workflow with multiple "steps" (forms). Also custom actions.
Dennis Koch
Dennis Koch15mo ago
Shouldn't something like this work on the Edit page?
protected function getFormSchema(): array
{
$tenant = getYourTenant();

return getFormForTenant($tenant);
}
protected function getFormSchema(): array
{
$tenant = getYourTenant();

return getFormForTenant($tenant);
}
John
JohnOP15mo ago
The step forms are already in saparate tenant specific files. But actions to proceed to next step, return to previous, and other custom actions with custom conditions are in the tenant specific edit page. I tried that approach. Actions were the next challenge, because I think I'd have to redirect them to the tenant specific method using __call() ? Does that even work with LW?
Dennis Koch
Dennis Koch15mo ago
I tried that approach.
So that works for the form?
because I think I'd have to redirect them to the tenant specific method using __call()
I don't understand what you are trying to do. What tenant specific methods to you have? Do you have an example?
John
JohnOP15mo ago
public function proceedToStatus($status): void
{
$status = RequestStatus::from($status);

$this->updateRequest();

$proceed = true;

// * Only proceed to ReviewedByCommittee when all committee members have reviewed
if ($status == RequestStatus::ReviewedByCommittee) {
$requestCommitteeMemberUserIds = $this->request->requestCommitteeMembers()->whereNull('date_invalidated')->pluck('user_id');
foreach ($this->request->commissielid_id as $object) {
if (!$requestCommitteeMemberUserIds->contains($object['user_id'])) {
$proceed = false;
break;
}
}
}

if ($proceed) {
$this->request->request_status_id = $status;
$this->request->save();
}

Notification::make()
->success()
->title(__('filament::resources/pages/edit-record.messages.saved'))
->send();

$this->redirectRoute('filament.resources.requests.index');
}
public function proceedToStatus($status): void
{
$status = RequestStatus::from($status);

$this->updateRequest();

$proceed = true;

// * Only proceed to ReviewedByCommittee when all committee members have reviewed
if ($status == RequestStatus::ReviewedByCommittee) {
$requestCommitteeMemberUserIds = $this->request->requestCommitteeMembers()->whereNull('date_invalidated')->pluck('user_id');
foreach ($this->request->commissielid_id as $object) {
if (!$requestCommitteeMemberUserIds->contains($object['user_id'])) {
$proceed = false;
break;
}
}
}

if ($proceed) {
$this->request->request_status_id = $status;
$this->request->save();
}

Notification::make()
->success()
->title(__('filament::resources/pages/edit-record.messages.saved'))
->send();

$this->redirectRoute('filament.resources.requests.index');
}
The "Only proceed ..." part is tenant specific.
public function returnToStatus($status): void
{
$status = RequestStatus::from($status);

$this->updateRequest();

$this->request->request_status_id = $status;
$this->request->save();

// * Notify users
$users = match ($status) {
RequestStatus::Open => [$this->request->requester],
RequestStatus::ForwardedToCommittee => $this->request->requestCommitteeMembers->map(fn(
RequestCommitteeMember $member
) => $member->user->person),
default => [],
};

foreach ($users as $user) {
Mail
::to((object)['name' => $user->name, 'email' => $user->email])
->send(new RequestReturnedToPreviousStatus($this->request, $user->name));
}

Notification::make()
->success()
->title(__('filament::resources/pages/edit-record.messages.saved'))
->send();

$this->redirectRoute('filament.resources.requests.index');
}
public function returnToStatus($status): void
{
$status = RequestStatus::from($status);

$this->updateRequest();

$this->request->request_status_id = $status;
$this->request->save();

// * Notify users
$users = match ($status) {
RequestStatus::Open => [$this->request->requester],
RequestStatus::ForwardedToCommittee => $this->request->requestCommitteeMembers->map(fn(
RequestCommitteeMember $member
) => $member->user->person),
default => [],
};

foreach ($users as $user) {
Mail
::to((object)['name' => $user->name, 'email' => $user->email])
->send(new RequestReturnedToPreviousStatus($this->request, $user->name));
}

Notification::make()
->success()
->title(__('filament::resources/pages/edit-record.messages.saved'))
->send();

$this->redirectRoute('filament.resources.requests.index');
}
Which users are notified for which step is tenant specific.
Dennis Koch
Dennis Koch15mo ago
Then maybe extract that code to an action (the action pattern, not Filament Action)? GetNotifiablesForTenant etc
John
JohnOP15mo ago
I guess I could do that. I just thought it would be more flexible to do whatever I want or need per tenant. I'll give it some thought. Thanks for your help.
Dennis Koch
Dennis Koch15mo ago
You still do whatever you want or need per tenant. You just do it somewhere else.
John
JohnOP15mo ago
I guess so. I'll have to refactor some actions, but it will be cleaner in the end.
Want results from more Discord servers?
Add your server