### Tenant Creation Example Source: https://v4.tenancyforlaravel.com/tenant-identification Example of creating a new tenant, showing the resulting tenant object with its ID and database name. ```php $tenant = Tenant::create(); ``` -------------------------------- ### Tenant Users Migration Example Source: https://v4.tenancyforlaravel.com/resource-syncing Example of a migration for a dedicated tenant_users pivot table. It defines foreign key constraints for tenant_id and user_id. ```php Schema::create('tenant_users', function (Blueprint $table) { $table->increments('id'); $table->string('tenant_id'); $table->unsignedBigInteger('global_user_id'); $table->unique(['tenant_id', 'global_user_id']); $table->foreign('tenant_id')->references('id')->on('tenants')->onDelete('cascade'); $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); }); ``` -------------------------------- ### Public Disk Usage Example Source: https://v4.tenancyforlaravel.com/bootstrappers/filesystem Demonstrates how to use the public disk for both central and tenant data, including accessing URLs. ```php Storage::disk('local')->put('foo.txt', "central foo\n"); Storage::disk('public')->put('bar.txt', "central bar\n"); Storage::disk('public')->url('bar.txt'); // http://example.test/storage/bar.txt tenancy()->initialize(App\Models\Tenant::first()); Storage::disk('local')->put('foo.txt', "tenant foo\n"); Storage::disk('public')->put('bar.txt', "tenant bar\n"); Storage::disk('public')->url('bar.txt'); // http://example.test/storage/tenant7c27cc0f-8ed6-4d2e-ac86-2ae9ac36acf5/bar.txt ``` -------------------------------- ### Domain Identification Examples Source: https://v4.tenancyforlaravel.com/tenant-identification These examples show how hostnames are parsed for domain identification. Use `InitializeTenancyByDomain` middleware with `PreventAccessFromUnwantedDomains`. ```text https://tenant1.test/foo -> tenant1.test https://tenant1.yourapp.com/bar -> tenant1.yourapp.com https://tenant1.app.example.test/baz -> tenant1.app.example.test ``` ```php Route::middleware([ 'web', InitializeTenancyByDomain::class, PreventAccessFromUnwantedDomains::class, ])->group(function () { Route::get('/foo', function () { return response('The ID of the current tenant is ' . tenant('id') . "\n"); }); }); ``` ```php $tenant = Tenant::create(); $tenant->createDomain('tenant1.example.test'); ``` ```text The id of the current tenant is 91ea01a5-17c5-4956-a309-5ec636447015 ``` -------------------------------- ### Basic Resource Syncing Example Source: https://v4.tenancyforlaravel.com/resource-syncing Demonstrates creating a central and tenant model with the same global ID and updating a tenant model to see if the change syncs. ```php use App\Models\CentralUser; use App\Models\TenantUser; use App\Models\Tenant; $centralUser = CentralUser::create([ 'name' => 'John Doe', 'email' => 'foo@acme.test', 'global_id' => 'foo', 'password' => 'password', ]); tenancy()->initialize(Tenant::create()); $tenantUser = TenantUser::create([ 'name' => 'John Doe', 'email' => 'foo@acme.test', 'global_id' => 'foo', 'password' => 'password', ]); $tenantUser->update(['email' => 'bar@acme.test']); tenancy()->end(); dump($centralUser->refresh()->email); ``` -------------------------------- ### Example Table Schema Source: https://v4.tenancyforlaravel.com/postgres-rls Illustrates a common database schema for a multi-tenant application, including posts, comments, reactions, and authors, all related to tenants. ```sql reactions - type string - comment_id foreign key referencing `comments.id` - author_id foreign key referencing `authors.id` comments - text string - post_id foreign key referencing `posts.id` - author_id foreign key referencing `authors.id` posts - text string - author_id foreign key referencing `authors.id` - tenant_id foreign key referencing `tenants.id` authors - name string - tenant_id foreign key referencing `tenants.id` ``` -------------------------------- ### Run Tenancy Installation Command Source: https://v4.tenancyforlaravel.com/getting-started Execute the Artisan command to generate necessary configuration files and directories for the package. ```bash php artisan tenancy:install ``` -------------------------------- ### Route Mode Configuration Examples Source: https://v4.tenancyforlaravel.com/early-identification Demonstrates how to apply 'central' and 'tenant' middleware to routes. The default route mode in configuration affects behavior when no flag is explicitly set. ```php // Tenancy will not be initialized here Route::middleware('central') ->get('/central-route', CentralController::class); // Tenancy will be initialized here Route::middleware('tenant') ->get('/tenant-route', TenantController::class); ``` -------------------------------- ### Bypass Parameter Usage Example Source: https://v4.tenancyforlaravel.com/bootstrappers/url-generator Example showing how to use the bypass parameter ('central') in the tenant context to generate a route ('foo') as if in the central context, resulting in 'foo' instead of 'tenant.foo'. ```php // In tenant context... route('foo'); // becomes 'tenant.foo' route('foo', ['central' => true]); // stays 'foo' ``` -------------------------------- ### Define Tenant Route Source: https://v4.tenancyforlaravel.com/getting-started Example of a route definition within `tenant.php` that displays tenant-specific information. ```php Route::get('/', function () { return 'This is your multi-tenant application. The id of the current tenant is ' . tenant('id'); }); ``` -------------------------------- ### Create and Read Tenant Files Source: https://v4.tenancyforlaravel.com/bootstrappers/filesystem Use the Storage facade to put and get files on 'local' and 'public' disks after initializing a tenant. Files are stored in tenant-specific directories. ```php use Illuminate\Support\Facades\Storage; Storage::disk('local')->put('foo.txt', "bar\n"); Storage::disk('public')->put('bar.txt', "baz\n"); tenancy()->initialize(App\Models\Tenant::first()); Storage::disk('local')->get('foo.txt'); // null Storage::disk('public')->get('bar.txt'); // null Storage::disk('local')->put('foo.txt', "tenant bar\n"); Storage::disk('public')->put('bar.txt', "tenant baz\n"); ``` -------------------------------- ### Tenant Creation Job Pipeline Source: https://v4.tenancyforlaravel.com/event-system Define a job pipeline for tenant creation, including database setup and migration. ```php Events\CreatingTenant::class => [], Events\TenantCreated::class => [ JobPipeline::make([ Jobs\CreateDatabase::class, Jobs\MigrateDatabase::class, // Jobs\SeedDatabase::class, // Jobs\CreateStorageSymlinks::class, // Your own jobs to prepare the tenant. // Provision API keys, create S3 buckets, anything you want! ])->send(function (Events\TenantCreated $event) { return $event->tenant; })->shouldBeQueued(false), // Listeners\CreateTenantStorage::class, ], ``` -------------------------------- ### Tenant Creation Job Pipeline Source: https://v4.tenancyforlaravel.com/database-managers Example of the job pipeline in `TenancyServiceProvider` that includes the `CreateDatabase` job, demonstrating how database managers are invoked during tenant creation. ```php Events\TenantCreated::class => [ JobPipeline::make([ Jobs\CreateDatabase::class, Jobs\MigrateDatabase::class, // Jobs\SeedDatabase::class, // Jobs\CreateStorageSymlinks::class, // Your own jobs to prepare the tenant. // Provision API keys, create S3 buckets, anything you want! ])->send(function (Events\TenantCreated $event) { return $event->tenant; })->shouldBeQueued(false), ], ``` -------------------------------- ### Controller Middleware Example Source: https://v4.tenancyforlaravel.com/early-identification Illustrates how middleware can be specified within a controller's constructor, highlighting why controllers are instantiated early by Laravel. ```php class PostController extends Controller { public function __construct() { $this->middleware('auth'); } } ``` -------------------------------- ### Route Generation Example - TenancyUrlGenerator Source: https://v4.tenancyforlaravel.com/bootstrappers/url-generator Demonstrates how route names are handled in the tenant context when `$prefixRouteNames` is enabled. 'foo' becomes 'tenant.foo', while 'tenant.foo' remains unchanged. ```php route('foo'); // becomes route('tenant.foo') route('tenant.foo'); // no change as the route name already starts with the tenant prefix ``` -------------------------------- ### Subdomain Identification Examples Source: https://v4.tenancyforlaravel.com/tenant-identification These examples illustrate subdomain identification. The middleware checks for subdomains of configured central domains. Use `InitializeTenancyBySubdomain` with `PreventAccessFromUnwantedDomains`. ```text centralDomains = ['example.test'] http://tenant1.example.test/foo -> tenant1 http://tenant2.another-domain.test/bar -> NotASubdomainException centralDomains = ['foo.test', 'bar.text'] http://tenant1.foo.test/foo -> tenant1 http://tenant2.bar.test/bar -> tenant2 http://tenant3.another-domain.test/bar -> NotASubdomainException ``` ```php Route::middleware([ 'web', InitializeTenancyBySubdomain::class, PreventAccessFromUnwantedDomains::class, ])->group(function () { Route::get('/foo', function () { return response('The ID of the current tenant is ' . tenant('id') . "\n"); }); }); ``` ```php 'identification' => [ 'central_domains' => [ 'example.test', ], ], ``` ```php $tenant = Tenant::create(); $tenant->createDomain('tenant1'); ``` ```text The id of the current tenant is 91ea01a5-17c5-4956-a309-5ec636447015 ``` -------------------------------- ### Disk Root Override Example Source: https://v4.tenancyforlaravel.com/bootstrappers/filesystem Illustrates how the root path for filesystem disks is overridden to include the tenant identifier, ensuring data isolation. ```php config('filesystems.disks.local.root'); // /Users/samuel/Sites/example/storage/app/ config('filesystems.disks.public.root'); // /Users/samuel/Sites/example/storage/app/public tenancy()->initialize($t1); config('filesystems.disks.local.root'); // /Users/samuel/Sites/example/storage/tenant7c27cc0f-8ed6-4d2e-ac86-2ae9ac36acf5/app/ config('filesystems.disks.public.root'); // /Users/samuel/Sites/example/storage/tenant7c27cc0f-8ed6-4d2e-ac86-2ae9ac36acf5/app/public ``` -------------------------------- ### Defining Synced Attributes Source: https://v4.tenancyforlaravel.com/resource-syncing Example of implementing the `Syncable` interface and defining which attributes should be synchronized between central and tenant models. ```php class TenantUser extends Model implements Syncable { use ResourceSyncing; // ... public function getSyncedAttributeNames(): array { return [ 'global_id', 'name', 'password', 'email', // There's also a 'role' attribute that we don't want to // sync so we do NOT include it here. ]; } } ``` -------------------------------- ### Asset Helper Tenancy Example Source: https://v4.tenancyforlaravel.com/bootstrappers/filesystem When `asset_helper_tenancy` is enabled, the `asset()` helper will prepend `/tenancy/assets/` to asset URLs. This demonstrates the change in output for an image tag. ```html ``` -------------------------------- ### Define Central Route Source: https://v4.tenancyforlaravel.com/getting-started Example of a route definition within `web.php` that displays the default welcome view. ```php Route::get('/', function () { return view('welcome'); }); ``` -------------------------------- ### Disk URL Override Example Source: https://v4.tenancyforlaravel.com/bootstrappers/filesystem Shows how the URL for filesystem disks is modified to include the tenant identifier, useful for accessing tenant-specific assets via URLs. ```php config('filesystems.disks.public.url'); // http://example.test/storage tenancy()->initialize($t1); config('filesystems.disks.public.url'); // http://example.test/public-7c27cc0f-8ed6-4d2e-ac86-2ae9ac36acf5 ``` -------------------------------- ### Install Stancl Tenancy Package with Composer Source: https://v4.tenancyforlaravel.com/getting-started Use Composer to add the package to your project. This command fetches the latest development version. ```bash composer require stancl/tenancy:dev-master ``` -------------------------------- ### Fetch users from API Source: https://v4.tenancyforlaravel.com/tenant-identification Example of a frontend making a request to an API. The `Origin` header is automatically added by the browser, allowing the backend to identify the tenant. ```javascript const users = await fetch('https://api.yourapp.com/users').then(res => res.json()); ``` -------------------------------- ### Define Tenant Connection Template in Array Syntax Source: https://v4.tenancyforlaravel.com/version-4 Configure the `template_tenant_connection` in your Tenancy configuration using array syntax for full or partial definitions. This allows for more flexible database template setup. ```php 'database' => [ 'template_tenant_connection' => 'tenant_template', ] ``` ```php 'template_tenant_connection' => [ 'driver' => 'mysql', 'url' => null, 'host' => 'mysql2', 'port' => '3306', 'database' => 'main', 'username' => 'root', 'password' => 'password', 'unix_socket' => '', 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', 'prefix' => '', 'prefix_indexes' => true, 'strict' => true, 'engine' => null, 'options' => [], ] ``` ```php 'template_tenant_connection' => [ 'host' => '1.2.3.4', 'username' => 'root', 'password' => 'password', // the rest of the connection is copied over from the central connection ] ``` ```php 'template_tenant_connection' => 'tenant_template' ``` -------------------------------- ### Initialize and Leave Tenant Context Source: https://v4.tenancyforlaravel.com/tenants Use `enter()` and `leave()` methods for manual initialization and termination of tenant context. ```php // Equivalent to tenancy()->initialize($tenant); $tenant->enter(); // Equivalent to tenancy()->end(); $tenant->leave(); ``` -------------------------------- ### Tenant Identification via Cookie Source: https://v4.tenancyforlaravel.com/tenant-identification Example of identifying a tenant using a cookie in a curl request. ```bash $ curl --cookie "tenant=84501e25-a744-468c-ab31-2d7309ab8b2a" example.test/foo The id of the current tenant is 84501e25-a744-468c-ab31-2d7309ab8b2a ``` -------------------------------- ### Create Tenant Resource and Sync to Central Source: https://v4.tenancyforlaravel.com/resource-syncing Creates a tenant resource and demonstrates how it automatically creates a corresponding central resource if one doesn't exist. Creation attributes are used for the new central resource. ```php use App\Models\Tenant; use App\Models\TenantUser; use App\Models\CentralUser; $tenant = Tenant::create(); tenancy()->initialize($tenant); $tenantUser = TenantUser::create([ 'name' => 'John Doe', 'email' => 'foo@bar.test', 'global_id' => 'foo', 'password' => 'password', ]); tenancy()->end(); // Assuming no central user with a 'foo' global_id exists yet, // the central user got created with the same name, email, // global_id and password as the tenant user. dump(CentralUser::firstWhere('global_id', 'foo')); ``` -------------------------------- ### Initialize Tenancy Events Source: https://v4.tenancyforlaravel.com/event-system Listen for tenancy initialization events to bootstrap the tenant context. ```php Events\InitializingTenancy::class => [], Events\TenancyInitialized::class => [ Listeners\BootstrapTenancy::class, ], ``` -------------------------------- ### Tenant Identification via Header Source: https://v4.tenancyforlaravel.com/tenant-identification Example of identifying a tenant using a custom header in a curl request. ```bash $ curl -H "X-Tenant: 84501e25-a744-468c-ab31-2d7309ab8b2a" example.test/foo The id of the current tenant is 84501e25-a744-468c-ab31-2d7309ab8b2a ``` -------------------------------- ### Route Generation with Default Tenant Parameters Source: https://v4.tenancyforlaravel.com/bootstrappers/url-generator Demonstrates how routes with bound tenant parameters can be generated using simplified syntax when default parameters are configured. This reduces code verbosity and improves consistency, especially when dealing with routes that include tenant identifiers or slugs. ```php Route::get('/{team:slug}/foo', ...)->name('tenant.foo'); // these would work the same: route('tenant.foo'); route('tenant.foo', ['team' => tenant()->slug]); ``` -------------------------------- ### Create a Tenant and Domain Source: https://v4.tenancyforlaravel.com/tenants Create tenants as Eloquent models and associate domains with them. ```php $tenant = Tenant::create(); $tenant->domains()->create(['domain' => 'tenant1.example.test']); ``` -------------------------------- ### Tenant Identification via Query Parameter Source: https://v4.tenancyforlaravel.com/tenant-identification Example of identifying a tenant using a query parameter in a curl request. ```bash $ curl example.test/foo?tenant=84501e25-a744-468c-ab31-2d7309ab8b2a The id of the current tenant is 84501e25-a744-468c-ab31-2d7309ab8b2a ``` -------------------------------- ### Enabling Queued Tenant Creation Source: https://v4.tenancyforlaravel.com/database-managers Shows how to enable queuing for the tenant creation pipeline. This is useful for slow or error-prone processes involving third-party services. ```php shouldBeQueued(true) ``` -------------------------------- ### StatefulTenantDatabaseManager Interface Source: https://v4.tenancyforlaravel.com/database-managers Extends TenantDatabaseManager to include methods for getting and setting the specific database connection used by the manager. ```php interface StatefulTenantDatabaseManager extends TenantDatabaseManager { /** Get the DB connection used by the tenant database manager. */ public function connection(): Connection; /** * Set the DB connection that should be used by the tenant database manager. * * @throws NoConnectionSetException */ public function setConnection(string $connection): void; } ``` -------------------------------- ### Register Global and Tenant Channels with Helpers Source: https://v4.tenancyforlaravel.com/broadcasting Use `global_channel()` for channels prefixed with `global__` and `tenant_channel()` for channels prefixed with `{tenant}.`. The tenant channel closure requires the tenant key for prefixing. ```php // Registers a channel prefixed with 'global__' global_channel('channel.{product}', function (User $user, Tenant $tenant, Product $product) { return $user->ownsProduct($product); }); // Registers a channel prefixed with '{tenant}.' tenant_channel('another-channel.{product}', function (User $user, Tenant $tenant, Product $product) { return $user->ownsProduct($product); }); ``` -------------------------------- ### Create Tenant and Domain Source: https://v4.tenancyforlaravel.com/getting-started Instantiate a new tenant and associate a domain with it. Ensure the domain is correctly pointed to your application for proper routing. ```php $tenant = App\Models\Tenant::create(); $tenant->createDomain('tenant1.example.test'); ``` -------------------------------- ### Implement Custom Tenant Bootstrapper Source: https://v4.tenancyforlaravel.com/bootstrappers Create a class that implements the `TenantBootstrapper` interface. Store original configuration values in the constructor to ensure they can be restored later. The `bootstrap` method applies tenant-specific changes, and `revert` restores the original state. ```php originalApiKey = config('services.foo.api_key'); } public function bootstrap(Tenant $tenant): void { if ($api_key = $tenant->foo_api_key) { config(['services.foo.api_key' => $api_key]); app(FooService::class)->setApiKey($api_key); } } public function revert(): void { config(['services.foo.api_key' => $this->originalApiKey]); app(FooService::class)->setApiKey($this->originalApiKey); } } ``` -------------------------------- ### Define Regional Database Connections Source: https://v4.tenancyforlaravel.com/customizing-databases Configure specific database connections for different regions. This example shows how to set a default connection for Europe. ```php Region::Europe, default => 'tenant_server_eu', }]); ``` -------------------------------- ### Initialize Tenancy by Path Middleware Source: https://v4.tenancyforlaravel.com/tenant-identification Use this middleware to identify tenants based on a `{tenant}` parameter in your routes. Ensure the `{tenant}` parameter is included in the route path and registered differently. ```php Route::middleware([ 'web', InitializeTenancyByPath::class ])->prefix('{tenant}')->group(function () { Route::get('/foo', function () { return response('The ID of the current tenant is ' . tenant('id') . "\n"); }); }); ``` -------------------------------- ### Tenant Database Creation Logic Source: https://v4.tenancyforlaravel.com/database-managers Illustrates the method call for creating a tenant's database within the job pipeline. This is part of the tenant creation process. ```php $this->tenant->database()->manager()->createDatabase($this->tenant) ``` -------------------------------- ### Demonstrate Customized Creation Attributes Source: https://v4.tenancyforlaravel.com/resource-syncing Creates a tenant user with custom attributes and then verifies that the automatically created central user has the correct attributes, including the default 'role' set in `getCreationAttributes()`. ```php use App\Models\Tenant; use App\Models\CentralUser; use App\Models\TenantUser; $tenant = Tenant::create(); tenancy()->initialize($tenant); $tenantUser = TenantUser::create([ 'global_id' => 'user', 'name' => 'User', 'password' => '1234', 'email' => 'user@testing.com', ]); tenancy()->end(); $centralUser = CentralUser::firstWhere('global_id', 'user'); dump($centralUser->name); // 'User' dump($centralUser->password); // '1234' dump($centralUser->email); // 'user@testing.com' dump($centralUser->role); // 'user' ``` -------------------------------- ### Example RLS Policy Generation Source: https://v4.tenancyforlaravel.com/postgres-rls Illustrates how TableRLSManager generates a PostgreSQL RLS policy to scope 'reactions' based on a relationship chain leading to the tenant. ```sql CREATE POLICY reactions_rls_policy ON reactions USING ( comment_id IN ( SELECT id FROM comments WHERE post_id IN ( SELECT id FROM posts WHERE tenant_id = current_setting('my.tenant') ) ) ); ``` -------------------------------- ### Fresh Migrate Tenant Databases Source: https://v4.tenancyforlaravel.com/migrations This command provides an equivalent to `migrate:fresh` for tenant databases. It reimplements the logic manually, so check `--help` for available options. ```bash php artisan tenants:migrate-fresh ``` -------------------------------- ### Conditionally Route to Tenant or Default Source: https://v4.tenancyforlaravel.com/integrations/inertia This example shows a conditional route generation that is necessary when views or controllers are shared across tenant and non-tenant contexts. ```php tenant() ? route('tenant.foo', tenant()) : route('foo'); ``` -------------------------------- ### Initialize and Manage Tenancy Source: https://v4.tenancyforlaravel.com/getting-started Code for initializing tenancy for a specific tenant, creating users within that tenant's context, and then ending tenancy. This demonstrates how to switch between tenant databases and the central database. ```php ‎ $tenant = App\Models\Tenant::first(); tenancy()->initialize($tenant); App\Models\User::create(['name' => 'foo', 'email' => 'foo@example.test', 'password' => bcrypt('password')]); $tenant2 = App\Models\Tenant::create(); $tenant2->createDomain('tenant2.example.test'); // make sure the domain is correct tenancy()->initialize($tenant2); App\Models\User::create(['name' => 'bar', 'email' => 'bar@example.test', 'password' => bcrypt('password')]); tenancy()->end(); App\Models\User::create(['name' => 'baz', 'email' => 'baz@example.test', 'password' => bcrypt('password')]); ``` -------------------------------- ### Configure Tenant Storage Symlinking with Events Source: https://v4.tenancyforlaravel.com/bootstrappers/filesystem Set up tenant storage symlinking by defining jobs within the `TenantCreated` and `DeletingTenant` events. Ensure `CreateStorageSymlinks` is included for creation events. ```php Events\TenantCreated::class => [ JobPipeline::make([ Jobs\CreateDatabase::class, Jobs\MigrateDatabase::class, // Jobs\SeedDatabase::class, // Jobs\CreateStorageSymlinks::class, // Your own jobs to prepare the tenant. // Provision API keys, create S3 buckets, anything you want! ])->send(function (Events\TenantCreated $event) { return $event->tenant; })->shouldBeQueued(false), // `false` by default, but you likely want to make this `true` in production. // Listeners\CreateTenantStorage::class, ], Events\DeletingTenant::class => [ JobPipeline::make([ Jobs\DeleteDomains::class, ])->send(function (Events\DeletingTenant $event) { return $event->tenant; })->shouldBeQueued(false), // Listeners\DeleteTenantStorage::class, ], ``` -------------------------------- ### Valid Controller Implementations Source: https://v4.tenancyforlaravel.com/early-identification Provides examples of different valid controller implementations in Laravel, including those that do not extend the base Controller class, which are not instantiated early. ```php class PostController {} class PostController extends Controller {} class PostController extends CustomClass {} ``` -------------------------------- ### Publish and Run Impersonation Migrations Source: https://v4.tenancyforlaravel.com/features/user-impersonation Publish the migration for the impersonation tokens table and then run the migrations. ```bash php artisan vendor:publish --tag=impersonation-migrations ``` ```bash php artisan migrate ``` -------------------------------- ### Using Binding Field Syntax for Path Identification Source: https://v4.tenancyforlaravel.com/tenant-identification Example of using the binding field syntax in routes to specify the tenant column directly, such as `/{tenant:slug}`. ```php Route::get('/{tenant:slug}/foo', ...); ``` -------------------------------- ### Create Central Resource and Attach Tenant Source: https://v4.tenancyforlaravel.com/resource-syncing Creates a central resource and then attaches a tenant to it. This demonstrates how a tenant resource is automatically created for the central resource, using the central resource's creation attributes. ```php use App\Models\Tenant; use App\Models\CentralUser; use App\Models\TenantUser; $centralUser = CentralUser::create([ 'name' => 'John Doe', 'email' => 'foo@bar.test', 'global_id' => 'foo', 'password' => 'password', ]); $tenant = Tenant::create(); $centralUser->tenants()->attach($tenant); tenancy()->initialize($tenant); dump(TenantUser::firstWhere('global_id', 'foo')->toArray()); ``` -------------------------------- ### Run Artisan Migrate Command Source: https://v4.tenancyforlaravel.com/getting-started Execute the Artisan migrate command to create the `tenants` and `domains` tables in your database. ```bash php artisan migrate ``` -------------------------------- ### Define Mailer Presets for MailTenancyBootstrapper Source: https://v4.tenancyforlaravel.com/misc Customize the static `$mapPresets` property to define credential mappings for different mailers. This example sets up presets for the 'smtp' mailer. ```php public function boot() { // ... // MailTenancyBootstrapper::$mapPresets['mailer_name'] = ['config.key' => 'tenant_attribute_name'] MailTenancyBootstrapper::$mapPresets['smtp'] = [ 'mail.mailers.smtp.host' => 'smtp_host', 'mail.mailers.smtp.port' => 'smtp_port', 'mail.mailers.smtp.username' => 'smtp_username', 'mail.mailers.smtp.password' => 'smtp_password', ]; } ``` -------------------------------- ### Configure Tenancy Bootstrappers Source: https://v4.tenancyforlaravel.com/bootstrappers Add bootstrappers to the `tenancy.bootstrappers` array in your configuration to enable tenant context switching for various application components. ```php // Basic Laravel features Bootstrappers\DatabaseTenancyBootstrapper::class, Bootstrappers\CacheTenancyBootstrapper::class, // Bootstrappers\CacheTagsBootstrapper::class, // Alternative to CacheTenancyBootstrapper // Bootstrappers\DatabaseCacheBootstrapper::class, // Separates cache by DB rather than by prefix, must run after DatabaseTenancyBootstrapper Bootstrappers\FilesystemTenancyBootstrapper::class, Bootstrappers\QueueTenancyBootstrapper::class, // Bootstrappers\RedisTenancyBootstrapper::class, // Note: phpredis is needed // Adds support for the database session driver Bootstrappers\DatabaseSessionBootstrapper::class, // Configurable bootstrappers // Bootstrappers\RootUrlBootstrapper::class, // Bootstrappers\UrlGeneratorBootstrapper::class, // Bootstrappers\MailConfigBootstrapper::class, // Bootstrappers\BroadcastingConfigBootstrapper::class, // Bootstrappers\BroadcastChannelPrefixBootstrapper::class, // Integration bootstrappers // Bootstrappers\Integrations\FortifyRouteBootstrapper::class, // Bootstrappers\Integrations\ScoutPrefixBootstrapper::class, // Bootstrappers\PostgresRLSBootstrapper::class, ``` -------------------------------- ### Force RLS Policies Source: https://v4.tenancyforlaravel.com/configuration Forces the creation of RLS policies, requiring table owners to have BYPASSRLS permission. Useful for custom setups requiring additional safety. ```php public static bool $forceRls = true; ``` -------------------------------- ### Rename Primary Database Connection to 'central' Source: https://v4.tenancyforlaravel.com/bootstrappers/queue For semantic clarity in multi-database setups, rename your primary database connection to 'central' and update your .env file accordingly. ```env DB_CONNECTION=mysql DB_CONNECTION=central ``` -------------------------------- ### Tenant Migrate Command Parameters Source: https://v4.tenancyforlaravel.com/migrations Configuration for the `tenants:migrate` command, including paths and schema dump usage. Ensure `--force` is true for production environments. ```php 'migration_parameters' => [ '--force' => true, // This needs to be true to run migrations in production. '--path' => [database_path('migrations/tenant')], '--schema-path' => database_path('schema/tenant-schema.dump'), '--realpath' => true, ], ``` -------------------------------- ### Configure RandomIntGenerator Min/Max Values Source: https://v4.tenancyforlaravel.com/tenants Example of how to customize the minimum and maximum values for the RandomIntGenerator in your application's boot method. This allows for a specific range of random integers. ```php public function boot() { RandomIntGenerator::$min = 10_000; RandomIntGenerator::$max = 10_000_000; // ... } ``` -------------------------------- ### Cascade Deletion of Tenant Resources Source: https://v4.tenancyforlaravel.com/resource-syncing When a central resource is deleted, all its tenant-specific versions are automatically removed. This example demonstrates the automatic deletion of tenant users when a central user is deleted. ```php use App\Models\Tenant; use App\Models\CentralUser; use App\Models\TenantUser; $tenant1 = Tenant::create(); $tenant2 = Tenant::create(); $centralUser = CentralUser::create([ 'name' => 'John Doe', 'email' => 'foo@bar.test', 'global_id' => 'user', 'password' => 'password' ]); $centralUser->tenants()->attach($tenant1); $centralUser->tenants()->attach($tenant2); dump([ $tenant1->run(fn () => TenantUser::firstWhere('global_id', 'user')), $tenant2->run(fn () => TenantUser::firstWhere('global_id', 'user')), ]); $centralUser->delete(); dump([ $tenant1->run(fn () => TenantUser::firstWhere('global_id', 'user')), $tenant2->run(fn () => TenantUser::firstWhere('global_id', 'user')), ]); ``` -------------------------------- ### Configure File Preview Controller Middleware Source: https://v4.tenancyforlaravel.com/integrations/livewire Apply web, universal, and default tenancy middleware to the FilePreviewController. This is crucial for handling tenant-specific file previews. ```php FilePreviewController::$middleware = [ 'web', 'universal', tenancy()->defaultMiddleware(), ]; ``` -------------------------------- ### Force Central Connection for Database Queue Driver Source: https://v4.tenancyforlaravel.com/bootstrappers/queue When using the database queue driver with multi-database setups, ensure jobs are stored in the central database by forcing the central connection. ```php 'connections' => [ 'database' => [ // ... 'connection' => env('DB_CONNECTION'), ], ], ``` -------------------------------- ### Define Tenant Key Name and Get Tenant Key Source: https://v4.tenancyforlaravel.com/tenants Shows how to specify a custom column for the tenant key and retrieve its value. Tenancy uses this key to interact with your models. ```php public function getTenantKeyName(): string { return 'uuid'; } public function getTenantKey(): int|string { return $this->getAttribute($this->getTenantKeyName()); } ``` -------------------------------- ### Verify Livewire Route Cloning Source: https://v4.tenancyforlaravel.com/integrations/livewire Use the `route:list` Artisan command to verify that Livewire routes, such as the file preview route, have been correctly cloned for tenant usage. ```bash GET|HEAD {tenant}/livewire/preview-file/{filename} tenant.livewire.preview-file › Livewire\Features › FilePreviewController@handle ``` -------------------------------- ### Specify Tenants for Migration via CLI Source: https://v4.tenancyforlaravel.com/migrations Run tenant migrations for specific tenants using the `--tenants` option on the command line. Multiple tenants can be specified. ```bash php artisan tenants:migrate --tenants=foo php artisan tenants:migrate --tenants=foo --tenants=bar ``` -------------------------------- ### Conditional Syncing with `shouldSync()` Source: https://v4.tenancyforlaravel.com/resource-syncing Override the `shouldSync(): bool` method to conditionally disable syncing. If `shouldSync()` returns false, syncing events are not fired for that model. This example syncs only if the `global_id` is not null. ```php class CentralUser extends Model { use ResourceSyncing; // ... public function shouldSync(): bool { return $this->global_id !== null; } protected function generateGlobalIdentifierKey(): void { // Do nothing. This can also depend on other fields. } } ``` -------------------------------- ### Equivalent Broadcast Channel Registration Source: https://v4.tenancyforlaravel.com/broadcasting This demonstrates the direct `Broadcast::channel()` equivalent for the helper functions shown previously. Note the unused `$tenant` parameter is necessary for additional arguments. ```php // global_channel() Broadcast::channel('global__channel.{product}', function (User $user, Tenant $tenant, Product $product) { return $user->ownsProduct($product); }); // tenant_channel() Broadcast::channel('{tenant}.another-channel.{product}', function ($tenant, $user, $userId) { return $user->ownsProduct($product); }); ``` -------------------------------- ### Map SMTP Username to Tenant Attribute Source: https://v4.tenancyforlaravel.com/misc Uncomment this in `tenancy.php` to map tenant attributes to mailer credentials. This example maps the tenant's `smtp_username` attribute to the `mail.mailers.smtp.username` config key. ```php TenancyServiceProvider public function boot() { // ... MailTenancyBootstrapper::$credentialsMap = [ 'mail.mailers.smtp.username' => 'smtp_username', ]; } ``` -------------------------------- ### Iterating and Deleting Tenants Source: https://v4.tenancyforlaravel.com/database-managers Demonstrates the correct way to delete tenant databases by iterating through tenants and calling delete on each. This ensures event listeners are triggered, unlike a simple bulk delete. ```php Tenant::cursor()->each->delete() ``` -------------------------------- ### Access Current Tenant (Model Methods) Source: https://v4.tenancyforlaravel.com/tenants Use `current()` and `currentOrFail()` on the Tenant model for accessing the current tenant. ```php Tenant::current(); // Tenant|null /** @throws TenancyNotInitializedException */ Tenant::currentOrFail(); // Tenant ``` -------------------------------- ### Enable Route Name Prefixing - TenancyUrlGenerator Source: https://v4.tenancyforlaravel.com/bootstrappers/url-generator Set `TenancyUrlGenerator:$prefixRouteNames` to true to enable prefixing route names with 'tenant.' in the tenant context. This is part of a common setup for path identification. ```php TenancyUrlGenerator:$prefixRouteNames = true; ``` -------------------------------- ### Get Template Connection Definition Source: https://v4.tenancyforlaravel.com/customizing-databases Retrieves the connection definition for tenants. It prioritizes tenant-specific DB connection settings, then the configured template connection, and finally falls back to the central connection. ```php public function getTemplateConnection(): array { if ($template = $this->tenant->getInternal('db_connection')) { return config("database.connections.{$template}"); } if ($template = config('tenancy.database.template_tenant_connection')) { return is_array($template) ? array_merge($this->getCentralConnection(), $template) : config("database.connections.{$template}"); } return $this->getCentralConnection(); } ``` -------------------------------- ### Initialize Tenancy by Request Data Middleware Source: https://v4.tenancyforlaravel.com/tenant-identification Use this middleware to identify tenants using headers, query parameters, or cookies. Configure the specific identifiers in the tenancy configuration. ```php 'identification' => [ 'resolvers' => [ Resolvers\RequestDataTenantResolver::class => [ // Set any of these to null to disable that method of identification 'header' => 'X-Tenant', 'cookie' => 'tenant', 'query_parameter' => 'tenant', 'tenant_model_column' => null, // null = tenant key ], ], ``` -------------------------------- ### Customize Creation Attributes with Default Values Source: https://v4.tenancyforlaravel.com/resource-syncing Overrides the `getCreationAttributes()` method to include a default value for the 'role' attribute. This ensures that when a tenant user is created, the corresponding central user gets a default role. ```php class TenantUser implements Syncable { public function getSyncedAttributeNames(): array { return [ 'global_id', 'name', 'password', 'email', ]; } public function getCreationAttributes(): array { return [ 'global_id', 'name', 'password', 'email', 'role' => 'user', ]; } } ``` -------------------------------- ### Use tenant:tinker with a specific tenant ID Source: https://v4.tenancyforlaravel.com/version-4 Execute the `tenant:tinker` command with a tenant ID to start an interactive Tinker session within that tenant's context. This is useful for debugging or performing tenant-specific operations. ```bash $ php artisan tenant:tinker 1fecd0e5-cf2a-4aba-9115-d08cb016ff9c Psy Shell v0.12.10 (PHP 8.4.11 — cli) by Justin Hileman > tenant()->id; = "1fecd0e5-cf2a-4aba-9115-d08cb016ff9c" ``` -------------------------------- ### Enable Broadcaster Overrides Source: https://v4.tenancyforlaravel.com/broadcasting Enable specific broadcaster overrides for Pusher, Reverb, or Ably by calling their respective methods within your `boot()` method. Only enable the one you are using. ```php public function boot() { BroadcastChannelPrefixBootstrapper::pusher(); BroadcastChannelPrefixBootstrapper::reverb(); BroadcastChannelPrefixBootstrapper::ably(); } ``` -------------------------------- ### Filesystem Tenancy Bootstrapper Configuration Source: https://v4.tenancyforlaravel.com/bootstrappers/cache Configure the filesystem to scope cache files per tenant. This requires `scope_cache` to be true and the cache store to be included in `tenancy.cache.stores`. ```php 'cache' => [ 'stores' => [ env('CACHE_STORE'), ], ], 'filesystem' => [ 'scope_cache' => true, ], ``` -------------------------------- ### Use tenant:tinker to select a tenant by ID Source: https://v4.tenancyforlaravel.com/version-4 When `tenant:tinker` is run without arguments, it prompts the user to select a tenant by ID, domain, or search. This interactive prompt simplifies choosing the target tenant for the Tinker session. ```bash $ php artisan tenant:tinker ┌ Which tenant do you want to run Tinker as? ──────────────────┐ │ › ● First tenant (1fecd0e5-cf2a-4aba-9115-d08cb016ff9c) │ │ ○ Search by id │ │ ○ Search by domain │ └──────────────────────────────────────────────────────────────┘ ``` -------------------------------- ### Create Tenant Resources Pivot Table Migration Source: https://v4.tenancyforlaravel.com/resource-syncing Sets up the 'tenant_resources' pivot table, which maps resources to tenants using their global IDs. This table is crucial for the resource syncing mechanism to identify which resources belong to which tenants. ```php return new class extends Migration { public function up(): void { Schema::create('tenant_resources', function (Blueprint $table) { $table->increments('id'); $table->string('tenant_id'); $table->string('resource_global_id'); $table->string('tenant_resources_type'); }); } public function down(): void { Schema::dropIfExists('tenant_resources'); } }; ``` -------------------------------- ### Configure Model Directories for Discovery Source: https://v4.tenancyforlaravel.com/configuration Specify directories where the manager should discover tenant models. Subdirectories are also scanned. Use '*' to exclude models in the root of a specified directory. ```php /** * Directories in which the manager will discover your models. * Subdirectories of the specified directories are also scanned. * * For example, specifying 'app/Models' will discover all models in the 'app/Models' directory and all of its subdirectories. * Specifying 'app/Models/*' will discover all models in the subdirectories of 'app/Models' (+ their subdirectories), * but not the models present directly in the 'app/Models' directory. */ public static array $modelDirectories = ['app/Models']; ``` -------------------------------- ### Compare Original and Cloned Routes Source: https://v4.tenancyforlaravel.com/route-cloning Illustrates the transformation of routes after cloning. Central routes are prefixed with `/{tenant}/` and their names with `tenant.`, making them accessible only in the tenant context. ```php // Original routes — central Route::get('/posts', [PostController::class, 'index'])->name('package.posts.index'); Route::get('/posts/{post}', [PostController::class, 'show'])->name('package.posts.show'); // Cloned routes — tenant Route::get('/{tenant}/posts', [PostController::class, 'index'])->name('tenant.package.posts.index'); Route::get('/{tenant}/posts/{post}', [PostController::class, 'show'])->name('tenant.package.posts.show'); ``` -------------------------------- ### Enable Asset Helper Tenancy Source: https://v4.tenancyforlaravel.com/bootstrappers/filesystem Enable this setting to have the `asset()` helper generate paths to the TenantAssetController. This is useful when deploying to environments like Laravel Vapor where `ASSET_URL` is set. ```php 'filesystem' => [ 'asset_helper_tenancy' => true, ], ``` -------------------------------- ### Serve Tenant Asset File Source: https://v4.tenancyforlaravel.com/bootstrappers/filesystem This controller method returns a file from the tenant's storage. It's useful for serving assets directly but can impact performance as it runs PHP on each request. ```PHP response()->file(storage_path("app/public/$path"), $headers); ``` -------------------------------- ### ImageService and PostController with Constructor Injection Source: https://v4.tenancyforlaravel.com/early-identification Demonstrates a scenario where a service class is injected into a controller's constructor, causing issues with tenant context due to early controller instantiation. ```php class ImageService { public string $apiKey; public function __construct(Repository $config) { $this->apiKey = $config->get('services.images.api_key'); }; public function store($image): string { // Pseudocode $response = Http::put('https://cloud-service.com/upload', [ 'image' => $image, 'api_key' => $this->apiKey, ]); return $response->json('url'); } } class PostController extends Controller { public function __construct( ImageService $images, ) {} public function update(Request $request, Post $post) { if ($request->has('image')) { $post->update([ 'image_url' => $this->images->store($image), ]); } } } // Tenancy middleware included Route::middleware(InitializeTenancyByDomain::class) ->post('/posts/update', [PostController::class, 'update']); ``` -------------------------------- ### Tenant Broadcasters Configuration Source: https://v4.tenancyforlaravel.com/configuration Lists broadcasters that should always be recreated using resolve() to ensure correct credentials are used when tenancy is initialized. Defaults to 'pusher' and 'ably'. ```php public static array $tenantBroadcasters = ['pusher', 'ably']; ``` -------------------------------- ### Abstract TenantDatabaseManager Implementation Source: https://v4.tenancyforlaravel.com/database-managers Provides a default implementation for StatefulTenantDatabaseManager, handling connection management and basic connection config construction. ```php abstract class TenantDatabaseManager implements StatefulTenantDatabaseManager { /** The database connection to the server. */ protected string $connection; public function connection(): Connection { if (! isset($this->connection)) { throw new NoConnectionSetException(static::class); } return DB::connection($this->connection); } public function setConnection(string $connection): void { $this->connection = $connection; } public function makeConnectionConfig(array $baseConfig, string $databaseName): array { $baseConfig['database'] = $databaseName; return $baseConfig; } } ``` -------------------------------- ### Attaching and Detaching Resources Source: https://v4.tenancyforlaravel.com/resource-syncing Shows how to attach and detach tenant resources to/from a central resource and observe the count changes. ```php use App\Models\CentralUser; use App\Models\TenantUser; $centralUser = CentralUser::first(); $tenant = $centralUser->tenants()->first(); dump($tenant->run(fn () => count(TenantUser::all()))); $centralUser->tenants()->detach($tenant); dump($tenant->run(fn () => count(TenantUser::all()))); $centralUser->tenants()->attach($tenant); dump($tenant->run(fn () => count(TenantUser::all()))); ``` -------------------------------- ### Tenant::current() and Tenant::currentOrFail() Methods Source: https://v4.tenancyforlaravel.com/version-4 These methods provide access to the currently active tenant. `current()` returns the tenant model or null, while `currentOrFail()` throws an exception if tenancy is not initialized. ```php public static function current(): static|null { return tenant(); } /** @throws TenancyNotInitializedException */ public static function currentOrFail(): static { return static::current() ?? throw new TenancyNotInitializedException; } ``` -------------------------------- ### Create Tenant Storage Symlinks via Artisan Command Source: https://v4.tenancyforlaravel.com/bootstrappers/filesystem Use the `php artisan tenants:link` command to create symlinks for tenant storage directories. The `--force` option can be used if links already exist. To remove links, use `php artisan tenants:link --remove`. ```bash $ php artisan tenants:link INFO The links have been created. ``` ```bash $ php artisan tenants:link --remove INFO The links have been removed. ``` -------------------------------- ### Enable TenantConfigBootstrapper Source: https://v4.tenancyforlaravel.com/bootstrappers/tenant-config Uncomment the TenantConfigBootstrapper class in the tenancy configuration file to enable this feature. ```php 'features' => [ ~~"// "~~Stancl\Tenancy\Bootstrappers\TenantConfigBootstrapper::class, ], ``` -------------------------------- ### Configuration to Drop Tenant Databases on Migrate Fresh Source: https://v4.tenancyforlaravel.com/version-4 Set the `tenancy.database.drop_tenant_databases_on_migrate_fresh` configuration key to `true` to automatically drop tenant databases when running `php artisan migrate:fresh`. ```php 'database' => [ 'drop_tenant_databases_on_migrate_fresh' => true, ] ``` -------------------------------- ### Asset Helper Scoping Source: https://v4.tenancyforlaravel.com/bootstrappers/filesystem Demonstrates how the asset helper is scoped per tenant. After initializing tenancy, asset paths are prefixed with the tenant identifier. ```php asset('foo.png'); // https://(long string).cloudfront.net/(long string)/foo.png tenancy()->initialize($t1); asset('foo.png'); // https://(long string).cloudfront.net/(long string)/tenant1/foo.png ```