mirror of
https://github.com/archtechx/tenancy.git
synced 2025-12-12 14:34:04 +00:00
Merge branch 'master' into shared-users
This commit is contained in:
commit
8915297c30
85 changed files with 880 additions and 3280 deletions
|
|
@ -8,7 +8,7 @@ use Stancl\Tenancy\Database\Models\Tenant;
|
||||||
return [
|
return [
|
||||||
'tenant_model' => Tenant::class,
|
'tenant_model' => Tenant::class,
|
||||||
'domain_model' => Domain::class,
|
'domain_model' => Domain::class,
|
||||||
'internal_column_prefix' => 'tenancy_',
|
'internal_prefix' => 'tenancy_',
|
||||||
|
|
||||||
'central_connection' => 'central',
|
'central_connection' => 'central',
|
||||||
'template_tenant_connection' => null,
|
'template_tenant_connection' => null,
|
||||||
|
|
@ -46,13 +46,6 @@ return [
|
||||||
*/
|
*/
|
||||||
'tenant_route_namespace' => 'App\Http\Controllers',
|
'tenant_route_namespace' => 'App\Http\Controllers',
|
||||||
|
|
||||||
/**
|
|
||||||
* Central domains (hostnames), e.g. domains which host landing pages, sign up pages, etc.
|
|
||||||
*/
|
|
||||||
'exempt_domains' => [
|
|
||||||
// 'localhost',
|
|
||||||
],
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tenancy bootstrappers are executed when tenancy is initialized.
|
* Tenancy bootstrappers are executed when tenancy is initialized.
|
||||||
* Their responsibility is making Laravel features tenant-aware.
|
* Their responsibility is making Laravel features tenant-aware.
|
||||||
|
|
@ -201,7 +194,7 @@ return [
|
||||||
* See the documentation page for each class to
|
* See the documentation page for each class to
|
||||||
* understand which ones you want to enable.
|
* understand which ones you want to enable.
|
||||||
*/
|
*/
|
||||||
'features' => [
|
'features' => [ // todo test features
|
||||||
// Stancl\Tenancy\Features\Timestamps::class, // https://tenancy.samuelstancl.me/docs/v2/features/timestamps/
|
// Stancl\Tenancy\Features\Timestamps::class, // https://tenancy.samuelstancl.me/docs/v2/features/timestamps/
|
||||||
// Stancl\Tenancy\Features\TenantConfig::class, // https://tenancy.samuelstancl.me/docs/v2/features/tenant-config/
|
// Stancl\Tenancy\Features\TenantConfig::class, // https://tenancy.samuelstancl.me/docs/v2/features/tenant-config/
|
||||||
// Stancl\Tenancy\Features\TelescopeTags::class, // https://tenancy.samuelstancl.me/docs/v2/telescope/
|
// Stancl\Tenancy\Features\TelescopeTags::class, // https://tenancy.samuelstancl.me/docs/v2/telescope/
|
||||||
|
|
@ -211,32 +204,15 @@ return [
|
||||||
/**
|
/**
|
||||||
* The URL to which users will be redirected when they try to acceess a central route on a tenant domain.
|
* The URL to which users will be redirected when they try to acceess a central route on a tenant domain.
|
||||||
*/
|
*/
|
||||||
'home_url' => '/app',
|
'home_url' => '/app', // todo move this to static
|
||||||
|
|
||||||
/**
|
|
||||||
* Should tenant databases be created asynchronously in a queued job.
|
|
||||||
*/
|
|
||||||
'queue_database_creation' => false, // todo make this a static property
|
|
||||||
|
|
||||||
'migration_parameters' => [
|
'migration_parameters' => [
|
||||||
'--force' => true, // Set this to true to be able to run migrations in production
|
'--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
|
'--path' => [database_path('migrations/tenant')],
|
||||||
],
|
],
|
||||||
|
|
||||||
'seeder_parameters' => [
|
'seeder_parameters' => [
|
||||||
'--class' => 'DatabaseSeeder', // root seeder class, e.g.: 'DatabaseSeeder'
|
'--class' => 'DatabaseSeeder', // root seeder class, e.g.: 'DatabaseSeeder'
|
||||||
// '--force' => true,
|
// '--force' => true,
|
||||||
],
|
],
|
||||||
|
|
||||||
/**
|
|
||||||
* Should tenant databases be deleted asynchronously in a queued job.
|
|
||||||
*/
|
|
||||||
'queue_database_deletion' => false,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Middleware pushed to the global middleware stack.
|
|
||||||
*/
|
|
||||||
'global_middleware' => [ // todo get rid of this
|
|
||||||
// Stancl\Tenancy\Middleware\InitializeTenancy::class,
|
|
||||||
],
|
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Commands;
|
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
use Stancl\Tenancy\Tenant;
|
|
||||||
|
|
||||||
class CreateTenant extends Command
|
|
||||||
{
|
|
||||||
protected $signature = 'tenants:create
|
|
||||||
{--d|domain=* : The tenant\'s domains.}
|
|
||||||
{data?* : The tenant\'s data. Separate keys and values by `=`, e.g. `plan=free`.}';
|
|
||||||
|
|
||||||
protected $description = 'Create a tenant.';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute the console command.
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
$tenant = Tenant::new()
|
|
||||||
->withDomains($this->getDomains())
|
|
||||||
->withData($this->getData())
|
|
||||||
->save();
|
|
||||||
|
|
||||||
$this->info($tenant->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getDomains(): array
|
|
||||||
{
|
|
||||||
return $this->option('domain');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getData(): array
|
|
||||||
{
|
|
||||||
return array_reduce($this->argument('data'), function ($data, $pair) {
|
|
||||||
[$key, $value] = explode('=', $pair, 2);
|
|
||||||
$data[$key] = $value;
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}, []);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -36,17 +36,6 @@ class Install extends Command
|
||||||
]);
|
]);
|
||||||
$this->info('✔️ Created config/tenancy.php');
|
$this->info('✔️ Created config/tenancy.php');
|
||||||
|
|
||||||
$newKernel = $this->setMiddlewarePriority();
|
|
||||||
|
|
||||||
$newKernel = str_replace("'web' => [", "'web' => [
|
|
||||||
\Stancl\Tenancy\Middleware\PreventAccessFromTenantDomains::class,", $newKernel);
|
|
||||||
|
|
||||||
$newKernel = str_replace("'api' => [", "'api' => [
|
|
||||||
\Stancl\Tenancy\Middleware\PreventAccessFromTenantDomains::class,", $newKernel);
|
|
||||||
|
|
||||||
file_put_contents(app_path('Http/Kernel.php'), $newKernel);
|
|
||||||
$this->info('✔️ Set middleware priority');
|
|
||||||
|
|
||||||
if (! file_exists(base_path('routes/tenant.php'))) {
|
if (! file_exists(base_path('routes/tenant.php'))) {
|
||||||
file_put_contents(base_path('routes/tenant.php'), file_get_contents(__DIR__ . '/../../assets/tenant_routes.php.stub'));
|
file_put_contents(base_path('routes/tenant.php'), file_get_contents(__DIR__ . '/../../assets/tenant_routes.php.stub'));
|
||||||
$this->info('✔️ Created routes/tenant.php');
|
$this->info('✔️ Created routes/tenant.php');
|
||||||
|
|
@ -54,15 +43,13 @@ class Install extends Command
|
||||||
$this->info('Found routes/tenant.php.');
|
$this->info('Found routes/tenant.php.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->line('');
|
// todo tenancy SP stub
|
||||||
$this->line('This package lets you store data about tenants either in Redis or in a relational database like MySQL. To store data about tenants in a relational database, you need a few database tables.');
|
|
||||||
if ($this->confirm('Do you wish to publish the migrations that create these tables?', true)) {
|
|
||||||
$this->callSilent('vendor:publish', [
|
$this->callSilent('vendor:publish', [
|
||||||
'--provider' => 'Stancl\Tenancy\TenancyServiceProvider',
|
'--provider' => 'Stancl\Tenancy\TenancyServiceProvider',
|
||||||
'--tag' => 'migrations',
|
'--tag' => 'migrations',
|
||||||
]);
|
]);
|
||||||
$this->info('✔️ Created migrations. Remember to run [php artisan migrate]!');
|
$this->info('✔️ Created migrations. Remember to run [php artisan migrate]!');
|
||||||
}
|
|
||||||
|
|
||||||
if (! is_dir(database_path('migrations/tenant'))) {
|
if (! is_dir(database_path('migrations/tenant'))) {
|
||||||
mkdir(database_path('migrations/tenant'));
|
mkdir(database_path('migrations/tenant'));
|
||||||
|
|
@ -71,34 +58,4 @@ class Install extends Command
|
||||||
|
|
||||||
$this->comment('✨️ stancl/tenancy installed successfully.');
|
$this->comment('✨️ stancl/tenancy installed successfully.');
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function setMiddlewarePriority(): string
|
|
||||||
{
|
|
||||||
if (app()->version()[0] === '6') {
|
|
||||||
return str_replace(
|
|
||||||
'protected $middlewarePriority = [',
|
|
||||||
"protected \$middlewarePriority = [
|
|
||||||
\Stancl\Tenancy\Middleware\PreventAccessFromTenantDomains::class,
|
|
||||||
\Stancl\Tenancy\Middleware\InitializeTenancy::class,",
|
|
||||||
file_get_contents(app_path('Http/Kernel.php'))
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return str_replace(
|
|
||||||
"];\n}",
|
|
||||||
"];\n\n protected \$middlewarePriority = [
|
|
||||||
\Stancl\Tenancy\Middleware\PreventAccessFromTenantDomains::class,
|
|
||||||
\Stancl\Tenancy\Middleware\InitializeTenancy::class,
|
|
||||||
\Illuminate\Session\Middleware\StartSession::class,
|
|
||||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
|
||||||
\Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,
|
|
||||||
\Illuminate\Routing\Middleware\ThrottleRequests::class,
|
|
||||||
\Illuminate\Session\Middleware\AuthenticateSession::class,
|
|
||||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
|
||||||
\Illuminate\Auth\Middleware\Authorize::class,
|
|
||||||
];
|
|
||||||
}",
|
|
||||||
file_get_contents(app_path('Http/Kernel.php'))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,18 +57,11 @@ class Migrate extends MigrateCommand
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
tenancy()
|
tenancy()->runForMultiple($this->option('tenants'), function ($tenant) {
|
||||||
->query()
|
|
||||||
->when($this->option('tenants'), function ($query) {
|
|
||||||
$query->whereIn(tenancy()->model()->getTenantKeyName(), $this->option('tenants'));
|
|
||||||
})
|
|
||||||
->each(function (TenantWithDatabase $tenant) {
|
|
||||||
$this->line("Tenant: {$tenant['id']}");
|
$this->line("Tenant: {$tenant['id']}");
|
||||||
|
|
||||||
$tenant->run(function () {
|
|
||||||
// Migrate
|
// Migrate
|
||||||
parent::handle();
|
parent::handle();
|
||||||
});
|
|
||||||
|
|
||||||
event(new DatabaseMigrated($tenant));
|
event(new DatabaseMigrated($tenant));
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||||
namespace Stancl\Tenancy\Commands;
|
namespace Stancl\Tenancy\Commands;
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
use Stancl\Tenancy\Contracts\TenantWithDatabase;
|
||||||
use Stancl\Tenancy\Traits\DealsWithMigrations;
|
use Stancl\Tenancy\Traits\DealsWithMigrations;
|
||||||
use Stancl\Tenancy\Traits\HasATenantsOption;
|
use Stancl\Tenancy\Traits\HasATenantsOption;
|
||||||
|
|
||||||
|
|
@ -33,10 +34,7 @@ final class MigrateFresh extends Command
|
||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
tenancy()->all($this->option('tenants'))->each(function ($tenant) {
|
tenancy()->runForMultiple($this->option('tenants'), function ($tenant) {
|
||||||
$this->line("Tenant: {$tenant->id}");
|
|
||||||
|
|
||||||
$tenant->run(function ($tenant) {
|
|
||||||
$this->info('Dropping tables.');
|
$this->info('Dropping tables.');
|
||||||
$this->call('db:wipe', array_filter([
|
$this->call('db:wipe', array_filter([
|
||||||
'--database' => 'tenant',
|
'--database' => 'tenant',
|
||||||
|
|
@ -49,7 +47,6 @@ final class MigrateFresh extends Command
|
||||||
'--force' => true,
|
'--force' => true,
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
$this->info('Done.');
|
$this->info('Done.');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ namespace Stancl\Tenancy\Commands;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Database\Console\Migrations\RollbackCommand;
|
use Illuminate\Database\Console\Migrations\RollbackCommand;
|
||||||
use Illuminate\Database\Migrations\Migrator;
|
use Illuminate\Database\Migrations\Migrator;
|
||||||
|
use Stancl\Tenancy\Contracts\TenantWithDatabase;
|
||||||
use Stancl\Tenancy\DatabaseManager;
|
use Stancl\Tenancy\DatabaseManager;
|
||||||
use Stancl\Tenancy\Traits\DealsWithMigrations;
|
use Stancl\Tenancy\Traits\DealsWithMigrations;
|
||||||
use Stancl\Tenancy\Traits\HasATenantsOption;
|
use Stancl\Tenancy\Traits\HasATenantsOption;
|
||||||
|
|
@ -55,13 +56,13 @@ class Rollback extends RollbackCommand
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
tenancy()->all($this->option('tenants'))->each(function ($tenant) {
|
tenancy()->runForMultiple($this->option('tenants'), function ($tenant) {
|
||||||
$this->line("Tenant: {$tenant['id']}");
|
$this->line("Tenant: {$tenant['id']}");
|
||||||
|
|
||||||
$tenant->run(function () {
|
|
||||||
// Rollback
|
// Rollback
|
||||||
parent::handle();
|
parent::handle();
|
||||||
});
|
|
||||||
|
// todo DatabaseRolledBack event
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,7 @@ class Run extends Command
|
||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$originalTenant = tenancy()->getTenant();
|
tenancy()->runForMultiple($this->option('tenants'), function ($tenant) {
|
||||||
tenancy()->all($this->option('tenants'))->each(function ($tenant) {
|
|
||||||
$this->line("Tenant: {$tenant['id']}");
|
$this->line("Tenant: {$tenant['id']}");
|
||||||
tenancy()->initialize($tenant);
|
tenancy()->initialize($tenant);
|
||||||
|
|
||||||
|
|
@ -54,12 +53,6 @@ class Run extends Command
|
||||||
|
|
||||||
// Run command
|
// Run command
|
||||||
$this->call($this->argument('commandname'), array_merge($arguments, $options));
|
$this->call($this->argument('commandname'), array_merge($arguments, $options));
|
||||||
|
|
||||||
tenancy()->endTenancy();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if ($originalTenant) {
|
|
||||||
tenancy()->initialize($originalTenant);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ namespace Stancl\Tenancy\Commands;
|
||||||
|
|
||||||
use Illuminate\Database\ConnectionResolverInterface;
|
use Illuminate\Database\ConnectionResolverInterface;
|
||||||
use Illuminate\Database\Console\Seeds\SeedCommand;
|
use Illuminate\Database\Console\Seeds\SeedCommand;
|
||||||
|
use Stancl\Tenancy\Contracts\TenantWithDatabase;
|
||||||
use Stancl\Tenancy\DatabaseManager;
|
use Stancl\Tenancy\DatabaseManager;
|
||||||
use Stancl\Tenancy\Events\DatabaseSeeded;
|
use Stancl\Tenancy\Events\DatabaseSeeded;
|
||||||
use Stancl\Tenancy\Traits\HasATenantsOption;
|
use Stancl\Tenancy\Traits\HasATenantsOption;
|
||||||
|
|
@ -54,13 +55,11 @@ class Seed extends SeedCommand
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
tenancy()->all($this->option('tenants'))->each(function ($tenant) {
|
tenancy()->runForMultiple($this->option('tenants'), function ($tenant) {
|
||||||
$this->line("Tenant: {$tenant['id']}");
|
$this->line("Tenant: {$tenant['id']}");
|
||||||
|
|
||||||
$tenant->run(function () {
|
|
||||||
// Seed
|
// Seed
|
||||||
parent::handle();
|
parent::handle();
|
||||||
});
|
|
||||||
|
|
||||||
event(new DatabaseSeeded($tenant));
|
event(new DatabaseSeeded($tenant));
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||||
namespace Stancl\Tenancy\Commands;
|
namespace Stancl\Tenancy\Commands;
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
use Stancl\Tenancy\Contracts\Tenant;
|
||||||
|
|
||||||
class TenantList extends Command
|
class TenantList extends Command
|
||||||
{
|
{
|
||||||
|
|
@ -30,8 +31,14 @@ class TenantList extends Command
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$this->info('Listing all tenants.');
|
$this->info('Listing all tenants.');
|
||||||
tenancy()->all()->each(function ($tenant) {
|
tenancy()
|
||||||
$this->line("[Tenant] id: {$tenant['id']} @ " . implode('; ', $tenant->domains));
|
->query()
|
||||||
|
->when($this->option('tenants'), function ($query) {
|
||||||
|
$query->whereIn(tenancy()->model()->getTenantKeyName(), $this->option('tenants'));
|
||||||
|
})
|
||||||
|
->cursor()
|
||||||
|
->each(function (Tenant $tenant) {
|
||||||
|
$this->line("[Tenant] id: {$tenant['id']} @ " . implode('; ', $tenant->domains ?? []));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,10 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Contracts;
|
namespace Stancl\Tenancy\Contracts;
|
||||||
|
|
||||||
use Stancl\Tenancy\TenantManager;
|
use Stancl\Tenancy\Facades\Tenancy;
|
||||||
|
|
||||||
/** Additional features, like Telescope tags and tenant redirects. */
|
/** Additional features, like Telescope tags and tenant redirects. */
|
||||||
interface Feature
|
interface Feature
|
||||||
{
|
{
|
||||||
public function bootstrap(TenantManager $tenantManager): void;
|
public function bootstrap(Tenancy $tenancy): void;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Contracts;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used by sqlite to wrap database name in database_path().
|
|
||||||
*/
|
|
||||||
interface ModifiesDatabaseNameForConnection
|
|
||||||
{
|
|
||||||
public function getDatabaseNameForConnection(string $original): string;
|
|
||||||
}
|
|
||||||
|
|
@ -1,95 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Contracts;
|
|
||||||
|
|
||||||
use Stancl\Tenancy\Tenant;
|
|
||||||
|
|
||||||
interface StorageDriver
|
|
||||||
{
|
|
||||||
public function createTenant(Tenant $tenant): void;
|
|
||||||
|
|
||||||
public function updateTenant(Tenant $tenant): void;
|
|
||||||
|
|
||||||
public function deleteTenant(Tenant $tenant): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find a tenant using an id.
|
|
||||||
*
|
|
||||||
* @param string $id
|
|
||||||
* @return Tenant
|
|
||||||
* @throws \Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedException
|
|
||||||
*/
|
|
||||||
public function findById(string $id): Tenant;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find a tenant using a domain name.
|
|
||||||
*
|
|
||||||
* @param string $domain
|
|
||||||
* @return Tenant
|
|
||||||
*/
|
|
||||||
public function findByDomain(string $domain): Tenant;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all tenants.
|
|
||||||
*
|
|
||||||
* @param string[] $ids
|
|
||||||
* @return Tenant[]
|
|
||||||
*/
|
|
||||||
public function all(array $ids = []): array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensure a tenant can be created.
|
|
||||||
*
|
|
||||||
* @param Tenant $tenant
|
|
||||||
* @return void
|
|
||||||
* @throws TenantCannotBeCreatedException
|
|
||||||
*/
|
|
||||||
public function ensureTenantCanBeCreated(Tenant $tenant): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set default tenant (will be used for get/put when no tenant is supplied).
|
|
||||||
*
|
|
||||||
* @param Tenant $tenant
|
|
||||||
* @return self
|
|
||||||
*/
|
|
||||||
public function withDefaultTenant(Tenant $tenant);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a value from storage.
|
|
||||||
*
|
|
||||||
* @param string $key
|
|
||||||
* @param ?Tenant $tenant
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function get(string $key, Tenant $tenant = null);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get multiple values from storage.
|
|
||||||
*
|
|
||||||
* @param array $keys
|
|
||||||
* @param ?Tenant $tenant
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function getMany(array $keys, Tenant $tenant = null);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Put a value into storage.
|
|
||||||
*
|
|
||||||
* @param string $key
|
|
||||||
* @param mixed $value
|
|
||||||
* @param ?Tenant $tenant
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function put(string $key, $value, Tenant $tenant = null): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Put multiple values into storage.
|
|
||||||
*
|
|
||||||
* @param mixed[string] $kvPairs
|
|
||||||
* @param ?Tenant $tenant
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function putMany(array $kvPairs, Tenant $tenant = null): void;
|
|
||||||
}
|
|
||||||
|
|
@ -9,6 +9,7 @@ namespace Stancl\Tenancy\Contracts;
|
||||||
*/
|
*/
|
||||||
interface TenancyBootstrapper
|
interface TenancyBootstrapper
|
||||||
{
|
{
|
||||||
|
// todo rename methods
|
||||||
public function start(Tenant $tenant);
|
public function start(Tenant $tenant);
|
||||||
|
|
||||||
public function end();
|
public function end();
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,6 @@ namespace Stancl\Tenancy\Contracts;
|
||||||
|
|
||||||
interface TenantDatabaseManager
|
interface TenantDatabaseManager
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Return the config key that separates databases (e.g. 'database' or 'schema').
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getSeparator(): string;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a database.
|
* Create a database.
|
||||||
*/
|
*/
|
||||||
|
|
@ -30,4 +23,13 @@ interface TenantDatabaseManager
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function databaseExists(string $name): bool;
|
public function databaseExists(string $name): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a DB connection config array.
|
||||||
|
*
|
||||||
|
* @param array $baseConfig
|
||||||
|
* @param string $databaseName
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function makeConnectionConfig(array $baseConfig, string $databaseName): array;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,7 @@ use Stancl\Tenancy\DatabaseConfig;
|
||||||
interface TenantWithDatabase extends Tenant
|
interface TenantWithDatabase extends Tenant
|
||||||
{
|
{
|
||||||
public function database(): DatabaseConfig;
|
public function database(): DatabaseConfig;
|
||||||
|
|
||||||
|
/** Get an internal key. */
|
||||||
|
public function getInternal(string $key);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,11 @@ use Illuminate\Routing\Controller;
|
||||||
|
|
||||||
class TenantAssetsController extends Controller
|
class TenantAssetsController extends Controller
|
||||||
{
|
{
|
||||||
|
public static $tenancyMiddleware = 'Stancl\Tenancy\Middleware\InitializeTenancyByDomain';
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->middleware('tenancy');
|
$this->middleware(static::$tenancyMiddleware);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function asset($path)
|
public function asset($path)
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,24 @@ namespace Stancl\Tenancy\Database\Models\Concerns;
|
||||||
// todo rename
|
// todo rename
|
||||||
trait HasADataColumn
|
trait HasADataColumn
|
||||||
{
|
{
|
||||||
|
public static $priorityListeners = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We need this property, because both created & saved event listeners
|
||||||
|
* decode the data (to take precedence before other created & saved)
|
||||||
|
* listeners, but we don't want the dadta to be decoded twice.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $dataEncodingStatus = 'decoded'; // todo write tests for this
|
||||||
|
|
||||||
public static function bootHasADataColumn()
|
public static function bootHasADataColumn()
|
||||||
{
|
{
|
||||||
$encode = function (self $model) {
|
$encode = function (self $model) {
|
||||||
|
if ($model->dataEncodingStatus === 'encoded') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($model->getAttributes() as $key => $value) {
|
foreach ($model->getAttributes() as $key => $value) {
|
||||||
if (! in_array($key, static::getCustomColums())) {
|
if (! in_array($key, static::getCustomColums())) {
|
||||||
$current = $model->getAttribute(static::getDataColumn()) ?? [];
|
$current = $model->getAttribute(static::getDataColumn()) ?? [];
|
||||||
|
|
@ -20,19 +35,64 @@ trait HasADataColumn
|
||||||
unset($model->attributes[$key]);
|
unset($model->attributes[$key]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$model->dataEncodingStatus = 'encoded';
|
||||||
};
|
};
|
||||||
|
|
||||||
$decode = function (self $model) {
|
$decode = function (self $model) {
|
||||||
|
if ($model->dataEncodingStatus === 'decoded') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($model->getAttribute(static::getDataColumn()) ?? [] as $key => $value) {
|
foreach ($model->getAttribute(static::getDataColumn()) ?? [] as $key => $value) {
|
||||||
$model->setAttribute($key, $value);
|
$model->setAttribute($key, $value);
|
||||||
}
|
}
|
||||||
|
|
||||||
$model->setAttribute(static::getDataColumn(), null);
|
$model->setAttribute(static::getDataColumn(), null);
|
||||||
|
|
||||||
|
$model->dataEncodingStatus = 'decoded';
|
||||||
};
|
};
|
||||||
|
|
||||||
static::saving($encode);
|
static::registerPriorityListener('retrieved', $decode);
|
||||||
static::saved($decode);
|
|
||||||
static::retrieved($decode);
|
static::registerPriorityListener('saving', $encode);
|
||||||
|
static::registerPriorityListener('creating', $encode);
|
||||||
|
static::registerPriorityListener('updating', $encode);
|
||||||
|
static::registerPriorityListener('saved', $decode);
|
||||||
|
static::registerPriorityListener('created', $decode);
|
||||||
|
static::registerPriorityListener('updated', $decode);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function fireModelEvent($event, $halt = true)
|
||||||
|
{
|
||||||
|
$this->runPriorityListeners($event, $halt);
|
||||||
|
|
||||||
|
return parent::fireModelEvent($event, $halt);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function runPriorityListeners($event, $halt = true)
|
||||||
|
{
|
||||||
|
$listeners = static::$priorityListeners[$event] ?? [];
|
||||||
|
|
||||||
|
if (! $event) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($listeners as $listener) {
|
||||||
|
if (is_string($listener)) {
|
||||||
|
$listener = app($listener);
|
||||||
|
$handle = [$listener, 'handle'];
|
||||||
|
} else {
|
||||||
|
$handle = $listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
$handle($this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function registerPriorityListener(string $event, callable $callback)
|
||||||
|
{
|
||||||
|
static::$priorityListeners[$event][] = $callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCasts()
|
public function getCasts()
|
||||||
|
|
|
||||||
|
|
@ -41,14 +41,11 @@ class Tenant extends Model implements Contracts\TenantWithDatabase
|
||||||
|
|
||||||
public static function internalPrefix(): string
|
public static function internalPrefix(): string
|
||||||
{
|
{
|
||||||
return config('tenancy.internal_column_prefix');
|
return config('tenancy.internal_prefix');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get an internal key.
|
* Get an internal key.
|
||||||
*
|
|
||||||
* @param string $key
|
|
||||||
* @return mixed
|
|
||||||
*/
|
*/
|
||||||
public function getInternal(string $key)
|
public function getInternal(string $key)
|
||||||
{
|
{
|
||||||
|
|
@ -57,14 +54,10 @@ class Tenant extends Model implements Contracts\TenantWithDatabase
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set internal key.
|
* Set internal key.
|
||||||
*
|
|
||||||
* @param string $key
|
|
||||||
* @param mixed $value
|
|
||||||
* @return $this
|
|
||||||
*/
|
*/
|
||||||
public function setInternal(string $key, $value)
|
public function setInternal(string $key, $value)
|
||||||
{
|
{
|
||||||
$this->setAttribute($key, $value);
|
$this->setAttribute(static::internalPrefix() . $key, $value);
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -102,19 +102,11 @@ class DatabaseConfig
|
||||||
public function connection(): array
|
public function connection(): array
|
||||||
{
|
{
|
||||||
$template = $this->getTemplateConnectionName();
|
$template = $this->getTemplateConnectionName();
|
||||||
|
|
||||||
$templateConnection = config("database.connections.{$template}");
|
$templateConnection = config("database.connections.{$template}");
|
||||||
|
|
||||||
// todo move a lot of this logic to the tenant DB manager so that we dont have to deal with the separators & modifying DB names here
|
return $this->manager()->makeConnectionConfig(
|
||||||
$databaseName = $this->getName();
|
array_merge($templateConnection, $this->tenantConfig()), $this->getName()
|
||||||
if (($manager = $this->manager()) instanceof ModifiesDatabaseNameForConnection) {
|
);
|
||||||
/** @var ModifiesDatabaseNameForConnection $manager */
|
|
||||||
$databaseName = $manager->getDatabaseNameForConnection($databaseName);
|
|
||||||
}
|
|
||||||
|
|
||||||
return array_merge($templateConnection, $this->tenantConfig(), [
|
|
||||||
$this->manager()->getSeparator() => $databaseName,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -83,84 +83,10 @@ class DatabaseManager
|
||||||
* @throws DatabaseManagerNotRegisteredException
|
* @throws DatabaseManagerNotRegisteredException
|
||||||
* @throws TenantDatabaseAlreadyExistsException
|
* @throws TenantDatabaseAlreadyExistsException
|
||||||
*/
|
*/
|
||||||
public function ensureTenantCanBeCreated(TenantWithDatabase $tenant): void
|
public function ensureTenantCanBeCreated(TenantWithDatabase $tenant): void // todo do we need this?
|
||||||
{
|
{
|
||||||
if ($tenant->database()->manager()->databaseExists($database = $tenant->database()->getName())) {
|
if ($tenant->database()->manager()->databaseExists($database = $tenant->database()->getName())) {
|
||||||
throw new TenantDatabaseAlreadyExistsException($database);
|
throw new TenantDatabaseAlreadyExistsException($database);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a database for a tenant.
|
|
||||||
*
|
|
||||||
* @param Tenant $tenant
|
|
||||||
* @param ShouldQueue[]|callable[] $afterCreating
|
|
||||||
* @return void
|
|
||||||
* @throws DatabaseManagerNotRegisteredException
|
|
||||||
*/
|
|
||||||
public function createDatabase(TenantWithDatabase $tenant, array $afterCreating = [])
|
|
||||||
{
|
|
||||||
// todo get rid of aftercreating logic
|
|
||||||
$afterCreating = array_merge(
|
|
||||||
$afterCreating,
|
|
||||||
$this->tenancy->event('database.creating', $tenant->database()->getName(), $tenant)
|
|
||||||
);
|
|
||||||
|
|
||||||
if ($this->app['config']['tenancy.queue_database_creation'] ?? false) {
|
|
||||||
$this->createDatabaseAsynchronously($tenant, $afterCreating);
|
|
||||||
} else {
|
|
||||||
$this->createDatabaseSynchronously($tenant, $afterCreating);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->tenancy->event('database.created', $tenant->database()->getName(), $tenant);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function createDatabaseAsynchronously(Tenant $tenant, array $afterCreating)
|
|
||||||
{
|
|
||||||
$chain = [];
|
|
||||||
foreach ($afterCreating as $item) {
|
|
||||||
if (is_string($item) && class_exists($item)) {
|
|
||||||
$chain[] = new $item($tenant); // Classes are instantiated and given $tenant
|
|
||||||
} elseif ($item instanceof ShouldQueue) {
|
|
||||||
$chain[] = $item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QueuedTenantDatabaseCreator::withChain($chain)->dispatch($tenant->database()->manager(), $tenant);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function createDatabaseSynchronously(Tenant $tenant, array $afterCreating)
|
|
||||||
{
|
|
||||||
$manager = $tenant->database()->manager();
|
|
||||||
$manager->createDatabase($tenant);
|
|
||||||
|
|
||||||
foreach ($afterCreating as $item) {
|
|
||||||
if (is_object($item) && ! $item instanceof Closure) {
|
|
||||||
$item->handle($tenant);
|
|
||||||
} else {
|
|
||||||
$item($tenant);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a tenant's database.
|
|
||||||
*
|
|
||||||
* @throws DatabaseManagerNotRegisteredException
|
|
||||||
*/
|
|
||||||
public function deleteDatabase(TenantWithDatabase $tenant)
|
|
||||||
{
|
|
||||||
$database = $tenant->database()->getName();
|
|
||||||
$manager = $tenant->database()->manager();
|
|
||||||
|
|
||||||
$this->tenancy->event('database.deleting', $database, $tenant);
|
|
||||||
|
|
||||||
if ($this->app['config']['tenancy.queue_database_deletion'] ?? false) {
|
|
||||||
QueuedTenantDatabaseDeleter::dispatch($manager, $tenant);
|
|
||||||
} else {
|
|
||||||
$manager->deleteDatabase($tenant);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->tenancy->event('database.deleted', $database, $tenant);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Stancl\Tenancy\Exceptions;
|
||||||
|
|
||||||
|
use Facade\IgnitionContracts\BaseSolution;
|
||||||
|
use Facade\IgnitionContracts\ProvidesSolution;
|
||||||
|
use Facade\IgnitionContracts\Solution;
|
||||||
|
use Stancl\Tenancy\Contracts\TenantCouldNotBeIdentifiedException;
|
||||||
|
|
||||||
|
class TenantCouldNotBeIdentifiedByRequestDataException extends TenantCouldNotBeIdentifiedException implements ProvidesSolution
|
||||||
|
{
|
||||||
|
public function __construct($tenant_id)
|
||||||
|
{
|
||||||
|
parent::__construct("Tenant could not be identified by request data with payload: $tenant_id");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSolution(): Solution
|
||||||
|
{
|
||||||
|
return BaseSolution::create('Tenant could not be identified with this request data')
|
||||||
|
->setSolutionDescription('Did you forget to create a tenant with this id?')
|
||||||
|
->setDocumentationLinks([
|
||||||
|
'Creating Tenants' => 'https://tenancyforlaravel.com/docs/v2/creating-tenants/', // todo update link for v3
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,13 +6,15 @@ namespace Stancl\Tenancy\Features;
|
||||||
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Stancl\Tenancy\Contracts\Feature;
|
use Stancl\Tenancy\Contracts\Feature;
|
||||||
use Stancl\Tenancy\TenantManager;
|
use Stancl\Tenancy\Tenancy;
|
||||||
|
|
||||||
class CrossDomainRedirect implements Feature
|
class CrossDomainRedirect implements Feature
|
||||||
{
|
{
|
||||||
public function bootstrap(TenantManager $tenantManager): void
|
public function bootstrap(Tenancy $tenancy): void
|
||||||
{
|
{
|
||||||
RedirectResponse::macro('domain', function (string $domain) {
|
RedirectResponse::macro('domain', function (string $domain) {
|
||||||
|
/** @var RedirectResponse $this */
|
||||||
|
|
||||||
// replace first occurance of hostname fragment with $domain
|
// replace first occurance of hostname fragment with $domain
|
||||||
$url = $this->getTargetUrl();
|
$url = $this->getTargetUrl();
|
||||||
$hostname = parse_url($url, PHP_URL_HOST);
|
$hostname = parse_url($url, PHP_URL_HOST);
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,9 @@ use Laravel\Telescope\IncomingEntry;
|
||||||
use Laravel\Telescope\Telescope;
|
use Laravel\Telescope\Telescope;
|
||||||
use Stancl\Tenancy\Contracts\Feature;
|
use Stancl\Tenancy\Contracts\Feature;
|
||||||
use Stancl\Tenancy\Middleware\PreventAccessFromTenantDomains;
|
use Stancl\Tenancy\Middleware\PreventAccessFromTenantDomains;
|
||||||
use Stancl\Tenancy\TenantManager;
|
use Stancl\Tenancy\Tenancy;
|
||||||
|
|
||||||
|
// todo rewrite this
|
||||||
class TelescopeTags implements Feature
|
class TelescopeTags implements Feature
|
||||||
{
|
{
|
||||||
/** @var callable User-specific callback that returns tags. */
|
/** @var callable User-specific callback that returns tags. */
|
||||||
|
|
@ -22,7 +23,7 @@ class TelescopeTags implements Feature
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public function bootstrap(TenantManager $tenantManager): void
|
public function bootstrap(Tenancy $tenancy): void
|
||||||
{
|
{
|
||||||
if (! class_exists(Telescope::class)) {
|
if (! class_exists(Telescope::class)) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -35,6 +36,7 @@ class TelescopeTags implements Feature
|
||||||
return $tags;
|
return $tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo lines below
|
||||||
$tenantRoute = PreventAccessFromTenantDomains::routeHasMiddleware(request()->route(), 'tenancy')
|
$tenantRoute = PreventAccessFromTenantDomains::routeHasMiddleware(request()->route(), 'tenancy')
|
||||||
|| PreventAccessFromTenantDomains::routeHasMiddleware(request()->route(), 'universal');
|
|| PreventAccessFromTenantDomains::routeHasMiddleware(request()->route(), 'universal');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,11 @@ namespace Stancl\Tenancy\Features;
|
||||||
|
|
||||||
use Illuminate\Contracts\Config\Repository;
|
use Illuminate\Contracts\Config\Repository;
|
||||||
use Stancl\Tenancy\Contracts\Feature;
|
use Stancl\Tenancy\Contracts\Feature;
|
||||||
|
use Stancl\Tenancy\Tenancy;
|
||||||
use Stancl\Tenancy\Tenant;
|
use Stancl\Tenancy\Tenant;
|
||||||
use Stancl\Tenancy\TenantManager;
|
use Stancl\Tenancy\TenantManager;
|
||||||
|
|
||||||
|
// todo rewrite this
|
||||||
class TenantConfig implements Feature
|
class TenantConfig implements Feature
|
||||||
{
|
{
|
||||||
/** @var Repository */
|
/** @var Repository */
|
||||||
|
|
@ -30,7 +32,7 @@ class TenantConfig implements Feature
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function bootstrap(TenantManager $tenantManager): void
|
public function bootstrap(Tenancy $tenancy): void
|
||||||
{
|
{
|
||||||
$tenantManager->eventListener('bootstrapped', function (TenantManager $manager) {
|
$tenantManager->eventListener('bootstrapped', function (TenantManager $manager) {
|
||||||
$this->setTenantConfig($manager->getTenant());
|
$this->setTenantConfig($manager->getTenant());
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ use Stancl\Tenancy\Contracts\Feature;
|
||||||
use Stancl\Tenancy\Tenant;
|
use Stancl\Tenancy\Tenant;
|
||||||
use Stancl\Tenancy\TenantManager;
|
use Stancl\Tenancy\TenantManager;
|
||||||
|
|
||||||
|
// todo rewrite this
|
||||||
class Timestamps implements Feature
|
class Timestamps implements Feature
|
||||||
{
|
{
|
||||||
/** @var Repository */
|
/** @var Repository */
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,8 @@ class CreateDatabase implements ShouldQueue
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
if ($this->tenant->getAttribute('_tenancy_create_database') !== false) {
|
if ($this->tenant->getInternal('create_database') !== false) {
|
||||||
|
$this->tenant->database()->makeCredentials();
|
||||||
$this->tenant->database()->manager()->createDatabase($this->tenant);
|
$this->tenant->database()->manager()->createDatabase($this->tenant);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,26 +6,28 @@ namespace Stancl\Tenancy\Middleware;
|
||||||
|
|
||||||
use Closure;
|
use Closure;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedException;
|
use Stancl\Tenancy\Resolvers\RequestDataTenantResolver;
|
||||||
|
use Stancl\Tenancy\Tenancy;
|
||||||
|
|
||||||
class InitializeTenancyByRequestData
|
// todo write tests for this
|
||||||
|
class InitializeTenancyByRequestData extends IdentificationMiddleware
|
||||||
{
|
{
|
||||||
/** @var string|null */
|
/** @var string|null */
|
||||||
protected $header;
|
public static $header = 'X-Tenant';
|
||||||
|
|
||||||
/** @var string|null */
|
/** @var string|null */
|
||||||
protected $queryParameter;
|
public static $queryParameter = 'tenant';
|
||||||
|
|
||||||
/** @var callable */
|
/** @var Tenancy */
|
||||||
protected $onFail;
|
protected $tenancy;
|
||||||
|
|
||||||
public function __construct(?string $header = 'X-Tenant', ?string $queryParameter = 'tenant', callable $onFail = null)
|
/** @var TenantResolver */
|
||||||
|
protected $resolver;
|
||||||
|
|
||||||
|
public function __construct(Tenancy $tenancy, RequestDataTenantResolver $resolver)
|
||||||
{
|
{
|
||||||
$this->header = $header;
|
$this->tenancy = $tenancy;
|
||||||
$this->queryParameter = $queryParameter;
|
$this->resolver = $resolver;
|
||||||
$this->onFail = $onFail ?? function ($e) {
|
|
||||||
throw $e;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -38,33 +40,21 @@ class InitializeTenancyByRequestData
|
||||||
public function handle($request, Closure $next)
|
public function handle($request, Closure $next)
|
||||||
{
|
{
|
||||||
if ($request->method() !== 'OPTIONS') {
|
if ($request->method() !== 'OPTIONS') {
|
||||||
try {
|
return $this->initializeTenancy($request, $next, $this->getPayload($request));
|
||||||
$this->initializeTenancy($request);
|
|
||||||
} catch (TenantCouldNotBeIdentifiedException $e) {
|
|
||||||
return ($this->onFail)($e, $request, $next);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function initializeTenancy(Request $request)
|
protected function getPayload(Request $request): ?string
|
||||||
{
|
{
|
||||||
if (tenancy()->initialized) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$tenant = null;
|
$tenant = null;
|
||||||
if ($this->header && $request->hasHeader($this->header)) {
|
if (static::$header && $request->hasHeader(static::$header)) {
|
||||||
$tenant = $request->header($this->header);
|
$tenant = $request->header(static::$header);
|
||||||
} elseif ($this->queryParameter && $request->has($this->queryParameter)) {
|
} elseif (static::$queryParameter && $request->has(static::$queryParameter)) {
|
||||||
$tenant = $request->get($this->queryParameter);
|
$tenant = $request->get(static::$queryParameter);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $tenant) {
|
return $tenant;
|
||||||
throw new TenantCouldNotBeIdentifiedException($request->getHost());
|
|
||||||
}
|
|
||||||
|
|
||||||
tenancy()->initialize(tenancy()->find($tenant));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ class PathTenantResolver implements TenantResolver
|
||||||
if ($id = $route->parameter(static::$tenantParameterName)) {
|
if ($id = $route->parameter(static::$tenantParameterName)) {
|
||||||
$route->forgetParameter(static::$tenantParameterName);
|
$route->forgetParameter(static::$tenantParameterName);
|
||||||
|
|
||||||
if ($tenant = config('tenancy.tenant_model')::find($id)) {
|
if ($tenant = tenancy()->find($id)) {
|
||||||
return $tenant;
|
return $tenant;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
21
src/Resolvers/RequestDataTenantResolver.php
Normal file
21
src/Resolvers/RequestDataTenantResolver.php
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Stancl\Tenancy\Resolvers;
|
||||||
|
|
||||||
|
use Stancl\Tenancy\Contracts\Tenant;
|
||||||
|
use Stancl\Tenancy\Contracts\TenantResolver;
|
||||||
|
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedByRequestDataException;
|
||||||
|
|
||||||
|
class RequestDataTenantResolver implements TenantResolver
|
||||||
|
{
|
||||||
|
public function resolve(...$args): Tenant
|
||||||
|
{
|
||||||
|
$payload = $args[0];
|
||||||
|
|
||||||
|
if ($payload && $tenant = tenancy()->find($payload)) {
|
||||||
|
return $tenant;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new TenantCouldNotBeIdentifiedByRequestDataException($payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Stancl\Tenancy\StorageDrivers\Database;
|
|
||||||
|
|
||||||
use Closure;
|
|
||||||
use Illuminate\Cache\CacheManager;
|
|
||||||
use Illuminate\Cache\Repository as CacheRepository;
|
|
||||||
use Illuminate\Config\Repository as ConfigRepository;
|
|
||||||
|
|
||||||
class CachedTenantResolver
|
|
||||||
{
|
|
||||||
/** @var CacheRepository */
|
|
||||||
protected $cache;
|
|
||||||
|
|
||||||
/** @var ConfigRepository */
|
|
||||||
protected $config;
|
|
||||||
|
|
||||||
public function __construct(CacheManager $cacheManager, ConfigRepository $config)
|
|
||||||
{
|
|
||||||
$this->cache = $cacheManager->store($config->get('tenancy.storage_drivers.db.cache_store'));
|
|
||||||
$this->config = $config;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function ttl(): int
|
|
||||||
{
|
|
||||||
return $this->config->get('tenancy.storage_drivers.db.cache_ttl');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTenantIdByDomain(string $domain, Closure $query): ?string
|
|
||||||
{
|
|
||||||
return $this->cache->remember('_tenancy_domain_to_id:' . $domain, $this->ttl(), $query);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getDataById(string $id, Closure $dataQuery): ?array
|
|
||||||
{
|
|
||||||
return $this->cache->remember('_tenancy_id_to_data:' . $id, $this->ttl(), $dataQuery);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getDomainsById(string $id, Closure $domainsQuery): ?array
|
|
||||||
{
|
|
||||||
return $this->cache->remember('_tenancy_id_to_domains:' . $id, $this->ttl(), $domainsQuery);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function invalidateTenant(string $id): void
|
|
||||||
{
|
|
||||||
$this->invalidateTenantData($id);
|
|
||||||
$this->invalidateTenantDomains($id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function invalidateTenantData(string $id): void
|
|
||||||
{
|
|
||||||
$this->cache->forget('_tenancy_id_to_data:' . $id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function invalidateTenantDomains(string $id): void
|
|
||||||
{
|
|
||||||
$this->cache->forget('_tenancy_id_to_domains:' . $id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function invalidateDomainToIdMapping(array $domains): void
|
|
||||||
{
|
|
||||||
foreach ($domains as $domain) {
|
|
||||||
$this->cache->forget('_tenancy_domain_to_id:' . $domain);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Stancl\Tenancy\StorageDrivers\Database;
|
|
||||||
|
|
||||||
trait CentralConnection
|
|
||||||
{
|
|
||||||
public function getConnectionName()
|
|
||||||
{
|
|
||||||
return DatabaseStorageDriver::getCentralConnectionName();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,256 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Stancl\Tenancy\StorageDrivers\Database;
|
|
||||||
|
|
||||||
use Illuminate\Config\Repository as ConfigRepository;
|
|
||||||
use Illuminate\Database\Connection;
|
|
||||||
use Illuminate\Foundation\Application;
|
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
use Stancl\Tenancy\Contracts\Future\CanDeleteKeys;
|
|
||||||
use Stancl\Tenancy\Contracts\Future\CanFindByAnyKey;
|
|
||||||
use Stancl\Tenancy\Contracts\StorageDriver;
|
|
||||||
use Stancl\Tenancy\DatabaseManager;
|
|
||||||
use Stancl\Tenancy\Exceptions\DomainsOccupiedByOtherTenantException;
|
|
||||||
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedException;
|
|
||||||
use Stancl\Tenancy\Exceptions\TenantDoesNotExistException;
|
|
||||||
use Stancl\Tenancy\Exceptions\TenantWithThisIdAlreadyExistsException;
|
|
||||||
use Stancl\Tenancy\Tenant;
|
|
||||||
|
|
||||||
class DatabaseStorageDriver implements StorageDriver, CanDeleteKeys, CanFindByAnyKey
|
|
||||||
{
|
|
||||||
/** @var Application */
|
|
||||||
public $app;
|
|
||||||
|
|
||||||
/** @var Connection */
|
|
||||||
public $centralDatabase;
|
|
||||||
|
|
||||||
/** @var TenantRepository */
|
|
||||||
public $tenants;
|
|
||||||
|
|
||||||
/** @var DomainRepository */
|
|
||||||
public $domains;
|
|
||||||
|
|
||||||
/** @var CachedTenantResolver */
|
|
||||||
public $cache;
|
|
||||||
|
|
||||||
/** @var Tenant The default tenant. */
|
|
||||||
public $tenant;
|
|
||||||
|
|
||||||
public function __construct(Application $app, ConfigRepository $config, CachedTenantResolver $cache)
|
|
||||||
{
|
|
||||||
$this->app = $app;
|
|
||||||
$this->cache = $cache;
|
|
||||||
$this->centralDatabase = $this->getCentralConnection();
|
|
||||||
$this->tenants = new TenantRepository($config);
|
|
||||||
$this->domains = new DomainRepository($config);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the central database connection.
|
|
||||||
*
|
|
||||||
* @return Connection
|
|
||||||
*/
|
|
||||||
public static function getCentralConnection(): Connection
|
|
||||||
{
|
|
||||||
return DB::connection(static::getCentralConnectionName());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function getCentralConnectionName(): string
|
|
||||||
{
|
|
||||||
return config('tenancy.storage_drivers.db.connection') ?? DatabaseManager::$originalDefaultConnectionName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function findByDomain(string $domain): Tenant
|
|
||||||
{
|
|
||||||
$query = function () use ($domain) {
|
|
||||||
return $this->domains->getTenantIdByDomain($domain);
|
|
||||||
};
|
|
||||||
|
|
||||||
if ($this->usesCache()) {
|
|
||||||
$id = $this->cache->getTenantIdByDomain($domain, $query);
|
|
||||||
} else {
|
|
||||||
$id = $query();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! $id) {
|
|
||||||
throw new TenantCouldNotBeIdentifiedException($domain);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->findById($id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function findById(string $id): Tenant
|
|
||||||
{
|
|
||||||
$dataQuery = function () use ($id) {
|
|
||||||
$data = $this->tenants->find($id);
|
|
||||||
|
|
||||||
return $data ? $this->tenants->decodeData($data) : null;
|
|
||||||
};
|
|
||||||
$domainsQuery = function () use ($id) {
|
|
||||||
return $this->domains->getTenantDomains($id);
|
|
||||||
};
|
|
||||||
|
|
||||||
if ($this->usesCache()) {
|
|
||||||
$data = $this->cache->getDataById($id, $dataQuery);
|
|
||||||
$domains = $this->cache->getDomainsById($id, $domainsQuery);
|
|
||||||
} else {
|
|
||||||
$data = $dataQuery();
|
|
||||||
$domains = $domainsQuery();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! $data) {
|
|
||||||
throw new TenantDoesNotExistException($id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Tenant::fromStorage($data)
|
|
||||||
->withDomains($domains);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find a tenant using an arbitrary key.
|
|
||||||
*
|
|
||||||
* @param string $key
|
|
||||||
* @param mixed $value
|
|
||||||
* @return Tenant
|
|
||||||
* @throws TenantDoesNotExistException
|
|
||||||
*/
|
|
||||||
public function findBy(string $key, $value): Tenant
|
|
||||||
{
|
|
||||||
$tenant = $this->tenants->findBy($key, $value);
|
|
||||||
|
|
||||||
if (! $tenant) {
|
|
||||||
throw new TenantDoesNotExistException($value, $key);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Tenant::fromStorage($this->tenants->decodeData($tenant))
|
|
||||||
->withDomains($this->domains->getTenantDomains($tenant['id']));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function ensureTenantCanBeCreated(Tenant $tenant): void
|
|
||||||
{
|
|
||||||
if ($this->tenants->exists($tenant)) {
|
|
||||||
throw new TenantWithThisIdAlreadyExistsException($tenant->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->domains->occupied($tenant->domains)) {
|
|
||||||
throw new DomainsOccupiedByOtherTenantException;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function withDefaultTenant(Tenant $tenant): self
|
|
||||||
{
|
|
||||||
$this->tenant = $tenant;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function createTenant(Tenant $tenant): void
|
|
||||||
{
|
|
||||||
$this->centralDatabase->transaction(function () use ($tenant) {
|
|
||||||
$this->tenants->insert($tenant);
|
|
||||||
$this->domains->insertTenantDomains($tenant);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public function updateTenant(Tenant $tenant): void
|
|
||||||
{
|
|
||||||
$originalDomains = $this->domains->getTenantDomains($tenant);
|
|
||||||
|
|
||||||
$this->centralDatabase->transaction(function () use ($tenant, $originalDomains) {
|
|
||||||
$this->tenants->updateTenant($tenant);
|
|
||||||
|
|
||||||
$this->domains->updateTenantDomains($tenant, $originalDomains);
|
|
||||||
});
|
|
||||||
|
|
||||||
if ($this->usesCache()) {
|
|
||||||
$this->cache->invalidateTenant($tenant->id);
|
|
||||||
$this->cache->invalidateDomainToIdMapping($originalDomains);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function deleteTenant(Tenant $tenant): void
|
|
||||||
{
|
|
||||||
$originalDomains = $this->domains->getTenantDomains($tenant);
|
|
||||||
|
|
||||||
$this->centralDatabase->transaction(function () use ($tenant) {
|
|
||||||
$this->tenants->where('id', $tenant->id)->delete();
|
|
||||||
$this->domains->where('tenant_id', $tenant->id)->delete();
|
|
||||||
});
|
|
||||||
|
|
||||||
if ($this->usesCache()) {
|
|
||||||
$this->cache->invalidateTenant($tenant->id);
|
|
||||||
$this->cache->invalidateDomainToIdMapping($originalDomains);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all tenants.
|
|
||||||
*
|
|
||||||
* @param string[] $ids
|
|
||||||
* @return Tenant[]
|
|
||||||
*/
|
|
||||||
public function all(array $ids = []): array
|
|
||||||
{
|
|
||||||
return $this->tenants->all($ids)->map(function ($data) {
|
|
||||||
return Tenant::fromStorage($data)
|
|
||||||
->withDomains($this->domains->getTenantDomains($data['id']));
|
|
||||||
})->toArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the current tenant.
|
|
||||||
*
|
|
||||||
* @return Tenant
|
|
||||||
*/
|
|
||||||
protected function currentTenant()
|
|
||||||
{
|
|
||||||
return $this->tenant ?? $this->app[Tenant::class];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function get(string $key, Tenant $tenant = null)
|
|
||||||
{
|
|
||||||
return $this->tenants->get($key, $tenant ?? $this->currentTenant());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getMany(array $keys, Tenant $tenant = null): array
|
|
||||||
{
|
|
||||||
return $this->tenants->getMany($keys, $tenant ?? $this->currentTenant());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function put(string $key, $value, Tenant $tenant = null): void
|
|
||||||
{
|
|
||||||
$tenant = $tenant ?? $this->currentTenant();
|
|
||||||
$this->tenants->put($key, $value, $tenant);
|
|
||||||
|
|
||||||
if ($this->usesCache()) {
|
|
||||||
$this->cache->invalidateTenantData($tenant->id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function putMany(array $kvPairs, Tenant $tenant = null): void
|
|
||||||
{
|
|
||||||
$tenant = $tenant ?? $this->currentTenant();
|
|
||||||
$this->tenants->putMany($kvPairs, $tenant);
|
|
||||||
|
|
||||||
if ($this->usesCache()) {
|
|
||||||
$this->cache->invalidateTenantData($tenant->id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function deleteMany(array $keys, Tenant $tenant = null): void
|
|
||||||
{
|
|
||||||
$tenant = $tenant ?? $this->currentTenant();
|
|
||||||
$this->tenants->deleteMany($keys, $tenant);
|
|
||||||
|
|
||||||
if ($this->usesCache()) {
|
|
||||||
$this->cache->invalidateTenantData($tenant->id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function usesCache(): bool
|
|
||||||
{
|
|
||||||
return $this->app['config']['tenancy.storage_drivers.db.cache_store'] !== null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Stancl\Tenancy\StorageDrivers\Database;
|
|
||||||
|
|
||||||
use Illuminate\Config\Repository as ConfigRepository;
|
|
||||||
use Stancl\Tenancy\Tenant;
|
|
||||||
|
|
||||||
class DomainRepository extends Repository
|
|
||||||
{
|
|
||||||
public function getTenantIdByDomain(string $domain): ?string
|
|
||||||
{
|
|
||||||
return $this->where('domain', $domain)->first()->tenant_id ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function occupied(array $domains): bool
|
|
||||||
{
|
|
||||||
return $this->whereIn('domain', $domains)->exists();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTenantDomains($tenant)
|
|
||||||
{
|
|
||||||
$id = $tenant instanceof Tenant ? $tenant->id : $tenant;
|
|
||||||
|
|
||||||
return $this->where('tenant_id', $id)->get('domain')->pluck('domain')->all();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function insertTenantDomains(Tenant $tenant)
|
|
||||||
{
|
|
||||||
$this->insert(array_map(function ($domain) use ($tenant) {
|
|
||||||
return ['domain' => $domain, 'tenant_id' => $tenant->id];
|
|
||||||
}, $tenant->domains));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function updateTenantDomains(Tenant $tenant, array $originalDomains)
|
|
||||||
{
|
|
||||||
$deletedDomains = array_diff($originalDomains, $tenant->domains);
|
|
||||||
$newDomains = array_diff($tenant->domains, $originalDomains);
|
|
||||||
|
|
||||||
$this->whereIn('domain', $deletedDomains)->delete();
|
|
||||||
|
|
||||||
foreach ($newDomains as $domain) {
|
|
||||||
$this->insert([
|
|
||||||
'tenant_id' => $tenant->id,
|
|
||||||
'domain' => $domain,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTable(ConfigRepository $config)
|
|
||||||
{
|
|
||||||
return $config->get('tenancy.storage_drivers.db.table_names.domains') ?? 'domains';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Stancl\Tenancy\StorageDrivers\Database;
|
|
||||||
|
|
||||||
use Illuminate\Config\Repository as ConfigRepository;
|
|
||||||
use Illuminate\Database\Connection;
|
|
||||||
use Illuminate\Database\Query\Builder;
|
|
||||||
|
|
||||||
/** @mixin Builder */
|
|
||||||
abstract class Repository
|
|
||||||
{
|
|
||||||
/** @var Connection */
|
|
||||||
public $database;
|
|
||||||
|
|
||||||
/** @var string */
|
|
||||||
protected $tableName;
|
|
||||||
|
|
||||||
/** @var Builder */
|
|
||||||
private $table;
|
|
||||||
|
|
||||||
public function __construct(ConfigRepository $config)
|
|
||||||
{
|
|
||||||
$this->database = DatabaseStorageDriver::getCentralConnection();
|
|
||||||
$this->tableName = $this->getTable($config);
|
|
||||||
$this->table = $this->database->table($this->tableName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function table()
|
|
||||||
{
|
|
||||||
return $this->table->newQuery()->from($this->tableName);
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract public function getTable(ConfigRepository $config);
|
|
||||||
|
|
||||||
public function __call($method, $parameters)
|
|
||||||
{
|
|
||||||
return $this->table()->$method(...$parameters);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,186 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Stancl\Tenancy\StorageDrivers\Database;
|
|
||||||
|
|
||||||
use Illuminate\Config\Repository as ConfigRepository;
|
|
||||||
use Stancl\Tenancy\Tenant;
|
|
||||||
use stdClass;
|
|
||||||
|
|
||||||
class TenantRepository extends Repository
|
|
||||||
{
|
|
||||||
public function all($ids = [])
|
|
||||||
{
|
|
||||||
if ($ids) {
|
|
||||||
$data = $this->whereIn('id', $ids)->get();
|
|
||||||
} else {
|
|
||||||
$data = $this->table()->get();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $data->map(function (stdClass $obj) {
|
|
||||||
return $this->decodeData((array) $obj);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public function find($tenant)
|
|
||||||
{
|
|
||||||
return (array) $this->table()->find(
|
|
||||||
$tenant instanceof Tenant ? $tenant->id : $tenant
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function findBy(string $key, $value)
|
|
||||||
{
|
|
||||||
if (in_array($key, static::customColumns())) {
|
|
||||||
return (array) $this->table()->where($key, $value)->first();
|
|
||||||
}
|
|
||||||
|
|
||||||
return (array) $this->table()->where(
|
|
||||||
static::dataColumn() . '->' . $key,
|
|
||||||
$value
|
|
||||||
)->first();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function updateTenant(Tenant $tenant)
|
|
||||||
{
|
|
||||||
$this->putMany($tenant->data, $tenant);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function exists(Tenant $tenant)
|
|
||||||
{
|
|
||||||
return $this->where('id', $tenant->id)->exists();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function get(string $key, Tenant $tenant)
|
|
||||||
{
|
|
||||||
return $this->decodeFreshDataForTenant($tenant)[$key] ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getMany(array $keys, Tenant $tenant)
|
|
||||||
{
|
|
||||||
$decodedData = $this->decodeFreshDataForTenant($tenant);
|
|
||||||
|
|
||||||
$result = [];
|
|
||||||
|
|
||||||
foreach ($keys as $key) {
|
|
||||||
$result[$key] = $decodedData[$key] ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function put(string $key, $value, Tenant $tenant)
|
|
||||||
{
|
|
||||||
$record = $this->where('id', $tenant->id);
|
|
||||||
|
|
||||||
if (in_array($key, static::customColumns())) {
|
|
||||||
$record->update([$key => $value]);
|
|
||||||
} else {
|
|
||||||
$data = json_decode($record->first()->{static::dataColumn()}, true);
|
|
||||||
$data[$key] = $value;
|
|
||||||
|
|
||||||
$record->update([static::dataColumn() => $data]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function putMany(array $kvPairs, Tenant $tenant)
|
|
||||||
{
|
|
||||||
$record = $this->where('id', $tenant->id);
|
|
||||||
|
|
||||||
$data = [];
|
|
||||||
$jsonData = json_decode($record->first()->{static::dataColumn()}, true);
|
|
||||||
foreach ($kvPairs as $key => $value) {
|
|
||||||
if (in_array($key, static::customColumns())) {
|
|
||||||
$data[$key] = $value;
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
$jsonData[$key] = $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$data[static::dataColumn()] = json_encode($jsonData);
|
|
||||||
|
|
||||||
$record->update($data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function deleteMany(array $keys, Tenant $tenant)
|
|
||||||
{
|
|
||||||
$record = $this->where('id', $tenant->id);
|
|
||||||
|
|
||||||
$data = [];
|
|
||||||
$jsonData = json_decode($record->first(static::dataColumn())->data, true);
|
|
||||||
foreach ($keys as $key) {
|
|
||||||
if (in_array($key, static::customColumns())) {
|
|
||||||
$data[$key] = null;
|
|
||||||
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
unset($jsonData[$key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$data[static::dataColumn()] = json_encode($jsonData);
|
|
||||||
|
|
||||||
$record->update($data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function decodeFreshDataForTenant(Tenant $tenant): array
|
|
||||||
{
|
|
||||||
return $this->decodeData(
|
|
||||||
(array) $this->table()->where('id', $tenant->id)->first()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function decodeData(array $columns): array
|
|
||||||
{
|
|
||||||
$dataColumn = static::dataColumn();
|
|
||||||
$decoded = json_decode($columns[$dataColumn], true);
|
|
||||||
$columns = array_merge($columns, $decoded);
|
|
||||||
|
|
||||||
// If $columns[$dataColumn] has been overriden by a value, don't delete the key.
|
|
||||||
if (! array_key_exists($dataColumn, $decoded)) {
|
|
||||||
unset($columns[$dataColumn]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $columns;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function insert(Tenant $tenant)
|
|
||||||
{
|
|
||||||
$this->table()->insert(array_merge(
|
|
||||||
$this->encodeData($tenant->data),
|
|
||||||
['id' => $tenant->id]
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function encodeData(array $data): array
|
|
||||||
{
|
|
||||||
$result = [];
|
|
||||||
foreach (array_intersect(static::customColumns(), array_keys($data)) as $customColumn) {
|
|
||||||
$result[$customColumn] = $data[$customColumn];
|
|
||||||
unset($data[$customColumn]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$result = array_merge($result, [static::dataColumn() => json_encode($data)]);
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function customColumns(): array
|
|
||||||
{
|
|
||||||
return config('tenancy.storage_drivers.db.custom_columns', []);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function dataColumn(): string
|
|
||||||
{
|
|
||||||
return config('tenancy.storage_drivers.db.data_column', 'data');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTable(ConfigRepository $config)
|
|
||||||
{
|
|
||||||
return $config->get('tenancy.storage_drivers.db.table_names.TenantModel') // legacy
|
|
||||||
?? $config->get('tenancy.storage_drivers.db.table_names.tenants')
|
|
||||||
?? 'tenants';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -77,15 +77,25 @@ class Tenancy
|
||||||
* Run a callback for multiple tenants.
|
* Run a callback for multiple tenants.
|
||||||
* More performant than running $tenant->run() one by one.
|
* More performant than running $tenant->run() one by one.
|
||||||
*
|
*
|
||||||
* @param Tenant[]|\Illuminate\Support\Collection $tenants
|
* @param Tenant[]|\Traversable|string[]|null $tenants
|
||||||
* @param callable $callback
|
* @param callable $callback
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function runForMultiple($tenants, callable $callback)
|
public function runForMultiple($tenants, callable $callback)
|
||||||
{
|
{
|
||||||
|
// Wrap string in array
|
||||||
|
$tenants = is_string($tenants) ? [$tenants] : $tenants;
|
||||||
|
|
||||||
|
// Use all tenants if $tenants is falsy
|
||||||
|
$tenants = $tenants ?: $this->model()->cursor();
|
||||||
|
|
||||||
$originalTenant = $this->tenant;
|
$originalTenant = $this->tenant;
|
||||||
|
|
||||||
foreach ($tenants as $tenant) {
|
foreach ($tenants as $tenant) {
|
||||||
|
if (is_string($tenant)) {
|
||||||
|
$tenant = $this->find($tenant);
|
||||||
|
}
|
||||||
|
|
||||||
$this->initialize($tenant);
|
$this->initialize($tenant);
|
||||||
$callback($tenant);
|
$callback($tenant);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,13 @@ class TenancyServiceProvider extends ServiceProvider
|
||||||
$this->app->bind(Contracts\UniqueIdentifierGenerator::class, $this->app['config']['tenancy.id_generator']);
|
$this->app->bind(Contracts\UniqueIdentifierGenerator::class, $this->app['config']['tenancy.id_generator']);
|
||||||
$this->app->singleton(DatabaseManager::class);
|
$this->app->singleton(DatabaseManager::class);
|
||||||
$this->app->singleton(Tenancy::class);
|
$this->app->singleton(Tenancy::class);
|
||||||
|
$this->app->extend(Tenancy::class, function (Tenancy $tenancy) {
|
||||||
|
foreach ($this->app['config']['tenancy.features'] as $feature) {
|
||||||
|
$this->app[$feature]->bootstrap($tenancy);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $tenancy;
|
||||||
|
});
|
||||||
$this->app->bind(Tenant::class, function ($app) {
|
$this->app->bind(Tenant::class, function ($app) {
|
||||||
return $app[Tenancy::class]->tenant;
|
return $app[Tenancy::class]->tenant;
|
||||||
});
|
});
|
||||||
|
|
@ -63,7 +70,6 @@ class TenancyServiceProvider extends ServiceProvider
|
||||||
Commands\Migrate::class,
|
Commands\Migrate::class,
|
||||||
Commands\Rollback::class,
|
Commands\Rollback::class,
|
||||||
Commands\TenantList::class,
|
Commands\TenantList::class,
|
||||||
Commands\CreateTenant::class,
|
|
||||||
Commands\MigrateFresh::class,
|
Commands\MigrateFresh::class,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use Illuminate\Database\Connection;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Stancl\Tenancy\Contracts\Future\CanSetConnection;
|
use Stancl\Tenancy\Contracts\Future\CanSetConnection;
|
||||||
use Stancl\Tenancy\Contracts\TenantDatabaseManager;
|
use Stancl\Tenancy\Contracts\TenantDatabaseManager;
|
||||||
use Stancl\Tenancy\Tenant;
|
use Stancl\Tenancy\Contracts\TenantWithDatabase;
|
||||||
|
|
||||||
class MySQLDatabaseManager implements TenantDatabaseManager, CanSetConnection
|
class MySQLDatabaseManager implements TenantDatabaseManager, CanSetConnection
|
||||||
{
|
{
|
||||||
|
|
@ -21,11 +21,6 @@ class MySQLDatabaseManager implements TenantDatabaseManager, CanSetConnection
|
||||||
$this->connection = $config->get('tenancy.database_manager_connections.mysql');
|
$this->connection = $config->get('tenancy.database_manager_connections.mysql');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSeparator(): string
|
|
||||||
{
|
|
||||||
return 'database';
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function database(): Connection
|
protected function database(): Connection
|
||||||
{
|
{
|
||||||
return DB::connection($this->connection);
|
return DB::connection($this->connection);
|
||||||
|
|
@ -36,7 +31,7 @@ class MySQLDatabaseManager implements TenantDatabaseManager, CanSetConnection
|
||||||
$this->connection = $connection;
|
$this->connection = $connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createDatabase(Tenant $tenant): bool
|
public function createDatabase(TenantWithDatabase $tenant): bool
|
||||||
{
|
{
|
||||||
$database = $tenant->database()->getName();
|
$database = $tenant->database()->getName();
|
||||||
$charset = $this->database()->getConfig('charset');
|
$charset = $this->database()->getConfig('charset');
|
||||||
|
|
@ -45,7 +40,7 @@ class MySQLDatabaseManager implements TenantDatabaseManager, CanSetConnection
|
||||||
return $this->database()->statement("CREATE DATABASE `{$database}` CHARACTER SET `$charset` COLLATE `$collation`");
|
return $this->database()->statement("CREATE DATABASE `{$database}` CHARACTER SET `$charset` COLLATE `$collation`");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteDatabase(Tenant $tenant): bool
|
public function deleteDatabase(TenantWithDatabase $tenant): bool
|
||||||
{
|
{
|
||||||
return $this->database()->statement("DROP DATABASE `{$tenant->database()->getName()}`");
|
return $this->database()->statement("DROP DATABASE `{$tenant->database()->getName()}`");
|
||||||
}
|
}
|
||||||
|
|
@ -54,4 +49,11 @@ class MySQLDatabaseManager implements TenantDatabaseManager, CanSetConnection
|
||||||
{
|
{
|
||||||
return (bool) $this->database()->select("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '$name'");
|
return (bool) $this->database()->select("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '$name'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function makeConnectionConfig(array $baseConfig, string $databaseName): array
|
||||||
|
{
|
||||||
|
$baseConfig['database'] = $databaseName;
|
||||||
|
|
||||||
|
return $baseConfig;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,4 +59,11 @@ class PermissionControlledMySQLDatabaseManager extends MySQLDatabaseManager impl
|
||||||
{
|
{
|
||||||
return (bool) $this->database()->select("SELECT count(*) FROM mysql.user WHERE user = '$username'")[0]->{'count(*)'};
|
return (bool) $this->database()->select("SELECT count(*) FROM mysql.user WHERE user = '$username'")[0]->{'count(*)'};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function makeConnectionConfig(array $baseConfig, string $databaseName): array
|
||||||
|
{
|
||||||
|
$baseConfig['database'] = $databaseName;
|
||||||
|
|
||||||
|
return $baseConfig;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use Illuminate\Database\Connection;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Stancl\Tenancy\Contracts\Future\CanSetConnection;
|
use Stancl\Tenancy\Contracts\Future\CanSetConnection;
|
||||||
use Stancl\Tenancy\Contracts\TenantDatabaseManager;
|
use Stancl\Tenancy\Contracts\TenantDatabaseManager;
|
||||||
use Stancl\Tenancy\Tenant;
|
use Stancl\Tenancy\Contracts\TenantWithDatabase;
|
||||||
|
|
||||||
class PostgreSQLDatabaseManager implements TenantDatabaseManager, CanSetConnection
|
class PostgreSQLDatabaseManager implements TenantDatabaseManager, CanSetConnection
|
||||||
{
|
{
|
||||||
|
|
@ -21,11 +21,6 @@ class PostgreSQLDatabaseManager implements TenantDatabaseManager, CanSetConnecti
|
||||||
$this->connection = $config->get('tenancy.database_manager_connections.pgsql');
|
$this->connection = $config->get('tenancy.database_manager_connections.pgsql');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSeparator(): string
|
|
||||||
{
|
|
||||||
return 'database';
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function database(): Connection
|
protected function database(): Connection
|
||||||
{
|
{
|
||||||
return DB::connection($this->connection);
|
return DB::connection($this->connection);
|
||||||
|
|
@ -36,12 +31,12 @@ class PostgreSQLDatabaseManager implements TenantDatabaseManager, CanSetConnecti
|
||||||
$this->connection = $connection;
|
$this->connection = $connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createDatabase(Tenant $tenant): bool
|
public function createDatabase(TenantWithDatabase $tenant): bool
|
||||||
{
|
{
|
||||||
return $this->database()->statement("CREATE DATABASE \"{$tenant->database()->getName()}\" WITH TEMPLATE=template0");
|
return $this->database()->statement("CREATE DATABASE \"{$tenant->database()->getName()}\" WITH TEMPLATE=template0");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteDatabase(Tenant $tenant): bool
|
public function deleteDatabase(TenantWithDatabase $tenant): bool
|
||||||
{
|
{
|
||||||
return $this->database()->statement("DROP DATABASE \"{$tenant->database()->getName()}\"");
|
return $this->database()->statement("DROP DATABASE \"{$tenant->database()->getName()}\"");
|
||||||
}
|
}
|
||||||
|
|
@ -50,4 +45,11 @@ class PostgreSQLDatabaseManager implements TenantDatabaseManager, CanSetConnecti
|
||||||
{
|
{
|
||||||
return (bool) $this->database()->select("SELECT datname FROM pg_database WHERE datname = '$name'");
|
return (bool) $this->database()->select("SELECT datname FROM pg_database WHERE datname = '$name'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function makeConnectionConfig(array $baseConfig, string $databaseName): array
|
||||||
|
{
|
||||||
|
$baseConfig['database'] = $databaseName;
|
||||||
|
|
||||||
|
return $baseConfig;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use Illuminate\Database\Connection;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Stancl\Tenancy\Contracts\Future\CanSetConnection;
|
use Stancl\Tenancy\Contracts\Future\CanSetConnection;
|
||||||
use Stancl\Tenancy\Contracts\TenantDatabaseManager;
|
use Stancl\Tenancy\Contracts\TenantDatabaseManager;
|
||||||
use Stancl\Tenancy\Tenant;
|
use Stancl\Tenancy\Contracts\TenantWithDatabase;
|
||||||
|
|
||||||
class PostgreSQLSchemaManager implements TenantDatabaseManager, CanSetConnection
|
class PostgreSQLSchemaManager implements TenantDatabaseManager, CanSetConnection
|
||||||
{
|
{
|
||||||
|
|
@ -21,11 +21,6 @@ class PostgreSQLSchemaManager implements TenantDatabaseManager, CanSetConnection
|
||||||
$this->connection = $config->get('tenancy.database_manager_connections.pgsql');
|
$this->connection = $config->get('tenancy.database_manager_connections.pgsql');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSeparator(): string
|
|
||||||
{
|
|
||||||
return 'schema';
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function database(): Connection
|
protected function database(): Connection
|
||||||
{
|
{
|
||||||
return DB::connection($this->connection);
|
return DB::connection($this->connection);
|
||||||
|
|
@ -36,12 +31,12 @@ class PostgreSQLSchemaManager implements TenantDatabaseManager, CanSetConnection
|
||||||
$this->connection = $connection;
|
$this->connection = $connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createDatabase(Tenant $tenant): bool
|
public function createDatabase(TenantWithDatabase $tenant): bool
|
||||||
{
|
{
|
||||||
return $this->database()->statement("CREATE SCHEMA \"{$tenant->database()->getName()}\"");
|
return $this->database()->statement("CREATE SCHEMA \"{$tenant->database()->getName()}\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteDatabase(Tenant $tenant): bool
|
public function deleteDatabase(TenantWithDatabase $tenant): bool
|
||||||
{
|
{
|
||||||
return $this->database()->statement("DROP SCHEMA \"{$tenant->database()->getName()}\"");
|
return $this->database()->statement("DROP SCHEMA \"{$tenant->database()->getName()}\"");
|
||||||
}
|
}
|
||||||
|
|
@ -50,4 +45,11 @@ class PostgreSQLSchemaManager implements TenantDatabaseManager, CanSetConnection
|
||||||
{
|
{
|
||||||
return (bool) $this->database()->select("SELECT schema_name FROM information_schema.schemata WHERE schema_name = '$name'");
|
return (bool) $this->database()->select("SELECT schema_name FROM information_schema.schemata WHERE schema_name = '$name'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function makeConnectionConfig(array $baseConfig, string $databaseName): array
|
||||||
|
{
|
||||||
|
$baseConfig['schema'] = $databaseName;
|
||||||
|
|
||||||
|
return $baseConfig;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,17 +4,11 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace Stancl\Tenancy\TenantDatabaseManagers;
|
namespace Stancl\Tenancy\TenantDatabaseManagers;
|
||||||
|
|
||||||
use Stancl\Tenancy\Contracts\ModifiesDatabaseNameForConnection;
|
|
||||||
use Stancl\Tenancy\Contracts\TenantDatabaseManager;
|
use Stancl\Tenancy\Contracts\TenantDatabaseManager;
|
||||||
use Stancl\Tenancy\Contracts\TenantWithDatabase;
|
use Stancl\Tenancy\Contracts\TenantWithDatabase;
|
||||||
|
|
||||||
class SQLiteDatabaseManager implements TenantDatabaseManager, ModifiesDatabaseNameForConnection
|
class SQLiteDatabaseManager implements TenantDatabaseManager
|
||||||
{
|
{
|
||||||
public function getSeparator(): string
|
|
||||||
{
|
|
||||||
return 'database';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function createDatabase(TenantWithDatabase $tenant): bool
|
public function createDatabase(TenantWithDatabase $tenant): bool
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
|
@ -38,8 +32,10 @@ class SQLiteDatabaseManager implements TenantDatabaseManager, ModifiesDatabaseNa
|
||||||
return file_exists(database_path($name));
|
return file_exists(database_path($name));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDatabaseNameForConnection(string $original): string
|
public function makeConnectionConfig(array $baseConfig, string $databaseName): array
|
||||||
{
|
{
|
||||||
return database_path($original);
|
$baseConfig['database'] = database_path($databaseName);
|
||||||
|
|
||||||
|
return $baseConfig;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ class TenantRouteServiceProvider extends RouteServiceProvider
|
||||||
{
|
{
|
||||||
$this->app->booted(function () {
|
$this->app->booted(function () {
|
||||||
if (file_exists(base_path('routes/tenant.php'))) {
|
if (file_exists(base_path('routes/tenant.php'))) {
|
||||||
Route::middleware(['web', 'tenancy'])
|
Route::middleware(['web'])
|
||||||
->namespace($this->app['config']['tenancy.tenant_route_namespace'] ?? 'App\Http\Controllers')
|
->namespace($this->app['config']['tenancy.tenant_route_namespace'] ?? 'App\Http\Controllers')
|
||||||
->group(base_path('routes/tenant.php'));
|
->group(base_path('routes/tenant.php'));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,11 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Traits;
|
namespace Stancl\Tenancy\Traits;
|
||||||
|
|
||||||
use Stancl\Tenancy\Tenant;
|
use Stancl\Tenancy\Contracts\TenantWithDatabase;
|
||||||
|
|
||||||
trait CreatesDatabaseUsers
|
trait CreatesDatabaseUsers
|
||||||
{
|
{
|
||||||
public function createDatabase(Tenant $tenant): bool
|
public function createDatabase(TenantWithDatabase $tenant): bool
|
||||||
{
|
{
|
||||||
return $this->database()->transaction(function () use ($tenant) {
|
return $this->database()->transaction(function () use ($tenant) {
|
||||||
parent::createDatabase($tenant);
|
parent::createDatabase($tenant);
|
||||||
|
|
@ -17,7 +17,7 @@ trait CreatesDatabaseUsers
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteDatabase(Tenant $tenant): bool
|
public function deleteDatabase(TenantWithDatabase $tenant): bool
|
||||||
{
|
{
|
||||||
return $this->database()->transaction(function () use ($tenant) {
|
return $this->database()->transaction(function () use ($tenant) {
|
||||||
parent::deleteDatabase($tenant);
|
parent::deleteDatabase($tenant);
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Traits;
|
namespace Stancl\Tenancy\Traits;
|
||||||
|
|
||||||
|
use Illuminate\Support\LazyCollection;
|
||||||
use Symfony\Component\Console\Input\InputOption;
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
|
||||||
trait HasATenantsOption
|
trait HasATenantsOption
|
||||||
|
|
@ -15,9 +16,14 @@ trait HasATenantsOption
|
||||||
], parent::getOptions());
|
], parent::getOptions());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getTenants(): array
|
protected function getTenants(): LazyCollection
|
||||||
{
|
{
|
||||||
return tenancy()->all($this->option('tenants'))->all();
|
return tenancy()
|
||||||
|
->query()
|
||||||
|
->when($this->option('tenants'), function ($query) {
|
||||||
|
$query->whereIn(tenancy()->model()->getTenantKeyName(), $this->option('tenants'));
|
||||||
|
})
|
||||||
|
->cursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
|
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Traits;
|
|
||||||
|
|
||||||
trait HasArrayAccess
|
|
||||||
{
|
|
||||||
public function offsetExists($offset): bool
|
|
||||||
{
|
|
||||||
return property_exists($this, $offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function offsetGet($offset)
|
|
||||||
{
|
|
||||||
return $this->$offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function offsetSet($offset, $value): void
|
|
||||||
{
|
|
||||||
$this->$offset = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function offsetUnset($offset): void
|
|
||||||
{
|
|
||||||
unset($this->$offset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -32,7 +32,7 @@ trait TenantAwareCommand
|
||||||
/**
|
/**
|
||||||
* Get an array of tenants for which the command should be executed.
|
* Get an array of tenants for which the command should be executed.
|
||||||
*
|
*
|
||||||
* @return Tenant[]
|
* @return Tenant[]|mixed
|
||||||
*/
|
*/
|
||||||
abstract protected function getTenants(): array;
|
abstract protected function getTenants();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
Route::middleware(['tenancy'])->group(function () {
|
use Illuminate\Support\Facades\Route;
|
||||||
Route::get('/tenancy/assets/{path?}', 'Stancl\Tenancy\Controllers\TenantAssetsController@asset')
|
|
||||||
|
Route::get('/tenancy/assets/{path?}', 'Stancl\Tenancy\Controllers\TenantAssetsController@asset')
|
||||||
->where('path', '(.*)')
|
->where('path', '(.*)')
|
||||||
->name('stancl.tenancy.asset');
|
->name('stancl.tenancy.asset');
|
||||||
});
|
|
||||||
|
|
|
||||||
|
|
@ -154,6 +154,12 @@ class BootstrapperTest extends TestCase
|
||||||
FilesystemTenancyBootstrapper::class
|
FilesystemTenancyBootstrapper::class
|
||||||
]]);
|
]]);
|
||||||
|
|
||||||
|
$old_storage_path = storage_path();
|
||||||
|
$old_storage_facade_roots = [];
|
||||||
|
foreach (config('tenancy.filesystem.disks') as $disk) {
|
||||||
|
$old_storage_facade_roots[$disk] = config("filesystems.disks.{$disk}.root");
|
||||||
|
}
|
||||||
|
|
||||||
$tenant1 = Tenant::create();
|
$tenant1 = Tenant::create();
|
||||||
$tenant2 = Tenant::create();
|
$tenant2 = Tenant::create();
|
||||||
|
|
||||||
|
|
@ -176,6 +182,27 @@ class BootstrapperTest extends TestCase
|
||||||
tenancy()->initialize($tenant3);
|
tenancy()->initialize($tenant3);
|
||||||
$this->assertFalse(Storage::disk('public')->exists('foo'));
|
$this->assertFalse(Storage::disk('public')->exists('foo'));
|
||||||
$this->assertFalse(Storage::disk('public')->exists('abc'));
|
$this->assertFalse(Storage::disk('public')->exists('abc'));
|
||||||
|
|
||||||
|
// Check suffixing logic
|
||||||
|
$new_storage_path = storage_path();
|
||||||
|
$this->assertEquals($old_storage_path . '/' . config('tenancy.filesystem.suffix_base') . tenant('id'), $new_storage_path);
|
||||||
|
|
||||||
|
foreach (config('tenancy.filesystem.disks') as $disk) {
|
||||||
|
$suffix = config('tenancy.filesystem.suffix_base') . tenant('id');
|
||||||
|
$current_path_prefix = Storage::disk($disk)->getAdapter()->getPathPrefix();
|
||||||
|
|
||||||
|
if ($override = config("tenancy.filesystem.root_override.{$disk}")) {
|
||||||
|
$correct_path_prefix = str_replace('%storage_path%', storage_path(), $override);
|
||||||
|
} else {
|
||||||
|
if ($base = $old_storage_facade_roots[$disk]) {
|
||||||
|
$correct_path_prefix = $base . "/$suffix/";
|
||||||
|
} else {
|
||||||
|
$correct_path_prefix = "$suffix/";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertSame($correct_path_prefix, $current_path_prefix);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// for queues see QueueTest
|
// for queues see QueueTest
|
||||||
|
|
@ -2,25 +2,41 @@
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Tests;
|
namespace Stancl\Tenancy\Tests\v3;
|
||||||
|
|
||||||
use Stancl\Tenancy\Tenant;
|
use Illuminate\Support\Facades\Event;
|
||||||
|
use Stancl\Tenancy\Database\Models\Tenant;
|
||||||
|
use Stancl\Tenancy\Events\Listeners\BootstrapTenancy;
|
||||||
|
use Stancl\Tenancy\Events\TenancyInitialized;
|
||||||
|
use Stancl\Tenancy\TenancyBootstrappers\CacheTenancyBootstrapper;
|
||||||
|
use Stancl\Tenancy\Tests\TestCase;
|
||||||
|
|
||||||
class CacheManagerTest extends TestCase
|
class CacheManagerTest extends TestCase
|
||||||
{
|
{
|
||||||
|
public function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
config(['tenancy.bootstrappers' => [
|
||||||
|
CacheTenancyBootstrapper::class,
|
||||||
|
]]);
|
||||||
|
|
||||||
|
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
|
||||||
|
}
|
||||||
|
|
||||||
/** @test */
|
/** @test */
|
||||||
public function default_tag_is_automatically_applied()
|
public function default_tag_is_automatically_applied()
|
||||||
{
|
{
|
||||||
$this->createTenant();
|
tenancy()->initialize(Tenant::create());
|
||||||
$this->initTenancy();
|
|
||||||
$this->assertArrayIsSubset([config('tenancy.cache.tag_base') . tenant('id')], cache()->tags('foo')->getTags()->getNames());
|
$this->assertArrayIsSubset([config('tenancy.cache.tag_base') . tenant('id')], cache()->tags('foo')->getTags()->getNames());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @test */
|
/** @test */
|
||||||
public function tags_are_merged_when_array_is_passed()
|
public function tags_are_merged_when_array_is_passed()
|
||||||
{
|
{
|
||||||
$this->createTenant();
|
tenancy()->initialize(Tenant::create());
|
||||||
$this->initTenancy();
|
|
||||||
$expected = [config('tenancy.cache.tag_base') . tenant('id'), 'foo', 'bar'];
|
$expected = [config('tenancy.cache.tag_base') . tenant('id'), 'foo', 'bar'];
|
||||||
$this->assertEquals($expected, cache()->tags(['foo', 'bar'])->getTags()->getNames());
|
$this->assertEquals($expected, cache()->tags(['foo', 'bar'])->getTags()->getNames());
|
||||||
}
|
}
|
||||||
|
|
@ -28,8 +44,8 @@ class CacheManagerTest extends TestCase
|
||||||
/** @test */
|
/** @test */
|
||||||
public function tags_are_merged_when_string_is_passed()
|
public function tags_are_merged_when_string_is_passed()
|
||||||
{
|
{
|
||||||
$this->createTenant();
|
tenancy()->initialize(Tenant::create());
|
||||||
$this->initTenancy();
|
|
||||||
$expected = [config('tenancy.cache.tag_base') . tenant('id'), 'foo'];
|
$expected = [config('tenancy.cache.tag_base') . tenant('id'), 'foo'];
|
||||||
$this->assertEquals($expected, cache()->tags('foo')->getTags()->getNames());
|
$this->assertEquals($expected, cache()->tags('foo')->getTags()->getNames());
|
||||||
}
|
}
|
||||||
|
|
@ -37,8 +53,8 @@ class CacheManagerTest extends TestCase
|
||||||
/** @test */
|
/** @test */
|
||||||
public function exception_is_thrown_when_zero_arguments_are_passed_to_tags_method()
|
public function exception_is_thrown_when_zero_arguments_are_passed_to_tags_method()
|
||||||
{
|
{
|
||||||
$this->createTenant();
|
tenancy()->initialize(Tenant::create());
|
||||||
$this->initTenancy();
|
|
||||||
$this->expectException(\Exception::class);
|
$this->expectException(\Exception::class);
|
||||||
cache()->tags();
|
cache()->tags();
|
||||||
}
|
}
|
||||||
|
|
@ -46,8 +62,8 @@ class CacheManagerTest extends TestCase
|
||||||
/** @test */
|
/** @test */
|
||||||
public function exception_is_thrown_when_more_than_one_argument_is_passed_to_tags_method()
|
public function exception_is_thrown_when_more_than_one_argument_is_passed_to_tags_method()
|
||||||
{
|
{
|
||||||
$this->createTenant();
|
tenancy()->initialize(Tenant::create());
|
||||||
$this->initTenancy();
|
|
||||||
$this->expectException(\Exception::class);
|
$this->expectException(\Exception::class);
|
||||||
cache()->tags(1, 2);
|
cache()->tags(1, 2);
|
||||||
}
|
}
|
||||||
|
|
@ -55,14 +71,14 @@ class CacheManagerTest extends TestCase
|
||||||
/** @test */
|
/** @test */
|
||||||
public function tags_separate_cache_well_enough()
|
public function tags_separate_cache_well_enough()
|
||||||
{
|
{
|
||||||
Tenant::new()->withDomains(['foo.localhost'])->save();
|
$tenant1 = Tenant::create();
|
||||||
tenancy()->init('foo.localhost');
|
tenancy()->initialize($tenant1);
|
||||||
|
|
||||||
cache()->put('foo', 'bar', 1);
|
cache()->put('foo', 'bar', 1);
|
||||||
$this->assertSame('bar', cache()->get('foo'));
|
$this->assertSame('bar', cache()->get('foo'));
|
||||||
|
|
||||||
Tenant::new()->withDomains(['bar.localhost'])->save();
|
$tenant2 = Tenant::create();
|
||||||
tenancy()->init('bar.localhost');
|
tenancy()->initialize($tenant2);
|
||||||
|
|
||||||
$this->assertNotSame('bar', cache()->get('foo'));
|
$this->assertNotSame('bar', cache()->get('foo'));
|
||||||
|
|
||||||
|
|
@ -73,14 +89,14 @@ class CacheManagerTest extends TestCase
|
||||||
/** @test */
|
/** @test */
|
||||||
public function invoking_the_cache_helper_works()
|
public function invoking_the_cache_helper_works()
|
||||||
{
|
{
|
||||||
Tenant::new()->withDomains(['foo.localhost'])->save();
|
$tenant1 = Tenant::create();
|
||||||
tenancy()->init('foo.localhost');
|
tenancy()->initialize($tenant1);
|
||||||
|
|
||||||
cache(['foo' => 'bar'], 1);
|
cache(['foo' => 'bar'], 1);
|
||||||
$this->assertSame('bar', cache('foo'));
|
$this->assertSame('bar', cache('foo'));
|
||||||
|
|
||||||
Tenant::new()->withDomains(['bar.localhost'])->save();
|
$tenant2 = Tenant::create();
|
||||||
tenancy()->init('bar.localhost');
|
tenancy()->initialize($tenant2);
|
||||||
|
|
||||||
$this->assertNotSame('bar', cache('foo'));
|
$this->assertNotSame('bar', cache('foo'));
|
||||||
|
|
||||||
|
|
@ -91,32 +107,32 @@ class CacheManagerTest extends TestCase
|
||||||
/** @test */
|
/** @test */
|
||||||
public function cache_is_persisted()
|
public function cache_is_persisted()
|
||||||
{
|
{
|
||||||
Tenant::new()->withDomains(['foo.localhost'])->save();
|
$tenant1 = Tenant::create();
|
||||||
tenancy()->init('foo.localhost');
|
tenancy()->initialize($tenant1);
|
||||||
|
|
||||||
cache(['foo' => 'bar'], 10);
|
cache(['foo' => 'bar'], 10);
|
||||||
$this->assertSame('bar', cache('foo'));
|
$this->assertSame('bar', cache('foo'));
|
||||||
|
|
||||||
tenancy()->endTenancy();
|
tenancy()->end();
|
||||||
|
|
||||||
tenancy()->init('foo.localhost');
|
tenancy()->initialize($tenant1);
|
||||||
$this->assertSame('bar', cache('foo'));
|
$this->assertSame('bar', cache('foo'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @test */
|
/** @test */
|
||||||
public function cache_is_persisted_when_reidentification_is_used()
|
public function cache_is_persisted_when_reidentification_is_used()
|
||||||
{
|
{
|
||||||
Tenant::new()->withDomains(['foo.localhost'])->save();
|
$tenant1 = Tenant::create();
|
||||||
Tenant::new()->withDomains(['bar.localhost'])->save();
|
$tenant2 = Tenant::create();
|
||||||
tenancy()->init('foo.localhost');
|
tenancy()->initialize($tenant1);
|
||||||
|
|
||||||
cache(['foo' => 'bar'], 10);
|
cache(['foo' => 'bar'], 10);
|
||||||
$this->assertSame('bar', cache('foo'));
|
$this->assertSame('bar', cache('foo'));
|
||||||
|
|
||||||
tenancy()->init('bar.localhost');
|
tenancy()->initialize($tenant2);
|
||||||
tenancy()->endTenancy();
|
tenancy()->end();
|
||||||
|
|
||||||
tenancy()->init('foo.localhost');
|
tenancy()->initialize($tenant1);
|
||||||
$this->assertSame('bar', cache('foo'));
|
$this->assertSame('bar', cache('foo'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,164 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Tests;
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Cache;
|
|
||||||
use Stancl\Tenancy\StorageDrivers\Database\DatabaseStorageDriver;
|
|
||||||
use Stancl\Tenancy\Tenant;
|
|
||||||
|
|
||||||
class CachedResolverTest extends TestCase
|
|
||||||
{
|
|
||||||
public $autoCreateTenant = false;
|
|
||||||
public $autoInitTenancy = false;
|
|
||||||
|
|
||||||
public function setUp(): void
|
|
||||||
{
|
|
||||||
parent::setUp();
|
|
||||||
|
|
||||||
if (config('tenancy.storage_driver') !== 'db') {
|
|
||||||
$this->markTestSkipped('This test is only relevant for the DB storage driver.');
|
|
||||||
}
|
|
||||||
|
|
||||||
config(['tenancy.storage_drivers.db.cache_store' => config('cache.default')]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function a_query_is_not_made_for_tenant_id_once_domain_is_cached()
|
|
||||||
{
|
|
||||||
$tenant = Tenant::new()
|
|
||||||
->withData(['foo' => 'bar'])
|
|
||||||
->withDomains(['foo.localhost'])
|
|
||||||
->save();
|
|
||||||
|
|
||||||
// query is made
|
|
||||||
$queried = tenancy()->findByDomain('foo.localhost');
|
|
||||||
$this->assertEquals($tenant->data, $queried->data);
|
|
||||||
$this->assertSame($tenant->domains, $queried->domains);
|
|
||||||
|
|
||||||
// cache is set
|
|
||||||
$this->assertEquals($tenant->id, Cache::get('_tenancy_domain_to_id:foo.localhost'));
|
|
||||||
$this->assertEquals($tenant->data, Cache::get('_tenancy_id_to_data:' . $tenant->id));
|
|
||||||
$this->assertSame($tenant->domains, Cache::get('_tenancy_id_to_domains:' . $tenant->id));
|
|
||||||
|
|
||||||
// query is not made
|
|
||||||
DatabaseStorageDriver::getCentralConnection()->enableQueryLog();
|
|
||||||
$cached = tenancy()->findByDomain('foo.localhost');
|
|
||||||
$this->assertEquals($tenant->data, $cached->data);
|
|
||||||
$this->assertSame($tenant->domains, $cached->domains);
|
|
||||||
$this->assertSame([], DatabaseStorageDriver::getCentralConnection()->getQueryLog());
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function a_query_is_not_made_for_tenant_once_id_is_cached()
|
|
||||||
{
|
|
||||||
$tenant = Tenant::new()
|
|
||||||
->withData(['foo' => 'bar'])
|
|
||||||
->withDomains(['foo.localhost'])
|
|
||||||
->save();
|
|
||||||
|
|
||||||
// query is made
|
|
||||||
$queried = tenancy()->find($tenant->id);
|
|
||||||
$this->assertEquals($tenant->data, $queried->data);
|
|
||||||
$this->assertSame($tenant->domains, $queried->domains);
|
|
||||||
|
|
||||||
// cache is set
|
|
||||||
$this->assertEquals($tenant->data, Cache::get('_tenancy_id_to_data:' . $tenant->id));
|
|
||||||
$this->assertSame($tenant->domains, Cache::get('_tenancy_id_to_domains:' . $tenant->id));
|
|
||||||
|
|
||||||
// query is not made
|
|
||||||
DatabaseStorageDriver::getCentralConnection()->enableQueryLog();
|
|
||||||
$cached = tenancy()->find($tenant->id);
|
|
||||||
$this->assertEquals($tenant->data, $cached->data);
|
|
||||||
$this->assertSame($tenant->domains, $cached->domains);
|
|
||||||
$this->assertSame([], DatabaseStorageDriver::getCentralConnection()->getQueryLog());
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function modifying_tenant_domains_invalidates_the_cached_domain_to_id_mapping()
|
|
||||||
{
|
|
||||||
$tenant = Tenant::new()
|
|
||||||
->withDomains(['foo.localhost', 'bar.localhost'])
|
|
||||||
->save();
|
|
||||||
|
|
||||||
// queried
|
|
||||||
$this->assertSame($tenant->id, tenancy()->findByDomain('foo.localhost')->id);
|
|
||||||
$this->assertSame($tenant->id, tenancy()->findByDomain('bar.localhost')->id);
|
|
||||||
|
|
||||||
// assert cache set
|
|
||||||
$this->assertSame($tenant->id, Cache::get('_tenancy_domain_to_id:foo.localhost'));
|
|
||||||
$this->assertSame($tenant->id, Cache::get('_tenancy_domain_to_id:bar.localhost'));
|
|
||||||
|
|
||||||
$tenant
|
|
||||||
->removeDomains(['foo.localhost', 'bar.localhost'])
|
|
||||||
->addDomains(['xyz.localhost'])
|
|
||||||
->save();
|
|
||||||
|
|
||||||
// assert neither domain is cached
|
|
||||||
$this->assertSame(null, Cache::get('_tenancy_domain_to_id:foo.localhost'));
|
|
||||||
$this->assertSame(null, Cache::get('_tenancy_domain_to_id:bar.localhost'));
|
|
||||||
$this->assertSame(null, Cache::get('_tenancy_domain_to_id:xyz.localhost'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function modifying_tenants_data_invalidates_tenant_data_cache()
|
|
||||||
{
|
|
||||||
$tenant = Tenant::new()->withData(['foo' => 'bar'])->save();
|
|
||||||
|
|
||||||
// cache record is set
|
|
||||||
$this->assertSame('bar', tenancy()->find($tenant->id)->get('foo'));
|
|
||||||
$this->assertSame('bar', Cache::get('_tenancy_id_to_data:' . $tenant->id)['foo']);
|
|
||||||
|
|
||||||
// cache record is invalidated
|
|
||||||
$tenant->set('foo', 'xyz');
|
|
||||||
$this->assertSame(null, Cache::get('_tenancy_id_to_data:' . $tenant->id));
|
|
||||||
|
|
||||||
// cache record is set
|
|
||||||
$this->assertSame('xyz', tenancy()->find($tenant->id)->get('foo'));
|
|
||||||
$this->assertSame('xyz', Cache::get('_tenancy_id_to_data:' . $tenant->id)['foo']);
|
|
||||||
|
|
||||||
// cache record is invalidated
|
|
||||||
$tenant->foo = 'abc';
|
|
||||||
$tenant->save();
|
|
||||||
$this->assertSame(null, Cache::get('_tenancy_id_to_data:' . $tenant->id));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function modifying_tenants_domains_invalidates_tenant_domain_cache()
|
|
||||||
{
|
|
||||||
$tenant = Tenant::new()
|
|
||||||
->withData(['foo' => 'bar'])
|
|
||||||
->withDomains(['foo.localhost'])
|
|
||||||
->save();
|
|
||||||
|
|
||||||
// cache record is set
|
|
||||||
$this->assertSame(['foo.localhost'], tenancy()->find($tenant->id)->domains);
|
|
||||||
$this->assertSame(['foo.localhost'], Cache::get('_tenancy_id_to_domains:' . $tenant->id));
|
|
||||||
|
|
||||||
// cache record is invalidated
|
|
||||||
$tenant->addDomains(['bar.localhost'])->save();
|
|
||||||
$this->assertEquals(null, Cache::get('_tenancy_id_to_domains:' . $tenant->id));
|
|
||||||
|
|
||||||
$this->assertEquals(['foo.localhost', 'bar.localhost'], tenancy()->find($tenant->id)->domains);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function deleting_a_tenant_invalidates_all_caches()
|
|
||||||
{
|
|
||||||
$tenant = Tenant::new()
|
|
||||||
->withData(['foo' => 'bar'])
|
|
||||||
->withDomains(['foo.localhost'])
|
|
||||||
->save();
|
|
||||||
|
|
||||||
tenancy()->findByDomain('foo.localhost');
|
|
||||||
$this->assertEquals($tenant->id, Cache::get('_tenancy_domain_to_id:foo.localhost'));
|
|
||||||
$this->assertEquals($tenant->data, Cache::get('_tenancy_id_to_data:' . $tenant->id));
|
|
||||||
$this->assertEquals(['foo.localhost'], Cache::get('_tenancy_id_to_domains:' . $tenant->id));
|
|
||||||
|
|
||||||
$tenant->delete();
|
|
||||||
$this->assertEquals(null, Cache::get('_tenancy_domain_to_id:foo.localhost'));
|
|
||||||
$this->assertEquals(null, Cache::get('_tenancy_id_to_data:' . $tenant->id));
|
|
||||||
$this->assertEquals(null, Cache::get('_tenancy_id_to_domains:' . $tenant->id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,18 +2,41 @@
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Tests;
|
namespace Stancl\Tenancy\Tests\v3;
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Artisan;
|
use Illuminate\Support\Facades\Artisan;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Event;
|
||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
use Stancl\Tenancy\Tenant;
|
|
||||||
use Stancl\Tenancy\Tests\Etc\ExampleSeeder;
|
use Stancl\Tenancy\Tests\Etc\ExampleSeeder;
|
||||||
|
use Stancl\Tenancy\Database\Models\Tenant;
|
||||||
|
use Stancl\Tenancy\Events\Listeners\BootstrapTenancy;
|
||||||
|
use Stancl\Tenancy\Events\Listeners\JobPipeline;
|
||||||
|
use Stancl\Tenancy\Events\Listeners\RevertToCentralContext;
|
||||||
|
use Stancl\Tenancy\Events\TenancyEnded;
|
||||||
|
use Stancl\Tenancy\Events\TenancyInitialized;
|
||||||
|
use Stancl\Tenancy\Events\TenantCreated;
|
||||||
|
use Stancl\Tenancy\Jobs\CreateDatabase;
|
||||||
|
use Stancl\Tenancy\TenancyBootstrappers\DatabaseTenancyBootstrapper;
|
||||||
|
use Stancl\Tenancy\Tests\TestCase;
|
||||||
|
|
||||||
class CommandsTest extends TestCase
|
class CommandsTest extends TestCase
|
||||||
{
|
{
|
||||||
public $autoCreateTenant = true;
|
public function setUp(): void
|
||||||
public $autoInitTenancy = false;
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
|
||||||
|
return $event->tenant;
|
||||||
|
})->toListener());
|
||||||
|
|
||||||
|
config(['tenancy.bootstrappers' => [
|
||||||
|
DatabaseTenancyBootstrapper::class
|
||||||
|
]]);
|
||||||
|
|
||||||
|
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
|
||||||
|
Event::listen(TenancyEnded::class, RevertToCentralContext::class);
|
||||||
|
}
|
||||||
|
|
||||||
/** @test */
|
/** @test */
|
||||||
public function migrate_command_doesnt_change_the_db_connection()
|
public function migrate_command_doesnt_change_the_db_connection()
|
||||||
|
|
@ -32,35 +55,42 @@ class CommandsTest extends TestCase
|
||||||
/** @test */
|
/** @test */
|
||||||
public function migrate_command_works_without_options()
|
public function migrate_command_works_without_options()
|
||||||
{
|
{
|
||||||
|
$tenant = Tenant::create();
|
||||||
|
|
||||||
$this->assertFalse(Schema::hasTable('users'));
|
$this->assertFalse(Schema::hasTable('users'));
|
||||||
Artisan::call('tenants:migrate');
|
Artisan::call('tenants:migrate');
|
||||||
$this->assertFalse(Schema::hasTable('users'));
|
$this->assertFalse(Schema::hasTable('users'));
|
||||||
tenancy()->init('test.localhost');
|
|
||||||
|
tenancy()->initialize($tenant);
|
||||||
|
|
||||||
$this->assertTrue(Schema::hasTable('users'));
|
$this->assertTrue(Schema::hasTable('users'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @test */
|
/** @test */
|
||||||
public function migrate_command_works_with_tenants_option()
|
public function migrate_command_works_with_tenants_option()
|
||||||
{
|
{
|
||||||
$tenant = Tenant::new()->withDomains(['test2.localhost'])->save();
|
$tenant = Tenant::create();
|
||||||
Artisan::call('tenants:migrate', [
|
Artisan::call('tenants:migrate', [
|
||||||
'--tenants' => [$tenant['id']],
|
'--tenants' => [$tenant['id']],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->assertFalse(Schema::hasTable('users'));
|
$this->assertFalse(Schema::hasTable('users'));
|
||||||
tenancy()->init('test.localhost');
|
tenancy()->initialize(Tenant::create());
|
||||||
$this->assertFalse(Schema::hasTable('users'));
|
$this->assertFalse(Schema::hasTable('users'));
|
||||||
|
|
||||||
tenancy()->init('test2.localhost');
|
tenancy()->initialize($tenant);
|
||||||
$this->assertTrue(Schema::hasTable('users'));
|
$this->assertTrue(Schema::hasTable('users'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @test */
|
/** @test */
|
||||||
public function rollback_command_works()
|
public function rollback_command_works()
|
||||||
{
|
{
|
||||||
|
$tenant = Tenant::create();
|
||||||
Artisan::call('tenants:migrate');
|
Artisan::call('tenants:migrate');
|
||||||
$this->assertFalse(Schema::hasTable('users'));
|
$this->assertFalse(Schema::hasTable('users'));
|
||||||
tenancy()->init('test.localhost');
|
|
||||||
|
tenancy()->initialize($tenant);
|
||||||
|
|
||||||
$this->assertTrue(Schema::hasTable('users'));
|
$this->assertTrue(Schema::hasTable('users'));
|
||||||
Artisan::call('tenants:rollback');
|
Artisan::call('tenants:rollback');
|
||||||
$this->assertFalse(Schema::hasTable('users'));
|
$this->assertFalse(Schema::hasTable('users'));
|
||||||
|
|
@ -93,7 +123,7 @@ class CommandsTest extends TestCase
|
||||||
/** @test */
|
/** @test */
|
||||||
public function database_connection_is_switched_to_default_when_tenancy_has_been_initialized()
|
public function database_connection_is_switched_to_default_when_tenancy_has_been_initialized()
|
||||||
{
|
{
|
||||||
tenancy()->init('test.localhost');
|
tenancy()->initialize(Tenant::create());
|
||||||
|
|
||||||
$this->database_connection_is_switched_to_default();
|
$this->database_connection_is_switched_to_default();
|
||||||
}
|
}
|
||||||
|
|
@ -101,7 +131,7 @@ class CommandsTest extends TestCase
|
||||||
/** @test */
|
/** @test */
|
||||||
public function run_commands_works()
|
public function run_commands_works()
|
||||||
{
|
{
|
||||||
$id = Tenant::new()->withDomains(['run.localhost'])->save()['id'];
|
$id = Tenant::create()->id;
|
||||||
|
|
||||||
Artisan::call('tenants:migrate', ['--tenants' => [$id]]);
|
Artisan::call('tenants:migrate', ['--tenants' => [$id]]);
|
||||||
|
|
||||||
|
|
@ -121,34 +151,25 @@ class CommandsTest extends TestCase
|
||||||
mkdir($dir, 0777, true);
|
mkdir($dir, 0777, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (app()->version()[0] === '6') {
|
$this->artisan('tenancy:install');
|
||||||
file_put_contents(app_path('Http/Kernel.php'), file_get_contents(__DIR__ . '/Etc/defaultHttpKernelv6.stub'));
|
|
||||||
} else {
|
|
||||||
file_put_contents(app_path('Http/Kernel.php'), file_get_contents(__DIR__ . '/Etc/defaultHttpKernelv7.stub'));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->artisan('tenancy:install')
|
|
||||||
->expectsQuestion('Do you wish to publish the migrations that create these tables?', 'yes');
|
|
||||||
$this->assertFileExists(base_path('routes/tenant.php'));
|
$this->assertFileExists(base_path('routes/tenant.php'));
|
||||||
$this->assertFileExists(base_path('config/tenancy.php'));
|
$this->assertFileExists(base_path('config/tenancy.php'));
|
||||||
$this->assertFileExists(database_path('migrations/2019_09_15_000010_create_tenants_table.php'));
|
$this->assertFileExists(database_path('migrations/2019_09_15_000010_create_tenants_table.php'));
|
||||||
$this->assertFileExists(database_path('migrations/2019_09_15_000020_create_domains_table.php'));
|
$this->assertFileExists(database_path('migrations/2019_09_15_000020_create_domains_table.php'));
|
||||||
$this->assertDirectoryExists(database_path('migrations/tenant'));
|
$this->assertDirectoryExists(database_path('migrations/tenant'));
|
||||||
|
|
||||||
if (app()->version()[0] === '6') {
|
|
||||||
$this->assertSame(file_get_contents(__DIR__ . '/Etc/modifiedHttpKernelv6.stub'), file_get_contents(app_path('Http/Kernel.php')));
|
|
||||||
} else {
|
|
||||||
$this->assertSame(file_get_contents(__DIR__ . '/Etc/modifiedHttpKernelv7.stub'), file_get_contents(app_path('Http/Kernel.php')));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @test */
|
/** @test */
|
||||||
public function migrate_fresh_command_works()
|
public function migrate_fresh_command_works()
|
||||||
{
|
{
|
||||||
|
$tenant = Tenant::create();
|
||||||
|
|
||||||
$this->assertFalse(Schema::hasTable('users'));
|
$this->assertFalse(Schema::hasTable('users'));
|
||||||
Artisan::call('tenants:migrate-fresh');
|
Artisan::call('tenants:migrate-fresh');
|
||||||
$this->assertFalse(Schema::hasTable('users'));
|
$this->assertFalse(Schema::hasTable('users'));
|
||||||
tenancy()->init('test.localhost');
|
|
||||||
|
tenancy()->initialize($tenant);
|
||||||
|
|
||||||
$this->assertTrue(Schema::hasTable('users'));
|
$this->assertTrue(Schema::hasTable('users'));
|
||||||
|
|
||||||
$this->assertFalse(DB::table('users')->exists());
|
$this->assertFalse(DB::table('users')->exists());
|
||||||
|
|
@ -159,17 +180,4 @@ class CommandsTest extends TestCase
|
||||||
Artisan::call('tenants:migrate-fresh');
|
Artisan::call('tenants:migrate-fresh');
|
||||||
$this->assertFalse(DB::table('users')->exists());
|
$this->assertFalse(DB::table('users')->exists());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function create_command_works()
|
|
||||||
{
|
|
||||||
Artisan::call('tenants:create -d aaa.localhost -d bbb.localhost plan=free email=foo@test.local');
|
|
||||||
$tenant = tenancy()->all()[1]; // a tenant is autocreated prior to this
|
|
||||||
$data = $tenant->data;
|
|
||||||
unset($data['id']);
|
|
||||||
unset($data['_tenancy_db_name']);
|
|
||||||
|
|
||||||
$this->assertSame(['plan' => 'free', 'email' => 'foo@test.local'], $data);
|
|
||||||
$this->assertSame(['aaa.localhost', 'bbb.localhost'], $tenant->domains);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,154 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Tests;
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Cache;
|
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
use Illuminate\Support\Facades\Redis;
|
|
||||||
use Illuminate\Support\Facades\Storage;
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Stancl\Tenancy\Tenant;
|
|
||||||
use Stancl\Tenancy\Tests\Etc\User;
|
|
||||||
|
|
||||||
class DataSeparationTest extends TestCase
|
|
||||||
{
|
|
||||||
public $autoCreateTenant = false;
|
|
||||||
public $autoInitTenancy = false;
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function databases_are_separated()
|
|
||||||
{
|
|
||||||
$tenant1 = Tenant::create('tenant1.localhost');
|
|
||||||
$tenant2 = Tenant::create('tenant2.localhost');
|
|
||||||
\Artisan::call('tenants:migrate', [
|
|
||||||
'--tenants' => [$tenant1['id'], $tenant2['id']],
|
|
||||||
]);
|
|
||||||
|
|
||||||
tenancy()->init('tenant1.localhost');
|
|
||||||
User::create([
|
|
||||||
'name' => 'foo',
|
|
||||||
'email' => 'foo@bar.com',
|
|
||||||
'email_verified_at' => now(),
|
|
||||||
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
|
|
||||||
'remember_token' => Str::random(10),
|
|
||||||
]);
|
|
||||||
$this->assertSame('foo', User::first()->name);
|
|
||||||
|
|
||||||
tenancy()->init('tenant2.localhost');
|
|
||||||
$this->assertSame(null, User::first());
|
|
||||||
|
|
||||||
User::create([
|
|
||||||
'name' => 'xyz',
|
|
||||||
'email' => 'xyz@bar.com',
|
|
||||||
'email_verified_at' => now(),
|
|
||||||
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
|
|
||||||
'remember_token' => Str::random(10),
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->assertSame('xyz', User::first()->name);
|
|
||||||
$this->assertSame('xyz@bar.com', User::first()->email);
|
|
||||||
|
|
||||||
tenancy()->init('tenant1.localhost');
|
|
||||||
$this->assertSame('foo', User::first()->name);
|
|
||||||
$this->assertSame('foo@bar.com', User::first()->email);
|
|
||||||
|
|
||||||
$tenant3 = Tenant::create('tenant3.localhost');
|
|
||||||
\Artisan::call('tenants:migrate', [
|
|
||||||
'--tenants' => [$tenant1['id'], $tenant3['id']],
|
|
||||||
]);
|
|
||||||
|
|
||||||
tenancy()->init('tenant3.localhost');
|
|
||||||
$this->assertSame(null, User::first());
|
|
||||||
|
|
||||||
tenancy()->init('tenant1.localhost');
|
|
||||||
DB::table('users')->where('id', 1)->update(['name' => 'xxx']);
|
|
||||||
$this->assertSame('xxx', User::first()->name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function redis_is_separated()
|
|
||||||
{
|
|
||||||
if (! config('tenancy.redis.tenancy')) {
|
|
||||||
$this->markTestSkipped('Redis tenancy disabled.');
|
|
||||||
}
|
|
||||||
|
|
||||||
Tenant::create('tenant1.localhost');
|
|
||||||
Tenant::create('tenant2.localhost');
|
|
||||||
|
|
||||||
tenancy()->init('tenant1.localhost');
|
|
||||||
Redis::set('foo', 'bar');
|
|
||||||
$this->assertSame('bar', Redis::get('foo'));
|
|
||||||
|
|
||||||
tenancy()->init('tenant2.localhost');
|
|
||||||
$this->assertSame(null, Redis::get('foo'));
|
|
||||||
Redis::set('foo', 'xyz');
|
|
||||||
Redis::set('abc', 'def');
|
|
||||||
$this->assertSame('xyz', Redis::get('foo'));
|
|
||||||
$this->assertSame('def', Redis::get('abc'));
|
|
||||||
|
|
||||||
tenancy()->init('tenant1.localhost');
|
|
||||||
$this->assertSame('bar', Redis::get('foo'));
|
|
||||||
$this->assertSame(null, Redis::get('abc'));
|
|
||||||
|
|
||||||
Tenant::create('tenant3.localhost');
|
|
||||||
tenancy()->init('tenant3.localhost');
|
|
||||||
$this->assertSame(null, Redis::get('foo'));
|
|
||||||
$this->assertSame(null, Redis::get('abc'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function cache_is_separated()
|
|
||||||
{
|
|
||||||
Tenant::create('tenant1.localhost');
|
|
||||||
Tenant::create('tenant2.localhost');
|
|
||||||
|
|
||||||
tenancy()->init('tenant1.localhost');
|
|
||||||
Cache::put('foo', 'bar', 60);
|
|
||||||
$this->assertSame('bar', Cache::get('foo'));
|
|
||||||
|
|
||||||
tenancy()->init('tenant2.localhost');
|
|
||||||
$this->assertSame(null, Cache::get('foo'));
|
|
||||||
Cache::put('foo', 'xyz', 60);
|
|
||||||
Cache::put('abc', 'def', 60);
|
|
||||||
$this->assertSame('xyz', Cache::get('foo'));
|
|
||||||
$this->assertSame('def', Cache::get('abc'));
|
|
||||||
|
|
||||||
tenancy()->init('tenant1.localhost');
|
|
||||||
$this->assertSame('bar', Cache::get('foo'));
|
|
||||||
$this->assertSame(null, Cache::get('abc'));
|
|
||||||
|
|
||||||
Tenant::create('tenant3.localhost');
|
|
||||||
tenancy()->init('tenant3.localhost');
|
|
||||||
$this->assertSame(null, Cache::get('foo'));
|
|
||||||
$this->assertSame(null, Cache::get('abc'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function filesystem_is_separated()
|
|
||||||
{
|
|
||||||
Tenant::create('tenant1.localhost');
|
|
||||||
Tenant::create('tenant2.localhost');
|
|
||||||
|
|
||||||
tenancy()->init('tenant1.localhost');
|
|
||||||
Storage::disk('public')->put('foo', 'bar');
|
|
||||||
$this->assertSame('bar', Storage::disk('public')->get('foo'));
|
|
||||||
|
|
||||||
tenancy()->init('tenant2.localhost');
|
|
||||||
$this->assertFalse(Storage::disk('public')->exists('foo'));
|
|
||||||
Storage::disk('public')->put('foo', 'xyz');
|
|
||||||
Storage::disk('public')->put('abc', 'def');
|
|
||||||
$this->assertSame('xyz', Storage::disk('public')->get('foo'));
|
|
||||||
$this->assertSame('def', Storage::disk('public')->get('abc'));
|
|
||||||
|
|
||||||
tenancy()->init('tenant1.localhost');
|
|
||||||
$this->assertSame('bar', Storage::disk('public')->get('foo'));
|
|
||||||
$this->assertFalse(Storage::disk('public')->exists('abc'));
|
|
||||||
|
|
||||||
Tenant::create('tenant3.localhost');
|
|
||||||
tenancy()->init('tenant3.localhost');
|
|
||||||
$this->assertFalse(Storage::disk('public')->exists('foo'));
|
|
||||||
$this->assertFalse(Storage::disk('public')->exists('abc'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Tests;
|
|
||||||
|
|
||||||
use Stancl\Tenancy\DatabaseManager;
|
|
||||||
use Stancl\Tenancy\Tenant;
|
|
||||||
|
|
||||||
class DatabaseManagerTest extends TestCase
|
|
||||||
{
|
|
||||||
/** @test */
|
|
||||||
public function reconnect_method_works()
|
|
||||||
{
|
|
||||||
$old_connection_name = app(\Illuminate\Database\DatabaseManager::class)->connection()->getName();
|
|
||||||
$this->createTenant();
|
|
||||||
$this->initTenancy();
|
|
||||||
app(\Stancl\Tenancy\DatabaseManager::class)->reconnect();
|
|
||||||
$new_connection_name = app(\Illuminate\Database\DatabaseManager::class)->connection()->getName();
|
|
||||||
|
|
||||||
$this->assertSame($old_connection_name, $new_connection_name);
|
|
||||||
$this->assertNotEquals('tenant', $new_connection_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function db_name_is_prefixed_with_db_path_when_sqlite_is_used()
|
|
||||||
{
|
|
||||||
if (file_exists(database_path('foodb'))) {
|
|
||||||
unlink(database_path('foodb')); // cleanup
|
|
||||||
}
|
|
||||||
config(['database.connections.fooconn.driver' => 'sqlite']);
|
|
||||||
$tenant = Tenant::new()->withData([
|
|
||||||
'_tenancy_db_name' => 'foodb',
|
|
||||||
'_tenancy_db_connection' => 'fooconn',
|
|
||||||
])->save();
|
|
||||||
app(DatabaseManager::class)->createTenantConnection($tenant);
|
|
||||||
|
|
||||||
$this->assertSame(config('database.connections.tenant.database'), database_path('foodb'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function the_default_db_is_used_when_template_connection_is_null()
|
|
||||||
{
|
|
||||||
$this->assertSame('central', config('database.default'));
|
|
||||||
config([
|
|
||||||
'database.connections.central.foo' => 'bar',
|
|
||||||
'tenancy.database.template_connection' => null,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->createTenant();
|
|
||||||
$this->initTenancy();
|
|
||||||
|
|
||||||
$this->assertSame('tenant', config('database.default'));
|
|
||||||
$this->assertSame('bar', config('database.connections.' . config('database.default') . '.foo'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function ending_tenancy_doesnt_purge_the_central_connection()
|
|
||||||
{
|
|
||||||
$this->markTestIncomplete('Seems like this only happens on MySQL?');
|
|
||||||
|
|
||||||
// regression test for https://github.com/stancl/tenancy/pull/189
|
|
||||||
// config(['tenancy.migrate_after_creation' => true]);
|
|
||||||
|
|
||||||
tenancy()->create(['foo.localhost']);
|
|
||||||
tenancy()->init('foo.localhost');
|
|
||||||
tenancy()->end();
|
|
||||||
|
|
||||||
$this->assertNotEmpty(tenancy()->all());
|
|
||||||
|
|
||||||
tenancy()->all()->each->delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
135
tests/DatabasePreparationTest.php
Normal file
135
tests/DatabasePreparationTest.php
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Stancl\Tenancy\Tests\v3;
|
||||||
|
|
||||||
|
use Illuminate\Database\Seeder;
|
||||||
|
use Illuminate\Foundation\Auth\User as Authenticable;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Event;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Stancl\Tenancy\Database\Models\Tenant;
|
||||||
|
use Stancl\Tenancy\Events\Listeners\JobPipeline;
|
||||||
|
use Stancl\Tenancy\Events\TenantCreated;
|
||||||
|
use Stancl\Tenancy\Jobs\CreateDatabase;
|
||||||
|
use Stancl\Tenancy\Jobs\MigrateDatabase;
|
||||||
|
use Stancl\Tenancy\Jobs\SeedDatabase;
|
||||||
|
use Stancl\Tenancy\TenantDatabaseManagers\MySQLDatabaseManager;
|
||||||
|
use Stancl\Tenancy\Tests\TestCase;
|
||||||
|
|
||||||
|
class DatabasePreparationTest extends TestCase
|
||||||
|
{
|
||||||
|
/** @test */
|
||||||
|
public function database_can_be_created_after_tenant_creation()
|
||||||
|
{
|
||||||
|
config(['tenancy.template_tenant_connection' => 'mysql']);
|
||||||
|
|
||||||
|
Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
|
||||||
|
return $event->tenant;
|
||||||
|
})->toListener());
|
||||||
|
|
||||||
|
$tenant = Tenant::create();
|
||||||
|
|
||||||
|
$this->assertTrue(app(MySQLDatabaseManager::class)->databaseExists($tenant->database()->getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function database_can_be_migrated_after_tenant_creation()
|
||||||
|
{
|
||||||
|
Event::listen(TenantCreated::class, JobPipeline::make([
|
||||||
|
CreateDatabase::class,
|
||||||
|
MigrateDatabase::class,
|
||||||
|
])->send(function (TenantCreated $event) {
|
||||||
|
return $event->tenant;
|
||||||
|
})->toListener());
|
||||||
|
|
||||||
|
$tenant = Tenant::create();
|
||||||
|
|
||||||
|
$tenant->run(function () {
|
||||||
|
$this->assertTrue(Schema::hasTable('users'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function database_can_be_seeded_after_tenant_creation()
|
||||||
|
{
|
||||||
|
config(['tenancy.seeder_parameters' => [
|
||||||
|
'--class' => TestSeeder::class,
|
||||||
|
]]);
|
||||||
|
|
||||||
|
Event::listen(TenantCreated::class, JobPipeline::make([
|
||||||
|
CreateDatabase::class,
|
||||||
|
MigrateDatabase::class,
|
||||||
|
SeedDatabase::class,
|
||||||
|
])->send(function (TenantCreated $event) {
|
||||||
|
return $event->tenant;
|
||||||
|
})->toListener());
|
||||||
|
|
||||||
|
$tenant = Tenant::create();
|
||||||
|
|
||||||
|
$tenant->run(function () {
|
||||||
|
$this->assertSame('Seeded User', User::first()->name);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function custom_job_can_be_added_to_the_pipeline()
|
||||||
|
{
|
||||||
|
config(['tenancy.seeder_parameters' => [
|
||||||
|
'--class' => TestSeeder::class,
|
||||||
|
]]);
|
||||||
|
|
||||||
|
Event::listen(TenantCreated::class, JobPipeline::make([
|
||||||
|
CreateDatabase::class,
|
||||||
|
MigrateDatabase::class,
|
||||||
|
SeedDatabase::class,
|
||||||
|
CreateSuperuser::class,
|
||||||
|
])->send(function (TenantCreated $event) {
|
||||||
|
return $event->tenant;
|
||||||
|
})->toListener());
|
||||||
|
|
||||||
|
$tenant = Tenant::create();
|
||||||
|
|
||||||
|
$tenant->run(function () {
|
||||||
|
$this->assertSame('Foo', User::all()[1]->name);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class User extends Authenticable
|
||||||
|
{
|
||||||
|
protected $guarded = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestSeeder extends Seeder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the database seeds.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function run()
|
||||||
|
{
|
||||||
|
DB::table('users')->insert([
|
||||||
|
'name' => 'Seeded User',
|
||||||
|
'email' => 'seeded@user',
|
||||||
|
'password' => bcrypt('password'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CreateSuperuser
|
||||||
|
{
|
||||||
|
protected $tenant;
|
||||||
|
|
||||||
|
public function __construct(Tenant $tenant)
|
||||||
|
{
|
||||||
|
$this->tenant = $tenant;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$this->tenant->run(function () {
|
||||||
|
User::create(['name' => 'Foo', 'email' => 'foo@bar.com', 'password' => 'secret']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,144 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Tests;
|
|
||||||
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Stancl\Tenancy\Tenant;
|
|
||||||
use Stancl\Tenancy\Tests\Etc\User;
|
|
||||||
|
|
||||||
class DatabaseSchemaManagerTest extends TestCase
|
|
||||||
{
|
|
||||||
public $autoCreateTenant = true;
|
|
||||||
public $autoInitTenancy = false;
|
|
||||||
|
|
||||||
protected function getEnvironmentSetUp($app)
|
|
||||||
{
|
|
||||||
parent::getEnvironmentSetUp($app);
|
|
||||||
|
|
||||||
$app['config']->set([
|
|
||||||
'database.default' => 'pgsql',
|
|
||||||
'tenancy.storage_drivers.db.connection' => 'pgsql',
|
|
||||||
'database.connections.pgsql.database' => 'main',
|
|
||||||
'database.connections.pgsql.schema' => 'public',
|
|
||||||
'tenancy.database.template_connection' => null,
|
|
||||||
'tenancy.database.suffix' => '',
|
|
||||||
'tenancy.database_managers.pgsql' => \Stancl\Tenancy\TenantDatabaseManagers\PostgreSQLSchemaManager::class,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function reconnect_method_works()
|
|
||||||
{
|
|
||||||
$old_connection_name = app(\Illuminate\Database\DatabaseManager::class)->connection()->getName();
|
|
||||||
|
|
||||||
tenancy()->init('test.localhost');
|
|
||||||
|
|
||||||
app(\Stancl\Tenancy\DatabaseManager::class)->reconnect();
|
|
||||||
|
|
||||||
$new_connection_name = app(\Illuminate\Database\DatabaseManager::class)->connection()->getName();
|
|
||||||
|
|
||||||
$this->assertSame($old_connection_name, $new_connection_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function the_default_db_is_used_when_template_connection_is_null()
|
|
||||||
{
|
|
||||||
$this->assertSame('pgsql', config('database.default'));
|
|
||||||
config([
|
|
||||||
'database.connections.pgsql.foo' => 'bar',
|
|
||||||
'tenancy.database.template_connection' => null,
|
|
||||||
]);
|
|
||||||
|
|
||||||
tenancy()->init('test.localhost');
|
|
||||||
|
|
||||||
$this->assertSame('tenant', config('database.default'));
|
|
||||||
$this->assertSame('bar', config('database.connections.tenant.foo'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function make_sure_using_schema_connection()
|
|
||||||
{
|
|
||||||
$tenant = tenancy()->create(['schema.localhost']);
|
|
||||||
tenancy()->init('schema.localhost');
|
|
||||||
|
|
||||||
$this->assertSame($tenant->database()->getName(), config('database.connections.' . config('database.default') . '.schema'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function databases_are_separated_using_schema_and_not_database()
|
|
||||||
{
|
|
||||||
tenancy()->create('foo.localhost');
|
|
||||||
tenancy()->init('foo.localhost');
|
|
||||||
$this->assertSame('tenant', config('database.default'));
|
|
||||||
$this->assertSame('main', config('database.connections.tenant.database'));
|
|
||||||
|
|
||||||
$schema1 = config('database.connections.' . config('database.default') . '.schema');
|
|
||||||
$database1 = config('database.connections.' . config('database.default') . '.database');
|
|
||||||
|
|
||||||
tenancy()->create('bar.localhost');
|
|
||||||
tenancy()->init('bar.localhost');
|
|
||||||
$this->assertSame('tenant', config('database.default'));
|
|
||||||
$this->assertSame('main', config('database.connections.tenant.database'));
|
|
||||||
|
|
||||||
$schema2 = config('database.connections.' . config('database.default') . '.schema');
|
|
||||||
$database2 = config('database.connections.' . config('database.default') . '.database');
|
|
||||||
|
|
||||||
$this->assertSame($database1, $database2);
|
|
||||||
$this->assertNotSame($schema1, $schema2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function schemas_are_separated()
|
|
||||||
{
|
|
||||||
// copied from DataSeparationTest
|
|
||||||
|
|
||||||
$tenant1 = Tenant::create('tenant1.localhost');
|
|
||||||
$tenant2 = Tenant::create('tenant2.localhost');
|
|
||||||
|
|
||||||
\Artisan::call('tenants:migrate', [
|
|
||||||
'--tenants' => [$tenant1['id'], $tenant2['id']],
|
|
||||||
]);
|
|
||||||
|
|
||||||
tenancy()->init('tenant1.localhost');
|
|
||||||
User::create([
|
|
||||||
'name' => 'foo',
|
|
||||||
'email' => 'foo@bar.com',
|
|
||||||
'email_verified_at' => now(),
|
|
||||||
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
|
|
||||||
'remember_token' => Str::random(10),
|
|
||||||
]);
|
|
||||||
$this->assertSame('foo', User::first()->name);
|
|
||||||
|
|
||||||
tenancy()->init('tenant2.localhost');
|
|
||||||
$this->assertSame(null, User::first());
|
|
||||||
|
|
||||||
User::create([
|
|
||||||
'name' => 'xyz',
|
|
||||||
'email' => 'xyz@bar.com',
|
|
||||||
'email_verified_at' => now(),
|
|
||||||
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
|
|
||||||
'remember_token' => Str::random(10),
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->assertSame('xyz', User::first()->name);
|
|
||||||
$this->assertSame('xyz@bar.com', User::first()->email);
|
|
||||||
|
|
||||||
tenancy()->init('tenant1.localhost');
|
|
||||||
$this->assertSame('foo', User::first()->name);
|
|
||||||
$this->assertSame('foo@bar.com', User::first()->email);
|
|
||||||
|
|
||||||
$tenant3 = Tenant::create('tenant3.localhost');
|
|
||||||
\Artisan::call('tenants:migrate', [
|
|
||||||
'--tenants' => [$tenant1['id'], $tenant3['id']],
|
|
||||||
]);
|
|
||||||
|
|
||||||
tenancy()->init('tenant3.localhost');
|
|
||||||
$this->assertSame(null, User::first());
|
|
||||||
|
|
||||||
tenancy()->init('tenant1.localhost');
|
|
||||||
\DB::table('users')->where('id', 1)->update(['name' => 'xxx']);
|
|
||||||
$this->assertSame('xxx', User::first()->name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,15 +2,20 @@
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Tests;
|
namespace Stancl\Tenancy\Tests\v3;
|
||||||
|
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Event;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Stancl\Tenancy\Contracts\ManagesDatabaseUsers;
|
use Stancl\Tenancy\Contracts\ManagesDatabaseUsers;
|
||||||
use Stancl\Tenancy\Exceptions\TenantDatabaseUserAlreadyExistsException;
|
use Stancl\Tenancy\Exceptions\TenantDatabaseUserAlreadyExistsException;
|
||||||
use Stancl\Tenancy\Tenant;
|
|
||||||
use Stancl\Tenancy\TenantDatabaseManagers\MySQLDatabaseManager;
|
use Stancl\Tenancy\TenantDatabaseManagers\MySQLDatabaseManager;
|
||||||
use Stancl\Tenancy\TenantDatabaseManagers\PermissionControlledMySQLDatabaseManager;
|
use Stancl\Tenancy\TenantDatabaseManagers\PermissionControlledMySQLDatabaseManager;
|
||||||
|
use Stancl\Tenancy\Database\Models\Tenant;
|
||||||
|
use Stancl\Tenancy\Events\Listeners\JobPipeline;
|
||||||
|
use Stancl\Tenancy\Events\TenantCreated;
|
||||||
|
use Stancl\Tenancy\Jobs\CreateDatabase;
|
||||||
|
use Stancl\Tenancy\Tests\TestCase;
|
||||||
|
|
||||||
class DatabaseUsersTest extends TestCase
|
class DatabaseUsersTest extends TestCase
|
||||||
{
|
{
|
||||||
|
|
@ -21,14 +26,18 @@ class DatabaseUsersTest extends TestCase
|
||||||
config([
|
config([
|
||||||
'tenancy.database_managers.mysql' => PermissionControlledMySQLDatabaseManager::class,
|
'tenancy.database_managers.mysql' => PermissionControlledMySQLDatabaseManager::class,
|
||||||
'tenancy.database.suffix' => '',
|
'tenancy.database.suffix' => '',
|
||||||
'tenancy.database.template_connection' => 'mysql',
|
'tenancy.template_tenant_connection' => 'mysql',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
|
||||||
|
return $event->tenant;
|
||||||
|
})->toListener());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @test */
|
/** @test */
|
||||||
public function users_are_created_when_permission_controlled_mysql_manager_is_used()
|
public function users_are_created_when_permission_controlled_mysql_manager_is_used()
|
||||||
{
|
{
|
||||||
$tenant = Tenant::new()->withData([
|
$tenant = new Tenant([
|
||||||
'id' => 'foo' . Str::random(10),
|
'id' => 'foo' . Str::random(10),
|
||||||
]);
|
]);
|
||||||
$tenant->database()->makeCredentials();
|
$tenant->database()->makeCredentials();
|
||||||
|
|
@ -46,9 +55,9 @@ class DatabaseUsersTest extends TestCase
|
||||||
public function a_tenants_database_cannot_be_created_when_the_user_already_exists()
|
public function a_tenants_database_cannot_be_created_when_the_user_already_exists()
|
||||||
{
|
{
|
||||||
$username = 'foo' . Str::random(8);
|
$username = 'foo' . Str::random(8);
|
||||||
$tenant = Tenant::new()->withData([
|
$tenant = Tenant::create([
|
||||||
'_tenancy_db_username' => $username,
|
'tenancy_db_username' => $username,
|
||||||
])->save();
|
]);
|
||||||
|
|
||||||
/** @var ManagesDatabaseUsers $manager */
|
/** @var ManagesDatabaseUsers $manager */
|
||||||
$manager = $tenant->database()->manager();
|
$manager = $tenant->database()->manager();
|
||||||
|
|
@ -56,9 +65,9 @@ class DatabaseUsersTest extends TestCase
|
||||||
$this->assertTrue($manager->databaseExists($tenant->database()->getName()));
|
$this->assertTrue($manager->databaseExists($tenant->database()->getName()));
|
||||||
|
|
||||||
$this->expectException(TenantDatabaseUserAlreadyExistsException::class);
|
$this->expectException(TenantDatabaseUserAlreadyExistsException::class);
|
||||||
$tenant2 = Tenant::new()->withData([
|
$tenant2 = Tenant::create([
|
||||||
'_tenancy_db_username' => $username,
|
'tenancy_db_username' => $username,
|
||||||
])->save();
|
]);
|
||||||
|
|
||||||
/** @var ManagesDatabaseUsers $manager */
|
/** @var ManagesDatabaseUsers $manager */
|
||||||
$manager = $tenant2->database()->manager();
|
$manager = $tenant2->database()->manager();
|
||||||
|
|
@ -73,9 +82,9 @@ class DatabaseUsersTest extends TestCase
|
||||||
'ALTER', 'ALTER ROUTINE', 'CREATE',
|
'ALTER', 'ALTER ROUTINE', 'CREATE',
|
||||||
];
|
];
|
||||||
|
|
||||||
$tenant = Tenant::new()->withData([
|
$tenant = Tenant::create([
|
||||||
'_tenancy_db_username' => $user = 'user' . Str::random(8),
|
'tenancy_db_username' => $user = 'user' . Str::random(8),
|
||||||
])->save();
|
]);
|
||||||
|
|
||||||
$query = DB::connection('mysql')->select("SHOW GRANTS FOR `{$tenant->database()->getUsername()}`@`{$tenant->database()->connection()['host']}`")[1];
|
$query = DB::connection('mysql')->select("SHOW GRANTS FOR `{$tenant->database()->getUsername()}`@`{$tenant->database()->connection()['host']}`")[1];
|
||||||
$this->assertStringStartsWith('GRANT CREATE, ALTER, ALTER ROUTINE ON', $query->{"Grants for {$user}@mysql"}); // @mysql because that's the hostname within the docker network
|
$this->assertStringStartsWith('GRANT CREATE, ALTER, ALTER ROUTINE ON', $query->{"Grants for {$user}@mysql"}); // @mysql because that's the hostname within the docker network
|
||||||
|
|
@ -90,15 +99,15 @@ class DatabaseUsersTest extends TestCase
|
||||||
'tenancy.database.template_connection' => 'mysql',
|
'tenancy.database.template_connection' => 'mysql',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$tenant = Tenant::new()->withData([
|
$tenant = Tenant::create([
|
||||||
'id' => 'foo' . Str::random(10),
|
'id' => 'foo' . Str::random(10),
|
||||||
])->save();
|
]);
|
||||||
|
|
||||||
$this->assertTrue($tenant->database()->manager() instanceof MySQLDatabaseManager);
|
$this->assertTrue($tenant->database()->manager() instanceof MySQLDatabaseManager);
|
||||||
|
|
||||||
$tenant = Tenant::new()->withData([
|
$tenant = Tenant::create([
|
||||||
'id' => 'foo' . Str::random(10),
|
'id' => 'foo' . Str::random(10),
|
||||||
])->save();
|
]);
|
||||||
|
|
||||||
tenancy()->initialize($tenant); // check if everything works
|
tenancy()->initialize($tenant); // check if everything works
|
||||||
tenancy()->end();
|
tenancy()->end();
|
||||||
|
|
|
||||||
|
|
@ -39,10 +39,6 @@ class HttpKernel extends Kernel
|
||||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||||
],
|
],
|
||||||
|
|
||||||
'tenancy' => [
|
|
||||||
\Stancl\Tenancy\Middleware\InitializeTenancy::class,
|
|
||||||
],
|
|
||||||
|
|
||||||
'api' => [
|
'api' => [
|
||||||
'throttle:60,1',
|
'throttle:60,1',
|
||||||
'bindings',
|
'bindings',
|
||||||
|
|
@ -66,18 +62,5 @@ class HttpKernel extends Kernel
|
||||||
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
|
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
|
||||||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
||||||
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
||||||
|
|
||||||
'tenancy' => \Stancl\Tenancy\Middleware\InitializeTenancy::class,
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The priority-sorted list of middleware.
|
|
||||||
*
|
|
||||||
* This forces non-global middleware to always be in the given order.
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $middlewarePriority = [
|
|
||||||
\Stancl\Tenancy\Middleware\InitializeTenancy::class,
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Tests;
|
|
||||||
|
|
||||||
use Tenancy;
|
|
||||||
use Tenant;
|
|
||||||
|
|
||||||
class FacadeTest extends TestCase
|
|
||||||
{
|
|
||||||
public $autoCreateTenant = true;
|
|
||||||
public $autoInitTenancy = true;
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function tenant_manager_can_be_accessed_using_the_Tenancy_facade()
|
|
||||||
{
|
|
||||||
$this->assertSame(tenancy()->getTenant(), Tenancy::getTenant());
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function tenant_storage_can_be_accessed_using_the_Tenant_facade()
|
|
||||||
{
|
|
||||||
tenant()->put('foo', 'bar');
|
|
||||||
Tenant::put('abc', 'xyz');
|
|
||||||
|
|
||||||
$this->assertSame('bar', Tenant::get('foo'));
|
|
||||||
$this->assertSame('xyz', Tenant::get('abc'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function tenant_can_be_created_using_the_Tenant_facade()
|
|
||||||
{
|
|
||||||
$this->assertSame('bar', Tenant::create(['foo.localhost'], ['foo' => 'bar'])->foo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,17 +2,15 @@
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Tests;
|
namespace Stancl\Tenancy\Tests\Features;
|
||||||
|
|
||||||
use Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
use Stancl\Tenancy\Features\CrossDomainRedirect;
|
use Stancl\Tenancy\Features\CrossDomainRedirect;
|
||||||
use Stancl\Tenancy\Tenant;
|
use Stancl\Tenancy\Database\Models\Tenant;
|
||||||
|
use Stancl\Tenancy\Tests\TestCase;
|
||||||
|
|
||||||
class RedirectTest extends TestCase
|
class RedirectTest extends TestCase
|
||||||
{
|
{
|
||||||
public $autoCreateTenant = false;
|
|
||||||
public $autoInitTenancy = false;
|
|
||||||
|
|
||||||
/** @test */
|
/** @test */
|
||||||
public function tenant_redirect_macro_replaces_only_the_hostname()
|
public function tenant_redirect_macro_replaces_only_the_hostname()
|
||||||
{
|
{
|
||||||
|
|
@ -28,8 +26,8 @@ class RedirectTest extends TestCase
|
||||||
return redirect()->route('home')->domain('abcd');
|
return redirect()->route('home')->domain('abcd');
|
||||||
});
|
});
|
||||||
|
|
||||||
Tenant::create('foo.localhost');
|
$tenant = Tenant::create();
|
||||||
tenancy()->init('foo.localhost');
|
tenancy()->initialize($tenant);
|
||||||
|
|
||||||
$this->get('/redirect')
|
$this->get('/redirect')
|
||||||
->assertRedirect('http://abcd/foobar');
|
->assertRedirect('http://abcd/foobar');
|
||||||
|
|
@ -42,9 +40,7 @@ class RedirectTest extends TestCase
|
||||||
return 'Foo';
|
return 'Foo';
|
||||||
})->name('foo');
|
})->name('foo');
|
||||||
|
|
||||||
$this->assertSame('http://foo.localhost/abcdef/as/df', tenant_route('foo', ['a' => 'as', 'b' => 'df'], 'foo.localhost'));
|
$this->assertSame('http://foo.localhost/abcdef/as/df', tenant_route('foo.localhost', 'foo', ['a' => 'as', 'b' => 'df']));
|
||||||
$this->assertSame('http://foo.localhost/abcdef', tenant_route('foo', [], 'foo.localhost'));
|
$this->assertSame('http://foo.localhost/abcdef', tenant_route('foo.localhost', 'foo', []));
|
||||||
|
|
||||||
$this->assertSame('http://' . request()->getHost() . '/abcdef/x/y', tenant_route('foo', ['a' => 'x', 'b' => 'y']));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Tests;
|
|
||||||
|
|
||||||
use Stancl\Tenancy\Contracts\Future\CanFindByAnyKey;
|
|
||||||
use Stancl\Tenancy\Tenant;
|
|
||||||
|
|
||||||
class FutureTest extends TestCase
|
|
||||||
{
|
|
||||||
public $autoCreateTenant = false;
|
|
||||||
public $autoInitTenancy = false;
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function keys_can_be_deleted_from_tenant_storage()
|
|
||||||
{
|
|
||||||
$tenant = Tenant::new()->withData(['email' => 'foo@example.com', 'role' => 'admin'])->save();
|
|
||||||
|
|
||||||
$this->assertArrayHasKey('email', $tenant->data);
|
|
||||||
$tenant->deleteKey('email');
|
|
||||||
$this->assertArrayNotHasKey('email', $tenant->data);
|
|
||||||
$this->assertArrayNotHasKey('email', tenancy()->all()->first()->data);
|
|
||||||
|
|
||||||
$tenant->put(['foo' => 'bar', 'abc' => 'xyz']);
|
|
||||||
$this->assertArrayHasKey('foo', $tenant->data);
|
|
||||||
$this->assertArrayHasKey('abc', $tenant->data);
|
|
||||||
|
|
||||||
$tenant->deleteKeys(['foo', 'abc']);
|
|
||||||
$this->assertArrayNotHasKey('foo', $tenant->data);
|
|
||||||
$this->assertArrayNotHasKey('abc', $tenant->data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function tenant_can_be_identified_using_an_arbitrary_string()
|
|
||||||
{
|
|
||||||
if (! tenancy()->storage instanceof CanFindByAnyKey) {
|
|
||||||
$this->markTestSkipped(get_class(tenancy()->storage) . ' does not implement the CanFindByAnyKey interface.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$tenant = Tenant::new()->withData(['email' => 'foo@example.com'])->save();
|
|
||||||
|
|
||||||
$this->assertSame($tenant->id, tenancy()->findByEmail('foo@example.com')->id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,15 +2,28 @@
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Tests;
|
namespace Stancl\Tenancy\Tests\v3;
|
||||||
|
|
||||||
use GlobalCache;
|
use Illuminate\Support\Facades\Event;
|
||||||
use Stancl\Tenancy\Tenant;
|
use Stancl\Tenancy\Facades\GlobalCache;
|
||||||
|
use Stancl\Tenancy\Database\Models\Tenant;
|
||||||
|
use Stancl\Tenancy\Events\Listeners\BootstrapTenancy;
|
||||||
|
use Stancl\Tenancy\Events\TenancyInitialized;
|
||||||
|
use Stancl\Tenancy\TenancyBootstrappers\CacheTenancyBootstrapper;
|
||||||
|
use Stancl\Tenancy\Tests\TestCase;
|
||||||
|
|
||||||
class GlobalCacheTest extends TestCase
|
class GlobalCacheTest extends TestCase
|
||||||
{
|
{
|
||||||
public $autoCreateTenant = false;
|
public function setUp(): void
|
||||||
public $autoInitTenancy = false;
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
config(['tenancy.bootstrappers' => [
|
||||||
|
CacheTenancyBootstrapper::class,
|
||||||
|
]]);
|
||||||
|
|
||||||
|
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
|
||||||
|
}
|
||||||
|
|
||||||
/** @test */
|
/** @test */
|
||||||
public function global_cache_manager_stores_data_in_global_cache()
|
public function global_cache_manager_stores_data_in_global_cache()
|
||||||
|
|
@ -19,28 +32,28 @@ class GlobalCacheTest extends TestCase
|
||||||
GlobalCache::put(['foo' => 'bar'], 1);
|
GlobalCache::put(['foo' => 'bar'], 1);
|
||||||
$this->assertSame('bar', GlobalCache::get('foo'));
|
$this->assertSame('bar', GlobalCache::get('foo'));
|
||||||
|
|
||||||
Tenant::new()->withDomains(['foo.localhost'])->save();
|
$tenant1 = Tenant::create();
|
||||||
tenancy()->init('foo.localhost');
|
tenancy()->initialize($tenant1);
|
||||||
$this->assertSame('bar', GlobalCache::get('foo'));
|
$this->assertSame('bar', GlobalCache::get('foo'));
|
||||||
|
|
||||||
GlobalCache::put(['abc' => 'xyz'], 1);
|
GlobalCache::put(['abc' => 'xyz'], 1);
|
||||||
cache(['def' => 'ghi'], 10);
|
cache(['def' => 'ghi'], 10);
|
||||||
$this->assertSame('ghi', cache('def'));
|
$this->assertSame('ghi', cache('def'));
|
||||||
|
|
||||||
tenancy()->endTenancy();
|
tenancy()->end();
|
||||||
$this->assertSame('xyz', GlobalCache::get('abc'));
|
$this->assertSame('xyz', GlobalCache::get('abc'));
|
||||||
$this->assertSame('bar', GlobalCache::get('foo'));
|
$this->assertSame('bar', GlobalCache::get('foo'));
|
||||||
$this->assertSame(null, cache('def'));
|
$this->assertSame(null, cache('def'));
|
||||||
|
|
||||||
Tenant::new()->withDomains(['bar.localhost'])->save();
|
$tenant2 = Tenant::create();
|
||||||
tenancy()->init('bar.localhost');
|
tenancy()->initialize($tenant2);
|
||||||
$this->assertSame('xyz', GlobalCache::get('abc'));
|
$this->assertSame('xyz', GlobalCache::get('abc'));
|
||||||
$this->assertSame('bar', GlobalCache::get('foo'));
|
$this->assertSame('bar', GlobalCache::get('foo'));
|
||||||
$this->assertSame(null, cache('def'));
|
$this->assertSame(null, cache('def'));
|
||||||
cache(['def' => 'xxx'], 1);
|
cache(['def' => 'xxx'], 1);
|
||||||
$this->assertSame('xxx', cache('def'));
|
$this->assertSame('xxx', cache('def'));
|
||||||
|
|
||||||
tenancy()->init('foo.localhost');
|
tenancy()->initialize($tenant1);
|
||||||
$this->assertSame('ghi', cache('def'));
|
$this->assertSame('ghi', cache('def'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
namespace Stancl\Tenancy\Tests\v3;
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Tests;
|
|
||||||
|
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
|
@ -11,26 +9,46 @@ use Illuminate\Queue\Events\JobProcessing;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Facades\Event;
|
use Illuminate\Support\Facades\Event;
|
||||||
|
use Spatie\Valuestore\Valuestore;
|
||||||
|
use Stancl\Tenancy\Database\Models\Tenant;
|
||||||
|
use Stancl\Tenancy\Events\Listeners\BootstrapTenancy;
|
||||||
|
use Stancl\Tenancy\Events\TenancyInitialized;
|
||||||
|
use Stancl\Tenancy\TenancyBootstrappers\QueueTenancyBootstrapper;
|
||||||
|
use Stancl\Tenancy\Tests\TestCase;
|
||||||
|
|
||||||
class QueueTest extends TestCase
|
class QueueTest extends TestCase
|
||||||
{
|
{
|
||||||
public $autoCreateTenant = true;
|
public $mockConsoleOutput = false;
|
||||||
public $autoInitTenancy = true;
|
|
||||||
|
|
||||||
/** @test */
|
/** @var Valuestore */
|
||||||
public function queues_use_non_tenant_db_connection()
|
protected $valuestore;
|
||||||
|
|
||||||
|
public function setUp(): void
|
||||||
{
|
{
|
||||||
// requires using the db driver
|
parent::setUp();
|
||||||
$this->markTestIncomplete();
|
|
||||||
|
config([
|
||||||
|
'tenancy.bootstrappers' => [
|
||||||
|
QueueTenancyBootstrapper::class,
|
||||||
|
],
|
||||||
|
'queue.default' => 'redis',
|
||||||
|
]);
|
||||||
|
|
||||||
|
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
|
||||||
|
|
||||||
|
$this->valuestore = Valuestore::make(__DIR__ . '/../Etc/tmp/queuetest.json')->flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @test */
|
/** @test */
|
||||||
public function tenancy_is_initialized_inside_queues()
|
public function tenant_id_is_passed_to_tenant_queues()
|
||||||
{
|
{
|
||||||
$this->loadLaravelMigrations(['--database' => 'tenant']);
|
$tenant = Tenant::create();
|
||||||
|
|
||||||
|
tenancy()->initialize($tenant);
|
||||||
|
|
||||||
Event::fake();
|
Event::fake();
|
||||||
|
|
||||||
dispatch(new TestJob());
|
dispatch(new TestJob($this->valuestore));
|
||||||
|
|
||||||
Event::assertDispatched(JobProcessing::class, function ($event) {
|
Event::assertDispatched(JobProcessing::class, function ($event) {
|
||||||
return $event->job->payload()['tenant_id'] === tenant('id');
|
return $event->job->payload()['tenant_id'] === tenant('id');
|
||||||
|
|
@ -38,40 +56,81 @@ class QueueTest extends TestCase
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @test */
|
/** @test */
|
||||||
public function tenancy_is_not_initialized_in_non_tenant_queues()
|
public function tenant_id_is_not_passed_to_central_queues()
|
||||||
{
|
{
|
||||||
$this->loadLaravelMigrations(['--database' => 'tenant']);
|
$tenant = Tenant::create();
|
||||||
|
|
||||||
|
tenancy()->initialize($tenant);
|
||||||
|
|
||||||
Event::fake();
|
Event::fake();
|
||||||
|
|
||||||
dispatch(new TestJob())->onConnection('central');
|
config(['queue.connections.central' => [
|
||||||
|
'driver' => 'sync',
|
||||||
|
'central' => true,
|
||||||
|
]]);
|
||||||
|
|
||||||
|
dispatch(new TestJob($this->valuestore))->onConnection('central');
|
||||||
|
|
||||||
Event::assertDispatched(JobProcessing::class, function ($event) {
|
Event::assertDispatched(JobProcessing::class, function ($event) {
|
||||||
return ! isset($event->job->payload()['tenant_id']);
|
return ! isset($event->job->payload()['tenant_id']);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function tenancy_is_initialized_inside_queues()
|
||||||
|
{
|
||||||
|
$tenant = Tenant::create([
|
||||||
|
'id' => 'acme',
|
||||||
|
]);
|
||||||
|
|
||||||
|
tenancy()->initialize($tenant);
|
||||||
|
|
||||||
|
dispatch(new TestJob($this->valuestore));
|
||||||
|
|
||||||
|
$this->assertFalse($this->valuestore->has('tenant_id'));
|
||||||
|
$this->artisan('queue:work --once');
|
||||||
|
|
||||||
|
$this->assertSame('The current tenant id is: acme', $this->valuestore->get('tenant_id'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function the_tenant_used_by_the_job_doesnt_change_when_the_current_tenant_changes()
|
||||||
|
{
|
||||||
|
$tenant1 = Tenant::create([
|
||||||
|
'id' => 'acme',
|
||||||
|
]);
|
||||||
|
|
||||||
|
tenancy()->initialize($tenant1);
|
||||||
|
|
||||||
|
dispatch(new TestJob($this->valuestore));
|
||||||
|
|
||||||
|
$tenant2 = Tenant::create([
|
||||||
|
'id' => 'foobar',
|
||||||
|
]);
|
||||||
|
|
||||||
|
tenancy()->initialize($tenant2);
|
||||||
|
|
||||||
|
$this->assertFalse($this->valuestore->has('tenant_id'));
|
||||||
|
$this->artisan('queue:work --once');
|
||||||
|
|
||||||
|
$this->assertSame('The current tenant id is: acme', $this->valuestore->get('tenant_id'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestJob implements ShouldQueue
|
class TestJob implements ShouldQueue
|
||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
/**
|
/** @var Valuestore */
|
||||||
* Create a new job instance.
|
protected $valuestore;
|
||||||
*
|
|
||||||
* @return void
|
public function __construct(Valuestore $valuestore)
|
||||||
*/
|
|
||||||
public function __construct()
|
|
||||||
{
|
{
|
||||||
//
|
$this->valuestore = $valuestore;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute the job.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
logger(json_encode(\DB::table('users')->get()));
|
$this->valuestore->put('tenant_id', "The current tenant id is: " . tenant('id'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Tests;
|
|
||||||
|
|
||||||
use Stancl\Tenancy\Tenant;
|
|
||||||
|
|
||||||
class ReidentificationTest extends TestCase
|
|
||||||
{
|
|
||||||
public $autoCreateTenant = true;
|
|
||||||
public $autoInitTenancy = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* These tests are run when a tenant is identified after another tenant has already been identified.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function storage_facade_roots_are_correct()
|
|
||||||
{
|
|
||||||
$originals = [];
|
|
||||||
|
|
||||||
foreach (config('tenancy.filesystem.disks') as $disk) {
|
|
||||||
$originals[$disk] = config("filesystems.disks.{$disk}.root");
|
|
||||||
}
|
|
||||||
|
|
||||||
tenancy()->init('test.localhost');
|
|
||||||
Tenant::new()->withDomains(['second.localhost'])->save();
|
|
||||||
tenancy()->init('second.localhost');
|
|
||||||
|
|
||||||
foreach (config('tenancy.filesystem.disks') as $disk) {
|
|
||||||
$suffix = config('tenancy.filesystem.suffix_base') . tenant('id');
|
|
||||||
$current_path_prefix = \Storage::disk($disk)->getAdapter()->getPathPrefix();
|
|
||||||
|
|
||||||
if ($override = config("tenancy.filesystem.root_override.{$disk}")) {
|
|
||||||
$correct_path_prefix = str_replace('%storage_path%', storage_path(), $override);
|
|
||||||
} else {
|
|
||||||
if ($base = $originals[$disk]) {
|
|
||||||
$correct_path_prefix = $base . "/$suffix/";
|
|
||||||
} else {
|
|
||||||
$correct_path_prefix = "$suffix/";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->assertSame($correct_path_prefix, $current_path_prefix);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function storage_path_is_correct()
|
|
||||||
{
|
|
||||||
$original = storage_path();
|
|
||||||
|
|
||||||
tenancy()->init('test.localhost');
|
|
||||||
Tenant::new()->withDomains(['second.localhost'])->save();
|
|
||||||
tenancy()->init('second.localhost');
|
|
||||||
|
|
||||||
$suffix = config('tenancy.filesystem.suffix_base') . tenant('id');
|
|
||||||
$this->assertSame($original . "/$suffix", storage_path());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,27 +2,28 @@
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Tests;
|
namespace Stancl\Tenancy\Tests\v3;
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
use Stancl\Tenancy\Database\Models\Tenant;
|
||||||
use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData;
|
use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData;
|
||||||
use Stancl\Tenancy\Tenant;
|
use Stancl\Tenancy\Tests\TestCase;
|
||||||
|
|
||||||
class RequestDataIdentificationTest extends TestCase
|
class RequestDataIdentificationTest extends TestCase
|
||||||
{
|
{
|
||||||
public $autoCreateTenant = false;
|
|
||||||
public $autoInitTenancy = false;
|
|
||||||
|
|
||||||
public function setUp(): void
|
public function setUp(): void
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
||||||
config([
|
config([
|
||||||
'tenancy.exempt_domains' => [
|
'tenancy.central_domains' => [
|
||||||
'localhost',
|
'localhost',
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
InitializeTenancyByRequestData::$header = 'X-Tenant';
|
||||||
|
InitializeTenancyByRequestData::$queryParameter = 'tenant';
|
||||||
|
|
||||||
Route::middleware(InitializeTenancyByRequestData::class)->get('/test', function () {
|
Route::middleware(InitializeTenancyByRequestData::class)->get('/test', function () {
|
||||||
return 'Tenant id: ' . tenant('id');
|
return 'Tenant id: ' . tenant('id');
|
||||||
});
|
});
|
||||||
|
|
@ -31,11 +32,9 @@ class RequestDataIdentificationTest extends TestCase
|
||||||
/** @test */
|
/** @test */
|
||||||
public function header_identification_works()
|
public function header_identification_works()
|
||||||
{
|
{
|
||||||
$this->app->bind(InitializeTenancyByRequestData::class, function () {
|
InitializeTenancyByRequestData::$header = 'X-Tenant';
|
||||||
return new InitializeTenancyByRequestData('X-Tenant');
|
$tenant = Tenant::create();
|
||||||
});
|
$tenant2 = Tenant::create();
|
||||||
$tenant = Tenant::new()->save();
|
|
||||||
$tenant2 = Tenant::new()->save();
|
|
||||||
|
|
||||||
$this
|
$this
|
||||||
->withoutExceptionHandling()
|
->withoutExceptionHandling()
|
||||||
|
|
@ -48,11 +47,11 @@ class RequestDataIdentificationTest extends TestCase
|
||||||
/** @test */
|
/** @test */
|
||||||
public function query_parameter_identification_works()
|
public function query_parameter_identification_works()
|
||||||
{
|
{
|
||||||
$this->app->bind(InitializeTenancyByRequestData::class, function () {
|
InitializeTenancyByRequestData::$header = null;
|
||||||
return new InitializeTenancyByRequestData(null, 'tenant');
|
InitializeTenancyByRequestData::$queryParameter = 'tenant';
|
||||||
});
|
|
||||||
$tenant = Tenant::new()->save();
|
$tenant = Tenant::create();
|
||||||
$tenant2 = Tenant::new()->save();
|
$tenant2 = Tenant::create();
|
||||||
|
|
||||||
$this
|
$this
|
||||||
->withoutExceptionHandling()
|
->withoutExceptionHandling()
|
||||||
|
|
|
||||||
|
|
@ -1,97 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Tests;
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Redis;
|
|
||||||
|
|
||||||
class TenancyBootstrappersTest extends TestCase
|
|
||||||
{
|
|
||||||
public $autoCreateTenant = true;
|
|
||||||
public $autoInitTenancy = false;
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function database_connection_is_switched()
|
|
||||||
{
|
|
||||||
$old_connection_name = app(\Illuminate\Database\DatabaseManager::class)->connection()->getName();
|
|
||||||
$this->initTenancy();
|
|
||||||
$new_connection_name = app(\Illuminate\Database\DatabaseManager::class)->connection()->getName();
|
|
||||||
|
|
||||||
$this->assertNotEquals($old_connection_name, $new_connection_name);
|
|
||||||
$this->assertEquals('tenant', $new_connection_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function redis_is_prefixed()
|
|
||||||
{
|
|
||||||
if (! config('tenancy.redis.tenancy')) {
|
|
||||||
$this->markTestSkipped('Redis tenancy disabled.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->initTenancy();
|
|
||||||
foreach (config('tenancy.redis.prefixed_connections', ['default']) as $connection) {
|
|
||||||
$prefix = config('tenancy.redis.prefix_base') . tenant('id');
|
|
||||||
$client = Redis::connection($connection)->client();
|
|
||||||
$this->assertEquals($prefix, $client->getOption($client::OPT_PREFIX));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function filesystem_is_suffixed()
|
|
||||||
{
|
|
||||||
$old_storage_path = storage_path();
|
|
||||||
$old_storage_facade_roots = [];
|
|
||||||
foreach (config('tenancy.filesystem.disks') as $disk) {
|
|
||||||
$old_storage_facade_roots[$disk] = config("filesystems.disks.{$disk}.root");
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->initTenancy();
|
|
||||||
|
|
||||||
$new_storage_path = storage_path();
|
|
||||||
$this->assertEquals($old_storage_path . '/' . config('tenancy.filesystem.suffix_base') . tenant('id'), $new_storage_path);
|
|
||||||
|
|
||||||
foreach (config('tenancy.filesystem.disks') as $disk) {
|
|
||||||
$suffix = config('tenancy.filesystem.suffix_base') . tenant('id');
|
|
||||||
$current_path_prefix = \Storage::disk($disk)->getAdapter()->getPathPrefix();
|
|
||||||
|
|
||||||
if ($override = config("tenancy.filesystem.root_override.{$disk}")) {
|
|
||||||
$correct_path_prefix = str_replace('%storage_path%', storage_path(), $override);
|
|
||||||
} else {
|
|
||||||
if ($base = $old_storage_facade_roots[$disk]) {
|
|
||||||
$correct_path_prefix = $base . "/$suffix/";
|
|
||||||
} else {
|
|
||||||
$correct_path_prefix = "$suffix/";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->assertSame($correct_path_prefix, $current_path_prefix);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function cache_is_tagged()
|
|
||||||
{
|
|
||||||
$this->assertSame(['foo'], cache()->tags('foo')->getTags()->getNames());
|
|
||||||
$this->initTenancy();
|
|
||||||
|
|
||||||
$expected = [config('tenancy.cache.tag_base') . tenant('id'), 'foo', 'bar'];
|
|
||||||
$this->assertEquals($expected, cache()->tags(['foo', 'bar'])->getTags()->getNames());
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function the_default_db_connection_is_used_when_the_config_value_is_null()
|
|
||||||
{
|
|
||||||
$original = config('database.default');
|
|
||||||
tenancy()->create(['foo.localhost']);
|
|
||||||
tenancy()->init('foo.localhost');
|
|
||||||
|
|
||||||
$this->assertSame(null, config("database.connections.$original.foo"));
|
|
||||||
|
|
||||||
config(["database.connections.$original.foo" => 'bar']);
|
|
||||||
tenancy()->create(['bar.localhost']);
|
|
||||||
tenancy()->init('bar.localhost');
|
|
||||||
|
|
||||||
$this->assertSame('bar', config("database.connections.$original.foo"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,29 +2,58 @@
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Tests;
|
namespace Stancl\Tenancy\Tests\v3;
|
||||||
|
|
||||||
use Stancl\Tenancy\Tenant;
|
use Illuminate\Support\Facades\Event;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Stancl\Tenancy\Controllers\TenantAssetsController;
|
||||||
|
use Stancl\Tenancy\Database\Models\Tenant;
|
||||||
|
use Stancl\Tenancy\Events\Listeners\BootstrapTenancy;
|
||||||
|
use Stancl\Tenancy\Events\TenancyInitialized;
|
||||||
|
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
|
||||||
|
use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData;
|
||||||
|
use Stancl\Tenancy\TenancyBootstrappers\FilesystemTenancyBootstrapper;
|
||||||
|
use Stancl\Tenancy\Tests\TestCase;
|
||||||
|
|
||||||
class TenantAssetTest extends TestCase
|
class TenantAssetTest extends TestCase
|
||||||
{
|
{
|
||||||
public $autoCreateTenant = false;
|
public function setUp(): void
|
||||||
public $autoInitTenancy = false;
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
config(['tenancy.bootstrappers' => [
|
||||||
|
FilesystemTenancyBootstrapper::class
|
||||||
|
]]);
|
||||||
|
|
||||||
|
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tearDown(): void
|
||||||
|
{
|
||||||
|
parent::tearDown();
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
TenantAssetsController::$tenancyMiddleware = InitializeTenancyByDomain::class;
|
||||||
|
}
|
||||||
|
|
||||||
/** @test */
|
/** @test */
|
||||||
public function asset_can_be_accessed_using_the_url_returned_by_the_tenant_asset_helper()
|
public function asset_can_be_accessed_using_the_url_returned_by_the_tenant_asset_helper()
|
||||||
{
|
{
|
||||||
Tenant::create('localhost');
|
TenantAssetsController::$tenancyMiddleware = InitializeTenancyByRequestData::class;
|
||||||
tenancy()->init('localhost');
|
|
||||||
|
$tenant = Tenant::create();
|
||||||
|
tenancy()->initialize($tenant);
|
||||||
|
|
||||||
$filename = 'testfile' . $this->randomString(10);
|
$filename = 'testfile' . $this->randomString(10);
|
||||||
\Storage::disk('public')->put($filename, 'bar');
|
Storage::disk('public')->put($filename, 'bar');
|
||||||
$path = storage_path("app/public/$filename");
|
$path = storage_path("app/public/$filename");
|
||||||
|
|
||||||
// response()->file() returns BinaryFileResponse whose content is
|
// response()->file() returns BinaryFileResponse whose content is
|
||||||
// inaccessible via getContent, so ->assertSee() can't be used
|
// inaccessible via getContent, so ->assertSee() can't be used
|
||||||
$this->assertFileExists($path);
|
$this->assertFileExists($path);
|
||||||
$response = $this->get(tenant_asset($filename));
|
$response = $this->get(tenant_asset($filename), [
|
||||||
|
'X-Tenant' => $tenant->id,
|
||||||
|
]);
|
||||||
|
|
||||||
$response->assertSuccessful();
|
$response->assertSuccessful();
|
||||||
|
|
||||||
|
|
@ -40,8 +69,8 @@ class TenantAssetTest extends TestCase
|
||||||
{
|
{
|
||||||
config(['app.asset_url' => null]);
|
config(['app.asset_url' => null]);
|
||||||
|
|
||||||
Tenant::create('foo.localhost');
|
$tenant = Tenant::create();
|
||||||
tenancy()->init('foo.localhost');
|
tenancy()->initialize($tenant);
|
||||||
|
|
||||||
$this->assertSame(route('stancl.tenancy.asset', ['path' => 'foo']), asset('foo'));
|
$this->assertSame(route('stancl.tenancy.asset', ['path' => 'foo']), asset('foo'));
|
||||||
}
|
}
|
||||||
|
|
@ -51,8 +80,8 @@ class TenantAssetTest extends TestCase
|
||||||
{
|
{
|
||||||
config(['app.asset_url' => 'https://an-s3-bucket']);
|
config(['app.asset_url' => 'https://an-s3-bucket']);
|
||||||
|
|
||||||
$tenant = Tenant::create(['foo.localhost']);
|
$tenant = Tenant::create();
|
||||||
tenancy()->init('foo.localhost');
|
tenancy()->initialize($tenant);
|
||||||
|
|
||||||
$this->assertSame("https://an-s3-bucket/tenant{$tenant->id}/foo", asset('foo'));
|
$this->assertSame("https://an-s3-bucket/tenant{$tenant->id}/foo", asset('foo'));
|
||||||
}
|
}
|
||||||
|
|
@ -63,8 +92,8 @@ class TenantAssetTest extends TestCase
|
||||||
$original = global_asset('foobar');
|
$original = global_asset('foobar');
|
||||||
$this->assertSame(asset('foobar'), global_asset('foobar'));
|
$this->assertSame(asset('foobar'), global_asset('foobar'));
|
||||||
|
|
||||||
Tenant::create(['foo.localhost']);
|
$tenant = Tenant::create();
|
||||||
tenancy()->init('foo.localhost');
|
tenancy()->initialize($tenant);
|
||||||
|
|
||||||
$this->assertSame($original, global_asset('foobar'));
|
$this->assertSame($original, global_asset('foobar'));
|
||||||
}
|
}
|
||||||
|
|
@ -79,8 +108,8 @@ class TenantAssetTest extends TestCase
|
||||||
'tenancy.filesystem.asset_helper_tenancy' => false,
|
'tenancy.filesystem.asset_helper_tenancy' => false,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Tenant::create('foo.localhost');
|
$tenant = Tenant::create();
|
||||||
tenancy()->init('foo.localhost');
|
tenancy()->initialize($tenant);
|
||||||
|
|
||||||
$this->assertSame($original, asset('foo'));
|
$this->assertSame($original, asset('foo'));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
34
tests/TenantAwareCommandTest.php
Normal file
34
tests/TenantAwareCommandTest.php
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Stancl\Tenancy\Tests\v3;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Artisan;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Stancl\Tenancy\Database\Models\Tenant;
|
||||||
|
use Stancl\Tenancy\Tests\TestCase;
|
||||||
|
|
||||||
|
class TenantAwareCommandTest extends TestCase
|
||||||
|
{
|
||||||
|
/** @test */
|
||||||
|
public function commands_run_globally_are_tenant_aware_and_return_valid_exit_code()
|
||||||
|
{
|
||||||
|
$tenant1 = Tenant::create();
|
||||||
|
$tenant2 = Tenant::create();
|
||||||
|
Artisan::call('tenants:migrate', [
|
||||||
|
'--tenants' => [$tenant1['id'], $tenant2['id']],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->artisan('user:add')
|
||||||
|
->assertExitCode(0);
|
||||||
|
|
||||||
|
tenancy()->initialize($tenant1);
|
||||||
|
$this->assertNotEmpty(DB::table('users')->get());
|
||||||
|
tenancy()->end();
|
||||||
|
|
||||||
|
tenancy()->initialize($tenant2);
|
||||||
|
$this->assertNotEmpty(DB::table('users')->get());
|
||||||
|
tenancy()->end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,227 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Tests;
|
|
||||||
|
|
||||||
use Mockery;
|
|
||||||
use Stancl\Tenancy\Contracts\StorageDriver;
|
|
||||||
use Stancl\Tenancy\Tenant;
|
|
||||||
use Tenancy;
|
|
||||||
|
|
||||||
class TenantClassTest extends TestCase
|
|
||||||
{
|
|
||||||
public $autoInitTenancy = false;
|
|
||||||
public $autoCreateTenant = false;
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function data_cache_works_properly()
|
|
||||||
{
|
|
||||||
// $spy = Mockery::spy(config('tenancy.storage_driver'))->makePartial();
|
|
||||||
// $this->instance(StorageDriver::class, $spy);
|
|
||||||
|
|
||||||
$tenant = Tenant::create(['foo.localhost'], ['foo' => 'bar']);
|
|
||||||
$this->assertSame('bar', $tenant->data['foo']);
|
|
||||||
|
|
||||||
$tenant->put('abc', 'xyz');
|
|
||||||
$this->assertSame('xyz', $tenant->data['abc']);
|
|
||||||
|
|
||||||
$tenant->put(['aaa' => 'bbb', 'ccc' => 'ddd']);
|
|
||||||
$this->assertSame('bbb', $tenant->data['aaa']);
|
|
||||||
$this->assertSame('ddd', $tenant->data['ccc']);
|
|
||||||
|
|
||||||
// $spy->shouldNotHaveReceived('get');
|
|
||||||
|
|
||||||
$this->assertSame(null, $tenant->dfuighdfuigfhdui);
|
|
||||||
// $spy->shouldHaveReceived('get')->once();
|
|
||||||
|
|
||||||
Mockery::close();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function tenant_can_have_multiple_domains()
|
|
||||||
{
|
|
||||||
$tenant = Tenant::create(['foo.localhost', 'bar.localhost']);
|
|
||||||
$this->assertSame(['foo.localhost', 'bar.localhost'], $tenant->domains);
|
|
||||||
$this->assertSame($tenant->id, Tenancy::findByDomain('foo.localhost')->id);
|
|
||||||
$this->assertSame($tenant->id, Tenancy::findByDomain('bar.localhost')->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function updating_a_tenant_works()
|
|
||||||
{
|
|
||||||
$id = 'abc' . $this->randomString();
|
|
||||||
$tenant = Tenant::create(['foo.localhost'], ['id' => $id]);
|
|
||||||
$tenant->foo = 'bar';
|
|
||||||
$tenant->save();
|
|
||||||
$this->assertEquals(['id' => $id, 'foo' => 'bar', '_tenancy_db_name' => $tenant->database()->getName()], $tenant->data);
|
|
||||||
$this->assertEquals(['id' => $id, 'foo' => 'bar', '_tenancy_db_name' => $tenant->database()->getName()], tenancy()->find($id)->data);
|
|
||||||
|
|
||||||
$tenant->addDomains('abc.localhost');
|
|
||||||
$tenant->save();
|
|
||||||
$this->assertEqualsCanonicalizing(['foo.localhost', 'abc.localhost'], $tenant->domains);
|
|
||||||
$this->assertEqualsCanonicalizing(['foo.localhost', 'abc.localhost'], tenancy()->find($id)->domains);
|
|
||||||
|
|
||||||
$tenant->removeDomains(['foo.localhost']);
|
|
||||||
$tenant->save();
|
|
||||||
$this->assertEqualsCanonicalizing(['abc.localhost'], $tenant->domains);
|
|
||||||
$this->assertEqualsCanonicalizing(['abc.localhost'], tenancy()->find($id)->domains);
|
|
||||||
|
|
||||||
$tenant->withDomains(['completely.localhost', 'different.localhost', 'domains.localhost']);
|
|
||||||
$tenant->save();
|
|
||||||
$this->assertEqualsCanonicalizing(['completely.localhost', 'different.localhost', 'domains.localhost'], $tenant->domains);
|
|
||||||
$this->assertEqualsCanonicalizing(['completely.localhost', 'different.localhost', 'domains.localhost'], tenancy()->find($id)->domains);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function with_methods_work()
|
|
||||||
{
|
|
||||||
$id = 'foo' . $this->randomString();
|
|
||||||
$tenant = Tenant::new()->withDomains(['foo.localhost'])->with('id', $id);
|
|
||||||
$this->assertSame($id, $tenant->id);
|
|
||||||
|
|
||||||
$id2 = 'bar' . $this->randomString();
|
|
||||||
$tenant2 = Tenant::new()->withDomains(['bar.localhost'])->withId($id2)->withFooBar('xyz');
|
|
||||||
$this->assertSame($id2, $tenant2->data['id']);
|
|
||||||
$this->assertSame('xyz', $tenant2->foo_bar);
|
|
||||||
$this->assertArrayHasKey('foo_bar', $tenant2->data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function an_exception_is_thrown_when_an_unknown_method_is_called()
|
|
||||||
{
|
|
||||||
$tenant = Tenant::new();
|
|
||||||
$this->expectException(\BadMethodCallException::class);
|
|
||||||
$tenant->sdjigndfgnjdfgj();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function tenant_data_can_be_set_during_creation()
|
|
||||||
{
|
|
||||||
Tenant::new()->withData(['foo' => 'bar'])->save();
|
|
||||||
|
|
||||||
$data = tenancy()->all()->first()->data;
|
|
||||||
unset($data['id']);
|
|
||||||
unset($data['_tenancy_db_name']);
|
|
||||||
|
|
||||||
$this->assertSame(['foo' => 'bar'], $data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function run_method_works()
|
|
||||||
{
|
|
||||||
$this->assertSame(null, tenancy()->getTenant());
|
|
||||||
|
|
||||||
$users_table_empty = function () {
|
|
||||||
return count(\DB::table('users')->get()) === 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
$tenant = Tenant::new()->save();
|
|
||||||
\Artisan::call('tenants:migrate', [
|
|
||||||
'--tenants' => [$tenant->id],
|
|
||||||
]);
|
|
||||||
tenancy()->initialize($tenant);
|
|
||||||
$this->assertTrue($users_table_empty());
|
|
||||||
tenancy()->end();
|
|
||||||
|
|
||||||
$foo = $tenant->run(function () {
|
|
||||||
\DB::table('users')->insert([
|
|
||||||
'name' => 'foo',
|
|
||||||
'email' => 'foo@bar.xy',
|
|
||||||
'password' => bcrypt('secret'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return 'foo';
|
|
||||||
});
|
|
||||||
|
|
||||||
// test return value
|
|
||||||
$this->assertSame('foo', $foo);
|
|
||||||
|
|
||||||
// test that tenancy was ended
|
|
||||||
$this->assertSame(false, tenancy()->initialized);
|
|
||||||
$this->assertSame(null, tenancy()->getTenant());
|
|
||||||
|
|
||||||
// test closure
|
|
||||||
tenancy()->initialize($tenant);
|
|
||||||
$this->assertFalse($users_table_empty());
|
|
||||||
|
|
||||||
// test returning to original tenant
|
|
||||||
$tenant2 = Tenant::new()->save();
|
|
||||||
\Artisan::call('tenants:migrate', [
|
|
||||||
'--tenants' => [$tenant2->id],
|
|
||||||
]);
|
|
||||||
|
|
||||||
tenancy()->initialize($tenant2);
|
|
||||||
$this->assertSame($tenant2, tenancy()->getTenant());
|
|
||||||
$this->assertTrue($users_table_empty());
|
|
||||||
|
|
||||||
$tenant->run(function () {
|
|
||||||
\DB::table('users')->insert([
|
|
||||||
'name' => 'bar',
|
|
||||||
'email' => 'bar@bar.xy',
|
|
||||||
'password' => bcrypt('secret'),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->assertSame($tenant2, tenancy()->getTenant());
|
|
||||||
|
|
||||||
$this->assertSame(2, $tenant->run(function () {
|
|
||||||
return \DB::table('users')->count();
|
|
||||||
}));
|
|
||||||
|
|
||||||
// test that the tenant variable can be accessed
|
|
||||||
$this->assertSame($tenant->id, $tenant->run(function ($tenant) {
|
|
||||||
return $tenant->id;
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function extra_config_is_merged_into_the_connection_config_array()
|
|
||||||
{
|
|
||||||
$tenant = Tenant::new()->withData([
|
|
||||||
'_tenancy_db_link' => 'foo',
|
|
||||||
'_tenancy_db_name' => 'dbname',
|
|
||||||
'_tenancy_db_username' => 'usernamefoo',
|
|
||||||
'_tenancy_db_password' => 'passwordfoo',
|
|
||||||
'_tenancy_db_connection' => 'mysql',
|
|
||||||
]);
|
|
||||||
|
|
||||||
config(['database.connections.mysql' => [
|
|
||||||
'driver' => 'mysql',
|
|
||||||
'url' => null,
|
|
||||||
'host' => 'mysql',
|
|
||||||
'port' => '3306',
|
|
||||||
'database' => 'main',
|
|
||||||
'username' => 'root',
|
|
||||||
'password' => 'password',
|
|
||||||
'unix_socket' => '',
|
|
||||||
'charset' => 'utf8mb4',
|
|
||||||
'collation' => 'utf8mb4_unicode_ci',
|
|
||||||
'prefix' => '',
|
|
||||||
'prefix_indexes' => true,
|
|
||||||
'strict' => true,
|
|
||||||
'engine' => null,
|
|
||||||
'options' => [],
|
|
||||||
]]);
|
|
||||||
|
|
||||||
$this->assertEquals([
|
|
||||||
'database' => 'dbname',
|
|
||||||
'username' => 'usernamefoo',
|
|
||||||
'password' => 'passwordfoo',
|
|
||||||
'link' => 'foo',
|
|
||||||
|
|
||||||
'driver' => 'mysql',
|
|
||||||
'url' => null,
|
|
||||||
'host' => 'mysql',
|
|
||||||
'port' => '3306',
|
|
||||||
'unix_socket' => '',
|
|
||||||
'charset' => 'utf8mb4',
|
|
||||||
'collation' => 'utf8mb4_unicode_ci',
|
|
||||||
'prefix' => '',
|
|
||||||
'prefix_indexes' => true,
|
|
||||||
'strict' => true,
|
|
||||||
'engine' => null,
|
|
||||||
'options' => [],
|
|
||||||
], $tenant->database()->connection());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,44 +2,50 @@
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Tests;
|
namespace Stancl\Tenancy\Tests\v3;
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Queue;
|
use Illuminate\Support\Facades\Event;
|
||||||
use Stancl\Tenancy\Jobs\QueuedTenantDatabaseCreator;
|
use Stancl\Tenancy\Database\Models\Tenant;
|
||||||
use Stancl\Tenancy\Jobs\QueuedTenantDatabaseDeleter;
|
use Stancl\Tenancy\DatabaseManager;
|
||||||
use Stancl\Tenancy\Tenant;
|
use Stancl\Tenancy\Events\Listeners\BootstrapTenancy;
|
||||||
|
use Stancl\Tenancy\Events\Listeners\JobPipeline;
|
||||||
|
use Stancl\Tenancy\Events\TenancyInitialized;
|
||||||
|
use Stancl\Tenancy\Events\TenantCreated;
|
||||||
|
use Stancl\Tenancy\Jobs\CreateDatabase;
|
||||||
|
use Stancl\Tenancy\TenancyBootstrappers\DatabaseTenancyBootstrapper;
|
||||||
use Stancl\Tenancy\TenantDatabaseManagers\MySQLDatabaseManager;
|
use Stancl\Tenancy\TenantDatabaseManagers\MySQLDatabaseManager;
|
||||||
use Stancl\Tenancy\TenantDatabaseManagers\PermissionControlledMySQLDatabaseManager;
|
use Stancl\Tenancy\TenantDatabaseManagers\PermissionControlledMySQLDatabaseManager;
|
||||||
use Stancl\Tenancy\TenantDatabaseManagers\PostgreSQLDatabaseManager;
|
use Stancl\Tenancy\TenantDatabaseManagers\PostgreSQLDatabaseManager;
|
||||||
use Stancl\Tenancy\TenantDatabaseManagers\PostgreSQLSchemaManager;
|
use Stancl\Tenancy\TenantDatabaseManagers\PostgreSQLSchemaManager;
|
||||||
use Stancl\Tenancy\TenantDatabaseManagers\SQLiteDatabaseManager;
|
use Stancl\Tenancy\TenantDatabaseManagers\SQLiteDatabaseManager;
|
||||||
|
use Stancl\Tenancy\Tests\TestCase;
|
||||||
|
|
||||||
class TenantDatabaseManagerTest extends TestCase
|
class TenantDatabaseManagerTest extends TestCase
|
||||||
{
|
{
|
||||||
public $autoInitTenancy = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @test
|
* @test
|
||||||
* @dataProvider database_manager_provider
|
* @dataProvider database_manager_provider
|
||||||
*/
|
*/
|
||||||
public function databases_can_be_created_and_deleted($driver, $databaseManager)
|
public function databases_can_be_created_and_deleted($driver, $databaseManager)
|
||||||
{
|
{
|
||||||
if (! $this->isContainerized()) {
|
Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
|
||||||
$this->markTestSkipped('As to not bloat your computer with test databases, this test is not run by default.');
|
return $event->tenant;
|
||||||
}
|
})->toListener());
|
||||||
|
|
||||||
config()->set([
|
config()->set([
|
||||||
"tenancy.database_managers.$driver" => $databaseManager,
|
"tenancy.database_managers.$driver" => $databaseManager,
|
||||||
|
'tenancy.internal_prefix' => 'tenancy_',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$name = 'db' . $this->randomString();
|
$name = 'db' . $this->randomString();
|
||||||
$tenant = Tenant::new()->withData([
|
|
||||||
'_tenancy_db_name' => $name,
|
|
||||||
'_tenancy_db_connection' => $driver,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->assertFalse(app($databaseManager)->databaseExists($name));
|
$this->assertFalse(app($databaseManager)->databaseExists($name));
|
||||||
$tenant->save(); // generate credentials & create DB
|
|
||||||
|
$tenant = Tenant::create([
|
||||||
|
'tenancy_db_name' => $name,
|
||||||
|
'tenancy_db_connection' => $driver,
|
||||||
|
]);
|
||||||
|
|
||||||
$this->assertTrue(app($databaseManager)->databaseExists($name));
|
$this->assertTrue(app($databaseManager)->databaseExists($name));
|
||||||
app($databaseManager)->deleteDatabase($tenant);
|
app($databaseManager)->deleteDatabase($tenant);
|
||||||
$this->assertFalse(app($databaseManager)->databaseExists($name));
|
$this->assertFalse(app($databaseManager)->databaseExists($name));
|
||||||
|
|
@ -50,60 +56,34 @@ class TenantDatabaseManagerTest extends TestCase
|
||||||
{
|
{
|
||||||
$this->assertSame('central', config('database.default'));
|
$this->assertSame('central', config('database.default'));
|
||||||
|
|
||||||
|
Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
|
||||||
|
return $event->tenant;
|
||||||
|
})->toListener());
|
||||||
|
|
||||||
|
// todo if the prefix is _tenancy_, this blows up. write a tenantmodel test that the prefix can be _tenancy_
|
||||||
|
config(['tenancy.internal_prefix' => 'tenancy_',]);
|
||||||
|
|
||||||
$database = 'db' . $this->randomString();
|
$database = 'db' . $this->randomString();
|
||||||
$tenant = Tenant::new()->withData([
|
|
||||||
'_tenancy_db_name' => $database,
|
|
||||||
'_tenancy_db_connection' => 'mysql',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->assertFalse(app(MySQLDatabaseManager::class)->databaseExists($database));
|
$this->assertFalse(app(MySQLDatabaseManager::class)->databaseExists($database));
|
||||||
$tenant->save(); // create DB
|
Tenant::create([
|
||||||
|
'tenancy_db_name' => $database,
|
||||||
|
'tenancy_db_connection' => 'mysql',
|
||||||
|
]);
|
||||||
|
|
||||||
$this->assertTrue(app(MySQLDatabaseManager::class)->databaseExists($database));
|
$this->assertTrue(app(MySQLDatabaseManager::class)->databaseExists($database));
|
||||||
|
|
||||||
$database = 'db' . $this->randomString();
|
$database = 'db' . $this->randomString();
|
||||||
$tenant = Tenant::new()->withData([
|
|
||||||
'_tenancy_db_name' => $database,
|
|
||||||
'_tenancy_db_connection' => 'pgsql',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->assertFalse(app(PostgreSQLDatabaseManager::class)->databaseExists($database));
|
$this->assertFalse(app(PostgreSQLDatabaseManager::class)->databaseExists($database));
|
||||||
$tenant->save(); // create DB
|
|
||||||
|
Tenant::create([
|
||||||
|
'tenancy_db_name' => $database,
|
||||||
|
'tenancy_db_connection' => 'pgsql',
|
||||||
|
]);
|
||||||
|
|
||||||
$this->assertTrue(app(PostgreSQLDatabaseManager::class)->databaseExists($database));
|
$this->assertTrue(app(PostgreSQLDatabaseManager::class)->databaseExists($database));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
* @dataProvider database_manager_provider
|
|
||||||
*/
|
|
||||||
public function databases_can_be_created_and_deleted_using_queued_commands($driver, $databaseManager)
|
|
||||||
{
|
|
||||||
if (! $this->isContainerized()) {
|
|
||||||
$this->markTestSkipped('As to not bloat your computer with test databases, this test is not run by default.');
|
|
||||||
}
|
|
||||||
|
|
||||||
config()->set([
|
|
||||||
'database.default' => $driver,
|
|
||||||
"tenancy.database_managers.$driver" => $databaseManager,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$name = 'db' . $this->randomString();
|
|
||||||
$tenant = Tenant::new()->withData([
|
|
||||||
'_tenancy_db_name' => $name,
|
|
||||||
'_tenancy_db_connection' => $driver,
|
|
||||||
]);
|
|
||||||
$tenant->database()->makeCredentials();
|
|
||||||
|
|
||||||
$this->assertFalse(app($databaseManager)->databaseExists($name));
|
|
||||||
$job = new QueuedTenantDatabaseCreator(app($databaseManager), $tenant);
|
|
||||||
|
|
||||||
$job->handle();
|
|
||||||
$this->assertTrue(app($databaseManager)->databaseExists($name));
|
|
||||||
|
|
||||||
$job = new QueuedTenantDatabaseDeleter(app($databaseManager), $tenant);
|
|
||||||
$job->handle();
|
|
||||||
$this->assertFalse(app($databaseManager)->databaseExists($name));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function database_manager_provider()
|
public function database_manager_provider()
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
|
@ -116,30 +96,49 @@ class TenantDatabaseManagerTest extends TestCase
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @test */
|
/** @test */
|
||||||
public function database_creation_can_be_queued()
|
public function db_name_is_prefixed_with_db_path_when_sqlite_is_used()
|
||||||
{
|
{
|
||||||
Queue::fake();
|
if (file_exists(database_path('foodb'))) {
|
||||||
|
unlink(database_path('foodb')); // cleanup
|
||||||
config()->set([
|
}
|
||||||
'tenancy.queue_database_creation' => true,
|
config([
|
||||||
|
'database.connections.fooconn.driver' => 'sqlite',
|
||||||
|
'tenancy.internal_prefix' => 'tenancy_',
|
||||||
]);
|
]);
|
||||||
Tenant::create(['test2.localhost']);
|
|
||||||
|
|
||||||
Queue::assertPushed(QueuedTenantDatabaseCreator::class);
|
$tenant = Tenant::create([
|
||||||
|
'tenancy_db_name' => 'foodb',
|
||||||
|
'tenancy_db_connection' => 'fooconn',
|
||||||
|
]);
|
||||||
|
app(DatabaseManager::class)->createTenantConnection($tenant);
|
||||||
|
|
||||||
|
$this->assertSame(config('database.connections.tenant.database'), database_path('foodb'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @test */
|
/** @test */
|
||||||
public function database_deletion_can_be_queued()
|
public function schema_manager_uses_schema_to_separate_tenant_dbs()
|
||||||
{
|
{
|
||||||
Queue::fake();
|
config([
|
||||||
|
'tenancy.database_managers.pgsql' => \Stancl\Tenancy\TenantDatabaseManagers\PostgreSQLSchemaManager::class,
|
||||||
$tenant = Tenant::create(['test2.localhost']);
|
'tenancy.boostrappers' => [
|
||||||
config()->set([
|
DatabaseTenancyBootstrapper::class,
|
||||||
'tenancy.queue_database_deletion' => true,
|
],
|
||||||
'tenancy.delete_database_after_tenant_deletion' => true,
|
|
||||||
]);
|
]);
|
||||||
$tenant->delete();
|
|
||||||
|
|
||||||
Queue::assertPushed(QueuedTenantDatabaseDeleter::class);
|
Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
|
||||||
|
return $event->tenant;
|
||||||
|
})->toListener());
|
||||||
|
|
||||||
|
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
|
||||||
|
|
||||||
|
$originalDatabaseName = config(['database.connections.pgsql.database']);
|
||||||
|
|
||||||
|
$tenant = Tenant::create([
|
||||||
|
'tenancy_db_connection' => 'pgsql',
|
||||||
|
]);
|
||||||
|
tenancy()->initialize($tenant);
|
||||||
|
|
||||||
|
$this->assertSame($tenant->database()->getName(), config('database.connections.' . config('database.default') . '.schema'));
|
||||||
|
$this->assertSame($originalDatabaseName, config(['database.connections.pgsql.database']));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,138 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Tests;
|
|
||||||
|
|
||||||
use Stancl\Tenancy\Tenant;
|
|
||||||
use Tenancy;
|
|
||||||
|
|
||||||
class TenantManagerEventsTest extends TestCase
|
|
||||||
{
|
|
||||||
public $autoInitTenancy = false;
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function bootstrapping_event_works()
|
|
||||||
{
|
|
||||||
$id = Tenant::new()->withDomains(['foo.localhost'])->save()['id'];
|
|
||||||
|
|
||||||
Tenancy::eventListener('bootstrapping', function ($tenantManager) use ($id) {
|
|
||||||
if ($tenantManager->getTenant('id') === $id) {
|
|
||||||
config(['tenancy.foo' => 'bar']);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->assertSame(null, config('tenancy.foo'));
|
|
||||||
tenancy()->init('foo.localhost');
|
|
||||||
$this->assertSame('bar', config('tenancy.foo'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function bootstrapped_event_works()
|
|
||||||
{
|
|
||||||
$id = Tenant::new()->withDomains(['foo.localhost'])->save()['id'];
|
|
||||||
|
|
||||||
Tenancy::eventListener('bootstrapped', function ($tenantManager) use ($id) {
|
|
||||||
if ($tenantManager->getTenant('id') === $id) {
|
|
||||||
config(['tenancy.foo' => 'bar']);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->assertSame(null, config('tenancy.foo'));
|
|
||||||
tenancy()->init('foo.localhost');
|
|
||||||
$this->assertSame('bar', config('tenancy.foo'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function ending_event_works()
|
|
||||||
{
|
|
||||||
$id = Tenant::new()->withDomains(['foo.localhost'])->save()['id'];
|
|
||||||
|
|
||||||
Tenancy::eventListener('ending', function ($tenantManager) use ($id) {
|
|
||||||
if ($tenantManager->getTenant('id') === $id) {
|
|
||||||
config(['tenancy.foo' => 'bar']);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->assertSame(null, config('tenancy.foo'));
|
|
||||||
tenancy()->init('foo.localhost');
|
|
||||||
$this->assertSame(null, config('tenancy.foo'));
|
|
||||||
tenancy()->endTenancy();
|
|
||||||
$this->assertSame('bar', config('tenancy.foo'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function ended_event_works()
|
|
||||||
{
|
|
||||||
Tenant::new()->withDomains(['foo.localhost'])->save()['id'];
|
|
||||||
|
|
||||||
Tenancy::eventListener('ended', function ($tenantManager) {
|
|
||||||
config(['tenancy.foo' => 'bar']);
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->assertSame(null, config('tenancy.foo'));
|
|
||||||
tenancy()->init('foo.localhost');
|
|
||||||
$this->assertSame(null, config('tenancy.foo'));
|
|
||||||
tenancy()->endTenancy();
|
|
||||||
$this->assertSame('bar', config('tenancy.foo'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function database_can_be_reconnected_using_event_hooks()
|
|
||||||
{
|
|
||||||
config(['database.connections.tenantabc' => [
|
|
||||||
'driver' => 'sqlite',
|
|
||||||
'database' => database_path('some_special_database.sqlite'),
|
|
||||||
]]);
|
|
||||||
|
|
||||||
$id = Tenant::create('abc.localhost')['id'];
|
|
||||||
|
|
||||||
Tenancy::eventListener('bootstrapping', function ($tenancy) use ($id) {
|
|
||||||
if ($tenancy->getTenant()['id'] === $id) {
|
|
||||||
$tenancy->database->switchConnection('tenantabc');
|
|
||||||
|
|
||||||
return ['database'];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->assertNotSame('tenantabc', \DB::connection()->getConfig()['name']);
|
|
||||||
tenancy()->init('abc.localhost');
|
|
||||||
$this->assertSame('tenantabc', \DB::connection()->getConfig()['name']);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function database_cannot_be_reconnected_without_using_prevents()
|
|
||||||
{
|
|
||||||
config(['database.connections.tenantabc' => [
|
|
||||||
'driver' => 'sqlite',
|
|
||||||
'database' => database_path('some_special_database.sqlite'),
|
|
||||||
]]);
|
|
||||||
|
|
||||||
$id = Tenant::create('abc.localhost')['id'];
|
|
||||||
|
|
||||||
Tenancy::eventListener('bootstrapping', function ($tenancy) use ($id) {
|
|
||||||
if ($tenancy->getTenant()['id'] === $id) {
|
|
||||||
$tenancy->database->switchConnection('tenantabc');
|
|
||||||
// return ['database'];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->assertNotSame('tenantabc', \DB::connection()->getConfig()['name']);
|
|
||||||
tenancy()->init('abc.localhost');
|
|
||||||
$this->assertSame('tenant', \DB::connection()->getConfig()['name']);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function tenant_is_persisted_before_the_created_hook_is_called()
|
|
||||||
{
|
|
||||||
$was_persisted = false;
|
|
||||||
|
|
||||||
Tenancy::eventListener('tenant.created', function ($tenancy, $tenant) use (&$was_persisted) {
|
|
||||||
$was_persisted = $tenant->persisted;
|
|
||||||
});
|
|
||||||
|
|
||||||
Tenant::new()->save();
|
|
||||||
|
|
||||||
$this->assertTrue($was_persisted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,383 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Tests;
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
use Illuminate\Support\Facades\Queue;
|
|
||||||
use Illuminate\Support\Facades\Storage;
|
|
||||||
use Stancl\Tenancy\Exceptions\DomainsOccupiedByOtherTenantException;
|
|
||||||
use Stancl\Tenancy\Exceptions\TenantDoesNotExistException;
|
|
||||||
use Stancl\Tenancy\Exceptions\TenantWithThisIdAlreadyExistsException;
|
|
||||||
use Stancl\Tenancy\Jobs\QueuedTenantDatabaseCreator;
|
|
||||||
use Stancl\Tenancy\Jobs\QueuedTenantDatabaseMigrator;
|
|
||||||
use Stancl\Tenancy\Jobs\QueuedTenantDatabaseSeeder;
|
|
||||||
use Stancl\Tenancy\Tenant;
|
|
||||||
use Stancl\Tenancy\TenantManager;
|
|
||||||
use Stancl\Tenancy\Tests\Etc\ExampleSeeder;
|
|
||||||
|
|
||||||
class TenantManagerTest extends TestCase
|
|
||||||
{
|
|
||||||
public $autoCreateTenant = false;
|
|
||||||
public $autoInitTenancy = false;
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function current_tenant_can_be_retrieved_using_getTenant()
|
|
||||||
{
|
|
||||||
$tenant = Tenant::new()->withDomains(['test2.localhost'])->save();
|
|
||||||
|
|
||||||
tenancy()->init('test2.localhost');
|
|
||||||
|
|
||||||
$this->assertEquals($tenant, tenancy()->getTenant());
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function initById_works()
|
|
||||||
{
|
|
||||||
$tenant = Tenant::new()->withDomains(['foo.localhost'])->save();
|
|
||||||
|
|
||||||
$this->assertNotEquals($tenant, tenancy()->getTenant());
|
|
||||||
|
|
||||||
tenancy()->initById($tenant['id']);
|
|
||||||
|
|
||||||
$this->assertEquals($tenant, tenancy()->getTenant());
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function findByDomain_works()
|
|
||||||
{
|
|
||||||
$tenant = Tenant::new()->withDomains(['foo.localhost'])->save();
|
|
||||||
|
|
||||||
$this->assertEquals($tenant, tenancy()->findByDomain('foo.localhost'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function find_works()
|
|
||||||
{
|
|
||||||
Tenant::new()->withDomains(['dev.localhost'])->save();
|
|
||||||
tenancy()->init('dev.localhost');
|
|
||||||
|
|
||||||
$this->assertEquals(tenant(), tenancy()->find(tenant('id')));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function findByDomain_throws_an_exception_when_an_unused_domain_is_supplied()
|
|
||||||
{
|
|
||||||
$this->expectException(\Exception::class);
|
|
||||||
tenancy()->findByDomain('nonexistent.domain');
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function tenancy_can_be_ended()
|
|
||||||
{
|
|
||||||
$originals = [
|
|
||||||
'databaseName' => DB::connection()->getDatabaseName(),
|
|
||||||
'storage_path' => storage_path(),
|
|
||||||
'storage_root' => Storage::disk('local')->getAdapter()->getPathPrefix(),
|
|
||||||
'cache' => app('cache'),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Verify that these assertions are the right way for testing this
|
|
||||||
$this->assertSame($originals['databaseName'], DB::connection()->getDatabaseName());
|
|
||||||
$this->assertSame($originals['storage_path'], storage_path());
|
|
||||||
$this->assertSame($originals['storage_root'], Storage::disk('local')->getAdapter()->getPathPrefix());
|
|
||||||
$this->assertSame($originals['cache'], app('cache'));
|
|
||||||
|
|
||||||
Tenant::new()->withDomains(['foo.localhost'])->save();
|
|
||||||
tenancy()->init('foo.localhost');
|
|
||||||
|
|
||||||
$this->assertNotSame($originals['databaseName'], DB::connection()->getDatabaseName());
|
|
||||||
$this->assertNotSame($originals['storage_path'], storage_path());
|
|
||||||
$this->assertNotSame($originals['storage_root'], Storage::disk('local')->getAdapter()->getPathPrefix());
|
|
||||||
$this->assertNotSame($originals['cache'], app('cache'));
|
|
||||||
|
|
||||||
tenancy()->endTenancy();
|
|
||||||
|
|
||||||
$this->assertSame($originals['databaseName'], DB::connection()->getDatabaseName());
|
|
||||||
$this->assertSame($originals['storage_path'], storage_path());
|
|
||||||
$this->assertSame($originals['storage_root'], Storage::disk('local')->getAdapter()->getPathPrefix());
|
|
||||||
$this->assertSame($originals['cache'], app('cache'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function tenancy_can_be_ended_after_reidentification()
|
|
||||||
{
|
|
||||||
$originals = [
|
|
||||||
'databaseName' => DB::connection()->getDatabaseName(),
|
|
||||||
'storage_path' => storage_path(),
|
|
||||||
'storage_root' => Storage::disk('local')->getAdapter()->getPathPrefix(),
|
|
||||||
'cache' => app('cache'),
|
|
||||||
];
|
|
||||||
|
|
||||||
Tenant::new()->withDomains(['foo.localhost'])->save();
|
|
||||||
tenancy()->init('foo.localhost');
|
|
||||||
|
|
||||||
$this->assertNotSame($originals['databaseName'], DB::connection()->getDatabaseName());
|
|
||||||
$this->assertNotSame($originals['storage_path'], storage_path());
|
|
||||||
$this->assertNotSame($originals['storage_root'], Storage::disk('local')->getAdapter()->getPathPrefix());
|
|
||||||
$this->assertNotSame($originals['cache'], app('cache'));
|
|
||||||
|
|
||||||
tenancy()->endTenancy();
|
|
||||||
|
|
||||||
$this->assertSame($originals['databaseName'], DB::connection()->getDatabaseName());
|
|
||||||
$this->assertSame($originals['storage_path'], storage_path());
|
|
||||||
$this->assertSame($originals['storage_root'], Storage::disk('local')->getAdapter()->getPathPrefix());
|
|
||||||
$this->assertSame($originals['cache'], app('cache'));
|
|
||||||
|
|
||||||
// Reidentify tenant
|
|
||||||
Tenant::new()->withDomains(['bar.localhost'])->save();
|
|
||||||
tenancy()->init('bar.localhost');
|
|
||||||
|
|
||||||
$this->assertNotSame($originals['databaseName'], DB::connection()->getDatabaseName());
|
|
||||||
$this->assertNotSame($originals['storage_path'], storage_path());
|
|
||||||
$this->assertNotSame($originals['storage_root'], Storage::disk('local')->getAdapter()->getPathPrefix());
|
|
||||||
$this->assertNotSame($originals['cache'], app('cache'));
|
|
||||||
|
|
||||||
tenancy()->endTenancy();
|
|
||||||
|
|
||||||
$this->assertSame($originals['databaseName'], DB::connection()->getDatabaseName());
|
|
||||||
$this->assertSame($originals['storage_path'], storage_path());
|
|
||||||
$this->assertSame($originals['storage_root'], Storage::disk('local')->getAdapter()->getPathPrefix());
|
|
||||||
$this->assertSame($originals['cache'], app('cache'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function tenant_can_be_deleted()
|
|
||||||
{
|
|
||||||
$tenant = Tenant::new()->withDomains(['foo.localhost'])->save();
|
|
||||||
$this->assertEquals([$tenant], tenancy()->all()->toArray());
|
|
||||||
$tenant->delete();
|
|
||||||
$this->assertEquals([], tenancy()->all()->toArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function all_returns_a_list_of_all_tenants()
|
|
||||||
{
|
|
||||||
$tenant1 = Tenant::new()->withDomains(['foo.localhost'])->save();
|
|
||||||
$tenant2 = Tenant::new()->withDomains(['bar.localhost'])->save();
|
|
||||||
$this->assertEqualsCanonicalizing([$tenant1, $tenant2], tenancy()->all()->toArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function data_can_be_passed_in_the_create_method()
|
|
||||||
{
|
|
||||||
$data = ['plan' => 'free', 'subscribed_until' => '2020-01-01'];
|
|
||||||
$tenant = Tenant::create(['foo.localhost'], $data);
|
|
||||||
|
|
||||||
$tenant_data = $tenant->data;
|
|
||||||
unset($tenant_data['id']);
|
|
||||||
unset($tenant_data['_tenancy_db_name']);
|
|
||||||
|
|
||||||
$this->assertSame($data, $tenant_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function database_name_can_be_passed_in_the_create_method()
|
|
||||||
{
|
|
||||||
$database = 'abc' . $this->randomString();
|
|
||||||
|
|
||||||
$tenant = tenancy()->create(['foo.localhost'], [
|
|
||||||
'_tenancy_db_name' => $database,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->assertSame($database, $tenant->database()->getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function id_cannot_be_changed()
|
|
||||||
{
|
|
||||||
$tenant = Tenant::create(['test2.localhost']);
|
|
||||||
|
|
||||||
$this->expectException(\Stancl\Tenancy\Exceptions\TenantStorageException::class);
|
|
||||||
$tenant->id = 'bar';
|
|
||||||
|
|
||||||
$tenant2 = Tenant::create(['test3.localhost']);
|
|
||||||
|
|
||||||
$this->expectException(\Stancl\Tenancy\Exceptions\TenantStorageException::class);
|
|
||||||
$tenant2->put('id', 'foo');
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function all_returns_a_collection_of_tenant_objects()
|
|
||||||
{
|
|
||||||
Tenant::create('foo.localhost');
|
|
||||||
$this->assertSame('Tenant', class_basename(tenancy()->all()[0]));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function Tenant_is_bound_correctly_to_the_service_container()
|
|
||||||
{
|
|
||||||
$this->assertSame(null, app(Tenant::class));
|
|
||||||
$tenant = Tenant::create(['foo.localhost']);
|
|
||||||
app(TenantManager::class)->initializeTenancy($tenant);
|
|
||||||
$this->assertSame($tenant->id, app(Tenant::class)->id);
|
|
||||||
$this->assertSame(app(Tenant::class), app(TenantManager::class)->getTenant());
|
|
||||||
app(TenantManager::class)->endTenancy();
|
|
||||||
$this->assertSame(app(Tenant::class), app(TenantManager::class)->getTenant());
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function id_can_be_supplied_during_creation()
|
|
||||||
{
|
|
||||||
$id = 'abc' . $this->randomString();
|
|
||||||
$this->assertSame($id, Tenant::create(['foo.localhost'], ['id' => $id])->id);
|
|
||||||
$this->assertTrue(tenancy()->all()->contains(function ($tenant) use ($id) {
|
|
||||||
return $tenant->id === $id;
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function automatic_migrations_work()
|
|
||||||
{
|
|
||||||
$tenant = Tenant::create(['foo.localhost']);
|
|
||||||
tenancy()->initialize($tenant);
|
|
||||||
$this->assertFalse(\Schema::hasTable('users'));
|
|
||||||
|
|
||||||
config(['tenancy.migrate_after_creation' => true]);
|
|
||||||
$tenant2 = Tenant::create(['bar.localhost']);
|
|
||||||
tenancy()->initialize($tenant2);
|
|
||||||
$this->assertTrue(\Schema::hasTable('users'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function automatic_seeding_works()
|
|
||||||
{
|
|
||||||
config(['tenancy.migrate_after_creation' => true]);
|
|
||||||
|
|
||||||
$tenant = Tenant::create(['foo.localhost']);
|
|
||||||
tenancy()->initialize($tenant);
|
|
||||||
$this->assertSame(0, \DB::table('users')->count());
|
|
||||||
|
|
||||||
config([
|
|
||||||
'tenancy.seed_after_migration' => true,
|
|
||||||
'tenancy.seeder_parameters' => [
|
|
||||||
'--class' => ExampleSeeder::class,
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$tenant2 = Tenant::create(['bar.localhost']);
|
|
||||||
tenancy()->initialize($tenant2);
|
|
||||||
$this->assertSame(1, \DB::table('users')->count());
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function ensureTenantCanBeCreated_works()
|
|
||||||
{
|
|
||||||
$id = 'foo' . $this->randomString();
|
|
||||||
Tenant::create(['foo.localhost'], ['id' => $id]);
|
|
||||||
$this->expectException(DomainsOccupiedByOtherTenantException::class);
|
|
||||||
Tenant::create(['foo.localhost']);
|
|
||||||
|
|
||||||
$this->expectException(TenantWithThisIdAlreadyExistsException::class);
|
|
||||||
Tenant::create(['bar.localhost'], ['id' => $id]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function automigration_is_queued_when_db_creation_is_queued()
|
|
||||||
{
|
|
||||||
Queue::fake();
|
|
||||||
|
|
||||||
config([
|
|
||||||
'tenancy.queue_database_creation' => true,
|
|
||||||
'tenancy.migrate_after_creation' => true,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$tenant = Tenant::new()->save();
|
|
||||||
|
|
||||||
Queue::assertPushedWithChain(QueuedTenantDatabaseCreator::class, [
|
|
||||||
QueuedTenantDatabaseMigrator::class,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// foreach (Queue::pushedJobs() as $job) {
|
|
||||||
// $job[0]['job']->handle(); // this doesn't execute the chained job
|
|
||||||
// }
|
|
||||||
// tenancy()->initialize($tenant);
|
|
||||||
// $this->assertTrue(\Schema::hasTable('users'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function autoseeding_is_queued_when_db_creation_is_queued()
|
|
||||||
{
|
|
||||||
Queue::fake();
|
|
||||||
|
|
||||||
config([
|
|
||||||
'tenancy.queue_database_creation' => true,
|
|
||||||
'tenancy.migrate_after_creation' => true,
|
|
||||||
'tenancy.seed_after_migration' => true,
|
|
||||||
]);
|
|
||||||
|
|
||||||
Tenant::new()->save();
|
|
||||||
|
|
||||||
Queue::assertPushedWithChain(QueuedTenantDatabaseCreator::class, [
|
|
||||||
QueuedTenantDatabaseMigrator::class,
|
|
||||||
QueuedTenantDatabaseSeeder::class,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function TenantDoesNotExistException_is_thrown_when_find_is_called_on_an_id_that_does_not_belong_to_any_tenant()
|
|
||||||
{
|
|
||||||
$this->expectException(TenantDoesNotExistException::class);
|
|
||||||
tenancy()->find('gjnfdgf');
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function event_listeners_can_accept_arguments()
|
|
||||||
{
|
|
||||||
tenancy()->hook('tenant.creating', function ($tenantManager, $tenant) {
|
|
||||||
$this->assertSame('bar', $tenant->foo);
|
|
||||||
});
|
|
||||||
|
|
||||||
Tenant::new()->withData(['foo' => 'bar'])->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function tenant_creating_hook_can_be_used_to_modify_tenants_data()
|
|
||||||
{
|
|
||||||
tenancy()->hook('tenant.creating', function ($tm, Tenant $tenant) {
|
|
||||||
$tenant->put([
|
|
||||||
'foo' => 'bar',
|
|
||||||
'abc123' => 'def456',
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
$tenant = Tenant::new()->save();
|
|
||||||
|
|
||||||
$this->assertArrayHasKey('foo', $tenant->data);
|
|
||||||
$this->assertArrayHasKey('abc123', $tenant->data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function database_creation_can_be_disabled()
|
|
||||||
{
|
|
||||||
config(['tenancy.create_database' => false]);
|
|
||||||
|
|
||||||
tenancy()->hook('database.creating', function () {
|
|
||||||
$this->fail();
|
|
||||||
});
|
|
||||||
|
|
||||||
$tenant = Tenant::new()->save();
|
|
||||||
|
|
||||||
$this->assertTrue(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function database_creation_can_be_disabled_for_specific_tenants()
|
|
||||||
{
|
|
||||||
config(['tenancy.create_database' => true]);
|
|
||||||
|
|
||||||
tenancy()->hook('database.creating', function () {
|
|
||||||
$this->assertTrue(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
$tenant = Tenant::new()->save();
|
|
||||||
|
|
||||||
tenancy()->hook('database.creating', function () {
|
|
||||||
$this->fail();
|
|
||||||
});
|
|
||||||
|
|
||||||
$tenant2 = Tenant::new()->withData([
|
|
||||||
'_tenancy_create_database' => false,
|
|
||||||
])->save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -12,6 +12,7 @@ use Stancl\Tenancy\Events\TenantCreated;
|
||||||
use Stancl\Tenancy\Tests\TestCase;
|
use Stancl\Tenancy\Tests\TestCase;
|
||||||
use Stancl\Tenancy\UniqueIDGenerators\UUIDGenerator;
|
use Stancl\Tenancy\UniqueIDGenerators\UUIDGenerator;
|
||||||
use Stancl\Tenancy\Contracts;
|
use Stancl\Tenancy\Contracts;
|
||||||
|
use Stancl\Tenancy\Contracts\UniqueIdentifierGenerator;
|
||||||
|
|
||||||
class TenantModelTest extends TestCase
|
class TenantModelTest extends TestCase
|
||||||
{
|
{
|
||||||
|
|
@ -34,7 +35,7 @@ class TenantModelTest extends TestCase
|
||||||
|
|
||||||
tenancy()->initialize($tenant);
|
tenancy()->initialize($tenant);
|
||||||
|
|
||||||
$this->assertSame($tenant->id, app(Tenant::class)->id);
|
$this->assertSame($tenant->id, app(Contracts\Tenant::class)->id);
|
||||||
|
|
||||||
tenancy()->end();
|
tenancy()->end();
|
||||||
|
|
||||||
|
|
@ -100,10 +101,10 @@ class TenantModelTest extends TestCase
|
||||||
$table->bigIncrements('id')->change();
|
$table->bigIncrements('id')->change();
|
||||||
});
|
});
|
||||||
|
|
||||||
config(['tenancy.id_generator' => null]);
|
unset(app()[UniqueIdentifierGenerator::class]);
|
||||||
|
|
||||||
$tenant1 = Tenant::create();
|
$tenant1 = MyTenant::create();
|
||||||
$tenant2 = Tenant::create();
|
$tenant2 = MyTenant::create();
|
||||||
|
|
||||||
$this->assertSame(1, $tenant1->id);
|
$this->assertSame(1, $tenant1->id);
|
||||||
$this->assertSame(2, $tenant2->id);
|
$this->assertSame(2, $tenant2->id);
|
||||||
|
|
@ -137,6 +138,7 @@ class TenantModelTest extends TestCase
|
||||||
class MyTenant extends Tenant
|
class MyTenant extends Tenant
|
||||||
{
|
{
|
||||||
protected $table = 'tenants';
|
protected $table = 'tenants';
|
||||||
|
public $increments = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
class AnotherTenant extends Model implements Contracts\Tenant
|
class AnotherTenant extends Model implements Contracts\Tenant
|
||||||
|
|
@ -153,4 +155,9 @@ class AnotherTenant extends Model implements Contracts\Tenant
|
||||||
{
|
{
|
||||||
return $this->getAttribute('id');
|
return $this->getAttribute('id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function run(callable $callback)
|
||||||
|
{
|
||||||
|
$callback();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,197 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Tests;
|
|
||||||
|
|
||||||
use Stancl\Tenancy\StorageDrivers\Database\TenantRepository;
|
|
||||||
use Stancl\Tenancy\Tenant;
|
|
||||||
|
|
||||||
class TenantStorageTest extends TestCase
|
|
||||||
{
|
|
||||||
public $autoCreateTenant = true;
|
|
||||||
public $autoInitTenancy = true;
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function deleting_a_tenant_works()
|
|
||||||
{
|
|
||||||
$abc = Tenant::new()->withDomains(['abc.localhost'])->save();
|
|
||||||
$exists = function () use ($abc) {
|
|
||||||
return tenancy()->all()->contains(function ($tenant) use ($abc) {
|
|
||||||
return $tenant->id === $abc->id;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$this->assertTrue($exists());
|
|
||||||
|
|
||||||
$abc->delete();
|
|
||||||
|
|
||||||
$this->assertFalse($exists());
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function set_is_a_working_alias_for_put()
|
|
||||||
{
|
|
||||||
tenant()->set('foo', 'bar');
|
|
||||||
|
|
||||||
$this->assertSame('bar', tenant()->get('foo'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function put_works_with_key_and_value_as_separate_args()
|
|
||||||
{
|
|
||||||
tenant()->put('foo', 'bar');
|
|
||||||
|
|
||||||
$this->assertSame('bar', tenant()->get('foo'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function put_works_with_key_and_value_as_a_single_arg()
|
|
||||||
{
|
|
||||||
$keys = ['foo', 'abc'];
|
|
||||||
$vals = ['bar', 'xyz'];
|
|
||||||
$data = array_combine($keys, $vals);
|
|
||||||
|
|
||||||
tenant()->put($data);
|
|
||||||
|
|
||||||
$this->assertSame($data, tenant()->get($keys));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function put_on_the_current_tenant_pushes_the_value_into_the_tenant_property_array()
|
|
||||||
{
|
|
||||||
tenant()->put('foo', 'bar');
|
|
||||||
|
|
||||||
$this->assertSame('bar', tenancy()->getTenant('foo'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function arrays_can_be_stored()
|
|
||||||
{
|
|
||||||
tenant()->put('foo', [1, 2]);
|
|
||||||
|
|
||||||
$this->assertSame([1, 2], tenant()->get('foo'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function associative_arrays_can_be_stored()
|
|
||||||
{
|
|
||||||
$data = ['a' => 'b', 'c' => 'd'];
|
|
||||||
tenant()->put('foo', $data);
|
|
||||||
|
|
||||||
$this->assertSame($data, tenant()->get('foo'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function correct_storage_driver_is_used()
|
|
||||||
{
|
|
||||||
if (config('tenancy.storage_driver') == 'db') {
|
|
||||||
$this->assertSame('DatabaseStorageDriver', class_basename(tenancy()->storage));
|
|
||||||
} elseif (config('tenancy.storage_driver') == 'redis') {
|
|
||||||
$this->assertSame('RedisStorageDriver', class_basename(tenancy()->storage));
|
|
||||||
} else {
|
|
||||||
dd(class_basename(config('tenancy.storage_driver')));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function data_is_stored_with_correct_data_types()
|
|
||||||
{
|
|
||||||
tenant()->put('someBool', false);
|
|
||||||
$this->assertSame('boolean', gettype(tenant()->get('someBool')));
|
|
||||||
$this->assertSame('boolean', gettype(tenant()->get(['someBool'])['someBool']));
|
|
||||||
|
|
||||||
tenant()->put('someInt', 5);
|
|
||||||
$this->assertSame('integer', gettype(tenant()->get('someInt')));
|
|
||||||
$this->assertSame('integer', gettype(tenant()->get(['someInt'])['someInt']));
|
|
||||||
|
|
||||||
tenant()->put('someDouble', 11.40);
|
|
||||||
$this->assertSame('double', gettype(tenant()->get('someDouble')));
|
|
||||||
$this->assertSame('double', gettype(tenant()->get(['someDouble'])['someDouble']));
|
|
||||||
|
|
||||||
tenant()->put('string', 'foo');
|
|
||||||
$this->assertSame('string', gettype(tenant()->get('string')));
|
|
||||||
$this->assertSame('string', gettype(tenant()->get(['string'])['string']));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function tenant_repository_uses_correct_connection()
|
|
||||||
{
|
|
||||||
config(['database.connections.foo' => config('database.connections.sqlite')]);
|
|
||||||
config(['tenancy.storage_drivers.db.connection' => 'foo']);
|
|
||||||
$this->assertSame('foo', app(TenantRepository::class)->database->getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function retrieving_data_without_cache_works()
|
|
||||||
{
|
|
||||||
Tenant::new()->withDomains(['foo.localhost'])->save();
|
|
||||||
tenancy()->init('foo.localhost');
|
|
||||||
|
|
||||||
tenant()->put('foo', 'bar');
|
|
||||||
$this->assertSame('bar', tenant()->get('foo'));
|
|
||||||
$this->assertSame(['foo' => 'bar'], tenant()->get(['foo']));
|
|
||||||
|
|
||||||
tenancy()->endTenancy();
|
|
||||||
tenancy()->init('foo.localhost');
|
|
||||||
$this->assertSame('bar', tenant()->get('foo'));
|
|
||||||
$this->assertSame(['foo' => 'bar'], tenant()->get(['foo']));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function custom_columns_work_with_db_storage_driver()
|
|
||||||
{
|
|
||||||
if (config('tenancy.storage_driver') != 'db') {
|
|
||||||
$this->markTestSkipped();
|
|
||||||
}
|
|
||||||
|
|
||||||
tenancy()->endTenancy();
|
|
||||||
|
|
||||||
$this->loadMigrationsFrom([
|
|
||||||
'--path' => __DIR__ . '/Etc',
|
|
||||||
'--database' => 'central',
|
|
||||||
]);
|
|
||||||
config(['database.default' => 'sqlite']); // fix issue caused by loadMigrationsFrom
|
|
||||||
|
|
||||||
config(['tenancy.storage_drivers.db.custom_columns' => [
|
|
||||||
'foo',
|
|
||||||
]]);
|
|
||||||
|
|
||||||
tenancy()->create(['foo.localhost']);
|
|
||||||
tenancy()->init('foo.localhost');
|
|
||||||
|
|
||||||
tenant()->put('foo', '111');
|
|
||||||
$this->assertSame('111', tenant()->get('foo'));
|
|
||||||
|
|
||||||
tenant()->put(['foo' => 'bar', 'abc' => 'xyz']);
|
|
||||||
$this->assertSame(['foo' => 'bar', 'abc' => 'xyz'], tenant()->get(['foo', 'abc']));
|
|
||||||
|
|
||||||
$this->assertSame('bar', \DB::connection('central')->table('tenants')->where('id', tenant('id'))->first()->foo);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function custom_columns_can_be_used_on_tenant_create()
|
|
||||||
{
|
|
||||||
if (config('tenancy.storage_driver') != 'db') {
|
|
||||||
$this->markTestSkipped();
|
|
||||||
}
|
|
||||||
|
|
||||||
tenancy()->endTenancy();
|
|
||||||
|
|
||||||
$this->loadMigrationsFrom([
|
|
||||||
'--path' => __DIR__ . '/Etc',
|
|
||||||
'--database' => 'central',
|
|
||||||
]);
|
|
||||||
config(['database.default' => 'sqlite']); // fix issue caused by loadMigrationsFrom
|
|
||||||
|
|
||||||
config(['tenancy.storage_drivers.db.custom_columns' => [
|
|
||||||
'foo',
|
|
||||||
]]);
|
|
||||||
|
|
||||||
tenancy()->create(['foo.localhost'], ['foo' => 'bar', 'abc' => 'xyz']);
|
|
||||||
tenancy()->init('foo.localhost');
|
|
||||||
|
|
||||||
$this->assertSame(['foo' => 'bar', 'abc' => 'xyz'], tenant()->get(['foo', 'abc']));
|
|
||||||
$this->assertSame('bar', \DB::connection('central')->table('tenants')->where('id', tenant('id'))->first()->foo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Tests\Traits;
|
|
||||||
|
|
||||||
use Stancl\Tenancy\Tenant;
|
|
||||||
use Stancl\Tenancy\Tests\TestCase;
|
|
||||||
|
|
||||||
class TenantAwareCommandTest extends TestCase
|
|
||||||
{
|
|
||||||
public $autoCreateTenant = false;
|
|
||||||
public $autoInitTenancy = false;
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function commands_run_globally_are_tenant_aware_and_return_valid_exit_code()
|
|
||||||
{
|
|
||||||
$tenant1 = Tenant::new()->save();
|
|
||||||
$tenant2 = Tenant::new()->save();
|
|
||||||
\Artisan::call('tenants:migrate', [
|
|
||||||
'--tenants' => [$tenant1['id'], $tenant2['id']],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->artisan('user:add')
|
|
||||||
->assertExitCode(0);
|
|
||||||
|
|
||||||
tenancy()->initializeTenancy($tenant1);
|
|
||||||
$this->assertNotEmpty(\DB::table('users')->get());
|
|
||||||
tenancy()->end();
|
|
||||||
|
|
||||||
tenancy()->initializeTenancy($tenant2);
|
|
||||||
$this->assertNotEmpty(\DB::table('users')->get());
|
|
||||||
tenancy()->end();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
// test DB creation, migration, seeding
|
|
||||||
|
|
@ -1,136 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Tests\v3;
|
|
||||||
|
|
||||||
use Illuminate\Bus\Queueable;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
|
||||||
use Illuminate\Queue\Events\JobProcessing;
|
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
|
||||||
use Illuminate\Support\Facades\Event;
|
|
||||||
use Spatie\Valuestore\Valuestore;
|
|
||||||
use Stancl\Tenancy\Database\Models\Tenant;
|
|
||||||
use Stancl\Tenancy\Events\Listeners\BootstrapTenancy;
|
|
||||||
use Stancl\Tenancy\Events\TenancyInitialized;
|
|
||||||
use Stancl\Tenancy\TenancyBootstrappers\QueueTenancyBootstrapper;
|
|
||||||
use Stancl\Tenancy\Tests\TestCase;
|
|
||||||
|
|
||||||
class QueueTest extends TestCase
|
|
||||||
{
|
|
||||||
public $mockConsoleOutput = false;
|
|
||||||
|
|
||||||
/** @var Valuestore */
|
|
||||||
protected $valuestore;
|
|
||||||
|
|
||||||
public function setUp(): void
|
|
||||||
{
|
|
||||||
parent::setUp();
|
|
||||||
|
|
||||||
config([
|
|
||||||
'tenancy.bootstrappers' => [
|
|
||||||
QueueTenancyBootstrapper::class,
|
|
||||||
],
|
|
||||||
'queue.default' => 'redis',
|
|
||||||
]);
|
|
||||||
|
|
||||||
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
|
|
||||||
|
|
||||||
$this->valuestore = Valuestore::make(__DIR__ . '/../Etc/tmp/queuetest.json')->flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function tenant_id_is_passed_to_tenant_queues()
|
|
||||||
{
|
|
||||||
$tenant = Tenant::create();
|
|
||||||
|
|
||||||
tenancy()->initialize($tenant);
|
|
||||||
|
|
||||||
Event::fake();
|
|
||||||
|
|
||||||
dispatch(new TestJob($this->valuestore));
|
|
||||||
|
|
||||||
Event::assertDispatched(JobProcessing::class, function ($event) {
|
|
||||||
return $event->job->payload()['tenant_id'] === tenant('id');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function tenant_id_is_not_passed_to_central_queues()
|
|
||||||
{
|
|
||||||
$tenant = Tenant::create();
|
|
||||||
|
|
||||||
tenancy()->initialize($tenant);
|
|
||||||
|
|
||||||
Event::fake();
|
|
||||||
|
|
||||||
config(['queue.connections.central' => [
|
|
||||||
'driver' => 'sync',
|
|
||||||
'central' => true,
|
|
||||||
]]);
|
|
||||||
|
|
||||||
dispatch(new TestJob($this->valuestore))->onConnection('central');
|
|
||||||
|
|
||||||
Event::assertDispatched(JobProcessing::class, function ($event) {
|
|
||||||
return ! isset($event->job->payload()['tenant_id']);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function tenancy_is_initialized_inside_queues()
|
|
||||||
{
|
|
||||||
$tenant = Tenant::create([
|
|
||||||
'id' => 'acme',
|
|
||||||
]);
|
|
||||||
|
|
||||||
tenancy()->initialize($tenant);
|
|
||||||
|
|
||||||
dispatch(new TestJob($this->valuestore));
|
|
||||||
|
|
||||||
$this->assertFalse($this->valuestore->has('tenant_id'));
|
|
||||||
$this->artisan('queue:work --once');
|
|
||||||
|
|
||||||
$this->assertSame('The current tenant id is: acme', $this->valuestore->get('tenant_id'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function the_tenant_used_by_the_job_doesnt_change_when_the_current_tenant_changes()
|
|
||||||
{
|
|
||||||
$tenant1 = Tenant::create([
|
|
||||||
'id' => 'acme',
|
|
||||||
]);
|
|
||||||
|
|
||||||
tenancy()->initialize($tenant1);
|
|
||||||
|
|
||||||
dispatch(new TestJob($this->valuestore));
|
|
||||||
|
|
||||||
$tenant2 = Tenant::create([
|
|
||||||
'id' => 'foobar',
|
|
||||||
]);
|
|
||||||
|
|
||||||
tenancy()->initialize($tenant2);
|
|
||||||
|
|
||||||
$this->assertFalse($this->valuestore->has('tenant_id'));
|
|
||||||
$this->artisan('queue:work --once');
|
|
||||||
|
|
||||||
$this->assertSame('The current tenant id is: acme', $this->valuestore->get('tenant_id'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TestJob implements ShouldQueue
|
|
||||||
{
|
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
||||||
|
|
||||||
/** @var Valuestore */
|
|
||||||
protected $valuestore;
|
|
||||||
|
|
||||||
public function __construct(Valuestore $valuestore)
|
|
||||||
{
|
|
||||||
$this->valuestore = $valuestore;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
$this->valuestore->put('tenant_id', "The current tenant id is: " . tenant('id'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue