Guides

Multi-tenancy

Keep the tenant migrations and seeders of each layer apart, and hand them to stancl/tenancy or spatie/laravel-multitenancy.

OSDD has no tenancy concept: tenancy belongs to the tenancy package and to your application. A multi-tenant OSDD project only needs plain Laravel and a few lines of configuration. This recipe keeps the tenant migrations and seeders of each layer apart from the central ones, and hands them to the tenancy package.

1. Keep tenant migrations in a sub-folder of each layer

functional/billing/database/migrations/          ← central migrations
functional/billing/database/migrations/tenant/   ← tenant migrations

Laravel's migrator does not read sub-folders, so the loadMigrationsFrom(__DIR__ . '/../../database/migrations') call of the layer provider never loads the tenant/ folder. migrate, migrate:fresh and RefreshDatabase only see the central migrations.

2. Create a tenant migration with Laravel's --path option

php artisan make:migration create_invoices_table --path=functional/billing/database/migrations/tenant

Nothing is registered in the layer provider, which is what you want for a tenant migration.

3. Give the tenant folders to the tenancy package

With stancl/tenancy, override its config from a technical layer, for example technical/tenancy/config/tenancy.php, loaded with overrideConfigFrom() in that layer's provider register():

technical/tenancy/src/Providers/TenancyServiceProvider.php
public function register(): void
{
    $this->overrideConfigFrom(__DIR__ . '/../../config/tenancy.php', 'tenancy');
}
technical/tenancy/config/tenancy.php
return [
    'migration_parameters' => [
        '--force' => true,
        '--path' => array_merge(
            glob(base_path('functional/*/database/migrations/tenant'), GLOB_ONLYDIR) ?: [],
            glob(base_path('technical/*/database/migrations/tenant'), GLOB_ONLYDIR) ?: [],
        ),
        '--realpath' => true,
    ],
];

tenants:migrate then runs every layer's tenant migrations, and only those. Adjust the globs if your osdd.layers.paths folders differ.

4. Tenant seeders

Keep tenant seeders out of loadSeeders(), so osdd:seed and db:seed never run them. Create one seeder in the technical layer that calls them:

technical/tenancy/database/seeders/TenantDatabaseSeeder.php
<?php

namespace Technical\Tenancy\Database\Seeders;

use Illuminate\Database\Seeder;

final class TenantDatabaseSeeder extends Seeder
{
    public function run(): void
    {
        $this->call([
            \Functional\Billing\Database\Seeders\TenantPlansSeeder::class,
        ]);
    }
}

Then point the tenancy package at it:

technical/tenancy/config/tenancy.php
'seeder_parameters' => [
    '--class' => \Technical\Tenancy\Database\Seeders\TenantDatabaseSeeder::class,
],

technical/tenancy uses classes of functional/billing here: require functional/billing in technical/tenancy/composer.json, or osdd:check reports it.

With spatie/laravel-multitenancy

The same layout works with spatie/laravel-multitenancy: pass the same folders to migrate --path=... --realpath when you migrate a tenant.