1
0
Fork 0
mirror of https://github.com/archtechx/tenancy.git synced 2025-12-12 08:04:03 +00:00
tenancy/README.md
2019-01-17 22:24:12 +01:00

343 lines
10 KiB
Markdown

# Tenancy
### *A Laravel multi-database tenancy implementation that respects your code.*
You won't have to change a thing in your application's code.
- :white_check_mark: No model traits to change database connection
- :white_check_mark: No replacing of Laravel classes (`Cache`, `Storage`, ...) with tenancy-aware classes
- :white_check_mark: Built-in tenant identification based on hostname
## Installation
### Installing the package
```
composer require stancl/tenancy
```
### Adding the `InitializeTenancy` middleware
Open `app/Http/Kernel.php` and make the following changes:
First, you want to create middleware groups so that we can apply this middleware on routes.
- Create a new middleware group in `$middlewareGroups`:
```php
'tenancy' => [
\Stancl\Tenancy\Middleware\InitializeTenancy::class,
],
```
- Create a new middleware group in `$routeMiddleware`:
```php
'tenancy' => \Stancl\Tenancy\Middleware\InitializeTenancy::class,
```
- Make the middleware top priority, so that it gets executed before anything else, thus making sure things like the database switch connections soon enough.
```php
protected $middlewarePriority = [
\Stancl\Tenancy\Middleware\InitializeTenancy::class,
```
#### Configuring the middleware
When a tenant route is visited, but the tenant can't be identified, an exception can be 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) {
// redirect
});
});
```
### Creating tenant routes
Add this method into `app/Providers/RouteServiceProvider.php`:
```php
/**
* Define the "tenant" routes for the application.
*
* These routes all receive session state, CSRF protection, etc.
*
* @return void
*/
protected function mapTenantRoutes()
{
Route::middleware(['web', 'tenancy'])
->namespace($this->namespace)
->group(base_path('routes/tenant.php'));
}
```
And add this line to `map()`:
```php
$this->mapTenantRoutes();
```
Now rename the `routes/web.php` file to `routes/tenant.php`. This file will contain routes accessible only with tenancy.
Create an empty `routes/web.php` file. This file will contain routes accessible without tenancy (such as the landing page.)
### Publishing the configuration file
```
php artisan vendor:publish --provider='Stancl\Tenancy\TenancyServiceProvider' --tag=config
```
You should see something along the lines of `Copied File [...] to [/config/tenancy.php]`.
#### `database`
Databases will be named like this:
```php
config('tenancy.database.prefix') . $uuid . config('tenancy.database.suffix')
```
They will use a connection based on the connection specified using the `based_on` setting. Using `mysql` or `sqlite` is fine, but if you need to change more things than just the database name, you can create a new `tenant` connection and set `tenancy.database.based_on` to `tenant`.
#### `redis`
Keys will be prefixed with:
```php
config('tenancy.redis.prefix_base') . $uuid
```
These changes will only apply for connections listed in `prefixed_connections`.
#### `cache`
Cache keys will be tagged with a tag:
```php
config('tenancy.cache.prefix_base') . $uuid
```
### `filesystem`
Filesystem paths will be suffixed with:
```php
config('tenancy.filesystem.suffix_base') . $uuid
```
These changes will only apply for disks listen in `disks`.
You can see an example in the [Filesystem](#Filesystem) section of the documentation.
# Usage
## Obtaining a `TenantManager` instance
You can use the `tenancy()` and `tenant()` helpers to resolve `Stancl\Tenancy\TenantManager` out of the service container. These two helpers are exactly the same, the only reason there are two is nice syntax. `tenancy()->init()` sounds better than `tenant()->init()` and `tenant()->create()` sounds better than `tenancy()->create()`.
### Creating a new tenant
```php
>>> tenant()->create('dev.localhost')
=> [
"uuid" => "49670df0-1a87-11e9-b7ba-cf5353777957",
"domain" => "dev.localhost",
]
```
### Starting a session as a tenant
This runs `TenantManager::bootstrap()` which switches the DB connection, prefixes Redis, changes filesystem root paths, etc.
```php
tenancy()->init();
// The domain will be autodetected unless specified as an argument
tenancy()->init('dev.localhost');
```
### Getting tenant information based on his UUID
You can use `find()`, which is an alias for `getTenantById()`.
You may use the second argument to specify the key(s) as a string/array.
```php
>>> tenant()->getTenantById('dbe0b330-1a6e-11e9-b4c3-354da4b4f339');
=> [
"uuid" => "dbe0b330-1a6e-11e9-b4c3-354da4b4f339",
"domain" => "localhost",
"foo" => "bar",
]
>>> tenant()->getTenantById('dbe0b330-1a6e-11e9-b4c3-354da4b4f339', 'foo');
=> [
"foo" => "bar",
]
>>> tenant()->getTenantById('dbe0b330-1a6e-11e9-b4c3-354da4b4f339', ['foo', 'domain']);
=> [
"foo" => "bar",
"domain" => "localhost",
]
```
### Getting tenant UUID based on his domain
```php
>>> tenant()->getTenantIdByDomain('localhost');
=> "b3ce3f90-1a88-11e9-a6b0-038c6337ae50"
>>> tenant()->getIdByDomain('localhost');
=> "b3ce3f90-1a88-11e9-a6b0-038c6337ae50"
```
### Getting tenant information based on his 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",
]
```
### Getting current tenant information
You can access the public array `tenant` of `TenantManager` like this:
```php
tenancy()->tenant
```
which returns an array. If you want to get the value of a specific key from the array, you can use one of the helpers with an argument --- the key on the `tenant` array.
```php
tenant('uuid'); // Does the same thing as tenant()->tenant['uuid']
```
### Listing all tenants
```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
```
## Storage driver
Currently, only Redis is supported, but you're free to code your own storage driver which follows the `Stancl\Tenancy\Interfaces\StorageDriver` interface. Just point the `tenancy.storage_driver` setting at your driver.
**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.
### Storing custom data
Along with the tenant and database info, you can store your own data in the storage. You can use:
```php
tenancy()->get($key);
tenancy()->put($key, $value);
tenancy()->set($key, $value); // alias for put()
```
Note that `$key` has to be a string.
## Database
The entire application will use a new database connection. The connection will be based on the connection specified in `tenancy.database.based_on`. A database name of `tenancy.database.prefix` + tenant UUID + `tenancy.database.suffix` will be used. You can set the suffix to `.sqlite` if you're using sqlite and want the files to be in the sqlite format and you can leave the suffix empty if you're using MySQL (for example).
## Redis
Connections listed in the `tenancy.redis.prefixed_connections` config array use a prefix based on the `tenancy.redis.prefix_base` and the tenant UUID.
**Note: You *must* use phpredis for prefixes to work. Predis doesn't support prefixes.**
## Cache
Both `cache()` and `Cache` will use `Stancl\Tenancy\CacheManager`, which adds a tag (`prefix_base` + tenant UUID) to all methods called on it.
## Filesystem
Assuming the following tenancy config:
```php
'filesystem' => [
'suffix_base' => 'tenant',
// Disks which should be suffixed with the prefix_base + tenant UUID.
'disks' => [
'local',
// 's3',
],
],
```
The `local` filesystem driver will be suffixed with a directory containing `tenant` and the tenant UUID.
```php
>>> Storage::disk('local')->getAdapter()->getPathPrefix()
=> "/var/www/laravel/multitenancy/storage/app/"
>>> tenancy()->init()
=> [
"uuid" => "dbe0b330-1a6e-11e9-b4c3-354da4b4f339",
"domain" => "localhost",
]
>>> Storage::disk('local')->getAdapter()->getPathPrefix()
=> "/var/www/laravel/multitenancy/storage/app/tenantdbe0b330-1a6e-11e9-b4c3-354da4b4f339/"
```
## Artisan commands
```
Available commands for the "tenants" namespace:
tenants:list List tenants.
tenants:migrate Run migrations for tenant(s)
tenants:rollback Rollback migrations for tenant(s).
tenants:seed Seed tenant database(s).
```
#### `tenants:list`
```
$ artisan tenants:list
Listing all tenants.
[Tenant] uuid: dbe0b330-1a6e-11e9-b4c3-354da4b4f339 @ localhost
[Tenant] uuid: 49670df0-1a87-11e9-b7ba-cf5353777957 @ dev.localhost
```
#### `tenants:migrate`, `tenants:rollback`, `tenants:seed`
- You may specify the tenant(s) UUIDs using the `--tenants` option.
## Some tips
- If you create a tenant using the interactive console (`artisan tinker`) and use sqlite, you might need to change the database's permissions and/or ownership (`chmod`/`chown`) so that the web application can access it.