Creare un progetto Laravel multi-tenant + Filament

Per prima cosa creo il progetto Laravel vaniglia:

# composer create-project laravel/laravel myapp
# mv myapp/* .
# mv myapp/* .
# mv myapp/.env .
# rm-Rf myapp

Poi passiamo a FilamentPHP:

# composer require filament/filament -W
# php artisan filament:install
# php artisan filament:install --panels

Poi creiamo il primo utente amministratore:

# php artisan migrate
# php artisan make:filament-user

Adesso possiamo passare al multi-tenant:

# composer require "spatie/laravel-multitenancy:^4.0"

E pubblichiamo i file necessari:

# php artisan vendor:publish --provider="Spatie\Multitenancy\MultitenancyServiceProvider" --tag="multitenancy-config"

Per proteggere le aree riservate dei vari tenant creiamo un nuovo gruppo Middleware e applichiamolo alle rotte che devono essere protette:

// in `bootstrap/app.php`

return Application::configure(basePath: dirname(__DIR__))
    // ...
    ->withMiddleware(function (Middleware $middleware) {
        $middleware
            ->group('tenant', [
                \Spatie\Multitenancy\Http\Middleware\NeedsTenant::class,
                \Spatie\Multitenancy\Http\Middleware\EnsureValidTenantSession::class,
            ]);
    });

Poi aggiungiamolo alle rotte nel file dentro la cartella routes:

// in un routes file come web.php
Route::middleware('tenant')->group(function() {
    // routes
});

Database multiplo, uno per ogni tenant

Questa impostazione presume che si abbiano 2 tipi di connessione al database:

  • landlord: la connessione al database principale che contiene la tabella dei tenants e le altre tabelle del sistema
  • tenant: la connessione al database “proprietà” del tenant

In config/database.php:

'tenant' => [
        'driver' => 'mysql',
        'database' => null,
        'host' => '127.0.0.1',
        'username' => env('DB_USERNAME', 'root'),
        'password' => env('DB_PASSWORD', ''),
        // And other options if needed ...
    ],

    'landlord' => [
        'driver' => 'mysql',
        'database' => env('DB_DATABASE', 'laravel'),
        'host' => '127.0.0.1',
        'username' => env('DB_USERNAME', 'root'),
        'password' => env('DB_PASSWORD', ''),
        // And other options if needed ...
    ],

Adesso pubblichiamo le migrations:

# php artisan vendor:publish --provider="Spatie\Multitenancy\MultitenancyServiceProvider" --tag="multitenancy-migrations"

Questo comando crea una cartella sotto database/migrations chiamata landlord al cui interno vanno messe le migrations specifiche dei tenant che andremo a creare.

# php artisan migrate --path=database/migrations/landlord --database=landlord

Una volta che vogliamo eseguirle per creare le varie tabelle dei tenant, dobbiamo eseguire:

# php artisan tenants:artisan "migrate --path=database/migrations/tenant --database=tenant"

Switchiamo il database specifico del tenant quando accede alla sua area

Ogni volta che un tenant viene indicato come current, il package eseguirà i tasks definiti alla voce switch_tenant_tasks nel file config/multitenancy.php

/*
 * These tasks will be performed to make a tenant current.
 *
 * A valid task is any class that implements Spatie\Multitenancy\Tasks\SwitchTenantTask
 */
'switch_tenant_tasks' => [
    Spatie\Multitenancy\Tasks\SwitchTenantDatabaseTask::class,
],

La creazione dei database dei singoli tenant non è gestita in automatico dal pacchetto. Io preferisco crearlo nel momento in cui si crea un nuovo tenant ed il modo per farlo è creando un Observer di questo evento, ma prima creiamo un Model Tenant a cui attaccarlo:

# php artisan make:model Tenant -m
# php artisan make:observer TenantObserver --model=Tenant