Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[1.7.0] Add DB storage driver #82

Merged
merged 65 commits into from
Aug 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
c6169c2
wip
stancl Aug 8, 2019
02e672e
Apply fixes from StyleCI
stancl Aug 8, 2019
bee7f34
wip
stancl Aug 9, 2019
adb92d5
Apply fixes from StyleCI
stancl Aug 9, 2019
01007c3
wip
stancl Aug 9, 2019
fd0712e
wip
stancl Aug 9, 2019
fc76da3
finish writing storage driver
stancl Aug 9, 2019
feaac4c
Apply fixes from StyleCI
stancl Aug 9, 2019
b8baeac
add test variant
stancl Aug 9, 2019
9fcfcf3
Merge branch 'db-storage-driver' of github.com:stancl/tenancy into db…
stancl Aug 9, 2019
de025a6
Set public key on model
stancl Aug 9, 2019
4d7c6b6
add TenantModel interface, fix issues pointed out by @carlos-mora
stancl Aug 10, 2019
e7e23d7
Apply fixes from StyleCI
stancl Aug 10, 2019
23bdaba
fix getTenantById
stancl Aug 10, 2019
533502d
Merge branch 'db-storage-driver' of github.com:stancl/tenancy into db…
stancl Aug 10, 2019
b07b848
remove unnecessary type conversion
stancl Aug 10, 2019
70234e1
add storage driver assertion
stancl Aug 10, 2019
dde40a5
Apply fixes from StyleCI
stancl Aug 10, 2019
299437a
set the storage driver
stancl Aug 10, 2019
401212b
Merge branch 'db-storage-driver' of github.com:stancl/tenancy into db…
stancl Aug 10, 2019
73223f0
Apply fixes from StyleCI
stancl Aug 10, 2019
03f233b
wip
stancl Aug 10, 2019
8f41fbf
Merge branch 'db-storage-driver' of github.com:stancl/tenancy into db…
stancl Aug 10, 2019
de30f37
wip
stancl Aug 10, 2019
c38dd1b
wip
stancl Aug 10, 2019
7fd274d
Apply fixes from StyleCI
stancl Aug 10, 2019
7a5ed86
wip
stancl Aug 10, 2019
63f19ff
wip
stancl Aug 10, 2019
c8ba47a
wip
stancl Aug 10, 2019
6d5a536
Apply fixes from StyleCI
stancl Aug 10, 2019
6c3f86a
change migration path
stancl Aug 10, 2019
9296b38
begin work on docs
stancl Aug 10, 2019
ef718f2
get rid of TenantModel interface
stancl Aug 10, 2019
d66ab0d
Apply fixes from StyleCI
stancl Aug 10, 2019
429b177
get rid of tenant model sc binding
stancl Aug 10, 2019
f073926
Apply fixes suggested by @carlos-mora
stancl Aug 11, 2019
a9877bc
merge
stancl Aug 11, 2019
978996b
Apply fixes from StyleCI
stancl Aug 11, 2019
4827a2c
Merge branch '1.x' into db-storage-driver
stancl Aug 13, 2019
23dd428
Merge branch '1.x' into db-storage-driver
stancl Aug 13, 2019
f183235
merge
stancl Aug 15, 2019
19409d7
fix ./test
stancl Aug 15, 2019
b1b5c60
Apply fixes from StyleCI
stancl Aug 15, 2019
e950515
wip
stancl Aug 15, 2019
db5cf1e
Merge branch 'db-storage-driver' of github.com:stancl/tenancy into db…
stancl Aug 15, 2019
170392f
wip
stancl Aug 15, 2019
f7ca293
finally fixed the migrations issue
stancl Aug 15, 2019
3036980
tearDown() no such table migrations
stancl Aug 15, 2019
71e1c17
wip
stancl Aug 15, 2019
10cf366
Apply fixes from StyleCI
stancl Aug 15, 2019
68e0922
wip
stancl Aug 15, 2019
9cdf3dc
Merge branch 'db-storage-driver' of github.com:stancl/tenancy into db…
stancl Aug 15, 2019
fefe12d
Fix issues caused by loadMigrationsFrom
stancl Aug 16, 2019
0176579
wip
stancl Aug 16, 2019
73fd4e6
Apply fixes from StyleCI
stancl Aug 16, 2019
643936d
add data type tests
stancl Aug 16, 2019
fbd600a
Merge branch 'db-storage-driver' of github.com:stancl/tenancy into db…
stancl Aug 16, 2019
405e314
getMany now returns only values
stancl Aug 16, 2019
ac5c63f
final changes
stancl Aug 16, 2019
0f43f55
Merge branch '1.x' into db-storage-driver
stancl Aug 16, 2019
588ca0a
assert migration file exists
stancl Aug 16, 2019
8b39ad0
Fix migration publishing
stancl Aug 16, 2019
e6ededa
wip
stancl Aug 16, 2019
b16e89f
Move assets to root
stancl Aug 16, 2019
a54ee56
Fix path to config
stancl Aug 16, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,35 @@ Note that deleting a tenant doesn't delete his database. You can do this manuall

