glimmer/tenancy

Opinionated and extended Spatie Multitenancy

dev-main 2025-04-18 04:37 UTC

This package is auto-updated.

Last update: 2025-04-18 04:37:34 UTC


README

An opinionated Spatie Multitenancy extension package for Laravel

Latest Version on Packagist GitHub Tests Action Status Laravel Octane Compatibility

This package extends spatie/laravel-multitenancy with additional opinionated features. For example, it defaults to multi-database tenancy but takes a different approach than Spatie's implementation.

Since this package builds upon Spatie’s, it retains most original concepts and functionality. It’s recommended to review Spatie's official documentation before using this package.

What makes it opinionated?

  • It uses a multi-database approach by default, creating a new connection for each tenant.
  • Different landlord/tenant migrations structure.
  • Auto-routing files with predefined middlewares.
  • Additional tasks and tenant finders that expect the model to have certain columns.
  • Allows queue jobs to run in both tenant and landlord contexts.

Installation

composer require glimmer/tenancy

All files (routes, config, migrations) can be published at once by running:

php artisan vendor:publish --provider="Glimmer\Tenancy\TenancyServiceProvider"

Features

Additional Tenant Switching Tasks:

  • PrefixFilesystemTask – Prefixes filesystem disk paths with the tenant key (ID) using league/flysystem-path-prefixing.
  • PrefixScoutTask – Prefixes Laravel Scout index names in config/scout.php with the tenant key.
  • PrefixSpatiePermissionTask – Adds a tenant-specific prefix to Spatie Permission cache keys.

Enhanced Tenant Switching Tasks:

  • SwitchDatabaseConnectionTask (Replaces Spatie’s SwitchTenantDatabaseTask) – Multi-database approach that creates a connection for each tenant using the landlord’s default connection (.env setting) and dynamically switches between them.
    • A different connection can be specified per tenant by setting the database_connection column in the tenant model.
    • Individual connection configurations can be overridden using the connection_config column.
    • The database name can be changed within the connection_config array by defining 'database' => 'name'.
    • This approach improves performance by avoiding frequent reconnections but slightly increases memory usage, especially in Laravel Octane environments.

Tenant Finders

  • DomainTenantFinder – Finds the tenant by matching the request domain with the entries in the tenant model’s hosts array.
  • SubdomainTenantFinder – Identifies the tenant using the subdomain from the request, based on the hosts array in the model.
  • PathTenantFinder – Determines the tenant by extracting its ID from the request path.
  • DomainAndSubdomainTenantFinder – Matches tenants using either the request’s domain or subdomain.

The hosts array in the tenant model can contain a list of either domains or subdomains.

Queue Enhancements:

  • MakeQueueMaybeTenantAwareAction – A modified MakeQueueTenantAwareAction allowing jobs to run in both tenant and landlord contexts if the job implements MaybeTenantAware.
  • MaybeTenantAware – An interface that allows jobs to be dispatched with/without tenant context depending on the job implementation.
  • tenant events - Allows executing certain jobs when a tenant event is fired. (Job must extend TenantEventQueue) (Jobs must be defined on multitenancy.php config file with its respective event)

Tenant events:

For these events to work, the HasTenantEvents trait must be used in the tenant model (which is already included in Glimmer's Tenant model).

  • CreateDatabase – Triggered when a tenant is created.
  • MigrateDatabase – Fired after tenant creation to apply migrations (expects migrations to be at: database/migrations/tenant).
  • SeedDatabase – Runs database seeders upon tenant creation.
    • Defaults to DatabaseSeeder.php but will use tenant/DatabaseSeeder.php if it exists.

Command Enhancements:

  • IsTenantAware|TenantAware – A modification to Spatie/Commands/Concerns/TenantAware trait enabling SQLite usage without lock issues.
  • MaybeTenantAware – A trait for commands that can be executed with/without tenant context.
  • tenants:artisan – A modification that uses Glimmer's IsTenantAware trait to allow SQLite databases to be used without blocking commands.

Middlewares:

  • EnsureNoTenantSession - Prevents tenant session to be used on landlord routes.
  • ForbidsTenant - Prevents tenant to access landlord routes.

Extended functionality:

  • Tenant ModelGlimmer/Tenancy/Models/Tenant extends Spatie\Multitenancy\Models\Tenant with additional functionality as running events and determining the database name as expected.
  • Separated Route Files – Landlord (landlord.php) and tenant (tenant.php) routes are managed separately, while web.php remains shared.
  • IsSharedModel trait – Enables models to be shared between tenants and the landlord, ensuring synchronization across instances.
  • Automatic Route Registration – Routes are automatically registered and assigned appropriate middlewares to prevent unauthorized access (this can be disabled in multitenancy.php config).
    • Tenant routes includes NeedsTenant and EnsureValidTenantSession middleware by default.
    • Landlord routes includes ForbidsTenant EnsureNoTenantSession middleware by default.
    • Tenant routes names are prefixed with tenant. and landlord routes with landlord., if not using auto-registration, you can override this by calling name() method.
    • If auto-registration is disabled, use TenancyRoutes::landlord() and/or TenancyRoutes::tenant() to register them by hand and group the routes you need.
      TenancyRoutes::landlord()->group(function () {
        Route::get('/dashboard', function () {
            return 'Landlord dashboard';
        })->name('dashboard'); // Named route: landlord.dashboard
      });
    
      TenancyRoutes::tenant()->group(function () {
          Route::get('/home', function () {
              return 'Tenant home';
          })->name('home'); // Named route: tenant.home
      });
Notes:
  • To avoid showing 500 error on NoCurrentTenant or TenantIsForbidden exception and instead redirect to a route, you can add the following to your bootstrap/app.php in to the exception handler:

    use Glimmer\Tenancy\Facades\LandlordTenantException;
    
    return Application::configure(basePath: dirname(__DIR__))
        ....
        ->withExceptions(function (Exceptions $exceptions) {
            $exceptions->render(LandlordTenantException::redirect('your-route'));
        })->create();
  • As disclosed before, major structural changes are migration paths for landlord and tenant, as landlord migrations are at database/migrations root and tenant migrations are at database/migrations/tenant, and so for migrating the landlord you need to run:

    php artisan migrate

    For migrating the tenant, you need to run:

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

    Or instead, use Glimmer's tenant events, which will automatically migrate the tenant after creation.

  • The SwitchRouteCacheTask should be considered hardly if needed. In many cases, middleware/controller checks can enforce route access restrictions without needing this task.