TenantAPI module
Handles multi-tenant in the application via Tenancy. You should read the Docs to get an understanding of how things work.
Setup used
- Multi-database tenancy (each tenant has their own database)
- Central site (holds the tenancy information)
Quick start
Database modes
This comes down to; does the laravel database user have permissions to create new databases or not. If so, use Own database if not use Table prefixing. Changing this after tenants have been created will break those tenants if you have not migrated the data to the new format too.
Really, just always use Own Database unless you have a valid reason not to.
Own Database (default)
This is the preference and should be used for ALL production sites. It means that each tenant will have their own database and no table prefixes are used.
To set this TENANCY_DATABASE_TYPE=own_database
Table prefixing (testing only)
The tenancy package has been extended via MySQLDatabaseManager to support table prefixes. This results in only a single database being required and each table has t_TENANTID_ prefixed. Due to a max length on table/index names of 64 characters it is key that table/index names account for this prefix. It should only be 8 characters, but that means a table/index name cannot be longer than 56 chars, so make sure you define your index names manually!
This mode should only be used for testing and results in the need for only ONE database.
To set this TENANCY_DATABASE_TYPE=prefix
Prepping databases
lando artisan migrate:fresh --seed= To migrate fresh and seed the central databaselando artisan db:seed Modules\\TenantApi\\Database\\Seeders\\TenantDemoSeeder- Seed some demo tenants (this will create a database for each tenant). See seeder class for test domains eg tenant.01.district-core.lndo.site- (optional)
lando tenant:migrate-fresh- To create all the tables in the tenant databases. This gets done with the above command but useful for testing. lando artisan tenant:seed- To seed content to all the tenants
Creating new tenants (Testing / POC)
IMPORTANT: Tenant databases/migrations get created via jobs. You should have
lando artisan horizonrunning.
Visit central.district-core.lndo.site/central and add a new domain name. Click "Create new tenant" and watch horizon until seeding complete.
Visit the new tenant domain!
Digging deeper
Queues and Jobs
Any tasks that may be queued, eg implements Illuminate\Contracts\Queue\ShouldQueue should also implement TenantAwareJobInterface and use TenantAwareJobTrait. This will add the required tag() method that identifies the tenant the job belongs to.
Routing and middleware
When exposing a route, consider whether it should be accessible by:
- Central domains
- Tenants
- Both
By default, all routes are exposed to both central and tenant domains. Core/Providers/BaseRouteServiceProvider wraps all routes in the tenant group middleware that will identify the current tenant via the domain used. The same routes are also defined "domain specific" for each central domain and without this middleware to allow central access.
This behaviour can be overridden if you:
- You place your route in
YourModule/Routes/central-dashfor central only dashboard routes - You place your route in
YourModule/Routes/central-webfor central only web routes - Wrap your route in the
tenant.onlymiddleware for tenant only routes.
Note that routes that are defined by third party modules (i.e fortify) are not domain specific and need both the tenant group and universal group. See Universal Routes
The middleware InitializeTenancyByDomain and PreventAccessFromCentralDomainshave been set as high priority to run before all other route middleware and identify tenants really early.
Events
All events are defined in tenancy.events config and listeners may be overwritten my other modules. This module provides essential listeners for tenant create & delete. The TenancyServiceProvider will bind all the events and listeners during boot.
Tests
Tests ideally run in the context of a tenant as that is where the bulk of the functionality lives. To do this, some extra trickery is required as we need to handle both tenant unique databases AND the ability to have these created in parallel.
Most of this has been solved for you already, and ideally you just need two things:
- Ensure your test extends
Modules\TenantApi\Tests\Base\TenantTestCase
If your test is to run on "central" then you don't need the above.
Example of a test that uses tenants, or specifically uses a get/post to a route:
class MyTest extends TestCase
{
use RefreshDatabase;
protected bool $tenancy = true;
public function test_something()
{
//
}
}
TODO
- Restrict what gets seeded for central database
- Test, test, test. Especially with
- Files/storage
- Jobs
- Caches
- Cookies + Login
- Check permissions https://tenancyforlaravel.com/docs/v3/integrations/spatie#laravel-permission
- Ideally use events to prevent the District Core module from relying on this module since all District modules depend on Core.
- Limit Central domains to a single one AND/OR change how menu URLs are built from FE to Backend instead to avoid issues with multiple Central domains clashing with named routes.