## 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.
### Database

The database storage driver lets you store information about tenants in a relational database like MySQL, PostgreSQL and SQLite.

To use this storage driver, publish the `create_tenants_table` migration:

```
php artisan vendor:publish --provider='Stancl\Tenancy\TenancyServiceProvider' --tag=migrations
```

By default, the table contains only `uuid`, `domain` and `data` columns.

The `data` column is used to store information about a tenant, such as their selected plan, in JSON form. This package does not store anything in the column by default.

You can store specific keys in your own columns. This is useful if you want to use RDBMS features like indexes.

If you don't need any custom columns, you can skip the next section and run:

```
php artisan migrate
```

#### Adding your own columns

To add your own columns, TODO.

### Redis

Using Redis as your storage driver is recommended due to its low overhead compared to a relational database like MySQL.

**Note that you need to configure persistence on your Redis instance** if you don't want to lose all information about tenants.

Expand Down
16 changes: 14 additions & 2 deletions src/config/tenancy.php → assets/config.php
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
<?php

return [
'storage_driver' => 'Stancl\Tenancy\StorageDrivers\RedisStorageDriver',
'storage_driver' => 'Stancl\Tenancy\StorageDrivers\DatabaseStorageDriver',
'storage' => [
'db' => [ // Stancl\Tenancy\StorageDrivers\DatabaseStorageDriver
'data_column' => 'data',
'custom_columns' => [
// 'plan',
],
'connection' => 'central',
],
'redis' => [ // Stancl\Tenancy\StorageDrivers\RedisStorageDriver
'connection' => 'tenancy',
],
],
'tenant_route_namespace' => 'App\Http\Controllers',
'exempt_domains' => [
// 'localhost',
],
'database' => [
'based_on' => 'mysql',
'based_on' => 'sqlite',
'prefix' => 'tenant',
'suffix' => '',
],
Expand Down
35 changes: 35 additions & 0 deletions assets/migrations/2019_08_08_000000_create_tenants_table.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateTenantsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('tenants', function (Blueprint $table) {
$table->string('uuid', 36)->primary(); // don't change this
$table->string('domain', 255)->index(); // don't change this

// your indexed columns go here

$table->json('data')->default('{}');
});
}

/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('tenants');
}
}
1 change: 1 addition & 0 deletions src/Interfaces/StorageDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ interface StorageDriver
{
public function identifyTenant(string $domain): array;

/** @return array[] */
public function getAllTenants(array $uuids = []): array;

public function getTenantById(string $uuid, array $fields = []): array;
Expand Down
84 changes: 84 additions & 0 deletions src/StorageDrivers/DatabaseStorageDriver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php

namespace Stancl\Tenancy\StorageDrivers;

use Stancl\Tenancy\Tenant;
use Stancl\Tenancy\Interfaces\StorageDriver;

stancl marked this conversation as resolved.
Show resolved Hide resolved
class DatabaseStorageDriver implements StorageDriver
{
public $useJson = false;

// todo use an instance of tenant model?
// todo write tests verifying that data is decoded and added to the array

public function identifyTenant(string $domain): array
{
$id = $this->getTenantIdByDomain($domain);
if (! $id) {
throw new \Exception("Tenant could not be identified on domain {$domain}");
}

return $this->getTenantById($id);
}

/**
* Get information about the tenant based on his uuid.
*
* @param string $uuid
* @param array $fields
* @return array
*/
public function getTenantById(string $uuid, array $fields = []): array
{
if ($fields) {
return Tenant::decodeData(Tenant::find($uuid)->only($fields));
} else {
return Tenant::find($uuid)->decoded();
}
}

public function getTenantIdByDomain(string $domain): ?string
{
return Tenant::where('domain', $domain)->first()->uuid ?? null;
}

public function createTenant(string $domain, string $uuid): array
{
return Tenant::create(['uuid' => $uuid, 'domain' => $domain])->toArray();
}

public function deleteTenant(string $id): bool
{
return Tenant::find($id)->delete();
}

public function getAllTenants(array $uuids = []): array
{
return Tenant::getAllTenants($uuids)->toArray();
}
stancl marked this conversation as resolved.
Show resolved Hide resolved

public function get(string $uuid, string $key)
{
return Tenant::find($uuid)->get($key);
}

public function getMany(string $uuid, array $keys): array
{
return Tenant::find($uuid)->getMany($keys);
}

public function put(string $uuid, string $key, $value)
{
return Tenant::find($uuid)->put($key, $value);
}

public function putMany(string $uuid, array $values): array
{
foreach ($values as $key => $value) {
Tenant::find($uuid)->put($key, $value);
}

return $values;
}
}
4 changes: 1 addition & 3 deletions src/StorageDrivers/RedisStorageDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class RedisStorageDriver implements StorageDriver

public function __construct()
{
$this->redis = Redis::connection('tenancy');
$this->redis = Redis::connection(config('tenancy.redis.connection', 'tenancy'));
}

public function identifyTenant(string $domain): array
Expand All @@ -33,8 +33,6 @@ public function identifyTenant(string $domain): array
*/
public function getTenantById(string $uuid, array $fields = []): array
{
$fields = (array) $fields;

if (! $fields) {
return $this->redis->hgetall("tenants:$uuid");
}
Expand Down
8 changes: 6 additions & 2 deletions src/TenancyServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,13 @@ public function boot()
]);

