mirror of
https://github.com/archtechx/tenancy.git
synced 2025-12-12 08:44:02 +00:00
Initial commit
This commit is contained in:
commit
deb3ad77f5
19 changed files with 1283 additions and 0 deletions
343
README.md
Normal file
343
README.md
Normal file
|
|
@ -0,0 +1,343 @@
|
|||
# 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue