3.x redesign

This commit is contained in:
Samuel Štancl 2020-06-08 21:20:15 +02:00
parent 857122540f
commit f8f354c323
229 changed files with 201175 additions and 22440 deletions

View file

@ -0,0 +1,8 @@
<!DOCTYPE HTML>
<html lang="en-US">
<head>
<meta charset="UTF-8">
<meta http-equiv="refresh" content="0; url={{ $page->baseUrl . '/docs/' . $page->defaultVersion }}">
<title>stancl/tenancy</title>
</head>
</html>

View file

@ -0,0 +1,51 @@
---
title: Application Testing
description: Application Testing with stancl/tenancy — A Laravel multi-database tenancy package that respects your code..
extends: _layouts.documentation
section: content
---
# Application Testing {#application-testing}
To test your application with this package installed, you can create tenants in the `setUp()` method of your test case:
```php
protected function setUp(): void
{
parent::setUp();
tenant()->create('test.localhost');
tenancy()->init('test.localhost');
}
```
If you're using the database storage driver, you will also need to run the `create_tenants_table` migration:
```php
protected function setUp(): void
{
parent::setUp();
$this->call('migrate', [
'--path' => database_path('migrations'),
'--database' => 'sqlite',
]);
tenant()->create('test.localhost');
tenancy()->init('test.localhost');
}
```
If you're using the Redis storage driver, flush the database in `setUp()`:
```php
protected function setUp(): void
{
parent::setUp();
// make sure you're using a different connection for testing to avoid losing data
Redis::connection('tenancyTesting')->flushdb();
tenant()->create('test.localhost');
tenancy()->init('test.localhost');
}
```

View file

@ -0,0 +1,56 @@
---
title: Configuration
description: Configuring stancl/tenancy — A Laravel multi-database tenancy package that respects your code..
extends: _layouts.documentation
section: content
---
# Configuration {#configuration}
The `config/tenancy.php` file lets you configure how the package behaves.
> If the `tenancy.php` file doesn't exist in your `config` directory, you can publish it by running `php artisan vendor:publish --provider='Stancl\Tenancy\TenancyServiceProvider' --tag=config`
### `storage_driver, storage` {#storage}
This lets you configure the driver for tenant storage, i.e. what will be used to store information about your tenants. You can read more about this on the [Storage Drivers]({{ $page->link('storage-drivers') }}) page.
Available storage drivers:
- `Stancl\Tenancy\StorageDrivers\RedisStorageDriver`
- `Stancl\Tenancy\StorageDrivers\DatabaseStorageDriver`
### `tenant_route_namespace` {#tenant-route-namespace}
Controller namespace used for routes in `routes/tenant.php`. The default value is the same as the namespace for `web.php` routes.
### `exempt_domains` {#exempt-domains}
If a hostname from this array is visited, the `tenant.php` routes won't be registered, letting you use the same routes as in that file.
### `database` {#database}
The application's default connection will be switched to a new one — `tenant`. This connection will be based on the connection specified in `tenancy.database.based_on`. The database name will be `tenancy.database.prefix + tenant UUID + tenancy.database.suffix`.
You can set the suffix to `.sqlite` if you're using sqlite and want the files to be with the `.sqlite` extension. Conversely, you can leave the suffix empty if you're using MySQL, for example.
### `redis` {#redis}
If `tenancy.redis.tenancy` is set to true, connections listed in `tenancy.redis.prefixed_connections` will be prefixed with `config('tenancy.redis.prefix_base') . $uuid`.
> Note: You need phpredis for multi-tenant Redis.
### `cache` {#cache}
The `CacheManager` instance that's resolved when you use the `Cache` or the `cache()` helper will be replaced by `Stancl\Tenancy\CacheManager`. This class automatically uses [tags](https://laravel.com/docs/master/cache#cache-tags). The tag will look like `config('tenancy.cache.tag_base') . $uuid`.
If you need to store something in global, non-tenant cache,
### `filesystem` {#filesystem}
The `storage_path()` will be suffixed with a directory named `config('tenancy.filesystem.suffix_base') . $uuid`.
The root of each disk listed in `tenancy.filesystem.disks` will be suffixed with `config('tenancy.filesystem.suffix_base') . $uuid`.
For disks listed in `root_override`, the root will be that string with `%storage_path%` replaced by `storage_path()` *after* tenancy has been initialized. All other disks will be simply suffixed with `tenancy.filesystem.suffix_base` + the tenant UUID.
Read more about this on the [Filesystem Tenancy]({{ $page->link('filesystem-tenancy') }}) page.

View file