$this->publishes([
__DIR__ . '/config/tenancy.php' => config_path('tenancy.php'),
__DIR__ . '/../assets/config.php' => config_path('tenancy.php'),
], 'config');

$this->publishes([
__DIR__ . '/../assets/migrations/' => database_path('migrations'),
], 'migrations');

$this->loadRoutesFrom(__DIR__ . '/routes.php');

Route::middlewareGroup('tenancy', [
Expand All @@ -52,7 +56,7 @@ public function boot()
*/
public function register()
{
$this->mergeConfigFrom(__DIR__ . '/config/tenancy.php', 'tenancy');
$this->mergeConfigFrom(__DIR__ . '/../assets/config.php', 'tenancy');

$this->app->bind(StorageDriver::class, $this->app['config']['tenancy.storage_driver']);
$this->app->bind(ServerConfigManager::class, $this->app['config']['tenancy.server.manager']);
Expand Down
102 changes: 102 additions & 0 deletions src/Tenant.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php

namespace Stancl\Tenancy;

use Illuminate\Database\Eloquent\Model;

class Tenant extends Model
{
protected $guarded = [];
protected $primaryKey = 'uuid';
public $incrementing = false;
public $timestamps = false;

stancl marked this conversation as resolved.
Show resolved Hide resolved
/**
* Decoded data from the data column.
*
* @var object
*/
private $dataObject;

public static function dataColumn()
{
return config('tenancy.storage.db.data_column', 'data');
}
stancl marked this conversation as resolved.
Show resolved Hide resolved

public static function customColumns()
{
return config('tenancy.storage.db.custom_columns', []);
}

public function getConnectionName()
{
return config('tenancy.storage.db.connection', 'central');
}

public static function getAllTenants(array $uuids)
{
$tenants = $uuids ? static::findMany($uuids) : static::all();

return $tenants->map([__CLASS__, 'decodeData'])->toBase();
}

public function decoded()
{
return static::decodeData($this);
}

/**
* Return a tenant array with data decoded into separate keys.
*
* @param Tenant|array $tenant
* @return array
*/
public static function decodeData($tenant)
{
$tenant = $tenant instanceof self ? (array) $tenant->attributes : $tenant;
$decoded = json_decode($tenant[$dataColumn = static::dataColumn()], true);

foreach ($decoded as $key => $value) {
$tenant[$key] = $value;
}

// If $tenant[$dataColumn] has been overriden by a value, don't delete the key.
if (! array_key_exists($dataColumn, $decoded)) {
unset($tenant[$dataColumn]);
}

return $tenant;
}
stancl marked this conversation as resolved.
Show resolved Hide resolved

public function getFromData(string $key)
{
$this->dataArray = $this->dataArray ?? json_decode($this->{$this->dataColumn()}, true);

return $this->dataArray[$key] ?? null;
}

public function get(string $key)
{
return $this->$key ?? $this->getFromData($key) ?? null;
}

/** @todo In v2, this should return an associative array. */
public function getMany(array $keys): array
{
return array_map([$this, 'get'], $keys);
}

public function put(string $key, $value)
{
if (array_key_exists($key, $this->customColumns())) {
$this->update([$key => $value]);
} else {
$obj = json_decode($this->{$this->dataColumn()});
$obj->$key = $value;

$this->update([$this->dataColumn() => json_encode($obj)]);
}

return $value;
}
}
Loading