23
NovMulti-tenancy is the ability to provide your service to multiple users (tenants) from a single hosted instance of the application. This is contrasted with deploying the application separately for each user. This package is built around the idea that multi-tenancy usually means letting tenants have their own users who have their own resources, e.g. todo tasks. Not just users having tasks.
There are two types of multi-tenancy:
where tenant_id = 1
clauses.This package lets you do both, though it focuses more on multi-database tenancy because that typically requires more work on the side of the package and less work on your side. Whereas for a single-database tenancy you’re provided with a class that keeps track of the current tenant and model traits and the rest is up to you.
For your application to be tenant-aware, a tenant has to be identified. This package ships with many identification middleware classes. You may identify tenants by domain, subdomain, domain, OR subdomain at the same time, path, or request data.
Here I’ll share some basic details that enable you to implement multi-database tenancy & domain identification. If you need a different implementation, then that’s definitely possible with this package and it’s very easy to refactor to a different implementation.
I suggest you follow this tutorial just to get things working so that you can work with this package. Then if you need to, you can refactor the details of the multi-tenancy implementation (e.g. single-database tenancy, request data identification, etc).
First, you’ve to require the package using composer for the installation:
composer require stancl/tenancy
Then, run the tenancy:install
command:
php artisan tenancy:install
This will create a few files: migrations, config file, route file, and a service provider.
Next, you’ve to run the migrations:
php artisan migrate
Register the service provider in config/app.php.
Next, you need to build a Tenant model. The package has a default Tenant model that has many features, but it strives to be mostly unopinionated and as such, you’ve to create a custom model to use domains & databases. Create the file app/Models/Tenant.php
like this:
<?php
namespace App\Models;
use Stancl\Tenancy\Database\Models\Tenant as BaseTenant;
use Stancl\Tenancy\Contracts\TenantWithDatabase;
use Stancl\Tenancy\Database\Concerns\HasDatabase;
use Stancl\Tenancy\Database\Concerns\HasDomains;
class Tenant extends BaseTenant implements TenantWithDatabase
{
use HasDatabase, HasDomains;
}
Now you need to tell the package to utilize this custom model. Open the config/tenancy.php
file and modify the line below:
'tenant_model' => \App\Models\Tenant::class,
The TenancyServiceProvider
file in your app/Providers
directory maps tenancy events to listeners. By default, when a tenant is created, it runs a JobPipeline
(a smart thing that’s part of this package) which makes sure that the CreateDatabase
, MigrateDatabase
, and optionally other jobs (e.g. SeedDatabase
) are run sequentially. In other words, it creates & migrates the tenant’s database after he’s created and it does this in the correct order.
Now you’ve to make a small change to the app/Providers/RouteServiceProvider.php
file. Specifically, Make sure that central routes are registered on central domains only.
protected function mapWebRoutes()
{
foreach ($this->centralDomains() as $domain) {
Route::middleware('web')
->domain($domain)
->namespace($this->namespace)
->group(base_path('routes/web.php'));
}
}
protected function mapApiRoutes()
{
foreach ($this->centralDomains() as $domain) {
Route::prefix('api')
->domain($domain)
->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
}
}
protected function centralDomains(): array
{
return config('tenancy.central_domains');
}
Call these methods manually from your RouteServiceProvider
’s boot()
method, instead of the $this->routes()
calls.
public function boot()
{
$this->configureRateLimiting();
$this->mapWebRoutes();
$this->mapApiRoutes();
}
Now you need to actually specify the central domains. A central domain is a domain that serves your central app
content, e.g. the landing page where tenants sign up. Open the config/tenancy.php
file and add them in:
'central_domains' => [
'saas.test',
// Add the ones that you use. I use this one with Laravel Valet.
],
If you’re using Laravel Sail, no changes are needed, default values are good to go:
'central_domains' => [
'127.0.0.1',
'localhost',
],
Your tenant routes will look like this by default:
Route::middleware([
'web',
InitializeTenancyByDomain::class,
PreventAccessFromCentralDomains::class,
])->group(function () {
Route::get('/', function () {
return 'This is your multi-tenant application. The id of the current tenant is ' . tenant('id');
});
});
These routes will only be accessible on tenant (non-central) domains, the PreventAccessFromCentralDomains
middleware enforces that.
Next, you’ve to make a small change to dump all the users in the database, so that you can actually see multi-tenancy working. Now open the file routes/tenant.php
and apply the modification below:
Route::get('/', function () {
dd(\App\Models\User::all());
return 'This is your multi-tenant application. The id of the current tenant is ' . tenant('id');
});
Next, You’ve to move the user’s table migration (the file database/migrations/2014_10_12_000000_create_users_table.php
or similar) to database/migrations/tenant
. This will prevent the table from being created in the central database, and it will be instead created in the tenant database when a tenant is created.
For testing purposes, You can create a tenant in tinker quickly.
$ php artisan tinker
>>> $tenant1 = App\Models\Tenant::create(['id' => 'foo']);
>>> $tenant1->domains()->create(['domain' => 'foo.localhost']);
>>>
>>> $tenant2 = App\Models\Tenant::create(['id' => 'bar']);
>>> $tenant2->domains()->create(['domain' => 'bar.localhost']);
Now we’ll create a user inside each tenant’s database:
App\Models\Tenant::all()->runForEach(function () {
App\Models\User::factory()->create();
});
Now you can visit foo.localhost
in our browser, Now replace localhost with one of the values of central_domains
in the file config/tenancy.php
as modified previously. You should see a dump of the users table where you see some users. If you visit bar.localhost
, you should see a different user.
This package has a lot of different features and options. If you want to learn more you can visit its complete documentation on its Official Website and get source code on Github.
Published at : 23-11-2021
I am a highly results-driven professional with 12+ years of collective experience in the grounds of web application development especially in laravel, native android application development in java, and desktop application development in the dot net framework. Now managing a team of expert developers at Codebrisk.
Launch project