@ -0,0 +1,64 @@
---
title: Console Commands
description: Console commands with stancl/tenancy — A Laravel multi-database tenancy package that respects your code..
extends: _layouts.documentation
section: content
---
# Console Commands {#console-commands}
The package comes with some artisan commands that will help you during development.
## Migrate {#migrate}
The most important command. To use tenants, you have to be able to migrate their databases.
You can use the `tenants:migrate` command to migrate tenant's databases. You can also specify which tenants' databases should be migrated using the `--tenants` option.
```
php artisan tenants:migrate --tenants=8075a580-1cb8-11e9-8822-49c5d8f8ff23
```
> Note: Tenant migrations must be located in `database/migrations/tenant`.
You can use these commands outside the command line as well. If you want to migrate a tenant's database in a controller, you can use the `Artisan` facade.
```php
$tenant = tenant()->create('tenant1.localhost');
\Artisan::call('tenants:migrate', [
'--tenants' => [$tenant['uuid']]
]);
```
## Rollback & seed {#rollback}
- Rollback: `tenants:rollback`
- Seed: `tenants:seed`
Similarly to [migrate](#migrate), these commands accept a `--tenants` option.
## Run {#run}
You can use the tenants:run command to run your own commands for tenants.
If your command's signature were `email:send {--queue} {--subject=} {body}`, you would run this command like this:
```
php artisan tenants:run email:send --tenants=8075a580-1cb8-11e9-8822-49c5d8f8ff23 --option="queue=1" --option="subject=New Feature" --argument="body=We have launched a new feature. ..."
```
## Tenant list {#tenant-list}
```none
php artisan tenants:list
Listing all tenants.
[Tenant] uuid: dbe0b330-1a6e-11e9-b4c3-354da4b4f339 @ localhost
[Tenant] uuid: 49670df0-1a87-11e9-b7ba-cf5353777957 @ dev.localhost
```
## Selectively clearing tenant cache {#selectively-clearing-tenant-cache}
You can delete specific tenants' cache by using the `--tags` option on `cache:clear`:
```
php artisan cache:clear --tags=tenantdbe0b330-1a6e-11e9-b4c3-354da4b4f339
```
The tag is `config('tenancy.cache.tag_base') . $uuid`.

View file

@ -0,0 +1,30 @@
---
title: Creating Tenants
description: Creating tenants with stancl/tenancy — A Laravel multi-database tenancy package that respects your code..
extends: _layouts.documentation
section: content
---
# Creating Tenants {#creating-tenants}
> **Make sure your database is correctly [configured]({{ $page->link('configuration/#database') }}) before creating tenants.**
To create a tenant, you can use
```php
tenant()->create('tenant1.yourapp.com');
```
> Tip: All domains under `.localhost` are routed to 127.0.0.1 on most operating systems. This is useful for development.
If you want to set some data while creating the tenant, you can pass an array with the data as the second argument:
```php
tenant()->create('tenant2.yourapp.com', [
'plan' => 'free'
]);
```
The `create` method returns an array with tenant information (`uuid`, `domain` and whatever else you supplied).
> Note: Creating a tenant doesn't run [migrations]({{ $page->link('console-commands/#migrate') }}) automatically. You have to do that yourself.

View file

@ -0,0 +1,21 @@
---
title: Custom Database Names
description: Custom Database Names with stancl/tenancy — A Laravel multi-database tenancy package that respects your code..
extends: _layouts.documentation
section: content
---
# Custom Database Names {#custom-database-names}
If you want to specify the tenant's database name, set the `tenancy.database_name_key` configuration key to the name of the key that is used to specify the database name in the tenant storage. You must use a name that you won't use for storing other data, so it's recommended to avoid names like `database` and use names like `_stancl_tenancy_database_name` instead. Then just give the key a value during the tenant creation process:
```php
>>> tenant()->create('example.com', [
'_stancl_tenancy_database_name' => 'example_com'
])
=> [
"uuid" => "49670df0-1a87-11e9-b7ba-cf5353777957",
"domain" => "example.com",
"_stancl_tenancy_database_name" => "example_com",
]
```

View file

@ -0,0 +1,18 @@
---
title: Development
description: Development | stancl/tenancy — A Laravel multi-database tenancy package that respects your code..
extends: _layouts.documentation
section: content
---
# Development {#development}
## Running tests {#running-tests}
### With Docker {#with-docker}
If you have Docker installed, simply run ./test. When you're done testing, run docker-compose down to shut down the containers.
### Without Docker {#without-docker}
If you run the tests of this package, please make sure you don't store anything in Redis @ 127.0.0.1:6379 db#14. The contents of this database are flushed everytime the tests are run.
Some tests are run only if the CI, TRAVIS and CONTINUOUS_INTEGRATION environment variables are set to true. This is to avoid things like bloating your MySQL instance with test databases.

View file

@ -0,0 +1,22 @@
---
title: Difference Between This Package And Others
description: Difference Between This Package And Others | with stancl/tenancy — A Laravel multi-database tenancy package that respects your code.
extends: _layouts.documentation
section: content
---
# Difference Between This Package And Others
A frequently asked question is the difference between this package and [tenancy/multi-tenant](https://github.com/tenancy/multi-tenant).
Packages like tenancy/multi-tenant and tenancy/tenancy give you an API for making your application multi-tenant. They give you a tenant DB connection, traits to apply on your models, a guide on creating your own tenant-aware cache, etc.
This package makes your application multi-tenant automatically and attempts to make you not have to change (m)any things in your code.
## Which one should you use?
Depends on what you prefer.
If you want full control and make your application multi-tenant yourself, use tenancy/multi-tenant.
If you want to focus on writing your application instead of tenancy implementations, use stancl/tenancy.

View file

@ -0,0 +1,9 @@
---
title: Digging Deeper
description: Digging Deeper | stancl/tenancy — A Laravel multi-database tenancy package that respects your code..
extends: _layouts.documentation
section: content
---
# Digging Deeper {#digging-deeper}

View file

@ -0,0 +1,47 @@
---
title: The Event System
description: The Event System | stancl/tenancy — A Laravel multi-database tenancy package that respects your code..
extends: _layouts.documentation
section: content
---
# The Event System
You can use event hooks to change the behavior of the tenancy boostrapping and tenancy ending processes.
The following events are available:
- `boostrapping`
- `boostrapped`
- `ending`
- `ended`
### Tenant-specific database connection example {#tenant-specific-database-connection-example}
You can hook into these events using `Tenancy::<eventName>`:
```php
\Tenancy::boostrapping(function ($tenantManager) {
if ($tenantManager->tenant['uuid'] === 'someUUID') {
config(['database.connections.someDatabaseConnection' => $tenantManager->tenant['databaseConnection']]);
$tenantManager->database->useConnection('someDatabaseConnection');
return ['database'];
}
});
```
The example above checks whether the current tenant has an uuid of `someUUID`. If yes, it creates a new database connection based on data stored in the tenant's storage. Then it changes the default database connection. Finally, it returns an array of the events that this callback prevents.
The following actions can be prevented:
- database connection switch: `database`
- Redis prefix: `redis`
- CacheManager switch: `cache`
- Filesystem changes: `filesystem`
### Tenant-specific configuration example {#tenant-specific-configuration-example}
Another common use case for events is tenant-specific config:
```php
\Tenancy::bootstrapped(function ($tenantManager) {
config(['some.api.key' => $tenantManager->tenant['api_key']);
});
```

View file

@ -0,0 +1,52 @@
---
title: Filesystem Tenancy
description: Filesystem Tenancy with stancl/tenancy — A Laravel multi-database tenancy package that respects your code..
extends: _layouts.documentation
section: content
---
# Filesystem Tenancy {#filesystem-tenancy}
> Note: It's important to differentiate between storage_path() and the Storage facade. The Storage facade is what you use to put files into storage, i.e. `Storage::disk('local')->put()`. `storage_path()` is used to get the path to the storage directory.
The `storage_path()` will be suffixed with a directory named `config('tenancy.filesystem.suffix_base') . $uuid`.
The root of each disk listed in `tenancy.filesystem.disks` will be suffixed with `config('tenancy.filesystem.suffix_base') . $uuid`.
**However, this alone would cause unwanted behavior.** It would work for S3 and similar disks, but for local disks, this would result in `/path_to_your_application/storage/app/tenant1e22e620-1cb8-11e9-93b6-8d1b78ac0bcd/`. That's not what we want. We want `/path_to_your_application/storage/tenant1e22e620-1cb8-11e9-93b6-8d1b78ac0bcd/app/`.
That's what the `root_override` section is for. `%storage_path%` gets replaced by `storage_path()` *after* tenancy has been initialized. The roots of disks listed in the `root_override` section of the config will be replaced accordingly. All other disks will be simply suffixed with `tenancy.filesystem.suffix_base` + the tenant UUID.
Since `storage_path()` will be suffixed, your folder structure will look like this:
![The folder structure](https://i.imgur.com/GAXQOnN.png)
If you write to these directories, you will need to create them after you create the tenant. See the docs for [PHP's mkdir](http://php.net/function.mkdir).
Logs will be saved to `storage/logs` regardless of any changes to `storage_path()`.
One thing that you **will** have to change if you use storage similarly to the example on the image is your use of the helper function `asset()` (that is, if you use it).
You need to make this change to your code:
```diff
- asset("storage/images/products/$product_id.png");
+ tenant_asset("images/products/$product_id.png");
```
Note that all (public) tenant assets have to be in the `app/public/` subdirectory of the tenant's storage directory, as shown in the image above.
This is what the backend of `tenant_asset()` returns:
```php
// TenantAssetsController
return response()->file(storage_path('app/public/' . $path));
```
With default filesystem configuration, these two commands are equivalent:
```php
Storage::disk('public')->put($filename, $data);
Storage::disk('local')->put("public/$filename", $data);
```
If you want to store something globally, simply create a new disk and *don't* add it to the `tenancy.filesystem.disks` config.

View file

@ -0,0 +1,34 @@
---
title: Getting Started
description: Getting started with stancl/tenancy — A Laravel multi-database tenancy package that respects your code.
extends: _layouts.documentation
section: content
---
# Getting Started {#getting-started}
[**stancl/tenancy**](https://github.com/stancl/tenancy) is a Laravel multi-database tenancy package. It is designed in a way that requires you to make no changes to your codebase. Instead of applying traits on models and replacing every single reference to cache by a reference to a tenant-aware cache, the package lets you write your app without thinking about tenancy. It handles tenancy automatically.
> Note: Filesystem is the only thing that can be a little problematic. Be sure to read [that page]({{ $page->link('filesystem-tenancy') }}).
## How does it work? {#how-does-it-work}
A user visits `client1.yourapp.com`. The package identifies the tenant who this domain belongs to, and automatically does the following:
- switches database connection
- replaces the default cache manager
- switches Redis connection
- changes filesystem root paths
The benefits of this being taken care of by the package are:
- separation of concerns: you should write your app, not tenancy implementations
- reliability: you won't have to fear that you forgot to replace a reference to cache by a tenant-aware cache call. This is something you might worry about if you're implementing tenancy into an existing application.
## What is multi-tenancy? {#what-is-multi-tenancy}
Multi-tenancy is the ability to provide your application to multiple customers (who have their own users and other resources) from a single instance of your application. Think Slack, Shopify, etc.
Multi-tenancy can be single-database and multi-database.
**Single-database tenancy** means that your application uses only a single database. The way this is usually implemented is that instead of having the `id`, `title`, `user_id` and `body` columns in your `posts` table, you will also have a `tenant_id` column. This approach works until you need custom databases for your clients. It's also easy to implement, it basically boils down to having your models use a trait which adds a [global scope](https://laravel.com/docs/master/eloquent#global-scopes).
**Multi-database tenancy**, the type that this package provides, lets you use a separate database for each tenant. The benefits of this approach are scalability, compliance (some clients need to have the database on their server) and mitigation of risks such as showing the wrong tenant's data to a user. The downside is that this model is harder to implement, which is why this package exists.

View file

@ -0,0 +1,18 @@
---
title: Horizon Integration
description: Horizon Integration with stancl/tenancy — A Laravel multi-database tenancy package that respects your code..
extends: _layouts.documentation
section: content
---
# Horizon Integration
> Make sure your queue is [correctly configured]({{ $page->link('jobs-queues') }}) before using Horizon.
Jobs are automatically tagged with the tenant's uuid and domain:
![UUID and domain tags](https://i.imgur.com/K2oWTJc.png)
You can use these tags to monitor specific tenants' jobs:
![Monitoring tags](https://i.imgur.com/qB6veK7.png)

View file

@ -0,0 +1,40 @@
---
title: HTTPS Certificates
description: HTTPS Certificates with stancl/tenancy — A Laravel multi-database tenancy package that respects your code..
extends: _layouts.documentation
section: content
---
# HTTPS certificates
HTTPS certificates are very easy to deal with if you use the `yourclient1.yourapp.com`, `yourclient2.yourapp.com` model. You can use a wildcard HTTPS certificate.
If you use the model where second level domains are used, there are multiple ways you can solve this.
This guide focuses on nginx.
### 1. Use nginx with the lua module
Specifically, you're interested in the [`ssl_certificate_by_lua_block`](https://github.com/openresty/lua-nginx-module#ssl_certificate_by_lua_block) directive. Nginx doesn't support using variables such as the hostname in the `ssl_certificate` directive, which is why the lua module is needed.
This approach lets you use one server block for all tenants.
### 2. Add a simple server block for each tenant
You can store most of your config in a file, such as `/etc/nginx/includes/tenant`, and include this file into tenant server blocks.
```nginx
server {
include includes/tenant;
server_name foo.bar;
# ssl_certificate /etc/foo/...;
}
```
### Generating certificates
You can generate a certificate using certbot. If you use the `--nginx` flag, you will need to run certbot as root. If you use the `--webroot` flag, you only need the user that runs it to have write access to the webroot directory (or perhaps webroot/.well-known is enough) and some certbot files (you can specify these using --work-dir, --config-dir and --logs-dir).
Creating this config dynamically from PHP is not easy, but is probably feasible. Giving `www-data` write access to `/etc/nginx/sites-available/tenants.conf` should work.
However, you still need to reload nginx configuration to apply the changes to configuration. This is problematic and I'm not sure if there is a simple and secure way to do this from PHP.

View file

@ -0,0 +1,8 @@
<!DOCTYPE HTML>
<html lang="en-US">
<head>
<meta charset="UTF-8">
<meta http-equiv="refresh" content="0; url=getting-started">
<title>stancl/tenancy</title>
</head>
</html>

View file

@ -0,0 +1,67 @@
---
title: Installation
description: Installing stancl/tenancy — A Laravel multi-database tenancy package that respects your code..
extends: _layouts.documentation
section: content
---
# Installation {#getting-started}
Laravel 5.8 is needed.
### Require the package via composer
First you need to require the package using composer:
```
composer require 'stancl/tenancy:~1.8'
```
### Automatic installation {#automatic-installation}
To install the package, simply run
```
php artisan tenancy:install
```
You will be asked if you want to store your data in Redis or a relational database. You can read more about this on the [Storage Drivers]({{ $page->link('storage-drivers') }}) page.
This will do all the steps listed in the [Manual installation](#manual-installation) section for you.
The only thing you have to do now is create a database/Redis connection. Read the [Storage Drivers]({{ $page->link('storage-drivers') }}) page for information about that.
### Manual installation {#manual-installation}
If you prefer installing the package manually, you can do that too. It shouldn't take more than a minute either way.
#### Setting up middleware
Now open `app/Http/Kernel.php` and make the `InitializeTenancy` middleware top priority, so that it gets executed before anything else, making sure things like the database switch connections soon enough:
```php
protected $middlewarePriority = [
\Stancl\Tenancy\Middleware\InitializeTenancy::class,
// ...
];
```
#### Creating routes
The package lets you have tenant routes and "exempt" routes. Tenant routes are your application's routes. Exempt routes are routes exempt from tenancy — landing pages, sign up forms, and routes for managing tenants.
Routes in `routes/web.php` are exempt, whereas routes in `routes/tenant.php` have the `InitializeTenancy` middleware automatically applied on them.
So, to create tenant routes, put those routes in a new file called `routes/tenant.php`.
#### Configuration
Run the following:
```
php artisan vendor:publish --provider='Stancl\Tenancy\TenancyServiceProvider' --tag=config
```
This creates a `config/tenancy.php`. You can use it to configure how the package works.
Configuration is explained in detail on the [Configuration]({{ $page->link('configuration') }}) page.

View file

@ -0,0 +1,12 @@
---
title: Integrations
description: Integrating stancl/tenancy — A Laravel multi-database tenancy package that respects your code..
extends: _layouts.documentation
section: content
---
# Integrations {#integrations}
This package naturally integrates well with Laravel packages, since it does not rely on you explicitly specifying database connections.
There are some exceptions, though. [Telescope integration]({{ $page->link('telescope') }}), for example, requires you to change the database connection in `config/telescope.php` to a non-default one, because the default connection is switched to the tenant connection. Some packages should use a central connection for data storage.

View file

@ -0,0 +1,23 @@
---
title: Jobs & Queues
description: Jobs & Queues with stancl/tenancy — A Laravel multi-database tenancy package that respects your code..
extends: _layouts.documentation
section: content
---
# Jobs & Queues {#jobs-queues}
Jobs are automatically multi-tenant, which means that if a job is dispatched while tenant A is initialized, the job will operate with tenant A's database, cache, filesystem, and Redis.
**However**, if you're using the `database` or `redis` queue driver, you have to make a small tweak to your queue configuration.
Open `config/queue.php` and make sure your queue driver has an explicitly set connection. Otherwise it would use the default one, which would cause issues, since `database.default` is changed by the package and Redis connections are prefixed.
**If you're using `database`, add a new line to `queue.connections.database`:**
```php
'connection' => 'mysql',
```
where `'mysql'` is the name of your non-tenant database connection with a `jobs` table.
**If you're using Redis, make sure its `'connection'` is not in `tenancy.redis.prefixed_connections`.**

View file

@ -0,0 +1,20 @@
---
title: Middleware Configuration
description: Middleware Configuration with stancl/tenancy — A Laravel multi-database tenancy package that respects your code..
extends: _layouts.documentation
section: content
---
# Middleware Configuration {#middleware-configuration}
When a tenant route is visited and the tenant can't be identified, an exception is thrown. If you want to change this behavior, to a redirect for example, add this to your `app/Providers/AppServiceProvider.php`'s `boot()` method:
```php
// use Stancl\Tenancy\Middleware\InitializeTenancy;
$this->app->bind(InitializeTenancy::class, function ($app) {
return new InitializeTenancy(function ($exception) {
// return redirect()->route('foo');
});
});
```

View file

@ -0,0 +1,36 @@
---
title: Miscellaneous Tips
description: Miscellaneous Tips | stancl/tenancy — A Laravel multi-database tenancy package that respects your code..
extends: _layouts.documentation
section: content
---
# Miscellaneous Tips {#misc-tips}
## Tenant Redirect {#tenant-redirect}
A customer has signed up on your website, you have created a new tenant and now you want to redirect the customer to their website. You can use the `tenant()` method on Redirect, like this:
```php
// tenant sign up controller
return redirect()->route('dashboard')->tenant($tenant['domain']);
```
## Custom ID scheme
If you don't want to use UUIDs and want to use something more human-readable (even domain concatenated with uuid, for example), you can create a custom class for this:
```php
use Stancl\Tenancy\Interfaces\UniqueIdentifierGenerator;
class MyUniqueIDGenerator implements UniqueIdentifierGenerator
{
public static function handle(string $domain, array $data): string
{
return $domain . \Webpatser\Uuid\Uuid::generate(1, $domain);
}
}
```
and then set the `tenancy.unique_id_generator` config to the full path to your class.

View file

@ -0,0 +1,17 @@
---
title: Stay Updated
description: Stay Updated | stancl/tenancy — A Laravel multi-database tenancy package that respects your code..
extends: _layouts.documentation
section: content
---
# Stay Updated {#stay-updated}
If you'd like to be notified about new versions, you can [sign up for e-mail notifications](https://stancl.github.io/tenancy/#stay-updated) or join our [Telegram channel](https://t.me/joinchat/AAAAAFjdrbSJg0ZCHTzxLA).
You can choose whether you want to receive emails about major versions and/or minor versions.
- Major versions include breaking changes. Composer won't know about these versions and won't update to them. Major versions will be released about once every 6 months.
- Minor versions include backwards-compatible features and bug fixes.
<!-- todo mailchimp dialog -->

View file

@ -0,0 +1,61 @@
---
title: Storage Drivers
description: Storage Drivers of stancl/tenancy — A Laravel multi-database tenancy package that respects your code..
extends: _layouts.documentation
section: content
---
# Storage Drivers {#storage-drivers}
Storage drivers are used to store a list of all tenants, their domains and any extra information you store about your tenants (e.g. their plan).
Currently, database and Redis storage drivers are available as part of the package. However, you can [write your own]({{ $page->link('writing-storage-drivers') }}) (and contribute ❤️) storage drivers.
## Database {#database}
The database storage driver lets you store tenant information in a relational database like MySQL, PostgreSQL and SQLite.
The benefit of this storage driver is that you don't have to use both Redis and a database for your data. Also you don't have to do as much configuration.
To use this driver, you need to have a `tenants` table. You may also use a custom database connection. By default, `tenancy.storage.db.connection` is set to `central`, which means that the `central` database connection will be used to store tenants. This connection is not automatically created, so you'd have to create it manually. You can create database connections in the `config/database.php` file.
If you'd like to use an existing connection, you can set this config to the name of the connection, e.g. `mysql`.
To create the `tenants` table, you can use the migration that comes with this package. If you haven't published it during installation, publish it now:
```
php artisan vendor:publish --provider='Stancl\Tenancy\TenancyServiceProvider' --tag=migrations
```
By default, all of your data will be stored in the JSON column `data`. If you want to store some data in a dedicated column (to leverage indexing, for example), add the column to the migration and to `tenancy.custom_columns` config.
Finally, run the migration:
```
php artisan migrate
```
> If you use a non-default connection, such as `central`, you have to specify which DB to migrate using the `--database` option.
>
> If you have existing migrations related to your app in `database/migrations`, move them to `database/migrations/tenant`. You can read more about tenant migrations [here]({{ $page->link('console-commands/#migrate') }}).
## Redis {#redis}
The Redis storage driver lets you store tenant information in Redis, a high-performance key-value store.
The benefit of this storage driver is its performance.
**Note that you need to configure persistence on your Redis instance if you don't want to lose all information about tenants.**
Read the [Redis documentation page on persistence](https://redis.io/topics/persistence). You should definitely use AOF and if you want to be even more protected from data loss, you can use RDB **in conjunction with AOF**.
If your cache driver is Redis and you don't want to use AOF with it, run two Redis instances. Otherwise, just make sure you use a different database (number) for tenancy and for anything else.
To use this driver, create a new Redis connection in the `database.redis` configuration (`config/database.php`) called `tenancy`.
```php
'tenancy' => [
'host' => env('TENANCY_REDIS_HOST', '127.0.0.1'),
'password' => env('TENANCY_REDIS_PASSWORD', null),
'port' => env('TENANCY_REDIS_PORT', 6380), // different port = separate Redis instance
'database' => env('TENANCY_REDIS_DB', 3), // alternatively, different database number
],
```

View file

@ -0,0 +1,29 @@
---
title: Telescope Integration
description: Telescope Integration with stancl/tenancy — A Laravel multi-database tenancy package that respects your code..
extends: _layouts.documentation
section: content
---
# Telescope Integration
Requests in Telescope are automatically tagged with the tenant uuid and domain:
![Telescope Request with tags](https://i.imgur.com/CEEluYj.png)
This lets you filter requests by uuid and domain:
![Filtering by uuid](https://i.imgur.com/SvbOa7S.png)
![Filtering by domain](https://i.imgur.com/dCJuEr1.png)
If you'd like to set Telescope tags in your own code, e.g. in your `AppServiceProvider`, replace your `Telescope::tag()` call like this:
```php
\Tenancy::integrationEvent('telescope', function ($entry) {
return ['abc']; // your logic
});
```
![Tenancy tags merged with tag abc](https://i.imgur.com/4p1wOiM.png)
Once Telescope 3 is released, you won't have to do this.
To have Telescope working, make sure your `telescope.storage.database.connection` points to a non-tenant connection. It's that way by default, so for most projects, Telescope should work out of the box.

View file

@ -0,0 +1,152 @@
---
title: Tenancy Initialization
description: Tenancy Initialization with stancl/tenancy — A Laravel multi-database tenancy package that respects your code..
extends: _layouts.documentation
section: content
---
# Tenancy Initialization {#tenancy-initialization}
Tenancy can be initialized by calling `tenancy()->init()`. The `InitializeTenancy` middleware calls this method automatically.
You can end a tenancy session using `tenancy()->end()`. This is useful if you need to run multiple tenant sessions or a mixed tenant/non-tenant session in a single request/command.
The `tenancy()->init()` method calls `bootstrap()`.
This method switches database connection, Redis connection (if Redis tenancy is enabled), cache and filesystem root paths.
This page goes through the code that actually makes this happen. You don't have to read this page to use the package, but it will give you insight into the magic that's happening in the background, so that you can be more confident in it.
## Database tenancy {#database-tenancy}
`bootstrap()` runs the following method:
```php
public function switchDatabaseConnection()
{
$this->database->connect($this->getDatabaseName());
}
```
If `tenancy.database_name_key` is set and present in the current tenant's data, the `getDatabaseName()` returns the stored database_name. Otherwise it returns the prefix + uuid + suffix.
```php
public function getDatabaseName($tenant = []): string
{
$tenant = $tenant ?: $this->tenant;
if ($key = $this->app['config']['tenancy.database_name_key']) {
if (isset($tenant[$key])) {
return $tenant[$key];
}
}
return $this->app['config']['tenancy.database.prefix'] . $tenant['uuid'] . $this->app['config']['tenancy.database.suffix'];
}
```
This is passed as an argument to the `connect()` method. This method creates a new database connection and sets it as the default one.
```php
public function connect(string $database)
{
$this->createTenantConnection($database);
$this->useConnection('tenant');
}
public function createTenantConnection(string $database_name)
{
// Create the `tenant` database connection.
$based_on = config('tenancy.database.based_on') ?: config('database.default');
config()->set([
'database.connections.tenant' => config('database.connections.' . $based_on),
]);
// Change DB name
$database_name = $this->getDriver() === 'sqlite' ? database_path($database_name) : $database_name;
config()->set(['database.connections.tenant.database' => $database_name]);
}
public function useConnection(string $connection)
{
// $this->database = Illuminate\Database\DatabaseManager
$this->database->setDefaultConnection($connection);
$this->database->reconnect($connection);
}
```
## Redis tenancy {#redis-tenancy}
The `bootstrap()` method calls `setPhpRedisPrefix()` if `tenancy.redis.tenancy` is `true`.
This method cycles through the `tenancy.redis.prefixed_connections` and sets their prefix to `tenancy.redis.prefix_base` + uuid.
```php
public function setPhpRedisPrefix($connections = ['default'])
{
// [...]
foreach ($connections as $connection) {
$prefix = $this->app['config']['tenancy.redis.prefix_base'] . $this->tenant['uuid'];
$client = Redis::connection($connection)->client();
try {
// [...]
$client->setOption($client::OPT_PREFIX, $prefix);
} catch (\Throwable $t) {
throw new PhpRedisNotInstalledException();
}
}
}
```
## Cache tenancy {#cache-tenancy}
`bootstrap()` calls `tagCache()` which replaces the `'cache'` key in the service container with a different `CacheManager`.
```php
public function tagCache()
{
// [...]
$this->app->extend('cache', function () {
return new \Stancl\Tenancy\CacheManager($this->app);
});
}
```
This `CacheManager` forwards all calls to the inner store, but also adds tag which "scope" the cache and allow for selective cache clearing:
```php
class CacheManager extends BaseCacheManager
{
public function __call($method, $parameters)
{
$tags = [config('tenancy.cache.tag_base') . tenant('uuid')];
if ($method === 'tags') {
if (\count($parameters) !== 1) {
throw new \Exception("Method tags() takes exactly 1 argument. {count($parameters)} passed.");
}
$names = $parameters[0];
$names = (array) $names; // cache()->tags('foo') https://laravel.com/docs/5.7/cache#removing-tagged-cache-items
return $this->store()->tags(\array_merge($tags, $names));
}
return $this->store()->tags($tags)->$method(...$parameters);
}
}
```
## Filesystem tenancy {#filesystem-tenancy}
`bootstrap()` calls `suffiexFilesystemRootPaths()`. This method changes `storage_path()` and the roots of disks listed in `config('tenancy.filesystem.disks)`. You can read more about this on the [Filesystem Tenancy]({{ $page->link('filesystem-tenancy') }}) page.
```php
public function suffixFilesystemRootPaths()
{
// [...]
$suffix = $this->app['config']['tenancy.filesystem.suffix_base'] . tenant('uuid');
// storage_path()
$this->app->useStoragePath($old['path'] . "/{$suffix}");
// Storage facade
foreach ($this->app['config']['tenancy.filesystem.disks'] as $disk) {
// [...]
if ($root = \str_replace('%storage_path%', storage_path(), $this->app['config']["tenancy.filesystem.root_override.{$disk}"])) {
Storage::disk($disk)->getAdapter()->setPathPrefix($root);
} else {
$root = $this->app['config']["filesystems.disks.{$disk}.root"];
Storage::disk($disk)->getAdapter()->setPathPrefix($root . "/{$suffix}");
}
}
// [...]
}
```

View file

@ -0,0 +1,112 @@
---
title: Tenant Manager
description: Tenant Manager | stancl/tenancy — A Laravel multi-database tenancy package that respects your code.
extends: _layouts.documentation
section: content
---
# Tenant Manager {#tenant-manager}
This page documents a couple of other `TenantManager` methods you may find useful.
### Finding tenant using UUID
`find()` is an alias for `getTenantById()`. You may use the second argument to specify the key(s) as a string/array.
```php
>>> tenant()->find('dbe0b330-1a6e-11e9-b4c3-354da4b4f339');
=> [
"uuid" => "dbe0b330-1a6e-11e9-b4c3-354da4b4f339",
"domain" => "localhost",
"foo" => "bar",
]
>>> tenant()->find('dbe0b330-1a6e-11e9-b4c3-354da4b4f339', 'foo');
=> [
"foo" => "bar",
]
>>> tenant()->getTenantById('dbe0b330-1a6e-11e9-b4c3-354da4b4f339', ['foo', 'domain']);
=> [
"foo" => "bar",
"domain" => "localhost",
]
```
### Getting tenant ID by domain
```php
>>> tenant()->getTenantIdByDomain('localhost');
=> "b3ce3f90-1a88-11e9-a6b0-038c6337ae50"
>>> tenant()->getIdByDomain('localhost');
=> "b3ce3f90-1a88-11e9-a6b0-038c6337ae50"
```
### Finding tenant by domain
You may use the second argument to specify the key(s) as a string/array.
```php
>>> tenant()->findByDomain('localhost');
=> [
"uuid" => "b3ce3f90-1a88-11e9-a6b0-038c6337ae50",
"domain" => "localhost",
]
```
### Accessing the array
You can access the public array tenant of TenantManager like this:
```php
tenancy()->tenant
```
which is an array. If you want to get the value of a specific key from the array, you can use one of the helpers the key on the tenant array as an argument.
```php
tenant('uuid'); // Does the same thing as tenant()->tenant['uuid']
```
### Getting all tenants
This method returns a collection of arrays.
```php
>>> tenant()->all();
=> Illuminate\Support\Collection {#2980
all: [
[
"uuid" => "32e20780-1a88-11e9-a051-4b6489a7edac",
"domain" => "localhost",
],
[
"uuid" => "49670df0-1a87-11e9-b7ba-cf5353777957",
"domain" => "dev.localhost",
],
],
}
>>> tenant()->all()->pluck('domain');
=> Illuminate\Support\Collection {#2983
all: [
"localhost",
"dev.localhost",
],
}
```
### Deleting a tenant
```php
>>> tenant()->delete('dbe0b330-1a6e-11e9-b4c3-354da4b4f339');
=> true
>>> tenant()->delete(tenant()->getTenantIdByDomain('dev.localhost'));
=> true
>>> tenant()->delete(tenant()->findByDomain('localhost')['uuid']);
=> true
```
This doesn't delete the tenant's database. If you want to delete it, save the database name prior to deleting the tenant. You can get the database name using `getDatabaseName()`
```php
>>> tenant()->getDatabaseName(tenant()->findByDomain('laravel.localhost'))
=> "tenant67412a60-1c01-11e9-a9e9-f799baa56fd9"
```

View file

@ -0,0 +1,34 @@
---
title: Tenant Routes
description: Tenant routes with stancl/tenancy — A Laravel multi-database tenancy package that respects your code..
extends: _layouts.documentation
section: content
---
# Tenant Routes {#tenant-routes}
Routes within `routes/tenant.php` will have the `web` middleware group and the `IntializeTenancy` middleware automatically applied on them. This middleware attempts to identify the tenant based on the current hostname. Once the tenant is identified, the database connection, cache, filesystem root paths and, optionally, Redis connection, will be switched.
Just like `routes/web.php`, these routes use the `App\Http\Controllers` namespace.
> If a tenant cannot be identified, anexception will be thrown. If you want to change this behavior (to a redirect, for example) read the [Middleware Configuration]({{ $page->link('middleware-configuration') }}) page.
## Exempt routes {#exempt-routes}
Routes outside the `routes/tenant.php` file will not have the tenancy middleware automatically applied on them. You can apply this middleware manually, though.
If you want some of your, say, API routes to be multi-tenant, simply wrap them in a Route group with this middleware:
```php
use Stancl\Tenancy\Middleware\InitializeTenancy;
Route::middleware(InitializeTenancy::class)->group(function () {
// Route::get('/', 'HelloWorld');
});
```
## Using the same routes for tenant and non-tenant parts of the application {#using-the-same-routes-for-tenant-and-non-tenant-parts-of-the-application}
The `Stancl\Tenancy\Middleware\PreventAccessFromTenantDomains` middleware makes sure 404 is returned when a user attempts to visit a web route on a tenant (non-exempt) domain.
The install command applies this middleware to the `web` group. If you want to do this for another route group, add this middleware manually to that group. You can do this in `app/Http/Kernel.php`.

View file

@ -0,0 +1,48 @@
---
title: Tenant Storage
description: Tenant storage with stancl/tenancy — A Laravel multi-database tenancy package that respects your code..
extends: _layouts.documentation
section: content
---
# Tenant Storage {#tenant-storage}
Tenant storage is where tenants' uuids and domains are stored. You can store things like the tenant's plan, subscription information, and tenant-specific application configuration in tenant storage. You can use these functions:
```php
get (string|array $key, string $uuid = null) // $uuid defaults to the current tenant's UUID
put (string|array $key, mixed $value = null, string $uuid = null) // if $key is array, make sure $value is null
```
To put something into the tenant storage, you can use `put()` or `set()`.
```php
tenancy()->put($key, $value);
tenancy()->set($key, $value); // alias for put()
tenancy()->put($key, $value, $uuid);
tenancy()->put(['key1' => 'value1', 'key2' => 'value2']);
tenancy()->put(['key1' => 'value1', 'key2' => 'value2'], null, $uuid);
```
To get something from the storage, you can use `get()`:
```php
tenancy()->get($key);
tenancy()->get($key, $uuid);
tenancy()->get(['key1', 'key2']);
```
> Note: `tenancy()->get(['key1', 'key2'])` returns an array with values only
Note that $key has to be a string or an array with string keys. The value(s) can be of any data type. Example with arrays:
```php
>>> tenant()->put('foo', ['a' => 'b', 'c' => 'd']);
=> [ // put() returns the supplied value(s)
"a" => "b",
"c" => "d",
]
>>> tenant()->get('foo');
=> [
"a" => "b",
"c" => "d",
]
```

View file

@ -0,0 +1,20 @@
---
title: Usage
description: Usage | stancl/tenancy — A Laravel multi-database tenancy package that respects your code..
extends: _layouts.documentation
section: content
---
# Usage {#usage}
This chapter describes usage of the package. That includes creating tenants, deleting tenants, storing data in the tenant storage.
Most pages will use the `tenancy()` helper function. This package comes with two helpers - `tenancy()` and `tenant()`. They do the same thing, so you can use the one that reads better given its context.
`tenant()->create()` reads better than `tenancy()->create()`, but `tenancy()->init()` reads better than `tenant()->init()`.
You can pass an argument to the helper function to get a value out of the tenant storage. `tenant('plan')` is identical to [`tenant()->get('plan')`]({{ $page->link('tenant-storage') }}).
The package also comes with two facades. `Tenancy` and `Tenant`. Use what feels the best.
Both the helpers and the facades resolve the `TenantManager` from the service container.

View file

@ -0,0 +1,80 @@
---
title: Writing Storage Drivers
description: Writing Storage Drivers with stancl/tenancy — A Laravel multi-database tenancy package that respects your code..
extends: _layouts.documentation
section: content
---
# Writing Storage Drivers
If you don't want to use the provided DB/Redis storage drivers, you can write your own driver.
To create a driver, create a class that implements the `Stancl\Tenancy\Interfaces\StorageDriver` interface.
For historical reasons, the `TenantManager` will try to json encode/decode data coming from the storage driver. If you want to avoid this, set `public $useJson = false;`. That will make `TenantManager` encode/decode only `put()` and `get()` data, so that data types can be stored correctly.
The DB storage driver has `public $useJson = false;`, while the Redis storage driver doesn't use this property, so it's false by default.
Here's an example:
```php
namespace App\StorageDrivers\MongoDBStorageDriver;
use Stancl\Tenancy\Interfaces\StorageDriver;
class MongoDBStorageDriver implements StorageDriver
{
public $useJson = false;
public function identifyTenant(string $domain): array
{
//
}
public function getAllTenants(array $uuids = []): array
{
//
}
public function getTenantById(string $uuid, array $fields = []): array
{
//
}
public function getTenantIdByDomain(string $domain): ?string
{
//
}
public function createTenant(string $domain, string $uuid): array
{
//
}
public function deleteTenant(string $uuid): bool
{
//
}
public function get(string $uuid, string $key)
{
//
}
public function getMany(string $uuid, array $keys): array
{
//
}
public function put(string $uuid, string $key, $value)
{
//
}
public function putMany(string $uuid, array $values): array
{
//
}
}
```

View file

@ -0,0 +1,158 @@
---
title: Application Testing
description: Application Testing..
extends: _layouts.documentation
section: content
---
# Application Testing {#application-testing}
> Note: At the moment it's not possible to use `:memory:` SQLite databases or the `RefreshDatabase` trait due to the switching of default database. This will hopefully change in the future.
### Initializing tenancy
You can create tenants in the `setUp()` method of your test case:
```php
protected function setUp(): void
{
parent::setUp();
tenancy()->create('test.localhost');
tenancy()->init('test.localhost');
}
```
If you don't want to initialize tenancy before each test, you may want to do something like this:
```php
class TestCase // extends ...
{
protected $tenancy = false;
public function setUp(): void
{
if ($this->tenancy) {
$this->initializeTenancy();
}
}
public function initializeTenancy($domain = 'test.localhost')
{
tenancy()->create($domain);
tenancy()->init($domain);
}
// ...
}
```
And in your individual test classes:
```php
class FooTest
{
protected $tenancy = true;
/** @test */
public function some_test()
{
$this->assertTrue(...);
}
}
```
### Cleanup
To delete tenants & their databases after tests, you may use this:
```php
public function tearDown(): void
{
config([
'tenancy.queue_database_deletion' => false,
'tenancy.delete_database_after_tenant_deletion' => true,
]);
tenancy()->all()->each->delete();
parent::tearDown();
}
```
### Storage setup
If you're using the database storage driver, you will need to run the migrations in `setUp()`:
```php
protected function setUp(): void
{
parent::setUp();
$this->artisan('migrate:fresh');
// ...
}
```
If you're using the Redis storage driver, flush the database in `setUp()`:
```php
protected function setUp(): void
{
parent::setUp();
// make sure you're using a different connection for testing to avoid losing data
Redis::connection('tenancyTesting')->flushdb();
// ...
}
```
### Sample TestCase
Put together, here's a ready-to-use base TestCase for the DB storage driver
```php
<?php
namespace Tests;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase
{
use CreatesApplication;
public function setUp(): void
{
parent::setUp();
$this->artisan('migrate:fresh');
config([
'tenancy.queue_database_creation' => false,
]);
config(['tenancy.exempt_domains' => [
'127.0.0.1',
'localhost',
]]);
}
public function tearDown(): void
{
config([
'tenancy.queue_database_deletion' => false,
'tenancy.delete_database_after_tenant_deletion' => true,
]);
tenancy()->all()->each->delete();
parent::tearDown();
}
}
```
phpunit.xml:
```xml
<server name="DB_CONNECTION" value="sqlite"/>
<server name="DB_DATABASE" value="database/testing.sqlite"/>
```
> Don't forget to create an empty database/testing.sqlite
You may also wish to add `testing.sqlite` to `database/.gitignore`.

View file

@ -0,0 +1,16 @@
---
title: Cached Tenant Lookup
description: Cached Tenant Lookup
extends: _layouts.documentation
section: content
---
# Cached Tenant Lookup {#cached-tenant-lookup}
If you're using the database storage driver, you may want to cache tenant lookup (domain -> tenant id -> `Tenant` object mapping). Running DB queries on each request to identify the tenant is somewhat expensive, as a separate database connection has to be established.
To avoid this, you may want to enable caching.
You may enable this feature by setting the `tenancy.storage_drivers.db.cache_store` config key to the name of your cache store (e.g. `redis`), and optionally setting `cache_ttl` (default is 3600 seconds).
The cache invalidation of course happens automatically, as long as you modify your tenants using `Tenant` objects and not direct DB calls.

View file

@ -0,0 +1,54 @@
---
title: Central App
description: Central App
extends: _layouts.documentation
section: content
---
# Central App {#central-app}
This package uses routes to separate the tenant part of the application from the central part of the application. The central part will commonly include a landing page, sign up form, and some sort of dashboard.
## Central routes {#central-routes}
Routes in the `routes/web.php` file are the central routes. When they are visited, tenancy is *not* intialized and any model, cache call, controller, job dispatch, Redis call and anything else that is called in during this request will be central.
## Central domains {#central-domains}
However, since you don't want routes related to the app on your main domain and sign up forms on tenant domains, you must also define what domains host the central stuff in the `tenancy.exempt_domains` config.
The exempt domains should not include the protocol. For example, you should include `example.com` rather than `https://example.com`.
## Using central things inside the tenant app {#using-central-things-inside-the-tenant-app}
To use central things (databases/caches/Redis connections/filesystems/...) on special places of your tenant app, you may do the following.
### Central database {#central-database}
Create a new connection and use it like `DB::connection($connectionName)->table('foo')->where(...)`
If you want to use models, create a `getConnectionName()` method that returns the name of the central connection
### Central redis {#central-redis}
Create a new connection, *don't* put it into `tenancy.redis.prefixed_connections`, and use it like `Redis::connection('foo')->get('bar')`
### Central cache {#central-cache}
Use the `GlobalCache` facade, or the `global_cache()` helper.
### Central storage {#central-storage}
Create a disk and *don't* add it to `tenancy.filesystem.disks`.
### Central assets {#central-assets}
Mix is intended for template-related assets and as such, it's not scoped to the current tenant.
Alternatively, the package provides a `global_asset()` helper which is a non-tenant-aware replacement for `asset()`, in case you don't want to use `mix()`.
It's recommended to use `mix()` though, due to its features such as version tagging.
### Central queues {#central-queues}
Create a new queue connection with the `central` key set to `true`.

View file

@ -0,0 +1,137 @@
---
title: Configuration
description: Configuring stancl/tenancy
extends: _layouts.documentation
section: content
---
# Configuration {#configuration}
The `config/tenancy.php` file lets you configure how the package behaves.
> If the `tenancy.php` file doesn't exist in your `config` directory, you can publish it by running `php artisan vendor:publish --provider='Stancl\Tenancy\TenancyServiceProvider' --tag=config`
### `storage_driver, storage` {#storage}
This lets you configure the driver for tenant storage, i.e. what will be used to store information about your tenants. You can read more about this on the [Storage Drivers]({{ $page->link('storage-drivers') }}) page.
Available storage drivers:
- `Stancl\Tenancy\StorageDrivers\RedisStorageDriver`
- `Stancl\Tenancy\StorageDrivers\Database\DatabaseStorageDriver`
#### db {#db-storage-driver}
- `data_column` - the name of column that holds the tenant's data in a single JSON string
- `custom_columns` - list of keys that shouldn't be put into the data column, but into their own column
- `connection` - what database connection should be used to store tenant data (`null` means the default connection)
- `table_names` - the table names used by the models that come with the storage driver
> Note: Don't use the models directly. You're supposed to use [storage]({{ $page->link('tenant-storage') }}) methods on `Tenant` objects.
#### redis {#redis-db-driver}
- `connection` - what Redis connection should be used to store tenant data. See the [Storage Drivers]({{ $page->link('storage-drivers') }}) documentation.
### `tenant_route_namespace` {#tenant-route-namespace}
Controller namespace used for routes in `routes/tenant.php`. The default value is the same as the namespace for `web.php` routes.
### `exempt_domains` {#exempt-domains}
If a hostname from this array is visited, the `tenant.php` routes won't be registered, letting you use the same routes as in that file. This should be the domain without the protocol (i.e., `example.com` rather than `https://example.com`).
### `database` {#database}
The application's default connection will be switched to a new one — `tenant`. This connection will be based on the connection specified in `tenancy.database.based_on`. The database name will be `tenancy.database.prefix + tenant id + tenancy.database.suffix`.
You can set the suffix to `.sqlite` if you're using sqlite and want the files to be with the `.sqlite` extension. Conversely, you can leave the suffix empty if you're using MySQL, for example.
### `redis` {#redis}
If the `RedisTenancyBootstrapper` is enabled (see `bootstrappers` below), any connections listed in `tenancy.redis.prefixed_connections` will be prefixed with `config('tenancy.redis.prefix_base') . $id`.
> Note: You need phpredis. Predis support will dropped by Laravel in version 7.
### `cache` {#cache}
The `CacheManager` instance that's resolved when you use the `Cache` or the `cache()` helper will be replaced by `Stancl\Tenancy\CacheManager`. This class automatically uses [tags](https://laravel.com/docs/master/cache#cache-tags). The tag will look like `config('tenancy.cache.tag_base') . $id`.
### `filesystem` {#filesystem}
The `storage_path()` will be suffixed with a directory named `config('tenancy.filesystem.suffix_base') . $id`.
The root of each disk listed in `tenancy.filesystem.disks` will be suffixed with `config('tenancy.filesystem.suffix_base') . $id`.
For disks listed in `root_override`, the root will be that string with `%storage_path%` replaced by `storage_path()` *after* tenancy has been initialized. All other disks will be simply suffixed with `tenancy.filesystem.suffix_base` + the tenant id.
Read more about this on the [Filesystem Tenancy]({{ $page->link('filesystem-tenancy') }}) page.
### `database_managers` {#database_managers}
Tenant database managers handle the creation & deletion of tenant databases. This configuration array maps the database driver name to the `TenantDatabaseManager`, e.g.:
```php
'mysql' => Stancl\Tenancy\TenantDatabaseManagers\MySQLDatabaseManager::class
```
### `database_manager_connections` {#database_maanger_connections}
Connections used by TenantDatabaseManagers. They tell, for example, that the manager for the `mysql` driver (`MySQLDatabaseManager`) should use the `mysql` connection. You may want to change this if your connection is named differently, e.g. a MySQL connection named `central`.
### `bootstrappers` {#bootstrappers}
These are the classes that do the magic. When tenancy is initialized, TenancyBootstrappers are executed, making Laravel tenant-aware.
This config is an array. The key is the alias and the value is the full class name.
```php
'cache' => Stancl\Tenancy\TenancyBootstrappers\CacheTenancyBootstrapper::class,
```
The aliases are used by the [event system]({{ $page->link('hooks') }})
### `features` {#bootstrappers}
[Features]({{ $page->link('optional-features') }}) are similar to bootstrappers, but they are executed regardless of whether tenancy has been initialized or not. Their purpose is to provide additional functionality beyond what is necessary for the package to work. Things like easy redirects to tenant domains, tags in Telescope, etc.
### `home_url` {#home-url}
When a user tries to visit a non-tenant route on a tenant domain, the `PreventAccessFromTenantDomains` middleware will return a redirect to this url.
### `queue_database_creation` {#queue-database-creation}
- Default: `false`
### `migrate_after_creation` {#migrate-after-creation}
Run migrations after creating a tenant.
- Default: `false`
### `seed_after_migration` {#seed-after-migration}
Run seeds after creating a tenant.
- Default: `false`
### `seeder_parameters` {#seeder_parameters}
Parameters passed to the `tenants:seed` command.
- Default: `['--class' => 'DatabaseSeeder']`
### `delete_database_after_tenant_deletion` {#delete-database-after-tenant-deletion}
Delete the tenant's database after deleting the tenant.
- Default: `false`
### `queue_database_deletion` {#queue-database-deletion}
- Default: `false`
### `unique_id_generator` {#unique-id-generator}
The class used to generate a random tenant ID (when no ID is supplied during the tenant creation process).
- Default: `Stancl\Tenancy\UUIDGenerator`

View file

@ -0,0 +1,91 @@
---
title: Console Commands
description: Console commands..
extends: _layouts.documentation
section: content
---
# Console Commands {#console-commands}
The package comes with some artisan commands that will help you during development.
## Migrate {#migrate}
The most important command. To use tenants, you have to be able to migrate their databases.
You can use the `tenants:migrate` command to migrate tenant's databases. You can also specify which tenants' databases should be migrated using the `--tenants` option.
```
php artisan tenants:migrate --tenants=8075a580-1cb8-11e9-8822-49c5d8f8ff23
```
You may use multiple `--tenants=<...>` options.
> Note: Tenant migrations must be located in `database/migrations/tenant`.
You can use these commands outside the command line as well. If you want to migrate a tenant's database in a controller, you can use the `Artisan` facade.
```php
$tenant = tenancy()->create('tenant1.localhost');
\Artisan::call('tenants:migrate', [
'--tenants' => [$tenant['id']]
]);
```
## Rollback & seed {#rollback}
- Rollback: `tenants:rollback`
- Seed: `tenants:seed`
Similarly to [migrate](#migrate), these commands accept a `--tenants` option.
## Migrate fresh {#migrate-fresh}
This package also offers a simplified, tenant-aware version of `migrate:fresh`. It runs `db:wipe` and `tenants:migrate` on the tenant's database.
You may use it like this:
```none
php artisan tenants:migrate-fresh --tenants=8075a580-1cb8-11e9-8822-49c5d8f8ff23
```
## Run {#run}
You can use the `tenants:run` command to run your own commands for tenants.
If your command's signature were `email:send {--queue} {--subject=} {body}`, you would run this command like this:
```
php artisan tenants:run email:send --tenants=8075a580-1cb8-11e9-8822-49c5d8f8ff23 --option="queue=1" --option="subject=New Feature" --argument="body=We have launched a new feature. ..."
```
## Tenant list {#tenant-list}
```none
php artisan tenants:list
Listing all tenants.
[Tenant] id: dbe0b330-1a6e-11e9-b4c3-354da4b4f339 @ localhost
[Tenant] id: 49670df0-1a87-11e9-b7ba-cf5353777957 @ dev.localhost
```
## Create tenant {#create-tenant}
This command lets you create tenants from the command line. You may find this useful if you need to create tenants from some service that's separate from your app.
You may specify any amount of domains using `-d <domain>`. To set data during the creation process, add arguments of the `<key>=<value>` format to the end of the call.
For example:
```none
php artisan tenants:create -d aaa.localhost -d bbb.localhost plan=free email=foo@test.local
5f6dbfb8-41da-4398-a361-5342a98d81a0
```
The command returns the created tenant's id.
## Selectively clearing tenant cache {#selectively-clearing-tenant-cache}
You can delete specific tenants' cache by using the `--tags` option on `cache:clear`:
```
php artisan cache:clear --tags=tenantdbe0b330-1a6e-11e9-b4c3-354da4b4f339
```
The tag is derived from `config('tenancy.cache.tag_base') . $id`.

View file

@ -0,0 +1,38 @@
---
title: Creating Tenants
description: Creating tenants
extends: _layouts.documentation
section: content
---
# Creating Tenants {#creating-tenants}
> **Make sure your database is correctly [configured]({{ $page->link('configuration/#database') }}) before creating tenants.**
To create a tenant, you can use
```php
use Stancl\Tenancy\Tenant;
Tenant::new()
->withDomains(['tenant1.yourapp.com', 'tenant1.com'])
->withData(['plan' => 'free'])
->save();
```
> Tip: All domains under `.localhost` are routed to 127.0.0.1 on most operating systems. This is useful for development.
The `withDomains()` and `withData()` methods are optional.
You can also create a tenant using a single method: `Tenant::create`:
```php
$domains = ['tenant1.myapp.com', 'tenant1.com'];
Tenant::create($domains, [
'plan' => 'free',
]);
```
`Tenant::create()` works with both `Stancl\Tenancy\Tenant` and the facade, `\Tenant`.
> Note: By default, creating a tenant doesn't run [migrations]({{ $page->link('tenant-migrations' )}}) automatically. You may change this behavior using the `migrate_after_creation` [configuration]({{ $page->link('configuration#migrate-after-creation') }}).

View file

@ -0,0 +1,20 @@
---
title: Custom Database Names
description: Custom Database Names..
extends: _layouts.documentation
section: content
---
# Custom Database Names {#custom-database-names}
To set a specific database name for a tenant, set the `_tenancy_db_name` key in the tenant's storage.
You should do this during the tenant creation process, to make sure the right database name is used during database creation.
```php
use Stancl\Tenancy\Tenant;
Tenant::create('example.com', [
'_tenancy_db_name' => 'example_com'
])
```

View file

@ -0,0 +1,26 @@
---
title: Custom Database Connections
description: Custom Database Connections
extends: _layouts.documentation
section: content
---
# Custom Database Connections {#custom-database-names}
To set a specific database connection for a tenant, set the `_tenancy_db_connection` key in the tenant's storage. The connection's database name will be still replaced by the tenant's database name. You can [customize that]({{ $page->link('custom-database-names') }}) too.
You may want custom connections to be dynamic (rather than adding them to the DB config manually), so can use something like this:
```php
// Make new tenants use your connection "template"
Tenant::new()->withData([
'_tenancy_db_connection' => 'someTenantConnectionTemplate',
]);
// Make tweaks to the connection before bootstrapping tenancy
tenancy()->hook('bootstrapping', function ($tenantManager) {
config(['database.connections.someTenantConnectionTemplate.name' => $tenantManager->getTenant('database_name')]);
config(['database.connections.someTenantConnectionTemplate.password' => $tenantManager->getTenant('database_password')]);
config(['database.connections.someTenantConnectionTemplate.host' => $tenantManager->getTenant('database_host')]);
});
```

View file

@ -0,0 +1,41 @@
---
title: Difference Between This Package And Others
description: Difference Between This Package And Others
extends: _layouts.documentation
section: content
---
# Difference Between This Package And Others
A frequently asked question is the difference between this package and other tenancy packages for Laravel.
## tenancy/multi-tenant (previously hyn/multi-tenant)
tenancy/multi-tenant gives you an API for making your application multi-tenant yourself. It gives you a tenant DB connection, traits to apply on your models, a guide on creating your own tenant-aware cache, etc.
## tenancy/tenancy (currently in alpha)
tenancy/tenancy is even less opinionated and is more of a framework to write your own tenancy implementation. For example, there is no tenant object. There is a tenant interface that you implement on some model in your application.
## stancl/tenancy
This package takes a completely new approach to multi-tenancy.
It makes your application multi-tenant automatically and attempts to make you not have to change anything in your code. The philosophy behind this approach is that you should write your app, not tenancy boilerplate.
We belive that your code will be a lot cleaner if tenancy and the actual app don't mix. Why pollute your code with tons of tenancy implementations, when you can push all of tenancy one layer below your actual application?
Apart from saving you a ton of time, the benefit of going with the automatic approach is that you can adapt easily, since you're not bound to a specific implementation of multi-tenancy. [You can always change how tenancy is bootstrapped.]({{ $page->link('tenancy-bootstrappers') }})
This approach is also more secure. Say you have written a SaaS. The application is finished &mdash; now you just need to make it multi-tenant before releasing it. With the tenancy/\* packages, you will have to rewrite significant portions of your code and hope you did not forget to change each, for example, `Cache::` call to some tenant-aware cache that you implement yourself.
With stancl/tenancy, you just install the package, decide what routes belong to the "central" part of the app and what routes belong to the tenant part of the app, and tell the config file on what domains you host the central app &mdash; the landing page & sign up form.
Everything else happens automatically in the background:
- Database connection is switched
- Cache is made multi-tenant
- Filesystem is made multi-tenant
- Queued jobs are made multi-tenant
- Redis is made multi-tenant
This means that you can also integrate with any packages you would normally use, without any difficulties.

View file

@ -0,0 +1,9 @@
---
title: Digging Deeper
description: Digging Deeper..
extends: _layouts.documentation
section: content
---
# Digging Deeper {#digging-deeper}

View file

@ -0,0 +1,16 @@
---
title: Disabling Database Creation
description: Disabling Database Creation
extends: _layouts.documentation
section: content
---
# Disabling Database Creation {#disabling-database-creation}
DB creation can be disabled for all tenants (`tenancy.create_database` config), or for individual tenants during tenant creation:
```php
Tenant::new()->withData([
'_tenancy_create_database' => false,
])->save();
```

View file

@ -0,0 +1,12 @@
---
title: Optional Features
description: Optional Features
extends: _layouts.documentation
section: content
---
# Optional Features {#optional-features}
Similarly to [TenancyBootstrappers]({{ $page->link('tenancy-bootstrappers') }}), `Features` are classes that provide extra functionality and can be enabled in the config. They're all disabled by default, because they're not critical to the core tenancy scaffolding.
You may enable and disable `Features` in the `tenancy.features` configuration.

View file

@ -0,0 +1,34 @@
---
title: Tenant Config
description: Tenant Config
extends: _layouts.documentation
section: content
---
# Tenant Config {#tenant-config}
It's likely you will need to use tenant-specific config in your application. That config could be API keys, things like "products per page" and many other things.
You could just use the [tenant storage]({{ $page->link('tenant-storage') }}) to get these values, but you may still want to use Laravel's `config()` because of:
- separation of concerns -- if you just write tenancy implementation-agnostic `config('shop.products_per_page')`, you will have a much better time changing tenancy implementations
- default values -- you may want to use the tenant storage only to override values in your config file
## Enabling the feature
Uncomment the following line in your `tenancy.features` config:
```php
// Stancl\Tenancy\Features\TenantConfig::class,
```
## Configuring the mappings
This feature maps keys in the tenant storage to config keys based on the `tenancy.storage_to_config_map` config.
For example, if your `storage_to_config_map` looked like this:
```php
'storage_to_config_map' => [
'paypal_api_key' => 'services.paypal.api_key',
],
```
the value of `paypal_api_key` in [tenant storage]({{ $page->link('tenant-storage') }}) would be copied to the `services.paypal.api_key` config when tenancy is initialized.

View file

@ -0,0 +1,17 @@
---
title: Tenant Redirect
description: Tenant Redirect
extends: _layouts.documentation
section: content
---
# Tenant Redirect {#tenant-redirect}
> To enable this feature, uncomment the `Stancl\Tenancy\Features\TenantRedirect::class` line in your `tenancy.features` config.
A customer has signed up on your website, you have created a new tenant and now you want to redirect the customer to their website. You can use the `tenant()` method on Redirect, like this:
```php
// tenant sign up controller
return redirect()->route('dashboard')->tenant($domain);
```

View file

@ -0,0 +1,16 @@
---
title: Timestamps
description: Timestamps
extends: _layouts.documentation
section: content
---
# Timestamps {#timestamps-redirect}
> To enable this feature, uncomment the `Stancl\Tenancy\Features\Timestamps::class` line in your `tenancy.features` config.
This `Feature` adds the following timestamps into the tenant storage:
- `created_at`
- `updated_at`
- `deleted_at` - for soft deletes

View file

@ -0,0 +1,69 @@
---
title: Filesystem Tenancy
description: Filesystem Tenancy..
extends: _layouts.documentation
section: content
---
# Filesystem Tenancy {#filesystem-tenancy}
> Note: It's important to differentiate between storage_path() and the Storage facade. The Storage facade is what you use to put files into storage, i.e. `Storage::disk('local')->put()`. `storage_path()` is used to get the path to the storage directory.
The `storage_path()` will be suffixed with a directory named `config('tenancy.filesystem.suffix_base') . $id`.
The root of each disk listed in `tenancy.filesystem.disks` will be suffixed with `config('tenancy.filesystem.suffix_base') . $id`.
**However, this alone would cause unwanted behavior.** It would work for S3 and similar disks, but for local disks Laravel does its own suffixing. For local storage we need the second of these examples:
```
/path_to_your_application/storage/app/tenant1e22e620-1cb8-11e9-93b6-8d1b78ac0bcd/
/path_to_your_application/storage/tenant1e22e620-1cb8-11e9-93b6-8d1b78ac0bcd/app/
```
Why? Because `storage_path()` returns:
`/path_to_your_application/storage/tenant1e22e620-1cb8-11e9-93b6-8d1b78ac0bcd/`
so Laravel's `storage_path('app')` means appending `app` to that.
That's what the `root_override` section is for. `%storage_path%` gets replaced by `storage_path()` *after* tenancy has been initialized. The roots of disks listed in the `root_override` section of the config will be replaced accordingly. All other disks will be simply suffixed with `tenancy.filesystem.suffix_base` + the tenant id.
Since `storage_path()` will be suffixed, your folder structure will look like this:
![The folder structure](https://i.imgur.com/GAXQOnN.png)
If you write to these directories, you will need to create them after you create the tenant. See the docs for [PHP's mkdir](http://php.net/function.mkdir).
Logs will be saved to `storage/logs` regardless of any changes to `storage_path()`, and regardless of tenant.
## Assets {#assets}
Laravel's `asset()` helper has two different paths of execution:
- If `config('app.asset_url')` has been set, it will simply append `tenant$id` to the end of the configured asset URL. This is useful if you use Laravel Vapor. Vapor sets the asset URL to something like `https://abcdefghijkl.cloudfrount.net/123-456-789`. That is the root for your assets. This package will append that with something like `tenant1e22e620-1cb8-11e9-93b6-8d1b78ac0bcd`.
- If `config('app.asset_url')` is null, as it is by default, the helper will return a URL (`/tenancy/assets/...`) to a controller provided by this package. That controller returns a file response from `storage_path("app/public/$path")`. This means that you need to store your assets in the public directory.
> Note: In 1.x, the `asset()` helper was not tenant-aware, and there was a `tenant_asset()` helper that followed the second option in the list above (a link to a controller). For backwards compatibility, that helper remains intact.
> If you have some non-tenant-specific assets, you may use the package's `global_asset()` helper.
Note that all tenant assets have to be in the `app/public/` subdirectory of the tenant's storage directory, as shown in the image above.
This is what the backend of `tenant_asset()` (and `asset()` when no asset URL is configured) returns:
```php
// TenantAssetsController
return response()->file(storage_path('app/public/' . $path));
```
With default filesystem configuration, these two commands are equivalent:
```php
Storage::disk('public')->put($filename, $data);
Storage::disk('local')->put("public/$filename", $data);
```
Note that every request for a tenant asset requires a full framework boot and tenancy initialization. This is not ideal if you have some assets that occur on each page (like logos). So for non-private assets, you may want to create a disk and use URLs from that disk instead. For example:
```php
Storage::disk('app-public')->url('tenants/logos/' . tenant()->id . '.png');
```
If you want to store something globally, simply create a new disk and *don't* add it to the `tenancy.filesystem.disks` config.

View file

@ -0,0 +1,34 @@
---
title: Getting Started
description: Getting started.
extends: _layouts.documentation
section: content
---
# Getting Started {#getting-started}
[**stancl/tenancy**](https://github.com/stancl/tenancy) is a Laravel multi-database tenancy package. It makes your app multi-tenant in a way that requires no changes to the codebase. Instead of applying traits on models and replacing every single reference to cache by a reference to a tenant-aware cache, the package lets you write your app without thinking about tenancy. It handles tenancy automatically in the background.
## How does it work? {#how-does-it-work}
A user visits `client1.yourapp.com`. The package identifies the tenant who this domain belongs to, and automatically does the following:
- switches database connection
- replaces the default cache manager
- switches Redis connection
- changes filesystem root paths
- makes jobs automatically tenant-aware
The benefits of this being taken care of by the package are:
- separation of concerns: you should write your app, not tenancy implementations
- reliability: you won't have to fear that you forgot to replace a reference to cache by a tenant-aware cache call. This is something you might worry about if you're implementing tenancy into an existing application.
## What is multi-tenancy? {#what-is-multi-tenancy}
Multi-tenancy is the ability to provide your application to multiple customers (who have their own users and other resources) from a single instance of your application. Think Slack, Shopify, etc.
Multi-tenancy can be single-database and multi-database.
**Single-database tenancy** means that your application uses only a single database. The way this is usually implemented is that instead of having the `id`, `title`, `user_id` and `body` columns in your `posts` table, you will also have a `tenant_id` column. This approach works until you need custom databases for your clients. It's also easy to implement, it basically boils down to having your models use a trait which adds a [global scope](https://laravel.com/docs/master/eloquent#global-scopes).
Again, to be clear: This package does not provide single-database tenancy features.
**Multi-database tenancy**, the type that this package provides, lets you use a separate database for each tenant. The benefits of this approach are scalability, compliance (some clients need to have the database on their server) and mitigation of risks such as showing the wrong tenant's data to a user.

View file

@ -0,0 +1,86 @@
---
title: Hooks / The Event System
description: Hooks / The Event System
extends: _layouts.documentation
section: content
---
# Hooks / The Event System
You can use event hooks to change the behavior of the package.
All hook callbacks receive the `TenantManager` as the first argument.
## Tenant events
A common use case for these events is seeding the tenant data during creation:
```php
// AppServiceProvider::boot()
tenancy()->hook('tenant.creating', function (TenantManager $tm, Tenant $tenant) {
$tenant->put([
'posts_per_page' => '15',
]);
});
```
The following events are available:
- `tenant.creating`
- `tenant.created`
- `tenant.updating`
- `tenant.updated`
- `tenant.deleting`
- `tenant.deleted`
- `tenant.softDeleting`
- `tenant.softDeleted`
Callbacks for these events may accept the following arguments:
```php
TenantManager $tenantManager, Tenant $tenant
```
## Database events
A use case for these events is executing something after the tenant database is created (& migrated/seeded) without running into race conditions.
Say you have a `AfterCreatingTenant` job that creates a superadmin user. You may use the `database.creating` event to add this job into the queue chain of the job that creates the tenant's database.
```php
tenancy()->hook('database.creating', function (TenantManager $tm, string $db, Tenant $tenant) {
return [
new AfterCreatingTenant($tenant->id);
]
});
```
The following events are available:
- `database.creating`
- `database.created`
- `database.deleting`
- `database.deleted`
Callbacks for these events may accept the following arguments:
```php
TenantManager $tenantManager, string $db, Tenant $tenant
```
## Bootstrapping/ending events
The following events are available:
- `bootstrapping`
- `bootstrapped`
- `ending`
- `ended`
You may use the `bootstrapping` & `ending` events to prevent some bootstrappers from being executed.
The following actions can be prevented:
- database connection switch: `database`
- Redis prefix: `redis`
- CacheManager switch: `cache`
- Filesystem changes: `filesystem`
- Queue tenancy: `queue`
- and anything else listed in the [`tenancy.bootstrappers` config]({{ $page->link('configuration#bootstrappers') }})
Callbacks for these events may accept the following arguments:
```php
TenantManager $tenantManager, Tenant $tenant
```

View file

@ -0,0 +1,18 @@
---
title: Horizon Integration
description: Horizon Integration..
extends: _layouts.documentation
section: content
---
# Horizon Integration
> Make sure your queue is [correctly configured]({{ $page->link('jobs-queues') }}) before using Horizon.
Jobs are automatically tagged with the tenant's id and domain:
![id and domain tags](https://i.imgur.com/K2oWTJc.png)
You can use these tags to monitor specific tenants' jobs:
![Monitoring tags](https://i.imgur.com/qB6veK7.png)

View file

@ -0,0 +1,40 @@
---
title: HTTPS Certificates
description: HTTPS Certificates..
extends: _layouts.documentation
section: content
---
# HTTPS Certificates
HTTPS certificates are very easy to deal with if you use the `yourclient1.yourapp.com`, `yourclient2.yourapp.com` model. You can use a wildcard HTTPS certificate.
If you use the model where second level domains are used, there are multiple ways you can solve this.
This guide focuses on nginx.
### 1. Use nginx with the lua module
Specifically, you're interested in the [`ssl_certificate_by_lua_block`](https://github.com/openresty/lua-nginx-module#ssl_certificate_by_lua_block) directive. Nginx doesn't support using variables such as the hostname in the `ssl_certificate` directive, which is why the lua module is needed.
This approach lets you use one server block for all tenants.
### 2. Add a simple server block for each tenant
You can store most of your config in a file, such as `/etc/nginx/includes/tenant`, and include this file into tenant server blocks.
```nginx
server {
include includes/tenant;
server_name foo.bar;
# ssl_certificate /etc/foo/...;
}
```
### Generating certificates
You can generate a certificate using certbot. If you use the `--nginx` flag, you will need to run certbot as root. If you use the `--webroot` flag, you only need the user that runs it to have write access to the webroot directory (or perhaps webroot/.well-known is enough) and some certbot files (you can specify these using --work-dir, --config-dir and --logs-dir).
Creating this config dynamically from PHP is not easy, but is probably feasible. Giving `www-data` write access to `/etc/nginx/sites-available/tenants.conf` should work.
However, you still need to reload nginx configuration to apply the changes to configuration. This is problematic and I'm not sure if there is a simple and secure way to do this from PHP.

View file

@ -0,0 +1,8 @@
<!DOCTYPE HTML>
<html lang="en-US">
<head>
<meta charset="UTF-8">
<meta http-equiv="refresh" content="0; url=getting-started">
<title>stancl/tenancy</title>
</head>
</html>

View file

@ -0,0 +1,77 @@
---
title: Installation
description: Installing stancl/tenancy
extends: _layouts.documentation
section: content
---
# Installation {#getting-started}
Laravel 6.0 or higher is needed.
### Require the package via composer
First you need to require the package using composer:
```
composer require stancl/tenancy
```
### Automatic installation {#automatic-installation}
To install the package, simply run
```
php artisan tenancy:install
```
This will do all the steps listed in the [Manual installation](#manual-installation) section for you.
You will be asked if you want to store your data in a relational database or Redis. Continue to the next page ([Storage Drivers]({{ $page->link('storage-drivers') }})) to know what that means.
### Manual installation {#manual-installation}
If you prefer installing the package manually, you can do that too. It shouldn't take more than a minute either way.
#### Setting up middleware
Now open `app/Http/Kernel.php` and make the package's middleware classes top priority, so that they get executed before anything else, making sure things like the database switch connections soon enough:
```php
protected $middlewarePriority = [
\Stancl\Tenancy\Middleware\PreventAccessFromTenantDomains::class,
\Stancl\Tenancy\Middleware\InitializeTenancy::class,
// ...
];
```
Add the `\Stancl\Tenancy\Middleware\PreventAccessFromTenantDomains::class` middleware to all route groups you use, so that's probably `'web'` and possibly `'api'`:
```php
protected $middlewareGroups = [
'web' => [
\Stancl\Tenancy\Middleware\PreventAccessFromTenantDomains::class,
// ...
],
// ...
]
```
#### Creating routes
The package lets you have tenant routes and "exempt" routes. Tenant routes are your application's routes. Exempt routes are routes exempt from tenancy — landing pages, sign up forms, and routes for managing tenants.
Routes in `routes/web.php` are exempt, while routes in `routes/tenant.php` have the tenancy middleware automatically applied to them.
So, to create tenant routes, put those routes in a new file called `routes/tenant.php`.
#### Configuration
Run the following:
```
php artisan vendor:publish --provider='Stancl\Tenancy\TenancyServiceProvider' --tag=config
```
This creates a `config/tenancy.php`. You can use it to configure how the package works.
Configuration is explained in detail on the [Configuration]({{ $page->link('configuration') }}) page.

View file

@ -0,0 +1,14 @@
---
title: Integrations
description: Integrating stancl/tenancy
extends: _layouts.documentation
section: content
---
# Integrations {#integrations}
This package naturally integrates well with Laravel packages, since it does not rely on you explicitly specifying database connections.
There are some exceptions, though. [Telescope integration]({{ $page->link('telescope') }}), for example, requires you to change the database connection in `config/telescope.php` to a non-default one, because the default connection is switched to the tenant connection. Some packages should use a central connection for data storage.
> You may be thinking, why does the DB storage driver work with the default, central DB connection, but Telescope doesn't? It's because the DB storage driver intelligently uses the **original** default DB connection, if it has been changed.

View file

@ -0,0 +1,25 @@
---
title: Jobs & Queues
description: Jobs & Queues..
extends: _layouts.documentation
section: content
---
# Jobs & Queues {#jobs-queues}
Jobs are automatically multi-tenant, which means that if a job is dispatched while tenant A is initialized, the job will operate with tenant A's database, cache, filesystem, and Redis.
**However**, if you're using the `database` or `redis` queue driver, you have to make a small tweak to your queue configuration.
Open `config/queue.php` and make sure your queue driver has an explicitly set connection. Otherwise it would use the default one, which would cause issues, since `database.default` is changed by the package and Redis connections are prefixed.
**If you're using `database`, add a new line to `queue.connections.database` and `queue.failed`:**
```php
'connection' => 'mysql',
```
where `'mysql'` is the name of your non-tenant database connection with a `jobs` table.
Also make sure you run the queue migrations **for the central database**, not your tenants.
**If you're using Redis, make sure its `'connection'` is not in `tenancy.redis.prefixed_connections`.**

View file

@ -0,0 +1,21 @@
---
title: Livewire Integration
description: Livewire Integration
extends: _layouts.documentation
section: content
---
# Livewire Integration {#livewire-integration}
Open the `config/livewire.php` file and change this line:
```php
'middleware_group' => ['web'],
```
to this:
```php
'middleware_group' => ['web', 'universal'],
```
Now you can use Livewire both in the central app and the tenant app.

View file

@ -0,0 +1,46 @@
---
title: Middleware Configuration
description: Middleware Configuration..
extends: _layouts.documentation
section: content
---
# Middleware Configuration {#middleware-configuration}
## Header or query parameter based identification {#header-or-query-parameter-based-identification}
To identify tenants using request headers or query parameters, you may use the `InitializeTenancyByRequestData` middleware.
Create a **central** route (don't apply the `tenancy` middleware group on it and don't put it into `routes/tenant.php`) and apply this middleware on the route:
```php
\Stancl\Tenancy\Middleware\InitializeTenancyByRequestData::class
```
To customize the header, query parameter, and `onFail` logic, you may do this in your `AppServiceProvider::boot()`:
```php
// use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData::class;
$this->app->bind(InitializeTenancyByRequestData::class, function ($app) {
return new InitializeTenancyByRequestData('header name', 'query parameter', function ($exception) {
// return redirect()->route('foo');
});
});
```
To disable identification using header or query parameter, set the respective parameter to `null`.
## Customizing the onFail logic {#customizing-the-onfail-logic}
When a tenant route is visited and the tenant can't be identified, an exception is thrown. If you want to change this behavior, to a redirect for example, add this to your `app/Providers/AppServiceProvider.php`'s `boot()` method:
```php
// use Stancl\Tenancy\Middleware\InitializeTenancy;
$this->app->bind(InitializeTenancy::class, function ($app) {
return new InitializeTenancy(function ($exception, $request, $next) {
// return redirect()->route('foo');
});
});
```

View file

@ -0,0 +1,28 @@
---
title: Miscellaneous Tips
description: Miscellaneous Tips..
extends: _layouts.documentation
section: content
---
# Miscellaneous Tips {#misc-tips}
## Custom ID scheme
If you don't want to use UUIDs and want to use something more human-readable (even domain concatenated with uuid, for example), you can create a custom class for this:
```php
use Stancl\Tenancy\Contracts\UniqueIdentifierGenerator;
class MyUniqueIDGenerator implements UniqueIdentifierGenerator
{
public static function generate(array $domains, array $data = []): string
{
return $domains[0] . \Ramsey\Uuid\Uuid::uuid4()->toString();
}
}
```
and then set the `tenancy.unique_id_generator` config to the full path to your class.
Note that you may have to make the `id` column on the `tenants` table larger, as it's set to the exact length of uuids by default.

View file

@ -0,0 +1,36 @@
---
title: Nova Integration
description: Nova Integration
extends: _layouts.documentation
section: content
---
# Nova Integration {#nova-integration}
To use Nova inside of the tenant part of your application, do the following:
- Publish the Nova migrations and move them to the `database/migrations/tenant` directory.
```none
php artisan vendor:publish --tag=nova-migrations
```
- Prevent Nova from adding its migrations to your central migrations by adding `Nova::ignoreMigrations()` to `NovaServiceProvider::boot()` (Don't do this if you want to use Nova [both in the central & tenant parts]({{ $page->link('universal-routes') }}) of the app.)
- Add the `'tenancy'` middleware group to your `nova.middleware` config. Example:
```php
'middleware' => [
'tenancy',
'web',
Authenticate::class,
DispatchServingNovaEvent::class,
BootTools::class,
Authorize::class,
],
```
- In your `NovaServiceProvider`'s `routes()` method, replace the following lines:
```php
->withAuthenticationRoutes()
->withPasswordResetRoutes()
```
with these lines:
```php
->withAuthenticationRoutes(['web', 'tenancy'])
->withPasswordResetRoutes(['web', 'tenancy'])
```

View file

@ -0,0 +1,41 @@
---
title: Passport Integration
description: Passport Integration
extends: _layouts.documentation
section: content
---
# Passport Integration {#passport-integration}
> If you just want to write an SPA, but don't need an API for some other use (e.g. mobile app), you can avoid **a lot** of the complexity of writing SPAs by using [Inertia.js](https://inertiajs.com).
To use Passport inside the tenant part of your application, you may do the following.
- Add this to the `register` method in your `AppServiceProvider`:
```php
Passport::ignoreMigrations();
Passport::routes(null, ['middleware' => 'tenancy']);
```
- `php artisan vendor:publish --tag=passport-migrations` & move to `database/migrations/tenant/` directory
## Shared keys
If you want to use the same keypair for all tenants, do the following.
- Don't use `passport:install`, use just `passport:keys`. The install command creates keys & two clients. Instead of creating clients centrally, create `Client`s manually in your [tenant database seeder]({{ $page->link('configuration/#seed-after-migration') }}).
## Tenant-specific keys
If you want to use a unique keypair for each tenant, do the following. (Note: The security benefit of doing this isn't probably that big, since you're likely already using the same `APP_KEY` for all tenants.)
There are multiple ways you can store & load tenant keys, but the most straightforward way is to store the keys in the [Tenant Storage]({{ $page->link('tenant-storage') }}) and load them into the `passport` configuration using the [Tenant Config]({{ $page->link('features/tenant-config') }}) feature:
- Uncomment the `TenantConfig` line in your `tenancy.features` config
- Add these keys to your `tenancy.storage_to_config_map` config:
```php
'storage_to_config_map' => [
'passport_public_key' => 'passport.public_key',
'passport_private_key' => 'passport.private_key',
],
```
And again, you need to create clients in your tenant database seeding process.

View file

@ -0,0 +1,15 @@
---
title: PostgreSQL schema separation
description: PostgreSQL schema separation
extends: _layouts.documentation
section: content
---
# PostgreSQL schema separation {#postgresql-schema-separation}
If you're using PostgreSQL, you can separate tenant databases by *schemas* instead of *databases*.
To enable this, set the config like this:
- `tenancy.database.separate_by`: `'schema'` to tell the package how we're separating databases
- `tenancy.database_managers.pgsql`: `Stancl\Tenancy\TenantDatabaseManagers\PostgreSQLSchemaManager::class` to tell the package to use the schema creator/deleter instead of database creator/deleter for pgsql databases.

View file

@ -0,0 +1,36 @@
---
title: Integration with Spatie packages
description: Integration with Spatie packages
extends: _layouts.documentation
section: content
---
# Integration with Spatie packages {#integration-with-spatie-packages}
## laravel-activitylog {#activitylog}
### For the tenant app: {#activitylog-tenant}
- Set the `database_connection` key in `config/activitylog.php` to `null`. This makes activitylog use the default connection.
- Publish the migrations and move them to `database/migrations/tenant`. (And, of course, don't forget to run `artisan tenants:migrate`.)
### For the central app: {#activitylog-central}
- Set the `database_connection` key in `config/activitylog.php` to the name of your central database connection.
## laravel-permission {#permission}
Install the package like usual, but publish the migrations and move them to `migrations/tenant`:
```
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="migrations"
mv database/migrations/*_create_permission_tables.php database/migrations/tenant
```
Then add this to your `AppServiceProvider::boot()` method:
```php
tenancy()->hook('bootstrapped', function (TenantManager $tenantManager) {
\Spatie\Permission\PermissionRegistrar::$cacheKey = 'spatie.permission.cache.tenant.' . $tenantManager->getTenant('id');
});
```
The reason for this is that spatie/laravel-permission caches permissions & roles to save DB queries, which means that we need to separate the permission cache by tenant.

View file

@ -0,0 +1,17 @@
---
title: Stay Updated
description: Stay Updated..
extends: _layouts.documentation
section: content
---
# Stay Updated {#stay-updated}
If you'd like to be notified about new versions, you can [sign up for e-mail notifications](https://stancl.github.io/tenancy/#stay-updated) or join our [Telegram channel](https://t.me/joinchat/AAAAAFjdrbSJg0ZCHTzxLA).
You can choose whether you want to receive emails about major versions and/or minor versions.
- Major versions include breaking changes. Composer won't know about these versions and won't update to them. Major versions will be released about once every 6 months.
- Minor versions include backwards-compatible features and bug fixes.
<!-- todo mailchimp dialog -->

View file

@ -0,0 +1,63 @@
---
title: Storage Drivers
description: Storage Drivers
extends: _layouts.documentation
section: content
---
# Storage Drivers {#storage-drivers}
Storage drivers are used to store a list of all tenants, their domains and any extra information you store about your tenants (their plan, API keys, etc).
Currently, database and Redis storage drivers are available as part of the package. However, you can [write your own]({{ $page->link('writing-storage-drivers') }}) (and contribute ❤️) storage drivers.
## Database {#database}
The database storage driver lets you store tenant information in a relational database like MySQL, PostgreSQL and SQLite.
The benefit of this storage driver is that you don't have to use both Redis and a database for your data. Also you don't have to do as much configuration.
To use this driver, you need to have a `tenants` table and a `domains` table. You may also use a custom database connection. By default, `tenancy.storage_drivers.db.connection` is set to `null`, which means that your app's default database connection will be used to store tenants.
If you wish to use a different connection, e.g. `central`, you may create it in the `config/database.php` file and set `tenancy.storage_drivers.db.connection` to the name of that connection. It's recommended to do this for easier changes in the future.
To create the `tenants` and `domains` tables, you can use the migrations that come with this package. If you haven't published them during installation, publish them now:
```
php artisan vendor:publish --provider='Stancl\Tenancy\TenancyServiceProvider' --tag=migrations
```
By default, all of your data will be stored in the JSON column `data`. If you want to store some data in a dedicated column (to leverage indexing, for example), add the column to the migration and to the `tenancy.custom_columns` config.
> If you have existing migrations related to your app in `database/migrations`, move them to `database/migrations/tenant`. You can read more about tenant migrations [here]({{ $page->link('tenant-migrations') }}).
Finally, run the migrations:
```
php artisan migrate
```
> If you use a non-default connection, such as `central`, you have to specify which DB to migrate using the `--database` option.
## Redis {#redis}
The Redis storage driver lets you store tenant information in Redis, a high-performance key-value store.
The benefit of this storage driver is its performance.
**Note that you need to configure persistence on your Redis instance if you don't want to lose all information about tenants.**
Read the [Redis documentation page on persistence](https://redis.io/topics/persistence). You should definitely use AOF and if you want to be even more protected from data loss, you can use RDB **in conjunction with AOF**.
If your cache driver is Redis and you don't want to use AOF with it, run two Redis instances. Otherwise, just make sure you use a different database (number) for tenancy and another for anything else.
To use this driver, create a new Redis connection in the `database.redis` configuration (`config/database.php`) called `tenancy` (or if you use another name, be sure to update it in the `tenancy.storage_drivers.redis.connection` configuration (`config/tenancy.php`)).
```php
'tenancy' => [
'host' => env('TENANCY_REDIS_HOST', '127.0.0.1'),
'password' => env('TENANCY_REDIS_PASSWORD', null),
'port' => env('TENANCY_REDIS_PORT', 6380), // different port = separate Redis instance
'database' => env('TENANCY_REDIS_DB', 3), // alternatively, different database number
],
```
> Note: You need phpredis. Predis support will dropped by Laravel in version 7.

View file

@ -0,0 +1,31 @@
---
title: Telescope Integration
description: Telescope Integration..
extends: _layouts.documentation
section: content
---
# Telescope Integration
> To enable this feature, uncomment the `Stancl\Tenancy\Features\TelescopeTags::class` line in your `tenancy.features` config.
Requests in Telescope are automatically tagged with the tenant id and domain:
![Telescope Request with tags](https://i.imgur.com/CEEluYj.png)
This lets you filter requests by id and domain:
![Filtering by id](https://i.imgur.com/SvbOa7S.png)
![Filtering by domain](https://i.imgur.com/dCJuEr1.png)
If you'd like to set Telescope tags in your own code, e.g. in your `AppServiceProvider`, replace your `Telescope::tag()` call like this:
```php
\Tenancy::integrationEvent('telescope', function ($entry) {
return ['abc']; // your logic
});
```
![Tenancy tags merged with tag abc](https://i.imgur.com/4p1wOiM.png)
Once Telescope 3 is released, you won't have to do this.
To have Telescope working, make sure your `telescope.storage.database.connection` points to a non-tenant connection. It's that way by default, so for most projects, Telescope should work out of the box.

View file

@ -0,0 +1,18 @@
---
title: Tenancy Bootstrappers
description: Tenancy Bootstrappers
extends: _layouts.documentation
section: content
---
# Tenancy Bootstrappers {#tenancy-bootstrappers}
These are the classes that do the magic. When tenancy is initialized, TenancyBootstrappers are executed, making Laravel tenant-aware.
All Tenancy Bootstrappers must implement the `Stancl\Tenancy\Contracts\TenancyBootstrapper` interface.
When tenancy is [initialized]({{ $page->link('tenancy-initialization') }}), the `start()` method on the [enabled bootstrappers]({{ $page->link('configuration#bootstrappers') }}) is called.
Conversely, when tenancy is ended, the `end()` method is called.
In the [`tenancy.bootstrappers` configuration]( {{ $page->link('configuration#bootstrappers') }} ), bootstrappers have an alias configured (e.g. `database`) that is used by [events]({{ $page->link('hooks') }}) to say which bootstrappers are prevented.

View file

@ -0,0 +1,21 @@
---
title: Tenancy Initialization
description: Tenancy Initialization..
extends: _layouts.documentation
section: content
---
# Tenancy Initialization {#tenancy-initialization}
Tenancy can be initialized using the following methods on `Stancl\Tenancy\TenantManager`:
- `initializeTenancy($tenant)`
- `initialize($tenant)`
- `init($domain)`
Similarly, tenancy can be ended using:
- `endTenancy()`
- `end()`
You can use these methods in `php artisan tinker`.
[Tenant Routes]({{ $page->link('tenant-routes') }}) have the `InitializeTenancy` middleware applied to them. That middleware automatically initializes tenancy for the current hostname.

View file

@ -0,0 +1,61 @@
---
title: Tenant-Aware Commands
description: Tenant-Aware Commands
extends: _layouts.documentation
section: content
---
# Tenant-Aware Commands {#tenant-aware-commands}
Even though [`tenants:run`]({{ $page->link('console-commands#run') }}) lets you run arbitrary artisan commands for tenants, you may want to have strictly tenant commands.
To make a command tenant-aware, utilize the `TenantAwareCommand` trait:
```php
class MyCommand extends Command
{
use TenantAwareCommand;
}
```
However, this trait requires you to implement a `getTenants()` method that returns an array of `Tenant` instances.
If you don't want to implement the options/arguments yourself, you may use one of these two traits:
- `HasATenantsOption` - accepts multiple tenant ids, optional -- by default the command is executed for all tenants
- `HasATenantArgument` - accepts a single tenant id, required argument
These traits implement the `getTenants()` method needed by `TenantAwareCommand`.
> Note: If you're using a custom constructor for your command, you need to add `$this->specifyParameters()` at the end for the option/argument traits to take effect.
So if you use these traits in combination with `TenantAwareCommand`, you won't have to change a thing in your command:
```php
class FooCommand extends Command
{
use TenantAwareCommand, HasATenantsOption;
public function handle()
{
//
}
}
class BarCommand extends Command
{
use TenantAwareCommand, HasATenantArgument;
public function handle()
{
//
}
}
```
### Custom implementation
If you want more control, you may implement this functionality yourself by simply accepting a `tenant_id` argument and then inside `handle()` doing something like this:
```php
tenancy()->find($this->argument('tenant_id'))->run(function () {
// your actual command code
});
```

View file

@ -0,0 +1,140 @@
---
title: Tenant Manager
description: Tenant Manager
extends: _layouts.documentation
section: content
---
# Tenant Manager {#tenant-manager}
This page documents a couple of `TenantManager` methods you may find useful.
To call methods on `TenantManager`, you may use the `tenancy()` helper or the `Tenancy` facade.
### Find tenant by id {#find-by-id}
```php
>>> \Tenancy::find('b07aa3b0-dc68-11e9-9352-9159b2055c42')
=> Stancl\Tenancy\Tenant {#3099
+data: [
"id" => "b07aa3b0-dc68-11e9-9352-9159b2055c42",
"plan" => "free",
],
+domains: [
"foo.localhost",
],
}
```
### Find tenant by domain {#find-by-domain}
```php
>>> tenancy()->findByDomain('bar.localhost')
=> Stancl\Tenancy\Tenant {#3091
+data: [
"id" => "b38b2bd0-dc68-11e9-adfc-ede94ab3b264",
],
+domains: [
"bar.localhost",
],
}
```
### Find tenant by arbitrary key {#find-by-arbitrary-key}
> Note: Only the DB storage driver implements this feature.
```php
tenancy()->findBy('email', $email);
tenancy()->findByEmail($email);
```
### Getting the current tenant
One more way to get the current [tenant]({{ $page->link('tenants') }}) is to call `getTenant()` on `TenantManager`:
```php
tenancy()->getTenant()
```
If you want to get the value of a specific key from the array, you can an argument with the key.
```php
tenancy()->getTenant('id') // Does the same thing as tenant('id')
```
### Getting all tenants
This method returns a collection of arrays.
```php
>>> tenancy()->all()
=> Illuminate\Support\Collection {#3080
all: [
Stancl\Tenancy\Tenant {#3076
+data: [
"id" => "b07aa3b0-dc68-11e9-9352-9159b2055c42",
],
+domains: [
"foo.localhost",
],
},
Stancl\Tenancy\Tenant {#3075
+data: [
"id" => "b38b2bd0-dc68-11e9-adfc-ede94ab3b264",
],
+domains: [
"bar.localhost",
],
},
],
}
>>> tenancy()->all()->pluck('domains')
=> Illuminate\Support\Collection {#3108
all: [
[
"foo.localhost",
],
[
"bar.localhost",
],
],
}
```
### Deleting a tenant {#deleting-a-tenant}
```php
>>> $tenant = tenancy()->findByDomain('foo.localhost');
=> Stancl\Tenancy\Tenant {#3119
+data: [
"id" => "b07aa3b0-dc68-11e9-9352-9159b2055c42",
"plan" => "free",
],
+domains: [
"foo.localhost",
],
}
>>> $tenant->delete();
=> true
```
NOTE: This doesn't delete the tenant's database.
If you want to delete it, get the database name prior to deleting the tenant using `getDatabaseName()`.
```php
>>> $tenant->getDatabaseName()
=> "tenant67412a60-1c01-11e9-a9e9-f799baa56fd9"
```
If you want tenant databases to be deleted automatically, you may use the [`delete_database_after_tenant_deletion` configuration]({{ $page->link('configuration#delete-database-after-tenant-deletion') }})
### Soft deleting a tenant {#soft-deleting-a-tenant}
You may also "soft delete" tenants. The `softDelete()` method detaches all domains from a tenant:
```php
$tenant->softDelete();
```
The list of original domains will be accessible under the `_tenancy_original_domains` key in the tenant storage.

View file

@ -0,0 +1,30 @@
---
title: Tenant Migrations
description: Tenant Migrations
extends: _layouts.documentation
section: content
---
# Tenant Migrations {#tenant-migrations}
You can run tenant migrations using the `php artisan tenants:migrate` command.
You may specify the tenant(s) using the `--tenants` option.
```
php artisan tenants:migrate --tenants=8075a580-1cb8-11e9-8822-49c5d8f8ff23
```
> Note: Tenant migrations must be located in `database/migrations/tenant`.
You can run migrations from outside the command line as well. To run migrations for a tenant in your code, use `Artisan::call()`:
```php
$tenant = \Tenant::create('tenant1.localhost');
\Artisan::call('tenants:migrate', [
'--tenants' => [$tenant->id];
]);
```
You may also [configure]({{ $page->link('configuration#migrate-after-creation') }}) the package to run migrations automatically, after creating tenants.

View file

@ -0,0 +1,99 @@
---
title: Tenant Routes
description: Tenant Routes
extends: _layouts.documentation
section: content
---
# Tenant Routes {#tenant-routes}
Routes within `routes/tenant.php` will have the `web` and `tenancy` middleware groups automatically applied on them.
Just like `routes/web.php`, these routes use the `App\Http\Controllers` namespace (you can [configure this]({{ $page->link('configuration#tenant-route-namespace') }}))
> If a tenant cannot be identified, an exception will be thrown. If you want to change this behavior (to a redirect, for example) read the [Middleware Configuration]({{ $page->link('middleware-configuration') }}) page.
## Middleware {#middleware}
The package automatically adds the `InitializeTenancy` middleware to the global middleware stack. This middleware checks if the current domain is not part of `tenancy.exempt_domains`. If not, it attempts to identify the tenant based on the current hostname. Once the tenant is identified, the database connection, cache, filesystem root paths and, optionally, Redis connection, will be switched.
After the *global* middleware is executed, the controllers are constructed.
After that, the *route* middleware is executed.
All route groups in your application should have the `\Stancl\Tenancy\Middleware\PreventAccessFromTenantDomains` middleware applied on them, to prevent access from tenant domains to central routes and vice versa. See below for more detail about the `PreventAccessFromTenantDomains` middleware.
All tenant routes in your application should have the `tenancy` middleware group applied on them.
The `tenancy` middleware group marks the route as a tenant route. That middleware functions as a "flag" for the `PreventAccessFromTenantDomains`, telling it that the route is a tenant route, since the middleware has no other way of distingushing central from tenant routes.
In previous versions, the `InitializeTenancy` middleware was applied only on tenant routes. However, that lead to tenancy not being initialized in controller constructors, which could cause bugs. So from 2.1.0 on, tenancy is initialized on all routes on non-exempt domains, and if the route is not tenant, the request gets aborted by the `PreventAccessFromTenantDomains` once Laravel reaches the route middleware step.
## Central routes {#central-routes}
Routes in files other than `routes/tenant.php` will not have the `tenancy` middleware automatically applied on them, so they will be central routes. If you want these routes to be tenant routes, you can apply the `tenancy` middleware manually, as described in custom route groups below.
## API routes / custom route groups {#custom-groups}
If you want certain routes (perhaps API routes) to be multi-tenant, wrap them in a Route group with this middleware:
```php
Route::middleware('tenancy')->group(function () {
// Route::get('/', 'HelloWorld');
});
```
and make sure the `Stancl\Tenancy\Middleware\PreventAccessFromTenantDomains` middleware is applied on the *entire* group:
```php
// app/Http/Kernel.php
protected $middlewareGroups = [
// ...
'api' => [
\Stancl\Tenancy\Middleware\PreventAccessFromTenantDomains::class,
// ...
]
];
```
## The `PreventAccess...` middleware {#prevent-access-middleware}
The `Stancl\Tenancy\Middleware\PreventAccessFromTenantDomains` middleware prevents access to non-tenant routes from tenant domains by returning a redirect to the tenant app's home page ([`tenancy.home_url`]({{ $page->link('configuration#home-url') }})). Conversely, it returns a 404 when a user attempts to visit a tenant route on a web (exempt) domain.
The `tenancy:install` command applies this middleware to the `web` and `api` groups. To apply it for another route group, add this middleware manually to that group. You can do this in `app/Http/Kernel.php`.
## Conflicting routes {#conflicting-routes}
By default, you cannot have conflicting routes in `web.php` and `tenant.php`. It would break `php artisan route:list` and route caching.
**However**, tenant routes are loaded after the web/api routes, so if you register your central routes only for domains listed in the `tenancy.exempt_domains` config, you **can use the same URLs for central and tenant routes**.
Here's an example implementation:
```php
// RouteServiceProvider
protected function mapWebRoutes()
{
foreach (config('tenancy.exempt_domains', []) as $domain) {
Route::middleware('web')
->domain($domain)
->namespace($this->namespace)
->group(base_path('routes/web.php'));
}
}
protected function mapApiRoutes()
{
foreach (config('tenancy.exempt_domains', []) as $domain) {
Route::prefix('api')
->middleware('api')
->domain($domain)
->namespace($this->namespace)
->group(base_path('routes/api.php'));
}
}
```
One thing to keep in mind though: If you use multiple exempt domains, you cannot use route names. They can be used only once, so the name would link to the URL on the first exempt domain.
If you don't need conflicting routes, you may want to do the following: Since you probably want cleaner URLs on your non-tenant part of the application (landing page, etc), prefix your tenant routes with something like `/app`.

View file

@ -0,0 +1,55 @@
---
title: Tenant Storage
description: Tenant storage..
extends: _layouts.documentation
section: content
---
# Tenant Storage {#tenant-storage}
Tenant storage is where tenants' ids and domains are stored. You can store things like the tenant's plan, subscription information, and tenant-specific application configuration in tenant storage. You may use these functions on `Tenant` objects:
```php
get (string|array $key)
put (string|array $key, mixed $value = null) // if $key is array, make sure $value is null
```
To put something into the tenant storage, you can use `put()` or `set()`.
```php
$tenant->put($key, $value);
$tenant->set($key, $value); // alias for put()
$tenant->put(['key1' => 'value1', 'key2' => 'value2']);
```
> **Note:** Don't start any keys with `_tenancy` unless instructed to by the docs. It is a reserved namespace used to store internal data by this package.
To get something from the storage, you can use `get()`:
```php
tenant()->get($key);
tenant()->get(['key1', 'key2']);
```
> In this example, we're calling the methods on the current tenant &mdash; `tenant()`.
> Note: `get(['key1', 'key2'])` returns an associative array.
Note that $key has to be a string or an array with string keys. The value(s) can be of any data type. Example with arrays:
```php
>>> tenant()->put('foo', ['a' => 'b', 'c' => 'd']);
=> [ // put() returns the supplied value(s)
"a" => "b",
"c" => "d",
]
>>> tenant()->get('foo');
=> [
"a" => "b",
"c" => "d",
]
```
To delete keys from the storage, you may use these methods:
```php
$tenant->deleteKey($key)
$tenant->deleteKeys(['foo', 'bar']);
```

View file

@ -0,0 +1,128 @@
---
title: Tenants
description: Tenants
extends: _layouts.documentation
section: content
---
# Tenants {#tenants}
This page covers the `Stancl\Tenancy\Tenant` object. Both [creating tenants]({{ $page->link('creating-tenants') }}) and interacting with the [tenant storage]({{ $page->link('tenant-storage') }}) are covered on separate pages. This page focuses on advanced usage, which can help you with writing nicer code.
## `$data` {#data}
An associative array that mirrors the tenant's data in the actual storage. It acts as a "cache" for [tenant storage methods]({{ $page->link('tenant-storage') }}). When you call `$tenant->get('foo')`, the `$data` property is checked for `'foo'` before trying to read from the storage. Similarly, when you call `$tenant->put()`, you write both to the actual storage and to the `$data` array.
If you try to access the tenant's properties using `$tenant->foo` or `$tenant['foo']`, those calls are redirected to the `$data` array.
The `put()` call always writes to the actual storage. If you do just:
```php
$tenant->foo = 'bar';
```
The data will not be persisted until you `->save()` the `$tenant`.
## `$domains` {#domains}
An array of domains that belong to the tenant.
You may add and remove domains using the following methods:
```php
$tenant->addDomains(['foo.yourapp.com'])->save();
$tenant->addDomains('foo.yourapp.com')->save();
$tenant->removeDomains(['foo.yourapp.com'])->save();
$tenant->removeDomains('foo.yourapp.com')->save();
```
> Don't forget to `->save()` after modifying the domains!
## `run()` {#run}
The `$tenant->run()` command lets you execute a closure inside a tenant's "environment".
```php
$tenant->run(function ($tenant) {
User::create(['name' => 'Admin', 'email' => 'admin@yourapp.com', ...]);
});
```
It also lets you get data from the tenant's environment:
```php
$tenantsUserCount = $tenant->run(function ($tenant) {
return User::count();
});
```
If you need access to the tenant within the closure, it's passed as the first argument.
This feature is a safe alternative to:
```php
tenancy()->initialize($tenant);
// make some changes
tenancy()->end();
```
and it also checks if tenancy was initialized. If it was, it returns to the original tenant after running the closure.
## `$persisted` {#persisted}
This property says whether the model has saved to the storage yet. In other words, if it's `false`, it's a new instance that has not been `->save()`d yet.
## `->save()` {#save}
If no ID is set in the tenant's `$data`, it will be generated using the `UniqueIDGenerator` specified in `config('tenancy.unique_id_generator')`.
Then, if the object has been persisted, it's updated in storage. Otherwise, it's written to storage.
## `->with()` {#with}
You may fluently change the tenant's `$data` using `->with()`:
```php
$tenant->with('foo', 'bar'); // equivalent to $tenant->foo = $bar
```
Don't forget to `->save()` when you use `->with()`:
```php
$tenant->with('foo', 'bar')->with('abc', 'xyz')->save();
```
You may also use `->with<PropertyNameInPascalCase>` and it will be stored in snake case:
```php
>>> \Tenancy::find('b07aa3b0-dc68-11e9-9352-9159b2055c42')
=> Stancl\Tenancy\Tenant {#3060
+data: [
"id" => "b07aa3b0-dc68-11e9-9352-9159b2055c42",
],
+domains: [
"foo.localhost",
],
}
>>> \Tenancy::find('b07aa3b0-dc68-11e9-9352-9159b2055c42')
->withPaypalApiKey('foobar')
->save();
=> Stancl\Tenancy\Tenant {#3087
+data: [
"id" => "b07aa3b0-dc68-11e9-9352-9159b2055c42",
"paypal_api_key" => "foobar",
],
+domains: [
"foo.localhost",
],
}
```
These methods make the most sense (= sound the best) during tenant creation:
```php
// $domains = ['foo.yourapp.com', 'foo.com'];
$primary_domain = $domains[0];
$id = $primary_domain . $this->randomString(24);
Tenant::new()
->withDomains($domains)
->withPaypalApiKey('defaultApiKey');
->withId($id)
->save();
```

View file

@ -0,0 +1,12 @@
---
title: Universal Routes
description: Universal Routes
extends: _layouts.documentation
section: content
---
# Universal Routes {#universal-routes}
You may want to use some routes, such as `Auth::routes()`, in both your tenant app and your central app. To do this, use the `universal` middleware group on the route(s).
Another example of when you might want to do this is if you want to use [Laravel Nova in the tenant app]({{ $page->link('nova') }}) as well as in the central app.

View file

@ -0,0 +1,56 @@
---
title: Upgrading
description: Upgrading
extends: _layouts.documentation
section: content
---
# Upgrading from 1.x {#upgrading}
The 2.0.0 release is essentialy a ~60% rewrite, with 3,187 additions and 1,896 deletions (lines of code). Version 2 introduces Laravel 6 support and drops Laravel 5.8 support.
This guide attempts to cover the main changes that were made to the package. The rewrite was mainly:
- an internal thing: much better code quality means much better maintainability and much more features in the future :)
- to provide a nicer API for working with tenants
Even though new syntax was one of the main goals, the rewrite was made with backwards compatibility in mind, so many old methods still work.
If you're coming from 1.x, it's recommended to read (or at least skim through) the entire documentation again.
## Main changes
- `Tenant` objects are now used, instead of arrays, to represent tenants. See the [Tenants]({{ $page->link('tenants') }}) page.
- Tenants can now have multiple domains, so a new `domains` table is used by the DB storage driver.
- The `uuid` property on tenants is now `id`.
- `tenancy()` helper now returns an instance of `TenantManager` while the `tenant()` helper returns an instance of the current `Tenant`. If no `Tenant` has been identified, `null` is returned. Same with the `Tenancy` and `Tenant` facades.
- Event listeners/hooks have a new syntax: `Tenancy::eventListener('bootstrapping', function () {})`
- The tenancy bootstrapping logic has been extracted into separate classes, such as `DatabaseTenancyBootstrapper`, `CacheTenancyBootstrapper` etc.
- A concept of `Feature`s was introduced. They're classes that provide additional functionality - functionality that is not necessary to bootstrap tenancy.
- predis support was dropped. Laravel will drop predis support in 7.x.
- There is new syntax for [creating]({{ $page->link('creating-tenants') }}) and [interacting]({{ $page->link('tenants') }}) with tenants, be sure to read those documentation pages again.
- The `_tenancy` namespace for keys in tenant storage is reserved by the package and should not be used unless instructed to by the documentation.
- The config was changed *a lot*, so you should publish and configure it again.
You can publish the configuration like this:
```none
php artisan vendor:publish --provider='Stancl\Tenancy\TenancyServiceProvider' --tag=config
```
## DB storage driver
- The `uuid` column in the `tenants` table was renamed to `id`. The `domain` column was dropped.
- A new migration was added to create the `domains` table. **The old migration was renamed**, so if you publish migrations again, be sure to delete the old migration, to avoid creating the table twice.
You can publish migrations like this:
```none
php artisan vendor:publish --provider='Stancl\Tenancy\TenancyServiceProvider' --tag=migrations
```
## Redis storage driver
- The `uuid` keys are now `id`.
- The `domain` key was dropped.
- The `_tenancy_domains` key is used to store an array of domains that belong to the tenant.
## New Features
- [Tenant Config]({{ $page->link('features/tenant-config') }})
- [Migrate Fresh]({{ $page->link('console-commands#migrate-fresh') }})
- [`tenants:create`]({{ $page->link('console-commands#create-tenant') }})

View file

@ -0,0 +1,16 @@
---
title: Usage
description: Usage..
extends: _layouts.documentation
section: content
---
# Usage {#usage}
This chapter describes usage of the package. That includes creating tenants, deleting tenants, storing data in the tenant storage.
The package comes with two helpers - `tenancy()` and `tenant()`.
- `tenancy()` returns an instance of [`TenantManager`]({{ $page->link('tenant-manager') }})
- `tenant()` returns an instance of the current tenant, or null if no tenant hs been identified yet. You can pass an argument to this helper, to get a value from the tenant storage: `tenant('plan')` is identical to [`tenant()->get('plan')`]({{ $page->link('tenant-storage') }}).
The package also comes with two facades. `Tenancy` -- for `TenantManager` -- and `Tenant` -- for the current `Tenant`.

View file

@ -0,0 +1,86 @@
---
title: Writing Storage Drivers
description: Writing Storage Drivers
extends: _layouts.documentation
section: content
---
# Writing Storage Drivers
If you don't want to use the provided DB/Redis storage drivers, you can write your own driver.
To create a driver, create a class that implements the `Stancl\Tenancy\Contracts\StorageDriver` interface.
Here's an example:
```php
<?php
namespace App\StorageDrivers\MongoDBStorageDriver;
use Stancl\Tenancy\Tenant;
use Stancl\Tenancy\Contracts\StorageDriver;
class MongoDBStorageDriver implements StorageDriver
{
public function createTenant(Tenant $tenant): void
{
//
}
public function updateTenant(Tenant $tenant): void
{
//
}
public function deleteTenant(Tenant $tenant): void
{
//
}
public function findById(string $id): Tenant
{
//
}
public function findByDomain(string $domain): Tenant
{
//
}
public function all(array $ids = []): array
{
//
}
public function ensureTenantCanBeCreated(Tenant $tenant): void
{
//
}
public function withDefaultTenant(Tenant $tenant)
{
//
}
public function get(string $key, Tenant $tenant = null)
{
//
}
public function getMany(array $keys, Tenant $tenant = null)
{
//
}
public function put(string $key, $value, Tenant $tenant = null): void
{
//
}
public function putMany(array $kvPairs, Tenant $tenant = null): void
{
//
}
}
```

View file

@ -0,0 +1,19 @@
---
title: Automatic tenancy mode
extends: _layouts.documentation
section: content
---
# Automatic mode
By default, the package bootstraps tenancy automatically in the background. This means that when a tenant is identified (usually using middleware), the default database/cache/filesystem/etc is switched to that tenant's context. You can read more about this on the [Tenancy bootstrappers]({{ $page->link('tenancy-bootstrappers') }}) page.
The flow goes like this:
`TenancyInitialized` fired → `BootstrapTenancy` listens → executes tenancy bootstrappers
It's recommended to use this mode, because:
- Separation of concerns. Tenancy happens one layer below your application. If you need to change the details how tenancy is bootstrapped, you can do that without having to change a ton of your app code.
- You don't have to think about the internals of how tenancy works when writing your application code. When you're writing the tenant part of the application, you're simply writing an e.g. e-commerce application, not a multi-tenant e-commerce application. No need to think about database connections when writing validation rules.
- Great integration with other packages. Switching the default database connection (and other things) is the only way to integrate many packages into the tenant part of the application. For example, you can use Laravel Nova to manage resources inside the tenant application.

View file

@ -0,0 +1,34 @@
---
title: Cached tenant lookup
extends: _layouts.documentation
section: content
---
# Cached lookup
If you're using multiple databases, you may want to avoid making a query to the central database on **each tenant request** — for tenant identification. Even though the queries are very simple, the app has to establish a connection with the central database which is expensive.
To avoid this, you may enable caching on the tenant resolvers (all in the `Stancl\Tenancy\Resolvers` namespace):
- `DomainTenantResolver` (also used for subdomain identification)
- `PathTenantResolver`
- `RequestDataTenantResolver`
On each of these classes, you may set the following static properties:
```php
// TenancyServiceProvider::boot()
use Stancl\Tenancy\Resolvers;
// enable cache
DomainTenantResolver::$shouldCache = true;
// seconds, 3600 is the default value
DomainTenantResolver::$cacheTTL = 3600;
// specify some cache store
// null resolves to the default cache store
DomainTenantResolver::$cacheStore = 'redis';
```

View file

@ -0,0 +1,102 @@
---
title: Configuration
extends: _layouts.documentation
section: content
---
# Configuration
The package is highly configurable. This page covers what you can configure in the `config/tenancy.php` file, but note that many more things are configurable. Some things can be changed by extending classes (e.g. the `Tenant` model), and **many** things can be changed using static properties. These things will *usually* be mentioned on the respective pages of the documentation, but not every time. For this reason, don't be afraid to dive into the package's source code — whenever the class you're using has a `public static` property, **it's intended to be configured**.
### Tenant model
`tenancy.tenant_model`
This config specifies what `Tenant` model should be used by the package. There's a high chance you're using a custom model, as instructed to by the [Tenants]({{ $page->link('tenants') }}) page, so be sure to change it in the config.
### Unique ID generator
`tenancy.id_generator`
The `Stancl\Tenancy\Database\Concerns\GeneratesIds` trait, which is applied on the default `Tenant` model, will generate a unique ID (uuid by default) if no tenant id is supplied.
If you wish to use autoincrement ids instead of uuids:
1. set this config key to null, or create a custom tenant model that doesn't use this trait
2. update the `tenants` table migration to use an incrementing column type instead of `string`
### Domain model
`tenancy.domain_model`
Similar to the Tenant model config. If you're using a custom model for domains, change it in this config. If you're not using domains (e.g. if you're using path or request data identification) at all, ignore this config key altogether.
### Central domains
`tenancy.central_domains`
The list of domains that host your [central app]({{ $page->link('the-two-applications') }}). This is used by (among other things):
- the `PreventAccessFromCentralDomains` middleware, to prevent access from central domains to tenant routes,
- the `InitializeTenancyBySubdomain` middleware, to check whether the current hostname is a subdomain on one of your central domains.
### Bootstrappers
`tenancy.bootstrappers`
This config array lets you enable, disable or add your own [tenancy bootstrappers]({{ $page->link('tenancy-bootstrappers') }}).
### Database
`tenancy.database.*`
This section is relevant to the multi-database tenancy, specifically, to the `DatabaseTenancyBootstrapper` and logic that manages tenant databases.
See this section in the config, it's documented with comments.
### Cache
`tenancy.cache.*`
This section is relevant to cache separation, specifically, to the `CacheTenancyBootstrapper`.
Note: To use the cache separation, you need to use a cache store that supports tagging, which is usually Redis.
See this section in the config, it's documented with comments.
### Filesystem
`tenancy.filesystem.*`
This section is relevant to cache separation, specifically, to the `FilesystemTenancyBootstrapper`.
See this section in the config, it's documented with comments.
### Redis
`tenancy.redis.*`
This section is relevant to Redis data separation, specifically, to the `RedisTenancyBootstrapper`.
Note: To use the this bootstrapper, you need phpredis.
See this section in the config, it's documented with comments.
### Features
`tenancy.features`
This config array lets you enable, disable or add your own [feature classes]({{ $page->link('feature-classes') }}).
### Migration parameters
`tenancy.migration_parameters`
This config array lets you set parameters used by default when running the `tenants:migrate` command (or when this command is executed using the `MigrateDatabase` job). Of course, all of these parameters can be overridden by passing them directly in the command call, be it in CLI or using `Artisan::call()`.
### Seeder parameters
`tenancy.seeder_parameters`
The same as migration parameters, but for `tenants:seed` and the `SeedDatabase` job.

View file

@ -0,0 +1,70 @@
---
title: Console commands
extends: _layouts.documentation
section: content
---
# Console commands
The package comes with some useful artisan commands.
## **Migrate**
The most important command. To use tenants, you have to be able to migrate their databases.
You can use the `tenants:migrate` command to migrate tenant's databases. You can also specify which tenants' databases should be migrated using the `--tenants` option.
```
php artisan tenants:migrate --tenants=8075a580-1cb8-11e9-8822-49c5d8f8ff23
```
You may use multiple `--tenants=<...>` options.
> Note: By default, the migrations should be in database/migrations/tenant. If you wish to use a different path, you may use the `--path` argument.
## **Rollback & seed**
- Rollback: `tenants:rollback`
- Seed: `tenants:seed`
Similarly to `migrate`, these commands accept a `--tenants` option.
## **Migrate fresh**
This package also offers a simplified, tenant-aware version of `migrate:fresh`. It runs `db:wipe` and `tenants:migrate` on the tenant's database.
You may use it like this:
```
php artisan tenants:migrate-fresh --tenants=8075a580-1cb8-11e9-8822-49c5d8f8ff23
```
## **Run**
You can use the `tenants:run` command to run your own commands for tenants.
If your command's signature were `email:send {--queue} {--subject=} {body}`, you would run this command like this:
```
php artisan tenants:run email:send --tenants=8075a580-1cb8-11e9-8822-49c5d8f8ff23 --option="queue=1" --option="subject=New Feature" --argument="body=We have launched a new feature. ..."
```
## **Tenant list**
```
php artisan tenants:list
Listing all tenants.
[Tenant] id: dbe0b330-1a6e-11e9-b4c3-354da4b4f339 @ localhost
[Tenant] id: 49670df0-1a87-11e9-b7ba-cf5353777957 @ dev.localhost
```
## **Selectively clearing tenant cache**
You can delete specific tenants' cache by using the `--tags` option on `cache:clear`:
```
php artisan cache:clear --tags=tenantdbe0b330-1a6e-11e9-b4c3-354da4b4f339
```
The tag is derived from `config('tenancy.cache.tag_base') . $id`.

View file

@ -0,0 +1,65 @@
---
title: Customizing tenant databases
extends: _layouts.documentation
section: content
---
# Customizing databases
You may customize how a tenant's DB connection is constructed by storing specific internal keys on the tenant.
If you changed the internal prefix on the tenant model, then use that instead of `tenancy_`.
## Specifying database names
You may specify the tenant's database name by setting the `tenancy_db_name` key when creating the tenant.
```php
Tenant::create([
'tenancy_db_name' => 'acme',
]);
```
When you don't specify the tenant's database name, it's constructed using:
`tenancy.database.prefix` config + tenant id + `tenancy.database.suffix` config
Therefore, another way to specify database names is to set the tenant id during creation, rather than letting it be randomly generated:
```php
Tenant::create([
'id' => 'acme',
]);
```
## Specifying database credentials
Database user & password are only created when you use the permission controlled MySQL database manager. See the database config for more info.
You may specify the username and password for the user that will be created along with the tenant database.
```php
Tenant::create([
'tenancy_db_username' => 'foo',
'tenancy_db_password' => 'bar',
]);
```
The user will be given the grants specified in the `PermissionControlledMySQLDatabaseManager::$grants` array. Feel free to customize this by setting it to a different value like any other public static property.
Note that you don't want to grant the users the ability to grant themselves more grants.
## Specifying template connections
To specify the connection that should be used to construct this tenant's database connection (the array like you'd find in `config/database.php`, set the `tenancy_db_connection` key. Otherwise, the connection whose name is in the `tenancy.database.template_connection` config will be used. If that key is null, the central connection will be used.
## Specifying other connection details
You may also set specific connection details without necessarily creating a new connection. The final "connection array" will be constructed by merging the following:
- the template connection
- the database name
- optionally, the username and password
- all `tenancy_db_*` keys
This means that you can store a value for e.g. `tenancy_db_charset` if you want to specify the charset for the tenant's database connection for whatever reason.

View file

@ -0,0 +1,19 @@
---
title: Domains
extends: _layouts.documentation
section: content
---
# Domains
Note: Domains are optional. If you're using path or request data identification, you don't need to worry about them.
To add a domain to a tenant, use the `domains` relationship:
```php
$tenant->domains()->create([
'domain' => 'acme',
]);
```
If you use the subdomain identification middleware, the example above will work for `acme.{any of your central domains}`. If you use the domain identification middleware, use the full hostname like `acme.com`. If you use the combined domain/subdomain identification middleware, `acme` will work as a subdomain and `acme.com` will work as a domain.

View file

@ -0,0 +1,76 @@
---
title: Early identification
extends: _layouts.documentation
section: content
---
# Early identification
A slight "gotcha" with using the automatic approach to transition the application's context based on a route middleware is that **route-level middleware is executed after controller constructors.**
The implication of this is if you're using dependency injection to inject some services in the controller constructors, **they will read from the central context**, because route-level middleware hasn't initialized tenancy yet.
There are two ways to solve it, the former of which is preferable.
## Not using constructor DI
You can inject dependencies in route **actions**, meaning: If you have a route that binds a `Post` model, you can still inject dependencies like this:
```php
// Note that this is sort-of pseudocode. Notice the route action DI
// and just skim the rest :)
Route::get('/post/{post}/edit', 'PostController@edit');
class PostController
{
public function edit(Request $request, Post $post, Cloudinary $cloudinary)
{
if ($request->has('image')) {
$post->image_url = $cloudinary->store($request->file('image'));
}
}
}
```
If you don't like that because you access some dependency from many actions, consider creating a memoized method:
```php
class PostController
{
protected Cloudinary $cloudinary;
protected cloudinary(): Cloudinary
{
// If you don't like the service location here, injecting Application
// in the controller constructor is one thing that's 100% safe.
return $this->cloudinary ??= app(Cloudinary::class);
}
public function edit(Request $request, Post $post)
{
if ($request->has('image')) {
$post->image_url = $this->cloudinary()->store(
$request->file('image')
);
}
}
}
```
## Using a more complex middleware setup
> Note: There's a new MW in v3 for preventing access from central domains. v2 was doing this a bit differently.
The manual for implementing this will come soon, for now you can look at how 2.x does this.
In short: The `InitializeTenancy` mw is part of the global middleware stack, which doesn't have access to route information, but is executed prior to controller constructors. The `PreventAccessFromTenantDomains` mw checks that we're vising a tenant route on a tenant domain, or a central route on a central domain — and if not, it aborts the request, either by 404 or by redirecting us to a home url on the tenant domain.
Here's the logic visually:
![Early identification middleware](/assets/images/stancl_tenancy_middleware.png)
And here are the relevant files in 2.x codebase:
- [https://github.com/stancl/tenancy/blob/2.x/src/Middleware/InitializeTenancy.php](https://github.com/stancl/tenancy/blob/2.x/src/Middleware/InitializeTenancy.php)
- [https://github.com/stancl/tenancy/blob/2.x/src/Middleware/PreventAccessFromTenantDomains.php](https://github.com/stancl/tenancy/blob/2.x/src/Middleware/PreventAccessFromTenantDomains.php)

View file

@ -0,0 +1,187 @@
---
title: Event system
extends: _layouts.documentation
section: content
---
# Event system
This package is heavily based around events, which makes it incredibly flexible.
By default, the events are configured in such a way that the package works like this:
- A request comes in for a tenant route and hits an identification middleware
- The identification middleware finds the correct tenant and runs
```php
$this->tenancy->initialize($tenant);
```
- The `Stancl\Tenancy\Tenancy` class sets the `$tenant` as the current tenant and fires a `TenancyInitialized` event
- The `BootstrapTenancy` class catches the event and executes classes known as [tenancy bootstrappers]({{ $page->link('tenancy-bootstrappers') }}).
- The tenancy bootstrappers make changes to the application to make it "scoped" to the current tenant. This by default includes:
- Switching the database connection
- Replacing `CacheManager` with a scoped cache manager
- Suffixing filesystem paths
- Making queues store the tenant id & initialize tenancy when being processed
Again, all of the above is configurable. You might even disable all tenancy bootstrappers, and just use tenant identification and scope your app manually around the tenant stored in `Stancl\Tenancy\Tenancy`. The choice is yours.
# TenancyServiceProvider
This package comes with a very convenient service provider that's added to your application when you install the package. This service provider is used for mapping listeners to events specific to the package and is the place where you should put any tenancy-specific service container calls — to not pollute your AppServiceProvider.
Note that you can register listeners to this package's events **anywhere you want**. The event/listener mapping in the service provider exists only to make your life easier. If you want to register the listeners manually, like in the example below, you can.
```php
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
```
# Bootstrapping tenancy
By default, the `BootstrapTenancy` class is listening to the `TenancyInitialized` event (exactly as you can see in the example above). That listener will execute the configured tenancy bootstrappers to transition the application into the tenant's context. You can read more about this on the [tenancy bootstrappers]({{ $page->link('tenancy-bootstrappers') }}) page.
Conversely, when the `TenancyEnded` event fires, the `RevertToCentralContext` event transitions the app back into the central context.
# Job pipelines
You may job pipelines even in projects that don't use this package — we think they're a cool concept so they're extracted into a separate package: [github.com/stancl/jobpipeline](https://github.com/stancl/jobpipeline)
The `JobPipeline` is a simple, yet **extremely powerful** class that lets you **convert any (series of) jobs into event listeners.**
You may use a job pipeline like any other listener, so you can register it in the `TenancyServiceProvider`, `EventServiceProvider` using the `$listen` array, or in any other place using `Event::listen()` — up to you.
## Creating job pipelines
To create a job pipeline, start by specifying the jobs you want to use:
```php
use Stancl\JobPipeline\JobPipeline;
use Stancl\Tenancy\Jobs\{CreateDatabase, MigrateDatabase, SeedDatabase};
JobPipeline::make([
CreateDatabase::class,
MigrateDatabase::class,
SeedDatabase::class,
])
```
Then, specify what variable you want to pass to the jobs. This will usually come from the event.
```php
use Stancl\JobPipeline\JobPipeline;
use Stancl\Tenancy\Jobs\{CreateDatabase, MigrateDatabase, SeedDatabase};
use Stancl\Tenancy\Events\TenantCreated;
JobPipeline::make([
CreateDatabase::class,
MigrateDatabase::class,
SeedDatabase::class,
])->send(function (TenantCreated $event) {
return $event->tenant;
})
```
Next, decide if you want to queue the pipeline. By default, pipelines are synchronous (= not queued) by default.
If you **do** want pipelines to be queued by default, you can do that by setting a static property:
`\Stancl\JobPipeline\JobPipeline::$shouldBeQueuedByDefault = true;`
```php
use Stancl\Tenancy\Events\TenantCreated;
use Stancl\JobPipeline\JobPipeline;
use Stancl\Tenancy\Jobs\{CreateDatabase, MigrateDatabase, SeedDatabase};
JobPipeline::make([
CreateDatabase::class,
MigrateDatabase::class,
SeedDatabase::class,
])->send(function (TenantCreated $event) {
return $event->tenant;
})->shouldBeQueued(true),
```
Finally, convert the pipeline to a listener and bind it to an event:
```php
use Stancl\Tenancy\Events\TenantCreated;
use Stancl\JobPipeline\JobPipeline;
use Stancl\Tenancy\Jobs\{CreateDatabase, MigrateDatabase, SeedDatabase};
use Illuminate\Support\Facades\Event;
Event::listen(TenantCreated::class, JobPipeline::make([
CreateDatabase::class,
MigrateDatabase::class,
SeedDatabase::class,
])->send(function (TenantCreated $event) {
return $event->tenant;
})->shouldBeQueued(true)->toListener());
```
Note that you can use job pipelines even for converting single jobs to event listeners. That's useful if you have some logic in job classes and don't want to create listener classes just to be able to run these jobs as a result of an event being fired.
# Available events
Note: Some database events (`DatabaseMigrated`, `DatabaseSeeded`, `DatabaseRolledback` and possibly others) are **fired in the tenant context.** Depending on how your application bootstraps tenancy, you might need to be specific about interacting with the central database in these events' listeners — that is, if you need to.
Note: All events are located in the `Stancl\Tenancy\Events` namespace.
### **Tenancy**
- `InitializingTenancy`
- `TenancyInitialized`
- `EndingTenancy`
- `TenancyEnded`
- `BootstrappingTenancy`
- `TenancyBootstrapped`
- `RevertingToCentralContext`
- `RevertedToCentralContext`
Note the difference between *initializing tenancy and bootstrapping* tenancy. Tenancy is initialized when a tenant is loaded into the `Tenancy` object. Whereas boostrapping happens **as a result of initialization** — if you're using automatic tenancy, the `BootstrapTenancy` class is listening to the `TenancyInitialized` event and after it's done executing bootstrappers, it fires an event saying that tenancy was bootstrapped. You want to use the bootstrapped event if you want to execute something **after the app has been transitioned to the tenant context.**
### Tenant
The following events are dispatched as a result of Eloquent events being fired in the default `Tenant` implementation (the most often used events are bold):
- `CreatingTenant`
- `**TenantCreated**`
- `SavingTenant`
- `TenantSaved`
- `UpdatingTenant`
- `TenantUpdated`
- `DeletingTenant`
- `**TenantDeleted**`
### Domain
These events are optional. They're only relevant to you if you're using domains for your tenants.
- `CreatingDomain`
- `**DomainCreated**`
- `SavingDomain`
- `DomainSaved`
- `UpdatingDomain`
- `DomainUpdated`
- `DeletingDomain`
- `**DomainDeleted**`
### Database
These events are also optional. They're relevant to you if you're using multi-database tenancy:
- `CreatingDatabase`
- `**DatabaseCreated**`
- `MigratingDatabase`
- `DatabaseMigrated`
- `SeedingDatabase`
- `DatabaseSeeded`
- `RollingBackDatabase`
- `DatabaseRolledBack`
- `DeletingDatabase`
- `**DatabaseDeleted**`
### Resource syncing
- `**SyncedResourceSaved**`
- `SyncedResourceChangedInForeignDatabase`

View file

@ -0,0 +1,18 @@
---
title: Feature classes
extends: _layouts.documentation
section: content
---
# Feature classes
"Features" are classes that provide additional functionality that's not needed for the core tenancy logic. Out of the box, the package comes with these Features:
- [`UserImpersonation`]({{ $page->link('features/user-impersonation') }}) for generating impersonation tokens for users of a tenant's DB from other contexts
- [`TelescopeTags`]({{ $page->link('features/telescope-tags') }}) for adding tags with the current tenant id to Telescope entries
- [`TenantConfig`]({{ $page->link('features/tenant-config') }}) for mapping keys from the tenant storage into the application config
- [`CrossDomainRedirect`]({{ $page->link('features/cross-domain-redirect') }}) for adding a `domain()` macro on `RedirectResponse` letting you change the intended hostname of the generated route
- [`UniversalRoutes`]({{ $page->link('features/universal-routes') }}) for route actions that work in both the central & tenant context
All of the package's Features are in the `Stancl\Tenancy\Features` namespace.
You may register features by adding their class names to the `tenancy.features` config.

View file

@ -0,0 +1,15 @@
---
title: Cross-domain redirect
extends: _layouts.documentation
section: content
---
# Cross-domain redirect
To enable this feature, uncomment the `Stancl\Tenancy\Features\CrossDomainRedirect::class` line in your `tenancy.features` config.
Sometimes you may want to redirect the user to a specific route on a different domain (than the current one). Let's say you want to redirect a tenant to the `home` path on their domain after they sign up:
```php
return redirect()->route('home')->domain($domain);
```

View file

@ -0,0 +1,9 @@
---
title: Telescope tags
extends: _layouts.documentation
section: content
---
# Telescope tags
TODO

View file

@ -0,0 +1,49 @@
---
title: Tenant-specific config
extends: _layouts.documentation
section: content
---
# Tenant config
It's likely you will need to use tenant-specific config in your application. That config could be API keys, things like "products per page" and many other things.
You could just use the the tenant model to get these values, but you may still want to use Laravel's `config()` because of:
- separation of concerns — if you just write tenancy implementation-agnostic `config('shop.products_per_page')`, you will have a much better time changing tenancy implementations
- default values — you may want to use the tenant storage only to override values in your config file
## **Enabling the feature**
Uncomment the following line in your `tenancy.features` config:
```php
// Stancl\Tenancy\Features\TenantConfig::class,
```
## **Configuring the mappings**
This feature maps keys in the tenant storage to config keys based on the `$storageToConfigMap` public property.
For example, if your `$storageToConfigMap` looked like this:
```php
\Stancl\Tenancy\Features\TenantConfig::$storageToConfigMap = [
'paypal_api_key' => 'services.paypal.api_key',
],
```
the value of `paypal_api_key` in tenant model would be copied to the `services.paypal.api_key` config when tenancy is initialized.
## Mapping the value to multiple config keys
Sometimes you may want to copy the value to multiple config keys. To do that, specify the config keys as an array:
```php
\Stancl\Tenancy\Features\TenantConfig::$storageToConfigMap = [
'locale' => [
'app.locale',
'locales.default',
],
],
```

View file

@ -0,0 +1,37 @@
---
title: Universal routes
extends: _layouts.documentation
section: content
---
# Universal Routes
Sometimes, you may want to use the exact same **route action** both in the central application and the tenant application. Note the emphasis on route **action** — you may use the same **path** with different actions in central & tenant routes, whereas this section covers using the same **route and action**.
Generally, try to avoid these use cases as much as possible and prefer duplicating the code. Using the same controller and model for users in central & tenant apps will break down once you need slightly different behavior — e.g. different views returned by controllers, different behavior on models, etc.
First, enable the `UniversalRoutes` feature by uncommenting the following line in your `tenancy.features` config:
```php
Stancl\Tenancy\Features\UniversalRoutes::class,
```
Next, go to your `app/Http/Kernel.php` file and add the following middleware group:
```php
'universal' => [],
```
We will use this middleware group as a "flag" on the route, to mark it as a universal route. We don't need any actual middleware inside the group.
Then, create a route like this:
```php
Route::get('/foo', function () {
// ...
})->middleware(['universal', InitializeTenancyByDomain::class]);
```
And the route will work in both central and tenant applications. Should a tenant be found, tenancy will be initialized. Otherwise, the central context will be used.
If you're using a different middleware, look at the `UniversalRoutes` feature source code and change the public static property accordingly.

View file

@ -0,0 +1,117 @@
---
title: User impersonation
extends: _layouts.documentation
section: content
---
# User impersonation
This package comes with a feature that lets you impersonate users inside tenant databases. This feature works with **any identification method** and **any auth guard** — even if you use multiple.
## How it works
You generate an impersonation token an store it in the central database, in the `tenant_user_impersonation_tokens` table.
Each record in the table holds the following data:
- The token value (128 character string)
- The tenant's id
- The user's id
- The name of the auth guard
- The URL to redirect to after the impersonation takes place
You visit an impersonation route that you create — though little work is needed on your side, your route will mostly just call a method provided by the feature. This route is a **tenant route**, meaning it's on the tenant domain if you use domain identification, or prefixed with the tenant id if you use path identification.
This route checks tries to find a record in that table based on the token, and if it's valid it authenticates you with the stored user id against the auth guard and redirects you to the stored URL.
If the impersonation succeeds, the token is deleted from the database.
All tokens expire after 60 seconds by default, and this TTL can be customized — see the section at the very bottom.
## Enabling the feature
To enable this feature, go to your `config/tenancy.php` file and make sure the following class is in your `features` part of the config:
```jsx
Stancl\Tenancy\Features\UserImpersonation::class,
```
Next, publish the migration for creating the table with impersonation tokens:
```jsx
php artisan vendor:publish --tag=impersonation-migrations
```
And finally, run the migration:
```jsx
php artisan migrate
```
## Usage
First, you need to create a tenant route that looks like this:
```jsx
use Stancl\Tenancy\Features\UserImpersonation;
// We're in your tenant routes!
Route::get('/impersonate/{token}', function ($token) {
return UserImpersonation::makeResponse($token);
});
// Of course use a controller in a production app and not a Closure route.
// Closure routes cannot be cached.
```
Note that the route path or name are completely up to you. The only logic that the package does is generating tokens, verifying tokens, and doing the impersonated user log in.
Then, to use impersonation in your app, generate a token like this:
```jsx
// Let's say we want to be redirected to the dashboard
// after we're logged in as the impersonated user.
$redirectUrl = '/dashboard';
$token = tenancy()->impersonate($tenant, $user->id, $redirectUrl);
```
And redirect the user (or, presumably an "admin") to the route you created.
### Domain identification
```jsx
// Note: This is not part of the package, it's up to you to implement
// a concept of "primary domains" if you need them. Or maybe you use
// one domain per tenant. The package lets you do anything you want.
$domain = $tenant->primary_domain;
return redirect("https://$domain/impersonate/$token");
```
### Path identification
```jsx
// Make sure you use the correct prefix for your routes.
return redirect("{$tenant->id}/impersonate/$token");
```
And that's it. The user will be redirected to your impersonation route, logged in as the impersonated user, and finally redirected to your redirect URL.
### Custom auth guards
If you're using multiple auth guards, you may want to specify what auth guard the impersonation logic should use.
To do this, simply pass the auth guard name as the fourth argument to the `impersonate()` method. So to expand on our example above:
```jsx
tenancy()->impersonate($tenant, $user->id, $redirectUrl, 'jwt');
```
## Customization
You may customize the TTL of impersonation tokens by setting the following static property to the amount of seconds you want to use:
```jsx
Stancl\Tenancy\Features\UserImpersonation::$ttl = 120; // 2 minutes
```

View file

@ -0,0 +1,24 @@
---
title: How it works
extends: _layouts.documentation
section: content
---
# How it works
This package is very flexible and lets you use tenancy however you want. But it comes with sensible defaults that work like this out of the box:
- a request comes in
- the domain is used to identify the tenant
- the database, cache, etc are switched to that tenant's context
This happens using identification middleware and events.
[Tenant identification]({{ $page->link('tenant-identification') }})
[Event system]({{ $page->link('event-system') }})
Note that even though the default assumes you're using domains and the database-per-tenant model, you're free to customize this any way you want. **And it's easy to customize!** Just read on to get an understanding of how everything works.
TODO: Expand. Why this approach, etc. Maybe on the other page?

View file

@ -0,0 +1,8 @@
<!DOCTYPE HTML>
<html lang="en-US">
<head>
<meta charset="UTF-8">
<meta http-equiv="refresh" content="0; url={{ $page->baseUrl }}/docs/v3/introduction">
<title>stancl/tenancy</title>
</head>
</html>

View file

@ -0,0 +1,42 @@
---
title: Installation
extends: _layouts.documentation
section: content
---
# Installation
Require the package using composer:
```php
composer require stancl/tenancy:3.x-dev
```
Then run the following command:
```php
php artisan tenancy:install
```
It will create:
- migrations
- a config file (`config/tenancy.php`),
- a routes file (`routes/tenant.php`),
- and a service provider file `app/Providers/TenancyServiceProvider.php`
Then add the service provider to your `config/app.php` file:
```php
/*
* Application Service Providers...
*/
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
App\Providers\TenancyServiceProvider::class, // <-- here
```
And finally, name your central connection (in `config/database.php`) `central` — or however else you want, but make sure it's the same name as the `tenancy.central_connection` config.

View file

@ -0,0 +1,22 @@
---
title: Integration with other packages
extends: _layouts.documentation
section: content
---
# Integrating with other packages
If you're using the [automatic mode]({{ $page->link('features/automatic-mode') }}) & [multi-database tenancy]({{ $page->link('multi-database-tenancy') }}), you'll be able to integrate with other packages easily.
[Integration with Spatie packages]({{ $page->link('features/spatie') }})
[Laravel Horizon]({{ $page->link('features/horizon') }})
[Laravel Passport]({{ $page->link('features/passport') }})
[Laravel Nova]({{ $page->link('features/nova') }})
[Laravel Telescope]({{ $page->link('features/telescope') }})
[Livewire]({{ $page->link('features/livewire') }})

View file

@ -0,0 +1,20 @@
---
title: Laravel Horizon integration
extends: _layouts.documentation
section: content
---
# Laravel Horizon
Make sure your [queues]({{ $page->link('queues') }}) are configured correctly before using this.
You may add the current tenant's id to your job tags:
```php
public function tags()
{
return [
'tenant' => tenant('id'),
];
}
```

View file

@ -0,0 +1,27 @@
---
title: Livewire integration
extends: _layouts.documentation
section: content
---
# Livewire
Open the `config/livewire.php` file and change this:
```php
'middleware_group' => ['web'],
```
to this:
```php
'middleware_group' => [
'web',
'universal',
InitializeTenancyByDomain::class, // or whatever tenancy middleware you use
],
```
Now you can use Livewire both in the central app and the tenant app.
Also make sure to enable *universal routes*:

View file

@ -0,0 +1,53 @@
---
title: Laravel Nova integration
extends: _layouts.documentation
section: content
---
# Laravel Nova
## In the central app
If you wish to use Laravel Nova in the central application (to manage tenants), you need to make a small change to the Nova migrations, they expect your model primary keys to always be unsigned big integers, but your tenants might be using `string` ids.
You can find the full Nova setup for managing tenants in the [SaaS boilerplate](/saas-boilerplate):
## In the tenant app
To use Nova inside of the tenant part of your application, do the following:
- Publish the Nova migrations and move them to the `database/migrations/tenant` directory.
```
php artisan vendor:publish --tag=nova-migrations
```
- Prevent Nova from adding its migrations to your central migrations by adding `Nova::ignoreMigrations()` to `NovaServiceProvider::boot()` (Don't do this if you want to use Nova **[both in the central & tenant parts]({{ $page->link('features/universal-routes') }})** of the app.)
- Add the tenancy middleware to your `nova.middleware` config. Example:
```php
'middleware' => [
// You can make this simpler by creating a tenancy route group
InitializeTenancyByDomain::class,
PreventAccessFromCentralDomains::class,
'web',
Authenticate::class,
DispatchServingNovaEvent::class,
BootTools::class,
Authorize::class,
],
```
- In your `NovaServiceProvider`'s `routes()` method, replace the following lines:
```php
->withAuthenticationRoutes()
->withPasswordResetRoutes()
```
with these lines:
```php
->withAuthenticationRoutes(['web', 'tenancy'])
->withPasswordResetRoutes(['web', 'tenancy'])
```

View file

@ -0,0 +1,54 @@
---
title: Laravel Passport integration
extends: _layouts.documentation
section: content
---
# Laravel Passport
> If you just want to write an SPA, but don't need an API for some other use (e.g. mobile app), you can avoid a lot of the complexity of writing SPAs by using [Inertia.js](https://inertiajs.com/).
To use Passport inside the tenant part of your application, you may do the following.
- Add this to the `register` method in your `AppServiceProvider`:
```php
Passport::ignoreMigrations();
Passport::routes(null, ['middleware' => [
// You can make this simpler by creating a tenancy route group
InitializeTenancyByDomain::class,
PreventAccessFromCentralDomains::class,
]]);
```
- `php artisan vendor:publish --tag=passport-migrations` & move to `database/migrations/tenant/` directory
## **Shared keys**
If you want to use the same keypair for all tenants, do the following.
- Don't use `passport:install`, use just `passport:keys`. The install command creates keys & two clients. Instead of creating clients centrally, create `Client`s manually in your [tenant database seeder]({{ $page->link('configuration#seeder-params') }}).
## **Tenant-specific keys**
If you want to use a unique keypair for each tenant, do the following. (Note: The security benefit of doing this isn't probably that big, since you're likely already using the same `APP_KEY` for all tenants.)
There are multiple ways you can store & load tenant keys, but the most straightforward way is to store the keys in the on the tenant model and load them into the `passport` configuration using the **[Tenant Config]({{ $page->link('features/tenant-config') }})** feature:
- Uncomment the `TenantConfig` line in your `tenancy.features` config
- Configure the mapping as follows:
```php
[
'passport_public_key' => 'passport.public_key',
'passport_private_key' => 'passport.private_key',
],
```
And again, you need to create clients in your tenant database seeding process.
## Using Passport in both the central & tenant app
![Passport for both central & tenant app](/assets/images/passport_universal.png)
And make sure you enable the *Universal Routes* feature.

View file

@ -0,0 +1,37 @@
---
title: Integration with Spatie packages
extends: _layouts.documentation
section: content
---
# Integration with Spatie packages
## **laravel-activitylog**
### For the tenant app:
- Set the `database_connection` key in `config/activitylog.php` to `null`. This makes activitylog use the default connection.
- Publish the migrations and move them to `database/migrations/tenant`. (And, of course, don't forget to run `artisan tenants:migrate`.)
### For the central app:
- Set the `database_connection` key in `config/activitylog.php` to the name of your central database connection.
## **laravel-permission**
Install the package like usual, but publish the migrations and move them to `migrations/tenant`:
```
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="migrations"
mv database/migrations/*_create_permission_tables.php database/migrations/tenant
```
Then add this to your `AppServiceProvider::boot()` method:
```php
Event::listen(TenancyBootstrapped::class, function (Tenancy $tenancy) {
\Spatie\Permission\PermissionRegistrar::$cacheKey = 'spatie.permission.cache.tenant.' . $tenancy->tenant->id;
});
```
The reason for this is that spatie/laravel-permission caches permissions & roles to save DB queries, which means that we need to separate the permission cache by tenant.

View file

@ -0,0 +1,11 @@
---
title: Laravel Telescope integration
extends: _layouts.documentation
section: content
---
# Laravel Telescope
Enable the the *Telescope tags* feature to have all Telescope requests tagged with the current tenant's id.
Note that Telescope (& its migrations) will be part of the central app.

View file

@ -0,0 +1,36 @@
---
title: Introduction
extends: _layouts.documentation
section: content
---
# Introduction
## What is multi-tenancy?
Multi-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.
You may find this talk insightful: [https://multitenantlaravel.com/](https://multitenantlaravel.com/). Simply going through the slides will give you 80% of the value of the talk in under five minutes.
Note that if you just want to, say, scope todo tasks to the current user, there's no need to use a multi-tenancy package. Just use calls like `auth()->user()->tasks()`. This is the simplest for of multi-tenancy.
This package is built around the idea that multi-tenancy usually means letting tenants have their own users which have their own resources, e.g. todo tasks. Not just users having tasks.
## Types of multi-tenancy
There are two **types** of multi-tenancy:
- single-database tenancy — tenants share one database and their data is separated using e.g. `where tenant_id = 1` clauses.
- multi-database tenancy — each tenant has his own database
This package lets you do both, though it focuses more on multi-database tenancy because that type requires more work on the side of the package and less work on your side. Whereas for 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.
## Modes of multi-tenancy
The tenancy "mode" is a unique property of this package. In previous versions, this package was intended primarily for [**automatic tenancy**]({{ $page->link('automatic-mode') }}), which means that after a tenant was identified, things like database connections, caches, filesystems, queues etc were switched to that tenant's context — his data completely isolated from the rest.
In the current version, we're also making [**manual tenancy**]({{ $page->link('manual-mode') }}) a first-class feature. We provide you with things like model traits if you wish to scope the data yourself.
## Tenant identification
For your application to be tenant-aware, a tenant has to be identified. This package ships with a large number of identification middlewares. You may identify tenants by domain, subdomain, domain OR subdomain at the same time, path or request data.

Some files were not shown because too many files have changed in this diff Show more