mirror of
https://github.com/archtechx/tenancy.git
synced 2026-02-05 16:14:03 +00:00
Merge branch '2.x' into laravel-7-support
This commit is contained in:
commit
ab8de77529
16 changed files with 571 additions and 26 deletions
27
DONATIONS.md
Normal file
27
DONATIONS.md
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
# Donations
|
||||||
|
|
||||||
|
Any donations will be greatly appreciated and help ensure that the package is developed and maintained in the future.
|
||||||
|
|
||||||
|
If you're a company and this package is helping you make money, please consider donating.
|
||||||
|
|
||||||
|
### PayPal
|
||||||
|
|
||||||
|
PayPal is the preferable donation method as it comes with the lowest fees.
|
||||||
|
|
||||||
|
You can donate here: [https://paypal.me/samuelstancl](https://paypal.me/samuelstancl)
|
||||||
|
|
||||||
|
### Other methods
|
||||||
|
|
||||||
|
If you can't use PayPal, you may use my Gumroad link. This comes with higher fees but any donations will be greatly appreciated nonetheless.
|
||||||
|
|
||||||
|
You can donate here: [https://gumroad.com/l/tenancy](https://gumroad.com/l/tenancy)
|
||||||
|
|
||||||
|
### Legal
|
||||||
|
|
||||||
|
If you're a business making a donation, you may want an invoice.
|
||||||
|
|
||||||
|
Contact me on [samuel.stancl@gmail.com](mailto:samuel.stancl@gmail.com) and let me know what you need to have on the invoice and I will make it happen.
|
||||||
|
|
||||||
|
### Thank you!
|
||||||
|
|
||||||
|
Again, any donations are greatly appreciated. Thanks to everyone who has donated, you're helping keep this package maintained.
|
||||||
|
|
@ -15,13 +15,19 @@ RUN apt-get install -y curl zip unzip git sqlite3 \
|
||||||
php7.4-imap php7.4-mysql php7.4-mbstring \
|
php7.4-imap php7.4-mysql php7.4-mbstring \
|
||||||
php7.4-xml php7.4-zip php7.4-bcmath php7.4-soap \
|
php7.4-xml php7.4-zip php7.4-bcmath php7.4-soap \
|
||||||
php7.4-intl php7.4-readline php7.4-xdebug \
|
php7.4-intl php7.4-readline php7.4-xdebug \
|
||||||
php7.4-redis php-msgpack php-igbinary \
|
php-msgpack php-igbinary \
|
||||||
&& php -r "readfile('http://getcomposer.org/installer');" | php -- --install-dir=/usr/bin/ --filename=composer \
|
&& php -r "readfile('http://getcomposer.org/installer');" | php -- --install-dir=/usr/bin/ --filename=composer \
|
||||||
&& mkdir /run/php
|
&& mkdir /run/php
|
||||||
|
|
||||||
RUN apt-get install -y python3
|
RUN apt-get install -y python3
|
||||||
|
|
||||||
RUN apt-get install -y php7.4-dev php-pear
|
RUN apt-get install -y php7.4-dev php-pear
|
||||||
|
|
||||||
|
RUN pecl install redis-4.3.0
|
||||||
|
RUN echo "extension=redis.so" > /etc/php/7.2/mods-available/redis.ini
|
||||||
|
RUN ln -sf /etc/php/7.2/mods-available/redis.ini /etc/php/7.2/fpm/conf.d/20-redis.ini
|
||||||
|
RUN ln -sf /etc/php/7.2/mods-available/redis.ini /etc/php/7.2/cli/conf.d/20-redis.ini
|
||||||
|
|
||||||
RUN pecl install xdebug
|
RUN pecl install xdebug
|
||||||
RUN echo 'zend_extension=/usr/lib/php/20190902/xdebug.so' > /etc/php/7.4/cli/conf.d/20-xdebug.ini
|
RUN echo 'zend_extension=/usr/lib/php/20190902/xdebug.so' > /etc/php/7.4/cli/conf.d/20-xdebug.ini
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
<a href="https://packagist.org/packages/stancl/tenancy"><img alt="Latest Stable Version" src="https://poser.pugx.org/stancl/tenancy/version"></a>
|
<a href="https://packagist.org/packages/stancl/tenancy"><img alt="Latest Stable Version" src="https://poser.pugx.org/stancl/tenancy/version"></a>
|
||||||
<a href="https://travis-ci.com/stancl/tenancy"><img alt="Travis CI build" src="https://travis-ci.com/stancl/tenancy.svg?branch=2.x"></a>
|
<a href="https://travis-ci.com/stancl/tenancy"><img alt="Travis CI build" src="https://travis-ci.com/stancl/tenancy.svg?branch=2.x"></a>
|
||||||
<a href="https://codecov.io/gh/stancl/tenancy"><img alt="codecov" src="https://codecov.io/gh/stancl/tenancy/branch/2.x/graph/badge.svg"></a>
|
<a href="https://codecov.io/gh/stancl/tenancy"><img alt="codecov" src="https://codecov.io/gh/stancl/tenancy/branch/2.x/graph/badge.svg"></a>
|
||||||
<a href="https://gumroad.com/l/tenancy"><img alt="Donate" src="https://img.shields.io/badge/Donate-%3C3-red"></a>
|
<a href="https://github.com/stancl/tenancy/blob/2.x/DONATIONS.md"><img alt="Donate" src="https://img.shields.io/badge/Donate-%3C3-red"></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h1><a href="https://tenancy.samuelstancl.me">stancl/tenancy</a></h1>
|
<h1><a href="https://tenancy.samuelstancl.me">stancl/tenancy</a></h1>
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@ return [
|
||||||
'tenants' => 'tenants',
|
'tenants' => 'tenants',
|
||||||
'domains' => 'domains',
|
'domains' => 'domains',
|
||||||
],
|
],
|
||||||
|
'cache_store' => false, // What store should be used to cache tenant resolution. Set to false to disable cache, null to use default store, or a string with a specific cache store name.
|
||||||
|
'cache_ttl' => 3600, // seconds
|
||||||
],
|
],
|
||||||
'redis' => [
|
'redis' => [
|
||||||
'driver' => Stancl\Tenancy\StorageDrivers\RedisStorageDriver::class,
|
'driver' => Stancl\Tenancy\StorageDrivers\RedisStorageDriver::class,
|
||||||
|
|
@ -30,6 +32,7 @@ return [
|
||||||
'based_on' => null, // The connection that will be used as a base for the dynamically created tenant connection. Set to null to use the default connection.
|
'based_on' => null, // The connection that will be used as a base for the dynamically created tenant connection. Set to null to use the default connection.
|
||||||
'prefix' => 'tenant',
|
'prefix' => 'tenant',
|
||||||
'suffix' => '',
|
'suffix' => '',
|
||||||
|
'separate_by' => 'database', // database or schema (only supported by pgsql)
|
||||||
],
|
],
|
||||||
'redis' => [
|
'redis' => [
|
||||||
'prefix_base' => 'tenant',
|
'prefix_base' => 'tenant',
|
||||||
|
|
@ -61,6 +64,7 @@ return [
|
||||||
'sqlite' => Stancl\Tenancy\TenantDatabaseManagers\SQLiteDatabaseManager::class,
|
'sqlite' => Stancl\Tenancy\TenantDatabaseManagers\SQLiteDatabaseManager::class,
|
||||||
'mysql' => Stancl\Tenancy\TenantDatabaseManagers\MySQLDatabaseManager::class,
|
'mysql' => Stancl\Tenancy\TenantDatabaseManagers\MySQLDatabaseManager::class,
|
||||||
'pgsql' => Stancl\Tenancy\TenantDatabaseManagers\PostgreSQLDatabaseManager::class,
|
'pgsql' => Stancl\Tenancy\TenantDatabaseManagers\PostgreSQLDatabaseManager::class,
|
||||||
|
// 'pgsql' => Stancl\Tenancy\TenantDatabaseManagers\PostgreSQLSchemaManager::class, // Separate by schema instead of database
|
||||||
],
|
],
|
||||||
'database_manager_connections' => [
|
'database_manager_connections' => [
|
||||||
// Connections used by TenantDatabaseManagers. This tells, for example, the
|
// Connections used by TenantDatabaseManagers. This tells, for example, the
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ final class MigrateFresh extends Command
|
||||||
$this->info('Migrating.');
|
$this->info('Migrating.');
|
||||||
$this->callSilent('tenants:migrate', [
|
$this->callSilent('tenants:migrate', [
|
||||||
'--tenants' => [$tenant->id],
|
'--tenants' => [$tenant->id],
|
||||||
|
'--force' => true,
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Database\DatabaseManager as BaseDatabaseManager;
|
use Illuminate\Database\DatabaseManager as BaseDatabaseManager;
|
||||||
use Illuminate\Foundation\Application;
|
use Illuminate\Foundation\Application;
|
||||||
use Stancl\Tenancy\Contracts\Future\CanSetConnection;
|
use Stancl\Tenancy\Contracts\Future\CanSetConnection;
|
||||||
|
use Stancl\Tenancy\Contracts\TenantCannotBeCreatedException;
|
||||||
use Stancl\Tenancy\Contracts\TenantDatabaseManager;
|
use Stancl\Tenancy\Contracts\TenantDatabaseManager;
|
||||||
use Stancl\Tenancy\Exceptions\DatabaseManagerNotRegisteredException;
|
use Stancl\Tenancy\Exceptions\DatabaseManagerNotRegisteredException;
|
||||||
use Stancl\Tenancy\Exceptions\TenantDatabaseAlreadyExistsException;
|
use Stancl\Tenancy\Exceptions\TenantDatabaseAlreadyExistsException;
|
||||||
|
|
@ -101,7 +102,9 @@ class DatabaseManager
|
||||||
|
|
||||||
// Change database name.
|
// Change database name.
|
||||||
$databaseName = $this->getDriver($connectionName) === 'sqlite' ? database_path($databaseName) : $databaseName;
|
$databaseName = $this->getDriver($connectionName) === 'sqlite' ? database_path($databaseName) : $databaseName;
|
||||||
$this->app['config']["database.connections.$connectionName.database"] = $databaseName;
|
$separateBy = $this->separateBy($connectionName);
|
||||||
|
|
||||||
|
$this->app['config']["database.connections.$connectionName.$separateBy"] = $databaseName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -147,6 +150,8 @@ class DatabaseManager
|
||||||
* @param Tenant $tenant
|
* @param Tenant $tenant
|
||||||
* @return void
|
* @return void
|
||||||
* @throws TenantCannotBeCreatedException
|
* @throws TenantCannotBeCreatedException
|
||||||
|
* @throws DatabaseManagerNotRegisteredException
|
||||||
|
* @throws TenantDatabaseAlreadyExistsException
|
||||||
*/
|
*/
|
||||||
public function ensureTenantCanBeCreated(Tenant $tenant): void
|
public function ensureTenantCanBeCreated(Tenant $tenant): void
|
||||||
{
|
{
|
||||||
|
|
@ -161,6 +166,7 @@ class DatabaseManager
|
||||||
* @param Tenant $tenant
|
* @param Tenant $tenant
|
||||||
* @param ShouldQueue[]|callable[] $afterCreating
|
* @param ShouldQueue[]|callable[] $afterCreating
|
||||||
* @return void
|
* @return void
|
||||||
|
* @throws DatabaseManagerNotRegisteredException
|
||||||
*/
|
*/
|
||||||
public function createDatabase(Tenant $tenant, array $afterCreating = [])
|
public function createDatabase(Tenant $tenant, array $afterCreating = [])
|
||||||
{
|
{
|
||||||
|
|
@ -202,6 +208,7 @@ class DatabaseManager
|
||||||
*
|
*
|
||||||
* @param Tenant $tenant
|
* @param Tenant $tenant
|
||||||
* @return void
|
* @return void
|
||||||
|
* @throws DatabaseManagerNotRegisteredException
|
||||||
*/
|
*/
|
||||||
public function deleteDatabase(Tenant $tenant)
|
public function deleteDatabase(Tenant $tenant)
|
||||||
{
|
{
|
||||||
|
|
@ -224,6 +231,7 @@ class DatabaseManager
|
||||||
*
|
*
|
||||||
* @param Tenant $tenant
|
* @param Tenant $tenant
|
||||||
* @return TenantDatabaseManager
|
* @return TenantDatabaseManager
|
||||||
|
* @throws DatabaseManagerNotRegisteredException
|
||||||
*/
|
*/
|
||||||
public function getTenantDatabaseManager(Tenant $tenant): TenantDatabaseManager
|
public function getTenantDatabaseManager(Tenant $tenant): TenantDatabaseManager
|
||||||
{
|
{
|
||||||
|
|
@ -243,4 +251,20 @@ class DatabaseManager
|
||||||
|
|
||||||
return $databaseManager;
|
return $databaseManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* What key on the connection config should be used to separate tenants.
|
||||||
|
*
|
||||||
|
* @param string $connectionName
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function separateBy(string $connectionName): string
|
||||||
|
{
|
||||||
|
if ($this->getDriver($this->getBaseConnection($connectionName)) === 'pgsql'
|
||||||
|
&& $this->app['config']['tenancy.database.separate_by'] === 'schema') {
|
||||||
|
return 'schema';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'database';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
68
src/StorageDrivers/Database/CachedTenantResolver.php
Normal file
68
src/StorageDrivers/Database/CachedTenantResolver.php
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
<?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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -32,12 +32,16 @@ class DatabaseStorageDriver implements StorageDriver, CanDeleteKeys, CanFindByAn
|
||||||
/** @var DomainRepository */
|
/** @var DomainRepository */
|
||||||
protected $domains;
|
protected $domains;
|
||||||
|
|
||||||
|
/** @var CachedTenantResolver */
|
||||||
|
protected $cache;
|
||||||
|
|
||||||
/** @var Tenant The default tenant. */
|
/** @var Tenant The default tenant. */
|
||||||
protected $tenant;
|
protected $tenant;
|
||||||
|
|
||||||
public function __construct(Application $app, ConfigRepository $config)
|
public function __construct(Application $app, ConfigRepository $config, CachedTenantResolver $cache)
|
||||||
{
|
{
|
||||||
$this->app = $app;
|
$this->app = $app;
|
||||||
|
$this->cache = $cache;
|
||||||
$this->centralDatabase = $this->getCentralConnection();
|
$this->centralDatabase = $this->getCentralConnection();
|
||||||
$this->tenants = new TenantRepository($config);
|
$this->tenants = new TenantRepository($config);
|
||||||
$this->domains = new DomainRepository($config);
|
$this->domains = new DomainRepository($config);
|
||||||
|
|
@ -60,7 +64,16 @@ class DatabaseStorageDriver implements StorageDriver, CanDeleteKeys, CanFindByAn
|
||||||
|
|
||||||
public function findByDomain(string $domain): Tenant
|
public function findByDomain(string $domain): Tenant
|
||||||
{
|
{
|
||||||
$id = $this->domains->getTenantIdByDomain($domain);
|
$query = function () use ($domain) {
|
||||||
|
return $this->domains->getTenantIdByDomain($domain);
|
||||||
|
};
|
||||||
|
|
||||||
|
if ($this->usesCache()) {
|
||||||
|
$id = $this->cache->getTenantIdByDomain($domain, $query);
|
||||||
|
} else {
|
||||||
|
$id = $query();
|
||||||
|
}
|
||||||
|
|
||||||
if (! $id) {
|
if (! $id) {
|
||||||
throw new TenantCouldNotBeIdentifiedException($domain);
|
throw new TenantCouldNotBeIdentifiedException($domain);
|
||||||
}
|
}
|
||||||
|
|
@ -70,14 +83,29 @@ class DatabaseStorageDriver implements StorageDriver, CanDeleteKeys, CanFindByAn
|
||||||
|
|
||||||
public function findById(string $id): Tenant
|
public function findById(string $id): Tenant
|
||||||
{
|
{
|
||||||
$tenant = $this->tenants->find($id);
|
$dataQuery = function () use ($id) {
|
||||||
|
$data = $this->tenants->find($id);
|
||||||
|
|
||||||
if (! $tenant) {
|
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);
|
throw new TenantDoesNotExistException($id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Tenant::fromStorage($this->tenants->decodeData($tenant))
|
return Tenant::fromStorage($data)
|
||||||
->withDomains($this->domains->getTenantDomains($id));
|
->withDomains($domains);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -128,19 +156,33 @@ class DatabaseStorageDriver implements StorageDriver, CanDeleteKeys, CanFindByAn
|
||||||
|
|
||||||
public function updateTenant(Tenant $tenant): void
|
public function updateTenant(Tenant $tenant): void
|
||||||
{
|
{
|
||||||
$this->centralDatabase->transaction(function () use ($tenant) {
|
$originalDomains = $this->domains->getTenantDomains($tenant);
|
||||||
|
|
||||||
|
$this->centralDatabase->transaction(function () use ($tenant, $originalDomains) {
|
||||||
$this->tenants->updateTenant($tenant);
|
$this->tenants->updateTenant($tenant);
|
||||||
|
|
||||||
$this->domains->updateTenantDomains($tenant);
|
$this->domains->updateTenantDomains($tenant, $originalDomains);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if ($this->usesCache()) {
|
||||||
|
$this->cache->invalidateTenant($tenant->id);
|
||||||
|
$this->cache->invalidateDomainToIdMapping($originalDomains);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteTenant(Tenant $tenant): void
|
public function deleteTenant(Tenant $tenant): void
|
||||||
{
|
{
|
||||||
|
$originalDomains = $this->domains->getTenantDomains($tenant);
|
||||||
|
|
||||||
$this->centralDatabase->transaction(function () use ($tenant) {
|
$this->centralDatabase->transaction(function () use ($tenant) {
|
||||||
$this->tenants->where('id', $tenant->id)->delete();
|
$this->tenants->where('id', $tenant->id)->delete();
|
||||||
$this->domains->where('tenant_id', $tenant->id)->delete();
|
$this->domains->where('tenant_id', $tenant->id)->delete();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if ($this->usesCache()) {
|
||||||
|
$this->cache->invalidateTenant($tenant->id);
|
||||||
|
$this->cache->invalidateDomainToIdMapping($originalDomains);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -179,16 +221,37 @@ class DatabaseStorageDriver implements StorageDriver, CanDeleteKeys, CanFindByAn
|
||||||
|
|
||||||
public function put(string $key, $value, Tenant $tenant = null): void
|
public function put(string $key, $value, Tenant $tenant = null): void
|
||||||
{
|
{
|
||||||
$this->tenants->put($key, $value, $tenant ?? $this->currentTenant());
|
$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
|
public function putMany(array $kvPairs, Tenant $tenant = null): void
|
||||||
{
|
{
|
||||||
$this->tenants->putMany($kvPairs, $tenant ?? $this->currentTenant());
|
$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
|
public function deleteMany(array $keys, Tenant $tenant = null): void
|
||||||
{
|
{
|
||||||
$this->tenants->deleteMany($keys, $tenant ?? $this->currentTenant());
|
$tenant = $tenant ?? $this->currentTenant();
|
||||||
|
$this->tenants->deleteMany($keys, $tenant);
|
||||||
|
|
||||||
|
if ($this->usesCache()) {
|
||||||
|
$this->cache->invalidateTenantData($tenant->id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function usesCache(): bool
|
||||||
|
{
|
||||||
|
// null is also truthy here
|
||||||
|
return $this->app['config']['tenancy.storage_drivers.db.cache_store'] !== false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,9 +33,8 @@ class DomainRepository extends Repository
|
||||||
}, $tenant->domains));
|
}, $tenant->domains));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updateTenantDomains(Tenant $tenant)
|
public function updateTenantDomains(Tenant $tenant, array $originalDomains)
|
||||||
{
|
{
|
||||||
$originalDomains = $this->getTenantDomains($tenant);
|
|
||||||
$deletedDomains = array_diff($originalDomains, $tenant->domains);
|
$deletedDomains = array_diff($originalDomains, $tenant->domains);
|
||||||
$newDomains = array_diff($tenant->domains, $originalDomains);
|
$newDomains = array_diff($tenant->domains, $originalDomains);
|
||||||
|
|
||||||
|
|
|
||||||
47
src/TenantDatabaseManagers/PostgreSQLSchemaManager.php
Normal file
47
src/TenantDatabaseManagers/PostgreSQLSchemaManager.php
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Stancl\Tenancy\TenantDatabaseManagers;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Config\Repository;
|
||||||
|
use Illuminate\Database\Connection;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Stancl\Tenancy\Contracts\Future\CanSetConnection;
|
||||||
|
use Stancl\Tenancy\Contracts\TenantDatabaseManager;
|
||||||
|
|
||||||
|
class PostgreSQLSchemaManager implements TenantDatabaseManager, CanSetConnection
|
||||||
|
{
|
||||||
|
/** @var string */
|
||||||
|
protected $connection;
|
||||||
|
|
||||||
|
public function __construct(Repository $config)
|
||||||
|
{
|
||||||
|
$this->connection = $config->get('tenancy.database_manager_connections.pgsql');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function database(): Connection
|
||||||
|
{
|
||||||
|
return DB::connection($this->connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setConnection(string $connection): void
|
||||||
|
{
|
||||||
|
$this->connection = $connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createDatabase(string $name): bool
|
||||||
|
{
|
||||||
|
return $this->database()->statement("CREATE SCHEMA \"$name\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteDatabase(string $name): bool
|
||||||
|
{
|
||||||
|
return $this->database()->statement("DROP SCHEMA \"$name\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function databaseExists(string $name): bool
|
||||||
|
{
|
||||||
|
return (bool) $this->database()->select("SELECT schema_name FROM information_schema.schemata WHERE schema_name = '$name'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -24,11 +24,7 @@ class TenantManager
|
||||||
{
|
{
|
||||||
use ForwardsCalls;
|
use ForwardsCalls;
|
||||||
|
|
||||||
/**
|
/** @var Tenant The current tenant. */
|
||||||
* The current tenant.
|
|
||||||
*
|
|
||||||
* @var Tenant
|
|
||||||
*/
|
|
||||||
protected $tenant;
|
protected $tenant;
|
||||||
|
|
||||||
/** @var Application */
|
/** @var Application */
|
||||||
|
|
|
||||||
8
test
8
test
|
|
@ -1,7 +1,7 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
printf "Variant 1\n\n"
|
printf "Variant 1 (DB)\n\n"
|
||||||
docker-compose exec test env TENANCY_TEST_STORAGE_DRIVER=db vendor/bin/phpunit --coverage-php coverage/2.cov "$@"
|
docker-compose exec test env TENANCY_TEST_STORAGE_DRIVER=db vendor/bin/phpunit --coverage-php coverage/1.cov "$@"
|
||||||
printf "Variant 2\n\n"
|
printf "Variant 2 (Redis)\n\n"
|
||||||
docker-compose exec test env TENANCY_TEST_STORAGE_DRIVER=redis vendor/bin/phpunit --coverage-php coverage/1.cov "$@"
|
docker-compose exec test env TENANCY_TEST_STORAGE_DRIVER=redis vendor/bin/phpunit --coverage-php coverage/2.cov "$@"
|
||||||
|
|
|
||||||
164
tests/CachedResolverTest.php
Normal file
164
tests/CachedResolverTest.php
Normal file
|
|
@ -0,0 +1,164 @@
|
||||||
|
<?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' => null]); // default driver
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @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));
|
||||||
|
}
|
||||||
|
}
|
||||||
143
tests/DatabaseSchemaManagerTest.php
Normal file
143
tests/DatabaseSchemaManagerTest.php
Normal file
|
|
@ -0,0 +1,143 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Stancl\Tenancy\Tests;
|
||||||
|
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Stancl\Tenancy\Tenant;
|
||||||
|
|
||||||
|
class DatabaseSchemaManagerTest extends TestCase
|
||||||
|
{
|
||||||
|
public $autoInitTenancy = false;
|
||||||
|
|
||||||
|
protected function getEnvironmentSetUp($app)
|
||||||
|
{
|
||||||
|
parent::getEnvironmentSetUp($app);
|
||||||
|
|
||||||
|
$app['config']->set([
|
||||||
|
'database.default' => 'pgsql',
|
||||||
|
'database.connections.pgsql.database' => 'main',
|
||||||
|
'database.connections.pgsql.schema' => 'public',
|
||||||
|
'tenancy.database.based_on' => null,
|
||||||
|
'tenancy.database.suffix' => '',
|
||||||
|
'tenancy.database.separate_by' => 'schema',
|
||||||
|
'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_based_on_is_null()
|
||||||
|
{
|
||||||
|
config(['database.default' => 'pgsql']);
|
||||||
|
|
||||||
|
$this->assertSame('pgsql', config('database.default'));
|
||||||
|
config([
|
||||||
|
'database.connections.pgsql.foo' => 'bar',
|
||||||
|
'tenancy.database.based_on' => null,
|
||||||
|
]);
|
||||||
|
|
||||||
|
tenancy()->init('test.localhost');
|
||||||
|
|
||||||
|
$this->assertSame('tenant', config('database.default'));
|
||||||
|
$this->assertSame('bar', config('database.connections.' . config('database.default') . '.foo'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function make_sure_using_schema_connection()
|
||||||
|
{
|
||||||
|
$tenant = tenancy()->create(['schema.localhost']);
|
||||||
|
tenancy()->init('schema.localhost');
|
||||||
|
|
||||||
|
$this->assertSame($tenant->getDatabaseName(), 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,7 @@ use Stancl\Tenancy\Jobs\QueuedTenantDatabaseDeleter;
|
||||||
use Stancl\Tenancy\Tenant;
|
use Stancl\Tenancy\Tenant;
|
||||||
use Stancl\Tenancy\TenantDatabaseManagers\MySQLDatabaseManager;
|
use Stancl\Tenancy\TenantDatabaseManagers\MySQLDatabaseManager;
|
||||||
use Stancl\Tenancy\TenantDatabaseManagers\PostgreSQLDatabaseManager;
|
use Stancl\Tenancy\TenantDatabaseManagers\PostgreSQLDatabaseManager;
|
||||||
|
use Stancl\Tenancy\TenantDatabaseManagers\PostgreSQLSchemaManager;
|
||||||
use Stancl\Tenancy\TenantDatabaseManagers\SQLiteDatabaseManager;
|
use Stancl\Tenancy\TenantDatabaseManagers\SQLiteDatabaseManager;
|
||||||
|
|
||||||
class TenantDatabaseManagerTest extends TestCase
|
class TenantDatabaseManagerTest extends TestCase
|
||||||
|
|
@ -78,6 +79,7 @@ class TenantDatabaseManagerTest extends TestCase
|
||||||
['mysql', MySQLDatabaseManager::class],
|
['mysql', MySQLDatabaseManager::class],
|
||||||
['sqlite', SQLiteDatabaseManager::class],
|
['sqlite', SQLiteDatabaseManager::class],
|
||||||
['pgsql', PostgreSQLDatabaseManager::class],
|
['pgsql', PostgreSQLDatabaseManager::class],
|
||||||
|
['pgsql', PostgreSQLSchemaManager::class],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,12 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
|
||||||
Redis::connection('tenancy')->flushdb();
|
Redis::connection('tenancy')->flushdb();
|
||||||
Redis::connection('cache')->flushdb();
|
Redis::connection('cache')->flushdb();
|
||||||
|
|
||||||
|
$originalConnection = config('database.default');
|
||||||
$this->loadMigrationsFrom([
|
$this->loadMigrationsFrom([
|
||||||
'--path' => realpath(__DIR__ . '/../assets/migrations'),
|
'--path' => realpath(__DIR__ . '/../assets/migrations'),
|
||||||
'--database' => 'central',
|
'--database' => 'central',
|
||||||
]);
|
]);
|
||||||
config(['database.default' => 'sqlite']); // fix issue caused by loadMigrationsFrom
|
config(['database.default' => $originalConnection]); // fix issue caused by loadMigrationsFrom
|
||||||
|
|
||||||
if ($this->autoCreateTenant) {
|
if ($this->autoCreateTenant) {
|
||||||
$this->createTenant();
|
$this->createTenant();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue