### 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
```