mirror of
https://github.com/archtechx/tenancy.git
synced 2025-12-12 14:14:04 +00:00
Rewrite old tests
This commit is contained in:
parent
64383b4c56
commit
89936187ce
71 changed files with 698 additions and 3203 deletions
|
|
@ -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');
|
||||
|
||||
$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'))) {
|
||||
file_put_contents(base_path('routes/tenant.php'), file_get_contents(__DIR__ . '/../../assets/tenant_routes.php.stub'));
|
||||
$this->info('✔️ Created routes/tenant.php');
|
||||
|
|
@ -54,15 +43,13 @@ class Install extends Command
|
|||
$this->info('Found routes/tenant.php.');
|
||||
}
|
||||
|
||||
$this->line('');
|
||||
$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', [
|
||||
'--provider' => 'Stancl\Tenancy\TenancyServiceProvider',
|
||||
'--tag' => 'migrations',
|
||||
]);
|
||||
$this->info('✔️ Created migrations. Remember to run [php artisan migrate]!');
|
||||
}
|
||||
// todo tenancy SP stub
|
||||
|
||||
$this->callSilent('vendor:publish', [
|
||||
'--provider' => 'Stancl\Tenancy\TenancyServiceProvider',
|
||||
'--tag' => 'migrations',
|
||||
]);
|
||||
$this->info('✔️ Created migrations. Remember to run [php artisan migrate]!');
|
||||
|
||||
if (! is_dir(database_path('migrations/tenant'))) {
|
||||
mkdir(database_path('migrations/tenant'));
|
||||
|
|
@ -71,34 +58,4 @@ class Install extends Command
|
|||
|
||||
$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,20 +57,13 @@ class Migrate extends MigrateCommand
|
|||
return;
|
||||
}
|
||||
|
||||
tenancy()
|
||||
->query()
|
||||
->when($this->option('tenants'), function ($query) {
|
||||
$query->whereIn(tenancy()->model()->getTenantKeyName(), $this->option('tenants'));
|
||||
})
|
||||
->each(function (TenantWithDatabase $tenant) {
|
||||
$this->line("Tenant: {$tenant['id']}");
|
||||
tenancy()->runForMultiple($this->option('tenants'), function ($tenant) {
|
||||
$this->line("Tenant: {$tenant['id']}");
|
||||
|
||||
// Migrate
|
||||
parent::handle();
|
||||
|
||||
$tenant->run(function () {
|
||||
// Migrate
|
||||
parent::handle();
|
||||
});
|
||||
|
||||
event(new DatabaseMigrated($tenant));
|
||||
});
|
||||
event(new DatabaseMigrated($tenant));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||
namespace Stancl\Tenancy\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Stancl\Tenancy\Contracts\TenantWithDatabase;
|
||||
use Stancl\Tenancy\Traits\DealsWithMigrations;
|
||||
use Stancl\Tenancy\Traits\HasATenantsOption;
|
||||
|
||||
|
|
@ -33,22 +34,18 @@ final class MigrateFresh extends Command
|
|||
*/
|
||||
public function handle()
|
||||
{
|
||||
tenancy()->all($this->option('tenants'))->each(function ($tenant) {
|
||||
$this->line("Tenant: {$tenant->id}");
|
||||
tenancy()->runForMultiple($this->option('tenants'), function ($tenant) {
|
||||
$this->info('Dropping tables.');
|
||||
$this->call('db:wipe', array_filter([
|
||||
'--database' => 'tenant',
|
||||
'--force' => true,
|
||||
]));
|
||||
|
||||
$tenant->run(function ($tenant) {
|
||||
$this->info('Dropping tables.');
|
||||
$this->call('db:wipe', array_filter([
|
||||
'--database' => 'tenant',
|
||||
'--force' => true,
|
||||
]));
|
||||
|
||||
$this->info('Migrating.');
|
||||
$this->callSilent('tenants:migrate', [
|
||||
'--tenants' => [$tenant->id],
|
||||
'--force' => true,
|
||||
]);
|
||||
});
|
||||
$this->info('Migrating.');
|
||||
$this->callSilent('tenants:migrate', [
|
||||
'--tenants' => [$tenant->id],
|
||||
'--force' => true,
|
||||
]);
|
||||
});
|
||||
|
||||
$this->info('Done.');
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ namespace Stancl\Tenancy\Commands;
|
|||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\Console\Migrations\RollbackCommand;
|
||||
use Illuminate\Database\Migrations\Migrator;
|
||||
use Stancl\Tenancy\Contracts\TenantWithDatabase;
|
||||
use Stancl\Tenancy\DatabaseManager;
|
||||
use Stancl\Tenancy\Traits\DealsWithMigrations;
|
||||
use Stancl\Tenancy\Traits\HasATenantsOption;
|
||||
|
|
@ -55,13 +56,13 @@ class Rollback extends RollbackCommand
|
|||
return;
|
||||
}
|
||||
|
||||
tenancy()->all($this->option('tenants'))->each(function ($tenant) {
|
||||
tenancy()->runForMultiple($this->option('tenants'), function ($tenant) {
|
||||
$this->line("Tenant: {$tenant['id']}");
|
||||
|
||||
$tenant->run(function () {
|
||||
// Rollback
|
||||
parent::handle();
|
||||
});
|
||||
// Rollback
|
||||
parent::handle();
|
||||
|
||||
// todo DatabaseRolledBack event
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,8 +32,7 @@ class Run extends Command
|
|||
*/
|
||||
public function handle()
|
||||
{
|
||||
$originalTenant = tenancy()->getTenant();
|
||||
tenancy()->all($this->option('tenants'))->each(function ($tenant) {
|
||||
tenancy()->runForMultiple($this->option('tenants'), function ($tenant) {
|
||||
$this->line("Tenant: {$tenant['id']}");
|
||||
tenancy()->initialize($tenant);
|
||||
|
||||
|
|
@ -54,12 +53,6 @@ class Run extends Command
|
|||
|
||||
// Run command
|
||||
$this->call($this->argument('commandname'), array_merge($arguments, $options));
|
||||
|
||||
tenancy()->endTenancy();
|
||||
});
|
||||
|
||||
if ($originalTenant) {
|
||||
tenancy()->initialize($originalTenant);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,18 +55,11 @@ class Seed extends SeedCommand
|
|||
return;
|
||||
}
|
||||
|
||||
tenancy()
|
||||
->query()
|
||||
->when($this->option('tenants'), function ($query) {
|
||||
$query->whereIn(tenancy()->model()->getTenantKeyName(), $this->option('tenants'));
|
||||
})
|
||||
->each(function (TenantWithDatabase $tenant) {
|
||||
tenancy()->runForMultiple($this->option('tenants'), function ($tenant) {
|
||||
$this->line("Tenant: {$tenant['id']}");
|
||||
|
||||
$tenant->run(function () {
|
||||
// Seed
|
||||
parent::handle();
|
||||
});
|
||||
// Seed
|
||||
parent::handle();
|
||||
|
||||
event(new DatabaseSeeded($tenant));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||
namespace Stancl\Tenancy\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Stancl\Tenancy\Contracts\Tenant;
|
||||
|
||||
class TenantList extends Command
|
||||
{
|
||||
|
|
@ -30,8 +31,14 @@ class TenantList extends Command
|
|||
public function handle()
|
||||
{
|
||||
$this->info('Listing all tenants.');
|
||||
tenancy()->all()->each(function ($tenant) {
|
||||
$this->line("[Tenant] id: {$tenant['id']} @ " . implode('; ', $tenant->domains));
|
||||
tenancy()
|
||||
->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;
|
||||
|
||||
use Stancl\Tenancy\TenantManager;
|
||||
use Stancl\Tenancy\Facades\Tenancy;
|
||||
|
||||
/** Additional features, like Telescope tags and tenant redirects. */
|
||||
interface Feature
|
||||
{
|
||||
public function bootstrap(TenantManager $tenantManager): void;
|
||||
public function bootstrap(Tenancy $tenancy): void;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
// todo rename methods
|
||||
public function start(Tenant $tenant);
|
||||
|
||||
public function end();
|
||||
|
|
|
|||
|
|
@ -7,4 +7,7 @@ use Stancl\Tenancy\DatabaseConfig;
|
|||
interface TenantWithDatabase extends Tenant
|
||||
{
|
||||
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
|
||||
{
|
||||
public static $tenancyMiddleware = 'Stancl\Tenancy\Middleware\InitializeTenancyByDomain';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('tenancy');
|
||||
$this->middleware(static::$tenancyMiddleware);
|
||||
}
|
||||
|
||||
public function asset($path)
|
||||
|
|
|
|||
|
|
@ -6,9 +6,24 @@ namespace Stancl\Tenancy\Database\Models\Concerns;
|
|||
// todo rename
|
||||
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()
|
||||
{
|
||||
$encode = function (self $model) {
|
||||
if ($model->dataEncodingStatus === 'encoded') {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($model->getAttributes() as $key => $value) {
|
||||
if (! in_array($key, static::getCustomColums())) {
|
||||
$current = $model->getAttribute(static::getDataColumn()) ?? [];
|
||||
|
|
@ -20,19 +35,64 @@ trait HasADataColumn
|
|||
unset($model->attributes[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
$model->dataEncodingStatus = 'encoded';
|
||||
};
|
||||
|
||||
$decode = function (self $model) {
|
||||
if ($model->dataEncodingStatus === 'decoded') {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($model->getAttribute(static::getDataColumn()) ?? [] as $key => $value) {
|
||||
$model->setAttribute($key, $value);
|
||||
}
|
||||
|
||||
$model->setAttribute(static::getDataColumn(), null);
|
||||
|
||||
$model->dataEncodingStatus = 'decoded';
|
||||
};
|
||||
|
||||
static::saving($encode);
|
||||
static::saved($decode);
|
||||
static::retrieved($decode);
|
||||
static::registerPriorityListener('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()
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use Stancl\Tenancy\Contracts;
|
|||
// todo @property
|
||||
class Tenant extends Model implements Contracts\TenantWithDatabase
|
||||
{
|
||||
use Concerns\CentralConnection, Concerns\HasADataColumn, Concerns\GeneratesIds, Concerns\HasADataColumn {
|
||||
use Concerns\CentralConnection, Concerns\HasADataColumn, Concerns\GeneratesIds, Concerns\HasADataColumn {
|
||||
Concerns\HasADataColumn::getCasts as dataColumnCasts;
|
||||
}
|
||||
|
||||
|
|
@ -41,14 +41,11 @@ class Tenant extends Model implements Contracts\TenantWithDatabase
|
|||
|
||||
public static function internalPrefix(): string
|
||||
{
|
||||
return config('tenancy.internal_column_prefix');
|
||||
return config('tenancy.internal_prefix');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an internal key.
|
||||
*
|
||||
* @param string $key
|
||||
* @return mixed
|
||||
*/
|
||||
public function getInternal(string $key)
|
||||
{
|
||||
|
|
@ -57,14 +54,10 @@ class Tenant extends Model implements Contracts\TenantWithDatabase
|
|||
|
||||
/**
|
||||
* Set internal key.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @return $this
|
||||
*/
|
||||
public function setInternal(string $key, $value)
|
||||
{
|
||||
$this->setAttribute($key, $value);
|
||||
$this->setAttribute(static::internalPrefix() . $key, $value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,84 +82,10 @@ class DatabaseManager
|
|||
* @throws DatabaseManagerNotRegisteredException
|
||||
* @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())) {
|
||||
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 Stancl\Tenancy\Contracts\Feature;
|
||||
use Stancl\Tenancy\TenantManager;
|
||||
use Stancl\Tenancy\Tenancy;
|
||||
|
||||
class CrossDomainRedirect implements Feature
|
||||
{
|
||||
public function bootstrap(TenantManager $tenantManager): void
|
||||
public function bootstrap(Tenancy $tenancy): void
|
||||
{
|
||||
RedirectResponse::macro('domain', function (string $domain) {
|
||||
/** @var RedirectResponse $this */
|
||||
|
||||
// replace first occurance of hostname fragment with $domain
|
||||
$url = $this->getTargetUrl();
|
||||
$hostname = parse_url($url, PHP_URL_HOST);
|
||||
|
|
|
|||
|
|
@ -8,8 +8,9 @@ use Laravel\Telescope\IncomingEntry;
|
|||
use Laravel\Telescope\Telescope;
|
||||
use Stancl\Tenancy\Contracts\Feature;
|
||||
use Stancl\Tenancy\Middleware\PreventAccessFromTenantDomains;
|
||||
use Stancl\Tenancy\TenantManager;
|
||||
use Stancl\Tenancy\Tenancy;
|
||||
|
||||
// todo rewrite this
|
||||
class TelescopeTags implements Feature
|
||||
{
|
||||
/** @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)) {
|
||||
return;
|
||||
|
|
@ -35,6 +36,7 @@ class TelescopeTags implements Feature
|
|||
return $tags;
|
||||
}
|
||||
|
||||
// todo lines below
|
||||
$tenantRoute = PreventAccessFromTenantDomains::routeHasMiddleware(request()->route(), 'tenancy')
|
||||
|| PreventAccessFromTenantDomains::routeHasMiddleware(request()->route(), 'universal');
|
||||
|
||||
|
|
|
|||
|
|
@ -6,9 +6,11 @@ namespace Stancl\Tenancy\Features;
|
|||
|
||||
use Illuminate\Contracts\Config\Repository;
|
||||
use Stancl\Tenancy\Contracts\Feature;
|
||||
use Stancl\Tenancy\Tenancy;
|
||||
use Stancl\Tenancy\Tenant;
|
||||
use Stancl\Tenancy\TenantManager;
|
||||
|
||||
// todo rewrite this
|
||||
class TenantConfig implements Feature
|
||||
{
|
||||
/** @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) {
|
||||
$this->setTenantConfig($manager->getTenant());
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ use Stancl\Tenancy\Contracts\Feature;
|
|||
use Stancl\Tenancy\Tenant;
|
||||
use Stancl\Tenancy\TenantManager;
|
||||
|
||||
// todo rewrite this
|
||||
class Timestamps implements Feature
|
||||
{
|
||||
/** @var Repository */
|
||||
|
|
|
|||
|
|
@ -27,7 +27,8 @@ class CreateDatabase implements ShouldQueue
|
|||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,26 +6,28 @@ namespace Stancl\Tenancy\Middleware;
|
|||
|
||||
use Closure;
|
||||
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 */
|
||||
protected $header;
|
||||
public static $header = 'X-Tenant';
|
||||
|
||||
/** @var string|null */
|
||||
protected $queryParameter;
|
||||
public static $queryParameter = 'tenant';
|
||||
|
||||
/** @var callable */
|
||||
protected $onFail;
|
||||
/** @var Tenancy */
|
||||
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->queryParameter = $queryParameter;
|
||||
$this->onFail = $onFail ?? function ($e) {
|
||||
throw $e;
|
||||
};
|
||||
$this->tenancy = $tenancy;
|
||||
$this->resolver = $resolver;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -38,33 +40,21 @@ class InitializeTenancyByRequestData
|
|||
public function handle($request, Closure $next)
|
||||
{
|
||||
if ($request->method() !== 'OPTIONS') {
|
||||
try {
|
||||
$this->initializeTenancy($request);
|
||||
} catch (TenantCouldNotBeIdentifiedException $e) {
|
||||
return ($this->onFail)($e, $request, $next);
|
||||
}
|
||||
return $this->initializeTenancy($request, $next, $this->getPayload($request));
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
protected function initializeTenancy(Request $request)
|
||||
protected function getPayload(Request $request): ?string
|
||||
{
|
||||
if (tenancy()->initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tenant = null;
|
||||
if ($this->header && $request->hasHeader($this->header)) {
|
||||
$tenant = $request->header($this->header);
|
||||
} elseif ($this->queryParameter && $request->has($this->queryParameter)) {
|
||||
$tenant = $request->get($this->queryParameter);
|
||||
if (static::$header && $request->hasHeader(static::$header)) {
|
||||
$tenant = $request->header(static::$header);
|
||||
} elseif (static::$queryParameter && $request->has(static::$queryParameter)) {
|
||||
$tenant = $request->get(static::$queryParameter);
|
||||
}
|
||||
|
||||
if (! $tenant) {
|
||||
throw new TenantCouldNotBeIdentifiedException($request->getHost());
|
||||
}
|
||||
|
||||
tenancy()->initialize(tenancy()->find($tenant));
|
||||
return $tenant;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class PathTenantResolver implements TenantResolver
|
|||
if ($id = $route->parameter(static::$tenantParameterName)) {
|
||||
$route->forgetParameter(static::$tenantParameterName);
|
||||
|
||||
if ($tenant = config('tenancy.tenant_model')::find($id)) {
|
||||
if ($tenant = tenancy()->find($id)) {
|
||||
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';
|
||||
}
|
||||
}
|
||||
|
|
@ -33,6 +33,10 @@ class Tenancy
|
|||
|
||||
public function end(): void
|
||||
{
|
||||
if (! $this->initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->initialized = false;
|
||||
|
||||
event(new Events\TenancyEnded($this));
|
||||
|
|
@ -68,4 +72,38 @@ class Tenancy
|
|||
{
|
||||
return $this->model()->find($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a callback for multiple tenants.
|
||||
* More performant than running $tenant->run() one by one.
|
||||
*
|
||||
* @param Tenant[]|\Illuminate\Support\Collection|string[]|null $tenants
|
||||
* @param callable $callback
|
||||
* @return void
|
||||
*/
|
||||
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;
|
||||
|
||||
foreach ($tenants as $tenant) {
|
||||
if (is_string($tenant)) {
|
||||
$tenant = $this->find($tenant);
|
||||
}
|
||||
|
||||
$this->initialize($tenant);
|
||||
$callback($tenant);
|
||||
}
|
||||
|
||||
if ($originalTenant) {
|
||||
$this->initialize($originalTenant);
|
||||
} else {
|
||||
$this->end();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,13 @@ class TenancyServiceProvider extends ServiceProvider
|
|||
$this->app->bind(Contracts\UniqueIdentifierGenerator::class, $this->app['config']['tenancy.id_generator']);
|
||||
$this->app->singleton(DatabaseManager::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) {
|
||||
return $app[Tenancy::class]->tenant;
|
||||
});
|
||||
|
|
@ -63,7 +70,6 @@ class TenancyServiceProvider extends ServiceProvider
|
|||
Commands\Migrate::class,
|
||||
Commands\Rollback::class,
|
||||
Commands\TenantList::class,
|
||||
Commands\CreateTenant::class,
|
||||
Commands\MigrateFresh::class,
|
||||
]);
|
||||
|
||||
|
|
|
|||
|
|
@ -21,11 +21,6 @@ class MySQLDatabaseManager implements TenantDatabaseManager, CanSetConnection
|
|||
$this->connection = $config->get('tenancy.database_manager_connections.mysql');
|
||||
}
|
||||
|
||||
public function getSeparator(): string
|
||||
{
|
||||
return 'database';
|
||||
}
|
||||
|
||||
protected function database(): Connection
|
||||
{
|
||||
return DB::connection($this->connection);
|
||||
|
|
|
|||
|
|
@ -21,11 +21,6 @@ class PostgreSQLDatabaseManager implements TenantDatabaseManager, CanSetConnecti
|
|||
$this->connection = $config->get('tenancy.database_manager_connections.pgsql');
|
||||
}
|
||||
|
||||
public function getSeparator(): string
|
||||
{
|
||||
return 'database';
|
||||
}
|
||||
|
||||
protected function database(): Connection
|
||||
{
|
||||
return DB::connection($this->connection);
|
||||
|
|
|
|||
|
|
@ -21,11 +21,6 @@ class PostgreSQLSchemaManager implements TenantDatabaseManager, CanSetConnection
|
|||
$this->connection = $config->get('tenancy.database_manager_connections.pgsql');
|
||||
}
|
||||
|
||||
public function getSeparator(): string
|
||||
{
|
||||
return 'schema';
|
||||
}
|
||||
|
||||
protected function database(): Connection
|
||||
{
|
||||
return DB::connection($this->connection);
|
||||
|
|
|
|||
|
|
@ -9,11 +9,6 @@ use Stancl\Tenancy\Contracts\TenantWithDatabase;
|
|||
|
||||
class SQLiteDatabaseManager implements TenantDatabaseManager
|
||||
{
|
||||
public function getSeparator(): string
|
||||
{
|
||||
return 'database';
|
||||
}
|
||||
|
||||
public function createDatabase(TenantWithDatabase $tenant): bool
|
||||
{
|
||||
try {
|
||||
|
|
@ -39,7 +34,7 @@ class SQLiteDatabaseManager implements TenantDatabaseManager
|
|||
|
||||
public function makeConnectionConfig(array $baseConfig, string $databaseName): array
|
||||
{
|
||||
$baseConfig['database'] = database_path($databaseName);;
|
||||
$baseConfig['database'] = database_path($databaseName);
|
||||
|
||||
return $baseConfig;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ class TenantRouteServiceProvider extends RouteServiceProvider
|
|||
{
|
||||
$this->app->booted(function () {
|
||||
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')
|
||||
->group(base_path('routes/tenant.php'));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ declare(strict_types=1);
|
|||
|
||||
namespace Stancl\Tenancy\Traits;
|
||||
|
||||
use Stancl\Tenancy\Tenant;
|
||||
use Stancl\Tenancy\Contracts\TenantWithDatabase;
|
||||
|
||||
trait CreatesDatabaseUsers
|
||||
{
|
||||
public function createDatabase(Tenant $tenant): bool
|
||||
public function createDatabase(TenantWithDatabase $tenant): bool
|
||||
{
|
||||
return $this->database()->transaction(function () use ($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) {
|
||||
parent::deleteDatabase($tenant);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Stancl\Tenancy\Traits;
|
||||
|
||||
use Illuminate\Support\LazyCollection;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
trait HasATenantsOption
|
||||
|
|
@ -15,9 +16,14 @@ trait HasATenantsOption
|
|||
], 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()
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
* @return Tenant[]
|
||||
* @return Tenant[]|mixed
|
||||
*/
|
||||
abstract protected function getTenants(): array;
|
||||
abstract protected function getTenants();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
Route::middleware(['tenancy'])->group(function () {
|
||||
Route::get('/tenancy/assets/{path?}', 'Stancl\Tenancy\Controllers\TenantAssetsController@asset')
|
||||
->where('path', '(.*)')
|
||||
->name('stancl.tenancy.asset');
|
||||
});
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::get('/tenancy/assets/{path?}', 'Stancl\Tenancy\Controllers\TenantAssetsController@asset')
|
||||
->where('path', '(.*)')
|
||||
->name('stancl.tenancy.asset');
|
||||
Loading…
Add table
Add a link
Reference in a new issue