Skip to content

Instantly share code, notes, and snippets.

@niladam
Forked from nathandaly/BaseResource.php
Created May 15, 2024 20:19
Show Gist options
  • Save niladam/2e5feccd7f128701cf3b9c1943c87cf3 to your computer and use it in GitHub Desktop.
Save niladam/2e5feccd7f128701cf3b9c1943c87cf3 to your computer and use it in GitHub Desktop.
Tenancy for Laravel & Filament V3 (Tenant per database)
<?php
/**
* Below is an the extended Filament resource your tenant panel resources
* will have to extend so that the queries are scoped properly.
*/
namespace App\Filament;
use Filament\Resources\Resource;
use Illuminate\Database\Eloquent\Builder;
class BaseResource extends Resource
{
public static function getEloquentQuery(): Builder
{
return static::getModel()::query();
}
}
@if(\Filament\Facades\Filament::getTenant() !== null)
<x-filament::button
color="danger"
icon="gmdi-logout"
:href="config('app.url') . '/admin/tenancy/tenants'"
tag="a"
>
Exit Tenant
</x-filament::button>
@endif
<?php
/**
* This middleware extends the Tenancy for Laravel one to catch
* tenant ID and scope future requests in the tenant panel.
*/
declare(strict_types=1);
namespace App\Http\Middleware;
use App\Context\Tenant\Models\Tenant;
use Illuminate\Contracts\Auth\Factory as AuthFactory;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Contracts\Session\Middleware\AuthenticatesSessions;
use Illuminate\Http\Request;
use Stancl\Tenancy\Middleware\InitializeTenancyBySubdomain as BaseMiddleware;
use Closure;
use Stancl\Tenancy\Resolvers\DomainTenantResolver;
use Stancl\Tenancy\Tenancy;
class InitializeTenancyBySubdomain extends BaseMiddleware implements AuthenticatesSessions
{
public function __construct(
protected Tenancy $tenancy,
protected DomainTenantResolver $resolver,
protected AuthFactory $auth
) {
parent::__construct($tenancy, $resolver);
}
public function handle(Request $request, Closure $next): mixed
{
$route = $request->route();
$record = $route->parameter('tenant');
if ($record && $tenant = Tenant::find($record)) {
if ($domain = $tenant->domain) {
$this->initializeTenancy($request, $next, $domain->domain);
}
}
return $next($request);
}
protected function guard(): AuthFactory|Guard
{
return $this->auth;
}
}
<?php
/**
* The below is an example Tenant panel provider created with the following command:
* php artisan make:filament-panel tenant
*
* The following code assumes you already have a central app scoped admin panel.
*/
declare(strict_types=1);
namespace App\Providers\Filament;
use App\Context\Tenant\Models\Tenant;
use App\Filament\Pages\Login;
use App\Http\Middleware\InitializeTenancyBySubdomain;
use Filament\Facades\Filament;
use Filament\Http\Middleware\Authenticate;
use Filament\Http\Middleware\DisableBladeIconComponents;
use Filament\Http\Middleware\DispatchServingFilamentEvent;
use Filament\Pages;
use Filament\Panel;
use Filament\PanelProvider;
use Filament\Support\Colors\Color;
use Filament\Widgets;
use Illuminate\Contracts\View\View;
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
use Illuminate\Cookie\Middleware\EncryptCookies;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
use Illuminate\Routing\Middleware\SubstituteBindings;
use Illuminate\Session\Middleware\AuthenticateSession;
use Illuminate\Session\Middleware\StartSession;
use Illuminate\Support\Facades\Blade;
use Illuminate\View\Middleware\ShareErrorsFromSession;
class TenantPanelProvider extends PanelProvider
{
public function panel(Panel $panel): Panel
{
return $panel
->id('tenant')
->path('tenant')
->viteTheme([
'resources/css/tailwind.css',
'resources/js/app.js',
'resources/css/filament/admin/theme.css',
])
->login(Login::class)
->tenant(Tenant::class)
->discoverResources(in: app_path('Filament/Tenant/Resources'), for: 'App\\Filament\\Tenant\\Resources')
->discoverPages(in: app_path('Filament/Tenant/Pages'), for: 'App\\Filament\\Tenant\\Pages')
->discoverWidgets(in: app_path('Filament/Tenant/Widgets'), for: 'App\\Filament\\Tenant\\Widgets')
->pages([
Pages\Dashboard::class,
])
->renderHook('panels::user-menu.before', fn (): View => view('components.button.exit-tenancy'))
->widgets([
Widgets\AccountWidget::class,
Widgets\FilamentInfoWidget::class,
])
->middleware([
InitializeTenancyBySubdomain::class,
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartSession::class,
AuthenticateSession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
SubstituteBindings::class,
DisableBladeIconComponents::class,
DispatchServingFilamentEvent::class,
])
->authMiddleware([
Authenticate::class,
]);
}
}
<?php
/**
* Below is an example of the Filament tenant resource in the central app.
* When you click on a tenant row your app will redirect to the tenant panel scoped to the selected tenant.
*/
declare(strict_types=1);
namespace App\Filament\Central\Tenancy;
use Filament\Resources\Resource;
class TenantsResource extends Resource
{
public static function table(Table $table): Table
{
return $table
->recordUrl(
fn (Model $record): string => route('filament.tenant.pages.dashboard', ['tenant' => $record]),
)
// Rest of your table definition...
;
}
// Rest of your resource code...
}
<?php
declare(strict_types=1);
namespace App\Models;
class User extends Authenticatable implements Syncable, MustVerifyEmail, HasTenants
{
use ResourceSyncing;
// Your other model logic...
/**
* Add the `HasTenants` interface.
* Add the below methods required by the above trait.
*/
public function getTenants(Panel $panel): Collection
{
return $this->tenants;
}
public function canAccessTenant(Model $tenant): bool
{
return true;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment