Skip to content

Tenancy bootstrappers

Tenancy bootstrappers are one of the most fundamental concepts in this package.

A tenancy bootstrapper is a class switches some part of your application to the tenant context. For instance, DatabaseTenancyBootstrapper switches the application’s default database connection to the database of the current tenant.

CacheTenancyBootstrapper adjusts the cache store prefix to be scoped to the current tenant. FilesystemTenancyBootstrapper scopes storage/filesystem-related calls to the current tenant.

These classes are executed when tenancy is bootstrapped, which is typically when tenancy()->initialize() is called — either manually or using a tenant identification middleware such as InitializeTenancyByDomain.

Tenancy bootstrappers are configured in the tenancy.bootstrappers array:

config/tenancy.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,

Out of the box, Tenancy ships bootstrappers for just about every “component” of Laravel.

If you’re integrating with third-party packages you may want to add your own bootstrappers.

You may also want to add your own bootstrappers for your own application logic. That becomes a bit of a matter of separation of concerns. For instance, if you have some fully isolated module that interacts with some service, and this module needs to use different API keys for each tenant, you may find it cleaner to create a bootstrapper than to add “tenancy-aware” logic to that module.

For that specific use case, the Tenant Config feature may be sufficient. It dynamically changes config at runtime, which may be all you need to make some feature tenant-aware. However, if there’s some persistent state involved (for instance a constructor that reads the config, stores that in a property, and that entire class is a singleton) you may need to use a bootstrapper that besides changing config also does app(FooService::class)->setApiKey(tenant()->foo_api_key).

Bootstrappers execute in the exact order they’re defined in, so they may have dependencies on each other. For instance, the DatabaseCacheBootstrapper separates cache by tenant database rather than by prefix. This means it uses the 'tenant' connection and as such depends on DatabaseTenancyBootstrapper.

When ending (reverting) tenancy, bootstrappers are reverted in the opposite order.

To write your own bootstrapper, simply create a class that implements the TenantBootstrapper interface and add it to the array above.

app/Bootstrappers/FooBootstrapper.php
<?php
namespace App\Bootstrappers;
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
use Stancl\Tenancy\Contracts\Tenant;
class FooBootstrapper implements TenancyBootstrapper
{
public string $originalApiKey;
public function __construct(): void
{
$this->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);
}
}

Only bootstrap() and revert() are required by the interface, but notice the constructor as well. It follows a common pattern where we store the original value in the bootstrapper.

Tenancy registers all bootstrappers as singletons, so they have persistent state. It’s important to store original state like this. Once bootstrap() changes the config, the original value may be inaccessible, so we make sure to store it.

Constructors are guaranteed to only execute once and before bootstrap(), so you can be sure that the values accessed there come from the “original” central context.

Alternatively, you could also use this pattern:

// no constructor
public function bootstrap(Tenant $tenant): void
{
$this->originalApiKey ??= $tenant->api_key;
}

Tenancy keeps track of which bootstrappers have been initialized (their bootstrap() has been executed for the first time). If an error occurs during the bootstrapping process and you run tenancy()->end(), it will only revert the bootstrappers that have already been initialized.

This would be a major edge case, but for the sake of completeness: revert() should ideally be idempotent and safe to execute as long as the bootstrapper has been initialized. In other words, it should ideally only work with the state stored in the bootstrapper’s properties during initialization (constructor or ??= assignments in bootstrap()). This is because if FooBootstrapper was bootstrapped just fine for the first time, and is therefore considered to be initialized, but on second bootstrap (e.g. for a different tenant when running a loop) it isn’t even reached due to a failure in a previous bootstrapper, FooBootstrapper will still be reverted.