Skip to content

Tenants

Tenants are database models that represent the tenants used by this package.

A tenant can be any model that implements the Stancl\Tenancy\Contracts\Tenant interface. To tell the package to use a custom tenant model, set the tenancy.models.tenant config key:

config/tenancy.php
'models' => [
'tenant' => App\Models\Tenant::class,
],

Default tenant model

By default, the package uses the Stancl\Tenancy\Database\Models\Tenant model. Out of the box, it adds the following behavior:

  • VirtualColumn: a trait from our VirtualColumn package
  • Forced central connection: so that you can interact with the tenant model even while connected to a tenant database (tenants are stored in the central database)
  • ID generation logic: on creation, the tenant’s id is automatically filled with a UUID instead of using the default autoincrement behavior
  • TenantRun + InitializationHelpers: helper methods for running code in the tenant context, see below for details
  • Cached resolver invalidation logic: any change made to a tenant will invalidate all resolver cache for that tenant
  • Some additional helpful methods and events, take a look at the code for details

Using a custom model

In most applications, you will want to use a custom tenant model. Usually, you’ll want to extend our base model to get the features outlined above. That said, it isn’t needed to extend our base model, and the Tenant interface doesn’t have too many requirements, but implementing them manually can be time-consuming.

For instance, to use domain identification and multi-database, you’ll want to create a model like this:

app/Models/Tenant.php
namespace App\Models;
use Stancl\Tenancy\Database\Models\Tenant as BaseTenant;
use Stancl\Tenancy\Database\Concerns\HasDatabase;
use Stancl\Tenancy\Database\Concerns\HasDomains;
use Stancl\Tenancy\Database\Concerns\MaintenanceMode;
use Stancl\Tenancy\Database\Contracts\TenantWithDatabase;
class Tenant extends BaseTenant implements TenantWithDatabase
{
use HasDatabase, HasDomains, MaintenanceMode;
}
config/tenancy.php
'models' => [
'tenant' => App\Models\Tenant::class,
],

Creating tenants

Since tenants are normal Eloquent models (with the exception of having the VirtualColumn trait, letting you store any data on them without having to add columns to migrations), you can create them as you’d expect:

$tenant = Tenant::create();
$tenant->domains()->create(['domain' => 'tenant1.example.test']);

Custom columns

To store some data in custom columns, i.e. not the JSON data column, add the names of these columns to the array returned by getCustomColumns():

app/Models/Tenant.php
public static function getCustomColumns(): array
{
return array_merge(parent::getCustomColumns(), [
'my_column',
]);
}

This is especially useful for data you may query often, or columns you want to add indices on.

database/migrations/2024_04_10_023627_add_my_column_to_tenants_table.php
$table->string('my_column')->index();

Running code in the tenant context

The TenantRun trait lets you easily run code in a tenant context:

$usersInTenant = $tenant->run(fn () => User::count());

The run() method safely reverts back to the previous context (central/another tenant) after the passed callback has finished executing.

The InitializationHelpers trait lets you use the enter() and leave() methods:

// Equivalent to tenancy()->initialize($tenant);
$tenant->enter();
// Equivalent to tenancy()->end();
$tenant->leave();

Accessing the current tenant

You may use the tenant() helper to access the current tenant, if tenancy has been initialized:

tenant(); // Tenant|null
// You can also access model attributes:
tenant('id'); // string|null

If your model is based on the base Stancl\Tenancy\Database\Models\Tenant model, you can also use the current() and currentOrFail() methods:

Tenant::current(); // Tenant|null
/** @throws TenancyNotInitializedException */
Tenant::currentOrFail(); // Tenant

Tenant keys

The default model and tenants table migration use UUIDs for primary keys. The reason for this is that your tenant keys must not be enumerable or derived from unsanitized user input. This is because:

  • with some setups, you may want to avoid path enumeration attacks e.g. for tenant assets,
  • tenant keys are used in raw queries e.g. for creating databases.

In the paragraphs above, we’ve used the term “tenant key”. Does that simply refer to the id? Yes by default, but not necessarily.

Similar to how Laravel lets you define separate keys to be used as model primary keys and keys for routing:

class Article
{
public $primaryKey = 'id';
public function getRouteKeyName()
{
return 'slug';
}
}
// Will use the `slug` column for route model binding
Route::get('/{article}/edit', EditArticle::class);

Tenancy lets you define a separate column to be used as the “tenant key”. Tenancy always uses the tenant key to interact with your models, so you may separate them from your primary keys:

public function getTenantKeyName(): string
{
return 'uuid';
}
public function getTenantKey(): int|string
{
return $this->getAttribute($this->getTenantKeyName());
}

Unique identifier generators

The only requirements for tenant keys is that they’re non-enumerable, unique, and not derived from unsanitized user input.

This means that randomly generated values work perfectly for this use case, which is why we use UUIDs (v4) out of the box.

However, UUIDs can be ugly and they’re excessively unique (long) for most applications. You won’t have so many tenants that you’d need a UUID. So while UUID is a good default, we let you customize how these values are generated.

To change this Unique Identifier Generator, update the tenancy.models.id_generator config. Any class that implements the Stancl\Tenancy\Contracts\UniqueIdentifierGenerator trait can be used as a unique identifier generator.

config/tenancy.php
'models' => [
'id_generator' => UniqueIdentifierGenerators\UUIDGenerator::class,
],

Out of the box, the package ships with these generators:

GeneratorConfigurableUnique combinationsOutput length
UUIDGeneratorNo2^12236
RandomHexGeneratorYes, $bytes property2^(8 * $bytes)2 * $bytes
RandomStringGeneratorYes, $length property61^$length$length

With default configuration, these produce:

GeneratorConfigurationUnique combinationsSample output
UUIDGeneratorNone2^12201058b4a-9382-496e-a29c-cfdacf3dac2c
RandomHexGenerator6 bytes281,474,976,710,6562a0b3a915249
RandomStringGenerator8 characters191,707,312,997,2815UYXaYFc

RandomHexGenerator

This generator can be a good alternative to the UUID generator. It lets you exactly configure how many bytes you want your tenant keys to have. The resulting strings are only contain lowercase hexadecimal values (0-9, a-f).

Configuration:

app/Providers/TenancyServiceProvider.php
public function boot()
{
RandomHexGenerator::$bytes = 8;
// ...
}
BytesUnique combinationsString lengthSample string
12562.56E+0220b
2655366.55E+0440bd4
3167772161.68E+0760bd428
442949672964.29E+0980bd428ff
510995116277761.10E+12100bd428ff1d
62814749767106562.81E+14120bd428ff1d0a
7720575940379279007.21E+16140bd428ff1d0a14
8184467440737096000001.84E+19160bd428ff1d0a1490
947223664828696500000004.72E+21180bd428ff1d0a14905d
1012089258196146300000000001.21E+24200bd428ff1d0a14905db7
113094850098213450000000000003.09E+26220bd428ff1d0a14905db770
12792281625142643000000000000007.92E+28240bd428ff1d0a14905db77098
13202824096036517000000000000000002.03E+31260bd428ff1d0a14905db77098b6
1451922968585348300000000000000000005.19E+33280bd428ff1d0a14905db77098b60e
1513292279957849200000000000000000000001.33E+36300bd428ff1d0a14905db77098b60e62
163402823669209380000000000000000000000003.40E+38320bd428ff1d0a14905db77098b60e62cc
17871122859317602000000000000000000000000008.71E+40340bd428ff1d0a14905db77098b60e62cc27
18223007451985306000000000000000000000000000002.23E+43360bd428ff1d0a14905db77098b60e62cc27d8

RandomStringGenerator

The random string uses Laravel’s Str::random(), which produces essentially a stripped-down and URL safe version of base64 encoding random bytes.

There are 61 possible values per character, packing more bytes per character into the string than the hex generator. However, the strings include the entire alphabet including uppercase letters, which can look uglier than hex strings.

Configuration:

app/Providers/TenancyServiceProvider.php
public function boot()
{
RandomStringGenerator::$length = 12;
// ...
}
LengthUnique combinationsSample string
1616.10E+01v
237213.72E+03vD
32269812.27E+05vDw
4138458411.38E+07vDw8
58445963018.45E+08vDw8d
6515203743615.15E+10vDw8dW
731427428360213.14E+12vDw8dWQ
81917073129972811.92E+14vDw8dWQc
9116941460928341001.17E+16vDw8dWQcM
107133429116628830007.13E+17vDw8dWQcMP
11435139176114358000004.35E+19vDw8dWQcMPY
1226543489742975900000002.65E+21vDw8dWQcMPYz
131619152874321530000000001.62E+23vDw8dWQcMPYzc
1498768325333613200000000009.88E+24vDw8dWQcMPYzcc
156024867845350400000000000006.02E+26vDw8dWQcMPYzcc7
16367516938566375000000000000003.68E+28vDw8dWQcMPYzcc7Q
1722418533252548900000000000000002.24E+30vDw8dWQcMPYzcc7QE
181367530528405480000000000000000001.37E+32vDw8dWQcMPYzcc7QE8

Internal keys

Tenancy stores some internal values on tenants, such as tenancy_db_name.

This is the main reason behind using the VirtualColumn trait: if we need more internal values in the future, you won’t have to create new migrations. The values will simply get written to the data JSON column.

To explicitly interact with internal values, you may use:

$tenant->setInternal('db_name', 'foo');
$tenant->getInternal('db_name');

Events

Since much of the tenancy logic is event-based, the base model fires some dedicated events:

Stancl\Tenancy\Database\Models\Tenant
protected $dispatchesEvents = [
'saving' => Events\SavingTenant::class,
'saved' => Events\TenantSaved::class,
'creating' => Events\CreatingTenant::class,
'created' => Events\TenantCreated::class,
'updating' => Events\UpdatingTenant::class,
'updated' => Events\TenantUpdated::class,
'deleting' => Events\DeletingTenant::class,
'deleted' => Events\TenantDeleted::class,
];