diff --git a/assets/TenancyServiceProvider.stub.php b/assets/TenancyServiceProvider.stub.php
new file mode 100644
index 00000000..f3e230b4
--- /dev/null
+++ b/assets/TenancyServiceProvider.stub.php
@@ -0,0 +1,78 @@
+ [
+ JobPipeline::make([
+ CreateDatabase::class,
+ MigrateDatabase::class, // triggers DatabaseMigrated event
+ SeedDatabase::class,
+ ])->send(function (TenantCreated $event) {
+ return $event->tenant;
+ })->queue(true),
+ ],
+ DatabaseCreated::class => [],
+ DatabaseMigrated::class => [],
+ DatabaseSeeded::class => [],
+ TenantDeleted::class => [
+ JobPipeline::make([
+ DeleteDatabase::class,
+ ])->send(function (TenantDeleted $event) {
+ return $event->tenant;
+ })->queue(true),
+ // DeleteStorage::class,
+ ],
+ DatabaseDeleted::class => [],
+ ];
+ }
+
+ public function register()
+ {
+ //
+ }
+
+ public function boot()
+ {
+ $this->bootEvents();
+
+ //
+ }
+
+ protected function bootEvents()
+ {
+ foreach ($this->events() as $event => $listeners) {
+ foreach (array_unique($listeners) as $listener) {
+ // Technically, the string|Closure typehint is not enforced by
+ // Laravel, but for type correctness, we wrap callables in
+ // simple Closures, to match Laravel's docblock typehint.
+ if (is_callable($listener) && !$listener instanceof Closure) {
+ $listener = function ($event) use ($listener) {
+ $listener($event);
+ };
+ }
+
+ Event::listen($event, $listener);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/assets/config.php b/assets/config.php
index 1ec13a91..8a266b83 100644
--- a/assets/config.php
+++ b/assets/config.php
@@ -2,68 +2,36 @@
declare(strict_types=1);
+use Stancl\Tenancy\Database\Models\Tenant;
+
return [
- /**
- * Storage drivers are used to store information about your tenants.
- * They hold the Tenant Storage data and keeps track of domains.
- */
- 'storage_driver' => 'db',
- 'storage_drivers' => [
- /**
- * The majority of applications will want to use this storage driver.
- * The information about tenants is persisted in a relational DB
- * like MySQL or PostgreSQL. The only downside is performance.
- *
- * A database connection to the central database has to be established on each
- * request, to identify the tenant based on the domain. This takes three DB
- * queries. Then, the connection to the tenant database is established.
- *
- * Note: From v2.3.0, the performance of the DB storage driver can be improved
- * by a lot by using Cached Tenant Lookup. Be sure to enable that if you're
- * using this storage driver. Enabling that feature can completely avoid
- * querying the central database to identify build the Tenant object.
- */
- 'db' => [
- 'driver' => Stancl\Tenancy\StorageDrivers\Database\DatabaseStorageDriver::class,
- 'data_column' => 'data',
- 'custom_columns' => [
- // 'plan',
- ],
+ 'tenant_model' => Tenant::class,
+ 'internal_prefix' => 'tenancy_',
- /**
- * Your central database connection. Set to null to use the default one.
- *
- * Note: It's recommended to create a designated central connection,
- * to let you easily use it in your app, e.g. via the DB facade.
- */
- 'connection' => null,
+ 'central_connection' => 'central',
+ 'template_tenant_connection' => null,
- 'table_names' => [
- 'tenants' => 'tenants',
- 'domains' => 'domains',
- ],
+ 'id_generator' => Stancl\Tenancy\UniqueIDGenerators\UUIDGenerator::class,
- /**
- * Here you can enable the Cached Tenant Lookup.
- *
- * You can specify what cache store should be used to cache the tenant resolution.
- * Set to string with a specific cache store name, or to null to disable cache.
- */
- 'cache_store' => null,
- 'cache_ttl' => 3600, // seconds
+ 'custom_columns' => [
+ //
+ ],
+
+
+ 'storage' => [
+ 'data_column' => 'data',
+ 'custom_columns' => [
+ // 'plan',
],
/**
- * The Redis storage driver is much more performant than the database driver.
- * However, by default, Redis is a not a durable data storage. It works well for ephemeral data
- * like cache, but to hold critical data, it needs to be configured in a way that guarantees
- * that data will be persisted permanently. Specifically, you want to enable both AOF and
- * RDB. Read this here: https://tenancy.samuelstancl.me/docs/v2/storage-drivers/#redis.
+ * Here you can enable the Cached Tenant Lookup.
+ *
+ * You can specify what cache store should be used to cache the tenant resolution.
+ * Set to string with a specific cache store name, or to null to disable cache.
*/
- 'redis' => [
- 'driver' => Stancl\Tenancy\StorageDrivers\RedisStorageDriver::class,
- 'connection' => 'tenancy',
- ],
+ 'cache_store' => null, // env('CACHE_DRIVER')
+ 'cache_ttl' => 3600, // seconds
],
/**
@@ -108,6 +76,7 @@ return [
*/
'prefix' => 'tenant',
'suffix' => '',
+ // todo get rid of this stuff, just set the closure instead
],
/**
@@ -237,55 +206,30 @@ return [
*/
'home_url' => '/app',
- /**
- * Automatically create a database when creating a tenant.
- */
- 'create_database' => true,
-
/**
* Should tenant databases be created asynchronously in a queued job.
*/
- 'queue_database_creation' => false,
+ 'queue_database_creation' => false, // todo make this a static property
- /**
- * Should tenant migrations be ran after the tenant's database is created.
- */
- 'migrate_after_creation' => false,
'migration_parameters' => [
'--force' => true, // Set this to true to be able to run migrations in production
// '--path' => [database_path('migrations/tenant')], // If you need to customize paths to tenant migrations
],
- /**
- * Should tenant databases be automatically seeded after they're created & migrated.
- */
- 'seed_after_migration' => false, // should the seeder run after automatic migration
'seeder_parameters' => [
'--class' => 'DatabaseSeeder', // root seeder class, e.g.: 'DatabaseSeeder'
// '--force' => true,
],
- /**
- * Automatically delete the tenant's database after the tenant is deleted.
- *
- * This will save space but permanently delete data which you might want to keep.
- */
- 'delete_database_after_tenant_deletion' => false,
-
/**
* Should tenant databases be deleted asynchronously in a queued job.
*/
'queue_database_deletion' => false,
- /**
- * If you don't supply an id when creating a tenant, this class will be used to generate a random ID.
- */
- 'unique_id_generator' => Stancl\Tenancy\UniqueIDGenerators\UUIDGenerator::class,
-
/**
* Middleware pushed to the global middleware stack.
*/
- 'global_middleware' => [
+ 'global_middleware' => [ // todo get rid of this
Stancl\Tenancy\Middleware\InitializeTenancy::class,
],
];
diff --git a/assets/migrations/2019_09_15_000010_create_tenants_table.php b/assets/migrations/2019_09_15_000010_create_tenants_table.php
index f779856f..37fc22c4 100644
--- a/assets/migrations/2019_09_15_000010_create_tenants_table.php
+++ b/assets/migrations/2019_09_15_000010_create_tenants_table.php
@@ -18,9 +18,10 @@ class CreateTenantsTable extends Migration
Schema::create('tenants', function (Blueprint $table) {
$table->string('id', 36)->primary(); // 36 characters is the default uuid length
- // (optional) your custom, indexed columns may go here
+ // your custom columns may go here
- $table->json('data');
+ $table->timestamps();
+ $table->json('data')->default('{}');
});
}
diff --git a/composer.json b/composer.json
index cac4568d..8c04d02e 100644
--- a/composer.json
+++ b/composer.json
@@ -1,6 +1,6 @@
{
"name": "stancl/tenancy",
- "description": "A Laravel multi-database tenancy package that respects your code.",
+ "description": "Automatic multi-tenancy for your Laravel application.",
"keywords": ["laravel", "multi-tenancy", "multi-database", "tenancy"],
"license": "MIT",
"authors": [
@@ -20,7 +20,7 @@
"laravel/framework": "^6.0|^7.0",
"orchestra/testbench-browser-kit": "^4.0|^5.0",
"league/flysystem-aws-s3-v3": "~1.0",
- "phpunit/phpcov": "^6.0|^7.0"
+ "doctrine/dbal": "^2.10"
},
"autoload": {
"psr-4": {
diff --git a/docker-compose.yml b/docker-compose.yml
index d5315fb9..fafd6316 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -16,9 +16,6 @@ services:
DB_PASSWORD: password
DB_USERNAME: root
DB_DATABASE: main
- TENANCY_TEST_REDIS_HOST: redis
- TENANCY_TEST_MYSQL_HOST: mysql
- TENANCY_TEST_PGSQL_HOST: postgres
stdin_open: true
tty: true
mysql:
diff --git a/fulltest b/fulltest
index de5d7542..fa5435e3 100755
--- a/fulltest
+++ b/fulltest
@@ -4,4 +4,3 @@ set -e
# for development
docker-compose up -d
./test "$@"
-docker-compose exec -T test vendor/bin/phpcov merge --clover clover.xml coverage/
diff --git a/phpunit.xml b/phpunit.xml
index a1c16a21..cfd22371 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -30,6 +30,5 @@
-
\ No newline at end of file
diff --git a/src/Commands/Migrate.php b/src/Commands/Migrate.php
index 4859d047..d10bcd06 100644
--- a/src/Commands/Migrate.php
+++ b/src/Commands/Migrate.php
@@ -8,6 +8,7 @@ use Illuminate\Console\Command;
use Illuminate\Database\Console\Migrations\MigrateCommand;
use Illuminate\Database\Migrations\Migrator;
use Stancl\Tenancy\DatabaseManager;
+use Stancl\Tenancy\Events\DatabaseMigrated;
use Stancl\Tenancy\Traits\DealsWithMigrations;
use Stancl\Tenancy\Traits\HasATenantsOption;
@@ -62,6 +63,8 @@ class Migrate extends MigrateCommand
// Migrate
parent::handle();
});
+
+ event(new DatabaseMigrated($tenant));
});
}
}
diff --git a/src/Commands/Seed.php b/src/Commands/Seed.php
index acc697e0..a31d0045 100644
--- a/src/Commands/Seed.php
+++ b/src/Commands/Seed.php
@@ -7,6 +7,7 @@ namespace Stancl\Tenancy\Commands;
use Illuminate\Database\ConnectionResolverInterface;
use Illuminate\Database\Console\Seeds\SeedCommand;
use Stancl\Tenancy\DatabaseManager;
+use Stancl\Tenancy\Events\DatabaseSeeded;
use Stancl\Tenancy\Traits\HasATenantsOption;
class Seed extends SeedCommand
@@ -60,6 +61,8 @@ class Seed extends SeedCommand
// Seed
parent::handle();
});
+
+ event(new DatabaseSeeded($tenant));
});
}
}
diff --git a/src/Contracts/UniqueIdentifierGenerator.php b/src/Contracts/UniqueIdentifierGenerator.php
index 0dd40eb5..ad560641 100644
--- a/src/Contracts/UniqueIdentifierGenerator.php
+++ b/src/Contracts/UniqueIdentifierGenerator.php
@@ -4,14 +4,12 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Contracts;
+use Stancl\Tenancy\Database\Models\Tenant;
+
interface UniqueIdentifierGenerator
{
/**
* Generate a unique identifier.
- *
- * @param string[] $domains
- * @param array $data
- * @return string
*/
- public static function generate(array $domains, array $data = []): string;
+ public static function generate(Tenant $tenant): string;
}
diff --git a/src/Database/Models/Concerns/CentralConnection.php b/src/Database/Models/Concerns/CentralConnection.php
new file mode 100644
index 00000000..1de8d3d5
--- /dev/null
+++ b/src/Database/Models/Concerns/CentralConnection.php
@@ -0,0 +1,11 @@
+id && config('tenancy.id_generator')) {
+ $model->id = app(config('tenancy.id_generator'))->generate($model);
+ }
+ });
+ }
+}
diff --git a/src/Database/Models/Concerns/HasADataColumn.php b/src/Database/Models/Concerns/HasADataColumn.php
new file mode 100644
index 00000000..8f10a407
--- /dev/null
+++ b/src/Database/Models/Concerns/HasADataColumn.php
@@ -0,0 +1,55 @@
+getAttributes() as $key => $value) {
+ if (! in_array($key, static::getCustomColums())) {
+ $current = $model->getAttribute(static::getDataColumn()) ?? [];
+
+ $model->setAttribute(static::getDataColumn(), array_merge($current, [
+ $key => $value,
+ ]));
+
+ unset($model->attributes[$key]);
+ }
+ }
+ };
+
+ $decode = function (self $model) {
+ foreach ($model->getAttribute(static::getDataColumn()) ?? [] as $key => $value) {
+ $model->setAttribute($key, $value);
+ }
+
+ $model->setAttribute(static::getDataColumn(), null);
+ };
+
+ static::saving($encode);
+ static::saved($decode);
+ static::retrieved($decode);
+ }
+
+ public function getCasts()
+ {
+ return array_merge(parent::getCasts(), [
+ static::getDataColumn() => 'array',
+ ]);
+ }
+
+ /**
+ * Get the name of the column that stores additional data.
+ */
+ public static function getDataColumn(): string
+ {
+ return 'data';
+ }
+
+ public static function getCustomColums(): array
+ {
+ return array_merge(['id'], config('tenancy.custom_columns'));
+ }
+}
\ No newline at end of file
diff --git a/src/Database/Models/Domain.php b/src/Database/Models/Domain.php
new file mode 100644
index 00000000..b9a8dac8
--- /dev/null
+++ b/src/Database/Models/Domain.php
@@ -0,0 +1,24 @@
+belongsTo(Tenant::class);
+ }
+
+ protected $dispatchEvents = [
+ 'saved' => DomainSaved::class,
+ 'created' => DomainCreated::class,
+ 'updated' => DomainUpdated::class,
+ 'deleted' => DomainDeleted::class,
+ ];
+}
diff --git a/src/Database/Models/Tenant.php b/src/Database/Models/Tenant.php
new file mode 100644
index 00000000..f65f414e
--- /dev/null
+++ b/src/Database/Models/Tenant.php
@@ -0,0 +1,93 @@
+dataColumnCasts(), [
+ 'id' => $this->getIncrementing() ? 'integer' : 'string',
+ ]);
+ }
+
+ public function getIncrementing()
+ {
+ return config('tenancy.id_generator') === null;
+ }
+
+ public $guarded = [];
+
+ public function domains() // todo not required
+ {
+ return $this->hasMany(Domain::class);
+ }
+
+ public static function internalPrefix(): string
+ {
+ return config('tenancy.database_prefix');
+ }
+
+ /**
+ * Get an internal key.
+ *
+ * @param string $key
+ * @return mixed
+ */
+ public function getInternal(string $key)
+ {
+ return $this->getAttribute(static::internalPrefix() . $key);
+ }
+
+ /**
+ * Set internal key.
+ *
+ * @param string $key
+ * @param mixed $value
+ * @return $this
+ */
+ public function setInternal(string $key, $value)
+ {
+ $this->setAttribute($key, $value);
+
+ return $this;
+ }
+
+ public function database(): DatabaseConfig
+ {
+ return new DatabaseConfig($this);
+ }
+
+ public function run(callable $callback)
+ {
+ $originalTenant = $this->manager->getTenant();
+
+ $this->manager->initializeTenancy($this);
+ $result = $callback($this);
+ $this->manager->endTenancy($this);
+
+ if ($originalTenant) {
+ $this->manager->initializeTenancy($originalTenant);
+ }
+
+ return $result;
+ }
+
+ protected $dispatchesEvents = [
+ 'saved' => Events\TenantSaved::class,
+ 'created' => Events\TenantCreated::class,
+ 'updated' => Events\TenantUpdated::class,
+ 'deleted' => Events\TenantDeleted::class,
+ ];
+}
diff --git a/src/DatabaseConfig.php b/src/DatabaseConfig.php
index 93248427..df081311 100644
--- a/src/DatabaseConfig.php
+++ b/src/DatabaseConfig.php
@@ -10,6 +10,7 @@ use Stancl\Tenancy\Contracts\Future\CanSetConnection;
use Stancl\Tenancy\Contracts\ManagesDatabaseUsers;
use Stancl\Tenancy\Contracts\ModifiesDatabaseNameForConnection;
use Stancl\Tenancy\Contracts\TenantDatabaseManager;
+use Stancl\Tenancy\Database\Models\Tenant;
use Stancl\Tenancy\Exceptions\DatabaseManagerNotRegisteredException;
class DatabaseConfig
@@ -65,33 +66,33 @@ class DatabaseConfig
public function getName(): ?string
{
- return $this->tenant->data['_tenancy_db_name'] ?? (static::$databaseNameGenerator)($this->tenant);
+ return $this->tenant->getInternal('db_name') ?? (static::$databaseNameGenerator)($this->tenant);
}
public function getUsername(): ?string
{
- return $this->tenant->data['_tenancy_db_username'] ?? null;
+ return $this->tenant->getInternal('db_username') ?? null;
}
public function getPassword(): ?string
{
- return $this->tenant->data['_tenancy_db_password'] ?? null;
+ return $this->tenant->getInternal('db_password') ?? null;
}
public function makeCredentials(): void
{
- $this->tenant->data['_tenancy_db_name'] = $this->getName() ?? (static::$databaseNameGenerator)($this->tenant);
+ $this->tenant->setInternal('db_name', $this->getName() ?? (static::$databaseNameGenerator)($this->tenant));
if ($this->manager() instanceof ManagesDatabaseUsers) {
- $this->tenant->data['_tenancy_db_username'] = $this->getUsername() ?? (static::$usernameGenerator)($this->tenant);
- $this->tenant->data['_tenancy_db_password'] = $this->getPassword() ?? (static::$passwordGenerator)($this->tenant);
+ $this->tenant->setInternal('db_username', $this->getUsername() ?? (static::$usernameGenerator)($this->tenant));
+ $this->tenant->setInternal('db_password', $this->getPassword() ?? (static::$passwordGenerator)($this->tenant));
}
}
public function getTemplateConnectionName(): string
{
- return $this->tenant->data['_tenancy_db_connection']
- ?? config('tenancy.database.template_connection')
+ return $this->tenant->getInternal('db_connection')
+ ?? config('tenancy.template_tenant_connection')
?? DatabaseManager::$originalDefaultConnectionName;
}
@@ -120,23 +121,23 @@ class DatabaseConfig
*/
public function tenantConfig(): array
{
- $dbConfig = array_filter(array_keys($this->tenant->data), function ($key) {
- return Str::startsWith($key, '_tenancy_db_');
+ $dbConfig = array_filter(array_keys($this->tenant->getAttributes()), function ($key) {
+ return Str::startsWith($key, $this->tenant->internalPrefix() . 'db_');
});
// Remove DB name because we set that separately
- if (($pos = array_search('_tenancy_db_name', $dbConfig)) !== false) {
+ if (($pos = array_search($this->tenant->internalPrefix() . 'db_name', $dbConfig)) !== false) {
unset($dbConfig[$pos]);
}
// Remove DB connection because that's not used inside the array
- if (($pos = array_search('_tenancy_db_connection', $dbConfig)) !== false) {
+ if (($pos = array_search($this->tenant->internalPrefix() . 'db_connection', $dbConfig)) !== false) {
unset($dbConfig[$pos]);
}
return array_reduce($dbConfig, function ($config, $key) {
return array_merge($config, [
- Str::substr($key, strlen('_tenancy_db_')) => $this->tenant[$key],
+ Str::substr($key, strlen($this->tenant->internalPrefix() . 'db_')) => $this->tenant->getAttribute($key),
]);
}, []);
}
diff --git a/src/Events/Contracts/DomainEvent.php b/src/Events/Contracts/DomainEvent.php
new file mode 100644
index 00000000..5e896afd
--- /dev/null
+++ b/src/Events/Contracts/DomainEvent.php
@@ -0,0 +1,19 @@
+domain = $domain;
+ }
+}
diff --git a/src/Events/Contracts/TenantEvent.php b/src/Events/Contracts/TenantEvent.php
new file mode 100644
index 00000000..56d43a28
--- /dev/null
+++ b/src/Events/Contracts/TenantEvent.php
@@ -0,0 +1,19 @@
+tenant = $tenant;
+ }
+}
diff --git a/src/Events/DatabaseCreated.php b/src/Events/DatabaseCreated.php
new file mode 100644
index 00000000..19cb039f
--- /dev/null
+++ b/src/Events/DatabaseCreated.php
@@ -0,0 +1,7 @@
+jobs = $jobs;
+ $this->send = $send ?? function ($event) {
+ // If no $send callback is set, we'll just pass the event through the jobs.
+ return $event;
+ };
+ $this->shouldQueue = $shouldQueue ?? static::$shouldQueueByDefault;
+ }
+
+ /** @param callable[]|string[] $jobs */
+ public static function make(array $jobs): self
+ {
+ return new static($jobs);
+ }
+
+ public function queue(bool $shouldQueue): self
+ {
+ $this->shouldQueue = $shouldQueue;
+
+ return $this;
+ }
+
+ public function send(callable $send): self
+ {
+ $this->send = $send;
+
+ return $this;
+ }
+
+ /** @return bool|$this */
+ public function shouldQueue(bool $shouldQueue = null)
+ {
+ if ($shouldQueue !== null) {
+ $this->shouldQueue = $shouldQueue;
+
+ return $this;
+ }
+
+ return $this->shouldQueue;
+ }
+
+ public function handle($event): void
+ {
+ /** @var Pipeline $pipeline */
+ $pipeline = app(Pipeline::class);
+
+ $pipeline
+ ->send(($this->send)($event))
+ ->through($this->jobs)
+ ->thenReturn();
+ }
+}
diff --git a/src/Events/Listeners/QueueableListener.php b/src/Events/Listeners/QueueableListener.php
new file mode 100644
index 00000000..75bc2a91
--- /dev/null
+++ b/src/Events/Listeners/QueueableListener.php
@@ -0,0 +1,15 @@
+getBootstrappers() as $bootstrapper) {
+ $bootstrapper->end();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Events/TenancyEnded.php b/src/Events/TenancyEnded.php
new file mode 100644
index 00000000..f22080d4
--- /dev/null
+++ b/src/Events/TenancyEnded.php
@@ -0,0 +1,16 @@
+tenant = $tenant;
+ }
+}
diff --git a/src/Events/TenancyInitialized.php b/src/Events/TenancyInitialized.php
new file mode 100644
index 00000000..43c53c38
--- /dev/null
+++ b/src/Events/TenancyInitialized.php
@@ -0,0 +1,16 @@
+tenant = $tenant;
+ }
+}
diff --git a/src/Events/TenantCreated.php b/src/Events/TenantCreated.php
new file mode 100644
index 00000000..eadde370
--- /dev/null
+++ b/src/Events/TenantCreated.php
@@ -0,0 +1,7 @@
+tenant = $tenant;
+ }
+
+ public function handle()
+ {
+ if ($this->tenant->getAttribute('_tenancy_create_database') !== false) {
+ $this->tenant->database()->manager()->createDatabase($this->tenant);
+ }
+ }
+}
diff --git a/src/Jobs/QueuedTenantDatabaseDeleter.php b/src/Jobs/DeleteDatabase.php
similarity index 55%
rename from src/Jobs/QueuedTenantDatabaseDeleter.php
rename to src/Jobs/DeleteDatabase.php
index dbd79d21..b897bc5c 100644
--- a/src/Jobs/QueuedTenantDatabaseDeleter.php
+++ b/src/Jobs/DeleteDatabase.php
@@ -10,31 +10,22 @@ use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Stancl\Tenancy\Contracts\TenantDatabaseManager;
-use Stancl\Tenancy\Tenant;
+use Stancl\Tenancy\Database\Models\Tenant;
-class QueuedTenantDatabaseDeleter implements ShouldQueue
+class DeleteDatabase implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
- /** @var TenantDatabaseManager */
- protected $databaseManager;
-
/** @var Tenant */
protected $tenant;
- public function __construct(TenantDatabaseManager $databaseManager, Tenant $tenant)
+ public function __construct(Tenant $tenant)
{
- $this->databaseManager = $databaseManager;
$this->tenant = $tenant;
}
- /**
- * Execute the job.
- *
- * @return void
- */
public function handle()
{
- $this->databaseManager->deleteDatabase($this->tenant);
+ $this->tenant->database()->manager()->deleteDatabase($this->tenant);
}
}
diff --git a/src/Jobs/QueuedTenantDatabaseCreator.php b/src/Jobs/MigrateDatabase.php
similarity index 57%
rename from src/Jobs/QueuedTenantDatabaseCreator.php
rename to src/Jobs/MigrateDatabase.php
index 066580b9..ea3834c6 100644
--- a/src/Jobs/QueuedTenantDatabaseCreator.php
+++ b/src/Jobs/MigrateDatabase.php
@@ -9,22 +9,18 @@ use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
-use Stancl\Tenancy\Contracts\TenantDatabaseManager;
-use Stancl\Tenancy\Tenant;
+use Illuminate\Support\Facades\Artisan;
+use Stancl\Tenancy\Database\Models\Tenant;
-class QueuedTenantDatabaseCreator implements ShouldQueue
+class MigrateDatabase implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
- /** @var TenantDatabaseManager */
- protected $databaseManager;
-
/** @var Tenant */
protected $tenant;
- public function __construct(TenantDatabaseManager $databaseManager, Tenant $tenant)
+ public function __construct(Tenant $tenant)
{
- $this->databaseManager = $databaseManager;
$this->tenant = $tenant;
}
@@ -35,6 +31,12 @@ class QueuedTenantDatabaseCreator implements ShouldQueue
*/
public function handle()
{
- $this->databaseManager->createDatabase($this->tenant);
+ $migrationParameters = [
+ // todo ...
+ ];
+
+ Artisan::call('tenants:migrate', [
+ '--tenants' => [$this->tenant->id],
+ ] + $migrationParameters);
}
}
diff --git a/src/Jobs/QueuedTenantDatabaseMigrator.php b/src/Jobs/QueuedTenantDatabaseMigrator.php
deleted file mode 100644
index c71696cc..00000000
--- a/src/Jobs/QueuedTenantDatabaseMigrator.php
+++ /dev/null
@@ -1,42 +0,0 @@
-tenantId = $tenant->id;
- $this->migrationParameters = $migrationParameters;
- }
-
- /**
- * Execute the job.
- *
- * @return void
- */
- public function handle()
- {
- Artisan::call('tenants:migrate', [
- '--tenants' => [$this->tenantId],
- ] + $this->migrationParameters);
- }
-}
diff --git a/src/Jobs/QueuedTenantDatabaseSeeder.php b/src/Jobs/SeedDatabase.php
similarity index 73%
rename from src/Jobs/QueuedTenantDatabaseSeeder.php
rename to src/Jobs/SeedDatabase.php
index e1ecea41..6a197cf0 100644
--- a/src/Jobs/QueuedTenantDatabaseSeeder.php
+++ b/src/Jobs/SeedDatabase.php
@@ -10,18 +10,18 @@ use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Artisan;
-use Stancl\Tenancy\Tenant;
+use Stancl\Tenancy\Database\Models\Tenant;
-class QueuedTenantDatabaseSeeder implements ShouldQueue
+class SeedDatabase implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
- /** @var string */
- protected $tenantId;
+ /** @var Tenant */
+ protected $tenant;
public function __construct(Tenant $tenant)
{
- $this->tenantId = $tenant->id;
+ $this->tenant = $tenant;
}
/**
@@ -32,7 +32,7 @@ class QueuedTenantDatabaseSeeder implements ShouldQueue
public function handle()
{
Artisan::call('tenants:seed', [
- '--tenants' => [$this->tenantId],
+ '--tenants' => [$this->tenant->id],
]);
}
}
diff --git a/src/StorageDrivers/RedisStorageDriver.php b/src/StorageDrivers/RedisStorageDriver.php
deleted file mode 100644
index 79852f8c..00000000
--- a/src/StorageDrivers/RedisStorageDriver.php
+++ /dev/null
@@ -1,241 +0,0 @@
-app = $app;
- $this->redis = $redis->connection($app['config']['tenancy.storage_drivers.redis.connection'] ?? 'tenancy');
- }
-
- /**
- * Get the current tenant.
- *
- * @return Tenant
- */
- protected function tenant()
- {
- return $this->tenant ?? $this->app[Tenant::class];
- }
-
- public function withDefaultTenant(Tenant $tenant): self
- {
- $this->tenant = $tenant;
-
- return $this;
- }
-
- public function ensureTenantCanBeCreated(Tenant $tenant): void
- {
- // Tenant ID
- if ($this->redis->exists("tenants:{$tenant->id}")) {
- throw new TenantWithThisIdAlreadyExistsException($tenant->id);
- }
-
- // Domains
- if ($this->redis->exists(...array_map(function ($domain) {
- return "domains:$domain";
- }, $tenant->domains))) {
- throw new DomainsOccupiedByOtherTenantException;
- }
- }
-
- public function findByDomain(string $domain): Tenant
- {
- $id = $this->getTenantIdByDomain($domain);
- if (! $id) {
- throw new TenantCouldNotBeIdentifiedException($domain);
- }
-
- return $this->findById($id);
- }
-
- public function findById(string $id): Tenant
- {
- $data = $this->redis->hgetall("tenants:$id");
-
- if (! $data) {
- throw new TenantDoesNotExistException($id);
- }
-
- return $this->makeTenant($data);
- }
-
- public function getTenantIdByDomain(string $domain): ?string
- {
- return $this->redis->hget("domains:$domain", 'tenant_id') ?: null;
- }
-
- public function createTenant(Tenant $tenant): void
- {
- $this->redis->transaction(function ($pipe) use ($tenant) {
- foreach ($tenant->domains as $domain) {
- $pipe->hmset("domains:$domain", ['tenant_id' => $tenant->id]);
- }
-
- $data = [];
- foreach ($tenant->data as $key => $value) {
- $data[$key] = json_encode($value);
- }
-
- $pipe->hmset("tenants:{$tenant->id}", array_merge($data, ['_tenancy_domains' => json_encode($tenant->domains)]));
- });
- }
-
- public function updateTenant(Tenant $tenant): void
- {
- $id = $tenant->id;
-
- $old_domains = json_decode($this->redis->hget("tenants:$id", '_tenancy_domains'), true);
- $deleted_domains = array_diff($old_domains, $tenant->domains);
- $domains = $tenant->domains;
-
- $data = [];
- foreach ($tenant->data as $key => $value) {
- $data[$key] = json_encode($value);
- }
-
- $this->redis->transaction(function ($pipe) use ($id, $data, $deleted_domains, $domains) {
- foreach ($deleted_domains as $deleted_domain) {
- $pipe->del("domains:$deleted_domain");
- }
-
- foreach ($domains as $domain) {
- $pipe->hset("domains:$domain", 'tenant_id', $id);
- }
-
- $pipe->hmset("tenants:$id", array_merge($data, ['_tenancy_domains' => json_encode($domains)]));
- });
- }
-
- public function deleteTenant(Tenant $tenant): void
- {
- $this->redis->transaction(function ($pipe) use ($tenant) {
- foreach ($tenant->domains as $domain) {
- $pipe->del("domains:$domain");
- }
-
- $pipe->del("tenants:{$tenant->id}");
- });
- }
-
- /**
- * Return a list of all tenants.
- *
- * @param string[] $ids
- * @return Tenant[]
- */
- public function all(array $ids = []): array
- {
- $hashes = array_map(function ($hash) {
- return "tenants:{$hash}";
- }, $ids);
-
- if (! $hashes) {
- // Prefix is applied to all functions except scan().
- // This code applies the correct prefix manually.
- $redis_prefix = $this->redis->getOption($this->redis->client()::OPT_PREFIX);
-
- $all_keys = $this->redis->keys('tenants:*');
-
- $hashes = array_map(function ($key) use ($redis_prefix) {
- // Left strip $redis_prefix from $key
- return substr($key, strlen($redis_prefix));
- }, $all_keys);
- }
-
- return array_map(function ($tenant) {
- return $this->makeTenant($this->redis->hgetall($tenant));
- }, $hashes);
- }
-
- /**
- * Make a Tenant instance from low-level array data.
- *
- * @param array $data
- * @return Tenant
- */
- protected function makeTenant(array $data): Tenant
- {
- foreach ($data as $key => $value) {
- $data[$key] = json_decode($value, true);
- }
-
- $domains = $data['_tenancy_domains'];
- unset($data['_tenancy_domains']);
-
- return Tenant::fromStorage($data)->withDomains($domains);
- }
-
- public function get(string $key, Tenant $tenant = null)
- {
- $tenant = $tenant ?? $this->tenant();
-
- $json_data = $this->redis->hget("tenants:{$tenant->id}", $key);
- if ($json_data === false) {
- return;
- }
-
- return json_decode($json_data, true);
- }
-
- public function getMany(array $keys, Tenant $tenant = null): array
- {
- $tenant = $tenant ?? $this->tenant();
-
- $result = [];
- $values = $this->redis->hmget("tenants:{$tenant->id}", $keys);
- foreach ($keys as $i => $key) {
- $result[$key] = json_decode($values[$i], true);
- }
-
- return $result;
- }
-
- public function put(string $key, $value, Tenant $tenant = null): void
- {
- $tenant = $tenant ?? $this->tenant();
- $this->redis->hset("tenants:{$tenant->id}", $key, json_encode($value));
- }
-
- public function putMany(array $kvPairs, Tenant $tenant = null): void
- {
- $tenant = $tenant ?? $this->tenant();
-
- foreach ($kvPairs as $key => $value) {
- $kvPairs[$key] = json_encode($value);
- }
-
- $this->redis->hmset("tenants:{$tenant->id}", $kvPairs);
- }
-
- public function deleteMany(array $keys, Tenant $tenant = null): void
- {
- $tenant = $tenant ?? $this->tenant();
-
- $this->redis->hdel("tenants:{$tenant->id}", ...$keys);
- }
-}
diff --git a/src/Tenancy.php b/src/Tenancy.php
new file mode 100644
index 00000000..24917c24
--- /dev/null
+++ b/src/Tenancy.php
@@ -0,0 +1,39 @@
+tenant = $tenant;
+
+ event(new Events\TenancyInitialized($tenant));
+ }
+
+ public function end(): void
+ {
+ event(new Events\TenancyEnded($this->tenant));
+
+ $this->tenant = null;
+ }
+
+ /** @return TenancyBootstrapper[] */
+ public function getBootstrappers(): array
+ {
+ $resolve = static::$getBootstrappers ?? function (Tenant $tenant) {
+ return config('tenancy.bootstrappers');
+ };
+
+ return $resolve($this->tenant);
+ }
+}
diff --git a/src/TenancyServiceProvider.php b/src/TenancyServiceProvider.php
index b5c41c73..9644576f 100644
--- a/src/TenancyServiceProvider.php
+++ b/src/TenancyServiceProvider.php
@@ -8,6 +8,9 @@ use Illuminate\Cache\CacheManager;
use Illuminate\Contracts\Http\Kernel;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider;
+use Stancl\Tenancy\Database\Models\Tenant;
+use Stancl\Tenancy\Database\TenantObserver;
+use Stancl\Tenancy\StorageDrivers\Database\DatabaseStorageDriver;
use Stancl\Tenancy\TenancyBootstrappers\FilesystemTenancyBootstrapper;
class TenancyServiceProvider extends ServiceProvider
@@ -22,13 +25,13 @@ class TenancyServiceProvider extends ServiceProvider
$this->mergeConfigFrom(__DIR__ . '/../assets/config.php', 'tenancy');
$this->app->bind(Contracts\StorageDriver::class, function ($app) {
- return $app->make($app['config']['tenancy.storage_drivers'][$app['config']['tenancy.storage_driver']]['driver']);
+ return $app->make(DatabaseStorageDriver::class);
});
$this->app->bind(Contracts\UniqueIdentifierGenerator::class, $this->app['config']['tenancy.unique_id_generator']);
$this->app->singleton(DatabaseManager::class);
- $this->app->singleton(TenantManager::class);
+ $this->app->singleton(Tenancy::class);
$this->app->bind(Tenant::class, function ($app) {
- return $app[TenantManager::class]->getTenant();
+ return $app[Tenancy::class]->tenant;
});
foreach ($this->app['config']['tenancy.bootstrappers'] as $bootstrapper) {
diff --git a/src/Tenant.php b/src/Tenant.php
deleted file mode 100644
index 4a9824f8..00000000
--- a/src/Tenant.php
+++ /dev/null
@@ -1,453 +0,0 @@
-config = $config;
- $this->storage = $storage->withDefaultTenant($this);
- $this->manager = $tenantManager;
- $this->idGenerator = $idGenerator;
- }
-
- /**
- * Public constructor.
- *
- * @param Application $app
- * @return self
- */
- public static function new(Application $app = null): self
- {
- $app = $app ?? app();
-
- return new static(
- $app[Repository::class],
- $app[StorageDriver::class],
- $app[TenantManager::class],
- $app[UniqueIdentifierGenerator::class]
- );
- }
-
- /**
- * Used by storage drivers to create persisted instances of Tenant.
- *
- * @param array $data
- * @return self
- */
- public static function fromStorage(array $data): self
- {
- return static::new()->withData($data)->persisted(true);
- }
-
- /**
- * Create a tenant in a single call.
- *
- * @param string|string[] $domains
- * @param array $data
- * @return self
- */
- public static function create($domains, array $data = []): self
- {
- return static::new()->withDomains((array) $domains)->withData($data)->save();
- }
-
- /**
- * DO NOT CALL THIS METHOD FROM USERLAND UNLESS YOU KNOW WHAT YOU ARE DOING.
- * Set $persisted.
- *
- * @param bool $persisted
- * @return self
- */
- public function persisted(bool $persisted): self
- {
- $this->persisted = $persisted;
-
- return $this;
- }
-
- /**
- * Does this model exist in the tenant storage.
- *
- * @return bool
- */
- public function isPersisted(): bool
- {
- return $this->persisted;
- }
-
- /**
- * Assign domains to the tenant.
- *
- * @param string|string[] $domains
- * @return self
- */
- public function addDomains($domains): self
- {
- $domains = (array) $domains;
- $this->domains = array_merge($this->domains, $domains);
-
- return $this;
- }
-
- /**
- * Unassign domains from the tenant.
- *
- * @param string|string[] $domains
- * @return self
- */
- public function removeDomains($domains): self
- {
- $domains = (array) $domains;
- $this->domains = array_diff($this->domains, $domains);
-
- return $this;
- }
-
- /**
- * Unassign all domains from the tenant.
- *
- * @return self
- */
- public function clearDomains(): self
- {
- $this->domains = [];
-
- return $this;
- }
-
- /**
- * Set (overwrite) the tenant's domains.
- *
- * @param string|string[] $domains
- * @return self
- */
- public function withDomains($domains): self
- {
- $domains = (array) $domains;
-
- $this->domains = $domains;
-
- return $this;
- }
-
- /**
- * Set (overwrite) tenant data.
- *
- * @param array $data
- * @return self
- */
- public function withData(array $data): self
- {
- $this->data = $data;
-
- return $this;
- }
-
- /**
- * Generate a random ID.
- *
- * @return void
- */
- public function generateId()
- {
- $this->id = $this->idGenerator->generate($this->domains, $this->data);
- }
-
- /**
- * Write the tenant's state to storage.
- *
- * @return self
- */
- public function save(): self
- {
- if (! isset($this->data['id'])) {
- $this->generateId();
- }
-
- if ($this->persisted) {
- $this->manager->updateTenant($this);
- } else {
- $this->database()->makeCredentials();
- $this->manager->createTenant($this);
- }
-
- return $this;
- }
-
- /**
- * Delete a tenant from storage.
- *
- * @return self
- */
- public function delete(): self
- {
- if ($this->persisted) {
- $this->manager->deleteTenant($this);
- $this->persisted = false;
- }
-
- return $this;
- }
-
- /**
- * Unassign all domains from the tenant and write to storage.
- *
- * @return self
- */
- public function softDelete(): self
- {
- $this->manager->event('tenant.softDeleting', $this);
-
- $this->put([
- '_tenancy_original_domains' => $this->domains,
- ]);
- $this->clearDomains();
- $this->save();
-
- $this->manager->event('tenant.softDeleted', $this);
-
- return $this;
- }
-
- /**
- * Get database config.
- *
- * @return DatabaseConfig
- */
- public function database(): DatabaseConfig
- {
- return new DatabaseConfig($this);
- }
-
- /**
- * Get a value from tenant storage.
- *
- * @param string|string[] $keys
- * @return void
- */
- public function get($keys)
- {
- if (is_array($keys)) {
- if ((array_intersect(array_keys($this->data), $keys) === $keys) ||
- ! $this->persisted) { // if all keys are present in cache
-
- return array_reduce($keys, function ($pairs, $key) {
- $pairs[$key] = $this->data[$key] ?? null;
-
- return $pairs;
- }, []);
- }
-
- return $this->storage->getMany($keys);
- }
-
- // single key
- $key = $keys;
-
- if (! isset($this->data[$key]) && $this->persisted) {
- $this->data[$key] = $this->storage->get($key);
- }
-
- return $this->data[$key];
- }
-
- /**
- * Set a value and write to storage.
- *
- * @param string|array $key
- * @param mixed $value
- * @return self
- */
- public function put($key, $value = null): self
- {
- $this->manager->event('tenant.updating', $this);
-
- if ($key === 'id') {
- throw new TenantStorageException("Tenant ids can't be changed.");
- }
-
- if (is_array($key)) {
- if ($this->persisted) {
- $this->storage->putMany($key);
- }
-
- foreach ($key as $k => $v) { // Add to cache
- $this->data[$k] = $v;
- }
- } else {
- if ($this->persisted) {
- $this->storage->put($key, $value);
- }
-
- $this->data[$key] = $value;
- }
-
- $this->manager->event('tenant.updated', $this);
-
- return $this;
- }
-
- /** @alias put */
- public function set($key, $value = null): self
- {
- return $this->put($key, $value);
- }
-
- /**
- * Delete a key from the tenant's storage.
- *
- * @param string $key
- * @return self
- */
- public function deleteKey(string $key): self
- {
- return $this->deleteKeys([$key]);
- }
-
- /**
- * Delete keys from the tenant's storage.
- *
- * @param string[] $keys
- * @return self
- */
- public function deleteKeys(array $keys): self
- {
- $this->manager->event('tenant.updating', $this);
-
- if (! $this->storage instanceof CanDeleteKeys) {
- throw new NotImplementedException(get_class($this->storage), 'deleteMany',
- 'This method was added to storage drivers provided by the package in 2.2.0 and will be part of the StorageDriver contract in 3.0.0.'
- );
- } else {
- $this->storage->deleteMany($keys);
- foreach ($keys as $key) {
- unset($this->data[$key]);
- }
- }
-
- $this->manager->event('tenant.updated', $this);
-
- return $this;
- }
-
- /**
- * Set a value in the data array without saving into storage.
- *
- * @param string $key
- * @param mixed $value
- * @return self
- */
- public function with(string $key, $value): self
- {
- $this->data[$key] = $value;
-
- return $this;
- }
-
- /**
- * Run a closure inside the tenant's environment.
- *
- * @param Closure $closure
- * @return mixed
- */
- public function run(Closure $closure)
- {
- $originalTenant = $this->manager->getTenant();
-
- $this->manager->initializeTenancy($this);
- $result = $closure($this);
- $this->manager->endTenancy($this);
-
- if ($originalTenant) {
- $this->manager->initializeTenancy($originalTenant);
- }
-
- return $result;
- }
-
- public function __get($key)
- {
- return $this->get($key);
- }
-
- public function __set($key, $value)
- {
- if ($key === 'id' && isset($this->data['id'])) {
- throw new TenantStorageException("Tenant ids can't be changed.");
- }
-
- $this->data[$key] = $value;
- }
-
- public function __call($method, $parameters)
- {
- if (Str::startsWith($method, 'with')) {
- return $this->with(Str::snake(substr($method, 4)), $parameters[0]);
- }
-
- static::throwBadMethodCallException($method);
- }
-}
diff --git a/src/TenantManager.php b/src/TenantManager.php
deleted file mode 100644
index 103ecc8c..00000000
--- a/src/TenantManager.php
+++ /dev/null
@@ -1,471 +0,0 @@
-app = $app;
- $this->storage = $storage;
- $this->artisan = $artisan;
- $this->database = $database->withTenantManager($this);
-
- $this->bootstrapFeatures();
- }
-
- /**
- * Write a new tenant to storage.
- *
- * @param Tenant $tenant
- * @return self
- */
- public function createTenant(Tenant $tenant): self
- {
- $this->event('tenant.creating', $tenant);
-
- $this->ensureTenantCanBeCreated($tenant);
-
- $this->storage->createTenant($tenant);
-
- $tenant->persisted = true;
-
- /** @var \Illuminate\Contracts\Queue\ShouldQueue[]|callable[] $afterCreating */
- $afterCreating = [];
-
- if ($this->shouldMigrateAfterCreation()) {
- $afterCreating[] = $this->databaseCreationQueued()
- ? new QueuedTenantDatabaseMigrator($tenant, $this->getMigrationParameters())
- : function () use ($tenant) {
- $this->artisan->call('tenants:migrate', [
- '--tenants' => [$tenant['id']],
- ] + $this->getMigrationParameters());
- };
- }
-
- if ($this->shouldSeedAfterMigration()) {
- $afterCreating[] = $this->databaseCreationQueued()
- ? new QueuedTenantDatabaseSeeder($tenant)
- : function () use ($tenant) {
- $this->artisan->call('tenants:seed', [
- '--tenants' => [$tenant['id']],
- ]);
- };
- }
-
- if ($this->shouldCreateDatabase($tenant)) {
- $this->database->createDatabase($tenant, $afterCreating);
- }
-
- $this->event('tenant.created', $tenant);
-
- return $this;
- }
-
- /**
- * Delete a tenant from storage.
- *
- * @param Tenant $tenant
- * @return self
- */
- public function deleteTenant(Tenant $tenant): self
- {
- $this->event('tenant.deleting', $tenant);
-
- $this->storage->deleteTenant($tenant);
-
- if ($this->shouldDeleteDatabase()) {
- $this->database->deleteDatabase($tenant);
- }
-
- $this->event('tenant.deleted', $tenant);
-
- return $this;
- }
-
- /**
- * Alias for Stancl\Tenancy\Tenant::create.
- *
- * @param string|string[] $domains
- * @param array $data
- * @return Tenant
- */
- public static function create($domains, array $data = []): Tenant
- {
- return Tenant::create($domains, $data);
- }
-
- /**
- * Ensure that a tenant can be created.
- *
- * @param Tenant $tenant
- * @return void
- * @throws TenantCannotBeCreatedException
- */
- public function ensureTenantCanBeCreated(Tenant $tenant): void
- {
- if ($this->shouldCreateDatabase($tenant)) {
- $this->database->ensureTenantCanBeCreated($tenant);
- }
-
- $this->storage->ensureTenantCanBeCreated($tenant);
- }
-
- /**
- * Update an existing tenant in storage.
- *
- * @param Tenant $tenant
- * @return self
- */
- public function updateTenant(Tenant $tenant): self
- {
- $this->event('tenant.updating', $tenant);
-
- $this->storage->updateTenant($tenant);
-
- $this->event('tenant.updated', $tenant);
-
- return $this;
- }
-
- /**
- * Find tenant by domain & initialize tenancy.
- *
- * @param string|null $domain
- * @return self
- */
- public function init(string $domain = null): self
- {
- $domain = $domain ?? request()->getHost();
- $this->initializeTenancy($this->findByDomain($domain));
-
- return $this;
- }
-
- /**
- * Find tenant by ID & initialize tenancy.
- *
- * @param string $id
- * @return self
- */
- public function initById(string $id): self
- {
- $this->initializeTenancy($this->find($id));
-
- return $this;
- }
-
- /**
- * Find a tenant using an id.
- *
- * @param string $id
- * @return Tenant
- * @throws TenantCouldNotBeIdentifiedException
- */
- public function find(string $id): Tenant
- {
- return $this->storage->findById($id);
- }
-
- /**
- * Find a tenant using a domain name.
- *
- * @param string $id
- * @return Tenant
- * @throws TenantCouldNotBeIdentifiedException
- */
- public function findByDomain(string $domain): Tenant
- {
- return $this->storage->findByDomain($domain);
- }
-
- /**
- * Find a tenant using an arbitrary key.
- *
- * @param string $key
- * @param mixed $value
- * @return Tenant
- * @throws TenantCouldNotBeIdentifiedException
- * @throws NotImplementedException
- */
- public function findBy(string $key, $value): Tenant
- {
- if ($key === null) {
- throw new Exception('No key supplied.');
- }
-
- if ($value === null) {
- throw new Exception('No value supplied.');
- }
-
- if (! $this->storage instanceof CanFindByAnyKey) {
- throw new NotImplementedException(get_class($this->storage), 'findBy',
- 'This method was added to the DB storage driver provided by the package in 2.2.0 and might be part of the StorageDriver contract in 3.0.0.'
- );
- }
-
- return $this->storage->findBy($key, $value);
- }
-
- /**
- * Get all tenants.
- *
- * @param Tenant[]|string[] $only
- * @return Collection
- */
- public function all($only = []): Collection
- {
- $only = array_map(function ($item) {
- return $item instanceof Tenant ? $item->id : $item;
- }, (array) $only);
-
- return collect($this->storage->all($only));
- }
-
- /**
- * Initialize tenancy.
- *
- * @param Tenant $tenant
- * @return self
- */
- public function initializeTenancy(Tenant $tenant): self
- {
- if ($this->initialized) {
- $this->endTenancy();
- }
-
- $this->setTenant($tenant);
- $this->bootstrapTenancy($tenant);
- $this->initialized = true;
-
- return $this;
- }
-
- /** @alias initializeTenancy */
- public function initialize(Tenant $tenant): self
- {
- return $this->initializeTenancy($tenant);
- }
-
- /**
- * Execute TenancyBootstrappers.
- *
- * @param Tenant $tenant
- * @return self
- */
- public function bootstrapTenancy(Tenant $tenant): self
- {
- $prevented = $this->event('bootstrapping', $tenant);
-
- foreach ($this->tenancyBootstrappers($prevented) as $bootstrapper) {
- $this->app[$bootstrapper]->start($tenant);
- }
-
- $this->event('bootstrapped', $tenant);
-
- return $this;
- }
-
- public function endTenancy(): self
- {
- if (! $this->initialized) {
- return $this;
- }
-
- $prevented = $this->event('ending', $this->tenant);
-
- foreach ($this->tenancyBootstrappers($prevented) as $bootstrapper) {
- $this->app[$bootstrapper]->end();
- }
-
- $this->initialized = false;
- $this->tenant = null;
-
- $this->event('ended');
-
- return $this;
- }
-
- /** @alias endTenancy */
- public function end(): self
- {
- return $this->endTenancy();
- }
-
- /**
- * Get the current tenant.
- *
- * @param string $key
- * @return Tenant|null|mixed
- */
- public function getTenant(string $key = null)
- {
- if (! $this->tenant) {
- return;
- }
-
- if (! is_null($key)) {
- return $this->tenant[$key];
- }
-
- return $this->tenant;
- }
-
- protected function setTenant(Tenant $tenant): self
- {
- $this->tenant = $tenant;
-
- return $this;
- }
-
- protected function bootstrapFeatures(): self
- {
- foreach ($this->app['config']['tenancy.features'] as $feature) {
- $this->app[$feature]->bootstrap($this);
- }
-
- return $this;
- }
-
- /**
- * Return a list of TenancyBootstrappers.
- *
- * @param string[] $except
- * @return Contracts\TenancyBootstrapper[]
- */
- public function tenancyBootstrappers($except = []): array
- {
- return array_diff_key($this->app['config']['tenancy.bootstrappers'], array_flip($except));
- }
-
- public function shouldCreateDatabase(Tenant $tenant): bool
- {
- if (array_key_exists('_tenancy_create_database', $tenant->data)) {
- return $tenant->data['_tenancy_create_database'];
- }
-
- return $this->app['config']['tenancy.create_database'] ?? true;
- }
-
- public function shouldMigrateAfterCreation(): bool
- {
- return $this->app['config']['tenancy.migrate_after_creation'] ?? false;
- }
-
- public function shouldSeedAfterMigration(): bool
- {
- return $this->shouldMigrateAfterCreation() && $this->app['config']['tenancy.seed_after_migration'] ?? false;
- }
-
- public function databaseCreationQueued(): bool
- {
- return $this->app['config']['tenancy.queue_database_creation'] ?? false;
- }
-
- public function shouldDeleteDatabase(): bool
- {
- return $this->app['config']['tenancy.delete_database_after_tenant_deletion'] ?? false;
- }
-
- public function getSeederParameters()
- {
- return $this->app['config']['tenancy.seeder_parameters'] ?? [];
- }
-
- public function getMigrationParameters()
- {
- return $this->app['config']['tenancy.migration_parameters'] ?? [];
- }
-
- /**
- * Add an event listener.
- *
- * @param string $name
- * @param callable $listener
- * @return self
- */
- public function eventListener(string $name, callable $listener): self
- {
- $this->eventListeners[$name] = $this->eventListeners[$name] ?? [];
- $this->eventListeners[$name][] = $listener;
-
- return $this;
- }
-
- /**
- * Add an event hook.
- * @alias eventListener
- *
- * @param string $name
- * @param callable $listener
- * @return self
- */
- public function hook(string $name, callable $listener): self
- {
- return $this->eventListener($name, $listener);
- }
-
- /**
- * Trigger an event and execute its listeners.
- *
- * @param string $name
- * @param mixed ...$args
- * @return string[]
- */
- public function event(string $name, ...$args): array
- {
- return array_reduce($this->eventListeners[$name] ?? [], function ($results, $listener) use ($args) {
- return array_merge($results, $listener($this, ...$args) ?? []);
- }, []);
- }
-
- public function __call($method, $parameters)
- {
- if (Str::startsWith($method, 'findBy')) {
- return $this->findBy(Str::snake(substr($method, 6)), $parameters[0]);
- }
-
- static::throwBadMethodCallException($method);
- }
-}
diff --git a/src/UniqueIDGenerators/UUIDGenerator.php b/src/UniqueIDGenerators/UUIDGenerator.php
index 7a90f06c..635a7d88 100644
--- a/src/UniqueIDGenerators/UUIDGenerator.php
+++ b/src/UniqueIDGenerators/UUIDGenerator.php
@@ -6,10 +6,11 @@ namespace Stancl\Tenancy\UniqueIDGenerators;
use Ramsey\Uuid\Uuid;
use Stancl\Tenancy\Contracts\UniqueIdentifierGenerator;
+use Stancl\Tenancy\Database\Models\Tenant;
class UUIDGenerator implements UniqueIdentifierGenerator
{
- public static function generate(array $domains, array $data = []): string
+ public static function generate(Tenant $tenant): string
{
return Uuid::uuid4()->toString();
}
diff --git a/src/helpers.php b/src/helpers.php
index 58aa8ccb..80f94c18 100644
--- a/src/helpers.php
+++ b/src/helpers.php
@@ -2,18 +2,14 @@
declare(strict_types=1);
-use Stancl\Tenancy\Tenant;
-use Stancl\Tenancy\TenantManager;
+use Stancl\Tenancy\Database\Models\Tenant;
+use Stancl\Tenancy\Tenancy;
if (! function_exists('tenancy')) {
- /** @return TenantManager|mixed */
- function tenancy($key = null)
+ /** @return Tenancy */
+ function tenancy()
{
- if ($key) {
- return app(TenantManager::class)->getTenant($key) ?? null;
- }
-
- return app(TenantManager::class);
+ return app(Tenancy::class);
}
}
diff --git a/test b/test
index 0d5a2556..49535a7a 100755
--- a/test
+++ b/test
@@ -1,7 +1,3 @@
#!/bin/bash
-set -e
-printf "Variant 1 (DB)\n\n"
-docker-compose exec -T test env TENANCY_TEST_STORAGE_DRIVER=db vendor/bin/phpunit --coverage-php coverage/1.cov "$@"
-printf "Variant 2 (Redis)\n\n"
-docker-compose exec -T test env TENANCY_TEST_STORAGE_DRIVER=redis vendor/bin/phpunit --coverage-php coverage/2.cov "$@"
+docker-compose exec -T test vendor/bin/phpunit "$@"
diff --git a/tests/TestCase.php b/tests/TestCase.php
index 918024e7..7594957c 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -21,8 +21,7 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
{
parent::setUp();
- Redis::connection('tenancy')->flushdb();
- Redis::connection('cache')->flushdb();
+ // Redis::connection('cache')->flushdb();
file_put_contents(database_path('central.sqlite'), '');
$this->artisan('migrate:fresh', [
@@ -102,7 +101,7 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
'--realpath' => true,
'--force' => true,
],
- 'tenancy.storage_drivers.db.connection' => 'central',
+ 'tenancy.storage.connection' => 'central',
'tenancy.bootstrappers.redis' => \Stancl\Tenancy\TenancyBootstrappers\RedisTenancyBootstrapper::class,
'queue.connections.central' => [
'driver' => 'sync',
@@ -112,8 +111,6 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
]);
$app->singleton(\Stancl\Tenancy\TenancyBootstrappers\RedisTenancyBootstrapper::class);
-
- $app['config']->set(['tenancy.storage_driver' => env('TENANCY_TEST_STORAGE_DRIVER', 'redis')]);
}
protected function getPackageProviders($app)
diff --git a/tests/v3/DatabasePreparationTest.php b/tests/v3/DatabasePreparationTest.php
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/v3/DomainTest.php b/tests/v3/DomainTest.php
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/v3/EventListenerTest.php b/tests/v3/EventListenerTest.php
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/v3/HostnameIdentificationTest.php b/tests/v3/HostnameIdentificationTest.php
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/v3/JobPipelineTest.php b/tests/v3/JobPipelineTest.php
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/v3/TenantModelTest.php b/tests/v3/TenantModelTest.php
new file mode 100644
index 00000000..97ec074f
--- /dev/null
+++ b/tests/v3/TenantModelTest.php
@@ -0,0 +1,109 @@
+initialize($tenant);
+
+ $this->assertSame($tenant->id, app(Tenant::class)->id);
+
+ tenancy()->end();
+
+ $this->assertSame(null, app(Tenant::class));
+ }
+
+ /** @test */
+ public function keys_which_dont_have_their_own_column_go_into_data_json_column()
+ {
+ $tenant = Tenant::create([
+ 'foo' => 'bar',
+ ]);
+
+ // Test that model works correctly
+ $this->assertSame('bar', $tenant->foo);
+ $this->assertSame(null, $tenant->data);
+
+ // Low level test to test database structure
+ $this->assertSame(json_encode(['foo' => 'bar']), DB::table('tenants')->where('id', $tenant->id)->first()->data);
+ $this->assertSame(null, DB::table('tenants')->where('id', $tenant->id)->first()->foo ?? null);
+
+ // Model has the correct structure when retrieved
+ $tenant = Tenant::first();
+ $this->assertSame('bar', $tenant->foo);
+ $this->assertSame(null, $tenant->data);
+
+ // Model can be updated
+ $tenant->update([
+ 'foo' => 'baz',
+ 'abc' => 'xyz',
+ ]);
+
+ $this->assertSame('baz', $tenant->foo);
+ $this->assertSame('xyz', $tenant->abc);
+ $this->assertSame(null, $tenant->data);
+
+ // Model can be retrieved after update & is structure correctly
+ $tenant = Tenant::first();
+
+ $this->assertSame('baz', $tenant->foo);
+ $this->assertSame('xyz', $tenant->abc);
+ $this->assertSame(null, $tenant->data);
+ }
+
+ /** @test */
+ public function id_is_generated_when_no_id_is_supplied()
+ {
+ config(['tenancy.id_generator' => UUIDGenerator::class]);
+
+ $this->mock(UUIDGenerator::class, function ($mock) {
+ return $mock->shouldReceive('generate')->once();
+ });
+
+ $tenant = Tenant::create();
+
+ $this->assertNotNull($tenant->id);
+ }
+
+ /** @test */
+ public function autoincrement_ids_are_supported()
+ {
+ Schema::table('tenants', function (Blueprint $table) {
+ $table->bigIncrements('id')->change();
+ });
+
+ config(['tenancy.id_generator' => null]);
+
+ $tenant1 = Tenant::create();
+ $tenant2 = Tenant::create();
+
+ $this->assertSame(1, $tenant1->id);
+ $this->assertSame(2, $tenant2->id);
+ }
+}