mirror of
https://github.com/archtechx/tenancy.git
synced 2025-12-12 12:44:02 +00:00
[2.2.0] [WIP] Add functionality (#206)
* TenantDatabaseDoesNotExistException * Apply fixes from StyleCI * User post-creation callbacks * Rename method * postCreationActions * pass $tenant as parameter * pass $tenant to async actions * WIP findBy() * findBy\* ForwardsCalls * Apply fixes from StyleCI * findBy DB storage driver * Redis SD TODO message * Apply fixes from StyleCI * Fix chained jobs * WIP event system * import str * instanceof closure check * findBy instead of find * Tenant -> Tenants * dots * Use DB hooks instead of a SC key * Don't allow callables for queue chain * CanDeleteKeys interface * Apply fixes from StyleCI * CanFindByAnyKey interface * Apply fixes from StyleCI * Ditch models for custom repositories * Resolve circular dependency * Apply fixes from StyleCI * Fix tests * Apply fixes from StyleCI * FutureTest * Prefix tenant events with 'tenant.' * Event listener arguments test
This commit is contained in:
parent
7389f44de9
commit
869ac32983
22 changed files with 654 additions and 254 deletions
|
|
@ -1,15 +1,14 @@
|
|||
# Contributing
|
||||
|
||||
## Code style
|
||||
|
||||
StyleCI will automatically fix code style violations in your pull requests.
|
||||
StyleCI will flag code style violations in your pull requests.
|
||||
|
||||
## Running tests
|
||||
|
||||
### With Docker
|
||||
If you have Docker installed, simply run ./test. When you're done testing, run docker-compose down to shut down the containers.
|
||||
If you have Docker installed, simply run ./fulltest. When you're done testing, run docker-compose down to shut down the containers.
|
||||
|
||||
### Without Docker
|
||||
If you run the tests of this package, please make sure you don't store anything in Redis @ 127.0.0.1:6379 db#14. The contents of this database are flushed everytime the tests are run.
|
||||
|
||||
Some tests are run only if the CI, TRAVIS and CONTINUOUS_INTEGRATION environment variables are set to true. This is to avoid things like bloating your MySQL instance with test databases.
|
||||
Some tests are run only if the `CONTINUOUS_INTEGRATION` or `DOCKER` environment variables are set to true. This is to avoid things like bloating your MySQL instance with test databases.
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ return [
|
|||
],
|
||||
'connection' => null, // Your central database connection. Set to null to use the default connection.
|
||||
'table_names' => [
|
||||
'TenantModel' => 'tenants',
|
||||
'DomainModel' => 'domains',
|
||||
'tenants' => 'tenants',
|
||||
'domains' => 'domains',
|
||||
],
|
||||
],
|
||||
'redis' => [
|
||||
|
|
@ -93,7 +93,7 @@ return [
|
|||
'migrate_after_creation' => false, // run migrations after creating a tenant
|
||||
'seed_after_migration' => false, // should the seeder run after automatic migration
|
||||
'seeder_parameters' => [
|
||||
'--class' => 'DatabaseSeeder', // root seeder class to run after automatic migrations, eg: 'DatabaseSeeder'
|
||||
'--class' => 'DatabaseSeeder', // root seeder class to run after automatic migrations, e.g.: 'DatabaseSeeder'
|
||||
],
|
||||
'queue_database_deletion' => false,
|
||||
'delete_database_after_tenant_deletion' => false, // delete the tenant's database after deleting the tenant
|
||||
|
|
|
|||
21
src/Contracts/Future/CanDeleteKeys.php
Normal file
21
src/Contracts/Future/CanDeleteKeys.php
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Stancl\Tenancy\Contracts\Future;
|
||||
|
||||
use Stancl\Tenancy\Tenant;
|
||||
|
||||
/**
|
||||
* This interface will be part of the StorageDriver interface in 3.x.
|
||||
*/
|
||||
interface CanDeleteKeys
|
||||
{
|
||||
/**
|
||||
* Delete keys from the storage.
|
||||
*
|
||||
* @param string[] $keys
|
||||
* @return void
|
||||
*/
|
||||
public function deleteMany(array $keys, Tenant $tenant = null): void;
|
||||
}
|
||||
24
src/Contracts/Future/CanFindByAnyKey.php
Normal file
24
src/Contracts/Future/CanFindByAnyKey.php
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Stancl\Tenancy\Contracts\Future;
|
||||
|
||||
use Stancl\Tenancy\Exceptions\TenantDoesNotExistException;
|
||||
use Stancl\Tenancy\Tenant;
|
||||
|
||||
/**
|
||||
* This interface *might* be part of the StorageDriver interface in 3.x.
|
||||
*/
|
||||
interface CanFindByAnyKey
|
||||
{
|
||||
/**
|
||||
* Find a tenant using an arbitrary key.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @return Tenant
|
||||
* @throws TenantDoesNotExistException
|
||||
*/
|
||||
public function findBy(string $key, $value): Tenant;
|
||||
}
|
||||
|
|
@ -4,6 +4,8 @@ declare(strict_types=1);
|
|||
|
||||
namespace Stancl\Tenancy;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Database\DatabaseManager as BaseDatabaseManager;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Stancl\Tenancy\Contracts\TenantDatabaseManager;
|
||||
|
|
@ -23,6 +25,9 @@ class DatabaseManager
|
|||
/** @var BaseDatabaseManager */
|
||||
protected $database;
|
||||
|
||||
/** @var TenantManager */
|
||||
protected $tenancy;
|
||||
|
||||
public function __construct(Application $app, BaseDatabaseManager $database)
|
||||
{
|
||||
$this->app = $app;
|
||||
|
|
@ -30,6 +35,19 @@ class DatabaseManager
|
|||
$this->originalDefaultConnectionName = $app['config']['database.default'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the TenantManager instance, used to dispatch tenancy events.
|
||||
*
|
||||
* @param TenantManager $tenantManager
|
||||
* @return self
|
||||
*/
|
||||
public function withTenantManager(TenantManager $tenantManager): self
|
||||
{
|
||||
$this->tenancy = $tenantManager;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to a tenant's database.
|
||||
*
|
||||
|
|
@ -140,7 +158,7 @@ class DatabaseManager
|
|||
* Create a database for a tenant.
|
||||
*
|
||||
* @param Tenant $tenant
|
||||
* @param \Illuminate\Contracts\Queue\ShouldQueue[]|callable[] $afterCreating
|
||||
* @param ShouldQueue[]|callable[] $afterCreating
|
||||
* @return void
|
||||
*/
|
||||
public function createDatabase(Tenant $tenant, array $afterCreating = [])
|
||||
|
|
@ -148,14 +166,34 @@ class DatabaseManager
|
|||
$database = $tenant->getDatabaseName();
|
||||
$manager = $this->getTenantDatabaseManager($tenant);
|
||||
|
||||
$afterCreating = array_merge(
|
||||
$afterCreating,
|
||||
$this->tenancy->event('database.creating', $database, $tenant)
|
||||
);
|
||||
|
||||
if ($this->app['config']['tenancy.queue_database_creation'] ?? false) {
|
||||
QueuedTenantDatabaseCreator::withChain($afterCreating)->dispatch($manager, $database);
|
||||
$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($manager, $database);
|
||||
} else {
|
||||
$manager->createDatabase($database);
|
||||
foreach ($afterCreating as $callback) {
|
||||
$callback();
|
||||
foreach ($afterCreating as $item) {
|
||||
if (is_object($item) && ! $item instanceof Closure) {
|
||||
$item->handle($tenant);
|
||||
} else {
|
||||
$item($tenant);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->tenancy->event('database.created', $database, $tenant);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -169,11 +207,15 @@ class DatabaseManager
|
|||
$database = $tenant->getDatabaseName();
|
||||
$manager = $this->getTenantDatabaseManager($tenant);
|
||||
|
||||
$this->tenancy->event('database.deleting', $database, $tenant);
|
||||
|
||||
if ($this->app['config']['tenancy.queue_database_deletion'] ?? false) {
|
||||
QueuedTenantDatabaseDeleter::dispatch($manager, $database);
|
||||
} else {
|
||||
$manager->deleteDatabase($database);
|
||||
}
|
||||
|
||||
$this->tenancy->event('database.deleted', $database, $tenant);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -182,7 +224,7 @@ class DatabaseManager
|
|||
* @param Tenant $tenant
|
||||
* @return TenantDatabaseManager
|
||||
*/
|
||||
protected function getTenantDatabaseManager(Tenant $tenant): TenantDatabaseManager
|
||||
public function getTenantDatabaseManager(Tenant $tenant): TenantDatabaseManager
|
||||
{
|
||||
$driver = $this->getDriver($this->getBaseConnection($tenant->getConnectionName()));
|
||||
|
||||
|
|
|
|||
15
src/Exceptions/NotImplementedException.php
Normal file
15
src/Exceptions/NotImplementedException.php
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Stancl\Tenancy\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class NotImplementedException extends Exception
|
||||
{
|
||||
public function __construct($class, $method, $extra)
|
||||
{
|
||||
parent::__construct("The $class class does not implement the $method method. $extra");
|
||||
}
|
||||
}
|
||||
15
src/Exceptions/TenantDatabaseDoesNotExistException.php
Normal file
15
src/Exceptions/TenantDatabaseDoesNotExistException.php
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Stancl\Tenancy\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class TenantDatabaseDoesNotExistException extends Exception
|
||||
{
|
||||
public function __construct($database)
|
||||
{
|
||||
parent::__construct("Database $database does not exist.");
|
||||
}
|
||||
}
|
||||
|
|
@ -8,8 +8,8 @@ use Exception;
|
|||
|
||||
class TenantDoesNotExistException extends Exception
|
||||
{
|
||||
public function __construct(string $id)
|
||||
public function __construct(string $id, string $key = 'id')
|
||||
{
|
||||
$this->message = "Tenant with this id does not exist: $id";
|
||||
$this->message = "Tenant with this $key does not exist: $id";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,41 +4,51 @@ 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\StorageDrivers\Database\DomainModel as Domains;
|
||||
use Stancl\Tenancy\StorageDrivers\Database\TenantModel as Tenants;
|
||||
use Stancl\Tenancy\Tenant;
|
||||
|
||||
class DatabaseStorageDriver implements StorageDriver
|
||||
class DatabaseStorageDriver implements StorageDriver, CanDeleteKeys, CanFindByAnyKey
|
||||
{
|
||||
/** @var Application */
|
||||
protected $app;
|
||||
|
||||
/** @var \Illuminate\Database\Connection */
|
||||
/** @var Connection */
|
||||
protected $centralDatabase;
|
||||
|
||||
/** @var TenantRepository */
|
||||
protected $tenants;
|
||||
|
||||
/** @var DomainRepository */
|
||||
protected $domains;
|
||||
|
||||
/** @var Tenant The default tenant. */
|
||||
protected $tenant;
|
||||
|
||||
public function __construct(Application $app)
|
||||
public function __construct(Application $app, ConfigRepository $config)
|
||||
{
|
||||
$this->app = $app;
|
||||
$this->centralDatabase = $this->getCentralConnection();
|
||||
$this->tenants = new TenantRepository($config);
|
||||
$this->domains = new DomainRepository($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the central database connection.
|
||||
*
|
||||
* @return \Illuminate\Database\Connection
|
||||
* @return Connection
|
||||
*/
|
||||
public static function getCentralConnection(): \Illuminate\Database\Connection
|
||||
public static function getCentralConnection(): Connection
|
||||
{
|
||||
return DB::connection(static::getCentralConnectionName());
|
||||
}
|
||||
|
|
@ -50,7 +60,7 @@ class DatabaseStorageDriver implements StorageDriver
|
|||
|
||||
public function findByDomain(string $domain): Tenant
|
||||
{
|
||||
$id = $this->getTenantIdByDomain($domain);
|
||||
$id = $this->domains->getTenantIdByDomain($domain);
|
||||
if (! $id) {
|
||||
throw new TenantCouldNotBeIdentifiedException($domain);
|
||||
}
|
||||
|
|
@ -60,30 +70,43 @@ class DatabaseStorageDriver implements StorageDriver
|
|||
|
||||
public function findById(string $id): Tenant
|
||||
{
|
||||
$tenant = Tenants::find($id);
|
||||
$tenant = $this->tenants->find($id);
|
||||
|
||||
if (! $tenant) {
|
||||
throw new TenantDoesNotExistException($id);
|
||||
}
|
||||
|
||||
return Tenant::fromStorage($tenant->decoded())
|
||||
->withDomains($this->getTenantDomains($id));
|
||||
return Tenant::fromStorage($this->tenants->decodeData($tenant))
|
||||
->withDomains($this->domains->getTenantDomains($id));
|
||||
}
|
||||
|
||||
protected function getTenantDomains($id)
|
||||
/**
|
||||
* Find a tenant using an arbitrary key.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @return Tenant
|
||||
* @throws TenantDoesNotExistException
|
||||
*/
|
||||
public function findBy(string $key, $value): Tenant
|
||||
{
|
||||
return Domains::where('tenant_id', $id)->get()->map(function ($model) {
|
||||
return $model->domain;
|
||||
})->toArray();
|
||||
$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 (Tenants::find($tenant->id)) {
|
||||
if ($this->tenants->exists($tenant)) {
|
||||
throw new TenantWithThisIdAlreadyExistsException($tenant->id);
|
||||
}
|
||||
|
||||
if (Domains::whereIn('domain', $tenant->domains)->exists()) {
|
||||
if ($this->domains->occupied($tenant->domains)) {
|
||||
throw new DomainsOccupiedByOtherTenantException;
|
||||
}
|
||||
}
|
||||
|
|
@ -95,53 +118,28 @@ class DatabaseStorageDriver implements StorageDriver
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function getTenantIdByDomain(string $domain): ?string
|
||||
{
|
||||
return Domains::where('domain', $domain)->first()->tenant_id ?? null;
|
||||
}
|
||||
|
||||
public function createTenant(Tenant $tenant): void
|
||||
{
|
||||
$this->centralDatabase->transaction(function () use ($tenant) {
|
||||
Tenants::create(array_merge(Tenants::encodeData($tenant->data), [
|
||||
'id' => $tenant->id,
|
||||
]))->toArray();
|
||||
|
||||
$domainData = [];
|
||||
foreach ($tenant->domains as $domain) {
|
||||
$domainData[] = ['domain' => $domain, 'tenant_id' => $tenant->id];
|
||||
}
|
||||
|
||||
Domains::insert($domainData);
|
||||
$this->tenants->insert($tenant);
|
||||
$this->domains->insertTenantDomains($tenant);
|
||||
});
|
||||
}
|
||||
|
||||
public function updateTenant(Tenant $tenant): void
|
||||
{
|
||||
$this->centralDatabase->transaction(function () use ($tenant) {
|
||||
Tenants::find($tenant->id)->putMany($tenant->data);
|
||||
$this->tenants->updateTenant($tenant);
|
||||
|
||||
$original_domains = Domains::where('tenant_id', $tenant->id)->get()->map(function ($model) {
|
||||
return $model->domain;
|
||||
})->toArray();
|
||||
$deleted_domains = array_diff($original_domains, $tenant->domains);
|
||||
|
||||
Domains::whereIn('domain', $deleted_domains)->delete();
|
||||
|
||||
foreach ($tenant->domains as $domain) {
|
||||
Domains::firstOrCreate([
|
||||
'tenant_id' => $tenant->id,
|
||||
'domain' => $domain,
|
||||
]);
|
||||
}
|
||||
$this->domains->updateTenantDomains($tenant);
|
||||
});
|
||||
}
|
||||
|
||||
public function deleteTenant(Tenant $tenant): void
|
||||
{
|
||||
$this->centralDatabase->transaction(function () use ($tenant) {
|
||||
Tenants::find($tenant->id)->delete();
|
||||
Domains::where('tenant_id', $tenant->id)->delete();
|
||||
$this->tenants->where('id', $tenant->id)->delete();
|
||||
$this->domains->where('tenant_id', $tenant->id)->delete();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -153,8 +151,9 @@ class DatabaseStorageDriver implements StorageDriver
|
|||
*/
|
||||
public function all(array $ids = []): array
|
||||
{
|
||||
return Tenants::getAllTenants($ids)->map(function ($data) {
|
||||
return Tenant::fromStorage($data)->withDomains($this->getTenantDomains($data['id']));
|
||||
return $this->tenants->all($ids)->map(function ($data) {
|
||||
return Tenant::fromStorage($data)
|
||||
->withDomains($this->domains->getTenantDomains($data['id']));
|
||||
})->toArray();
|
||||
}
|
||||
|
||||
|
|
@ -170,27 +169,26 @@ class DatabaseStorageDriver implements StorageDriver
|
|||
|
||||
public function get(string $key, Tenant $tenant = null)
|
||||
{
|
||||
$tenant = $tenant ?? $this->currentTenant();
|
||||
|
||||
return Tenants::find($tenant->id)->get($key);
|
||||
return $this->tenants->get($key, $tenant ?? $this->currentTenant());
|
||||
}
|
||||
|
||||
public function getMany(array $keys, Tenant $tenant = null): array
|
||||
{
|
||||
$tenant = $tenant ?? $this->currentTenant();
|
||||
|
||||
return Tenants::find($tenant->id)->getMany($keys);
|
||||
return $this->tenants->getMany($keys, $tenant ?? $this->currentTenant());
|
||||
}
|
||||
|
||||
public function put(string $key, $value, Tenant $tenant = null): void
|
||||
{
|
||||
$tenant = $tenant ?? $this->currentTenant();
|
||||
Tenants::find($tenant->id)->put($key, $value);
|
||||
$this->tenants->put($key, $value, $tenant ?? $this->currentTenant());
|
||||
}
|
||||
|
||||
public function putMany(array $kvPairs, Tenant $tenant = null): void
|
||||
{
|
||||
$tenant = $tenant ?? $this->currentTenant();
|
||||
Tenants::find($tenant->id)->putMany($kvPairs);
|
||||
$this->tenants->putMany($kvPairs, $tenant ?? $this->currentTenant());
|
||||
}
|
||||
|
||||
public function deleteMany(array $keys, Tenant $tenant = null): void
|
||||
{
|
||||
$this->tenants->deleteMany($keys, $tenant ?? $this->currentTenant());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Stancl\Tenancy\StorageDrivers\Database;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* @internal Class is subject to breaking changes in minor and patch versions.
|
||||
*/
|
||||
class DomainModel extends Model
|
||||
{
|
||||
use CentralConnection;
|
||||
|
||||
protected $guarded = [];
|
||||
protected $primaryKey = 'domain';
|
||||
public $incrementing = false;
|
||||
public $timestamps = false;
|
||||
|
||||
public function getTable()
|
||||
{
|
||||
return config('tenancy.storage_drivers.db.table_names.DomainModel', 'domains');
|
||||
}
|
||||
}
|
||||
58
src/StorageDrivers/Database/DomainRepository.php
Normal file
58
src/StorageDrivers/Database/DomainRepository.php
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
<?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)
|
||||
{
|
||||
$originalDomains = $this->getTenantDomains($tenant);
|
||||
$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.DomainModel') // legacy
|
||||
?? $config->get('tenancy.storage_drivers.db.table_names.domains')
|
||||
?? 'domains';
|
||||
}
|
||||
}
|
||||
41
src/StorageDrivers/Database/Repository.php
Normal file
41
src/StorageDrivers/Database/Repository.php
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<?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,142 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Stancl\Tenancy\StorageDrivers\Database;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* @internal Class is subject to breaking changes in minor and patch versions.
|
||||
*/
|
||||
class TenantModel extends Model
|
||||
{
|
||||
use CentralConnection;
|
||||
|
||||
protected $guarded = [];
|
||||
protected $primaryKey = 'id';
|
||||
public $incrementing = false;
|
||||
public $timestamps = false;
|
||||
|
||||
public function getTable()
|
||||
{
|
||||
return config('tenancy.storage_drivers.db.table_names.TenantModel', 'tenants');
|
||||
}
|
||||
|
||||
public static function dataColumn()
|
||||
{
|
||||
return config('tenancy.storage_drivers.db.data_column', 'data');
|
||||
}
|
||||
|
||||
public static function customColumns()
|
||||
{
|
||||
return config('tenancy.storage_drivers.db.custom_columns', []);
|
||||
}
|
||||
|
||||
public static function encodeData(array $data)
|
||||
{
|
||||
$result = [];
|
||||
$jsonData = [];
|
||||
|
||||
foreach ($data as $key => $value) {
|
||||
if (in_array($key, static::customColumns(), true)) {
|
||||
$result[$key] = $value;
|
||||
} else {
|
||||
$jsonData[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$result['data'] = $jsonData ? json_encode($jsonData) : '{}';
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function getAllTenants(array $ids)
|
||||
{
|
||||
$tenants = $ids ? static::findMany($ids) : static::all();
|
||||
|
||||
return $tenants->map([__CLASS__, 'decodeData'])->toBase();
|
||||
}
|
||||
|
||||
public function decoded()
|
||||
{
|
||||
return static::decodeData($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a tenant array with data decoded into separate keys.
|
||||
*
|
||||
* @param self|array $tenant
|
||||
* @return array
|
||||
*/
|
||||
public static function decodeData($tenant)
|
||||
{
|
||||
$tenant = $tenant instanceof self ? (array) $tenant->attributes : $tenant;
|
||||
$decoded = json_decode($tenant[$dataColumn = static::dataColumn()], true);
|
||||
|
||||
foreach ($decoded as $key => $value) {
|
||||
$tenant[$key] = $value;
|
||||
}
|
||||
|
||||
// If $tenant[$dataColumn] has been overriden by a value, don't delete the key.
|
||||
if (! array_key_exists($dataColumn, $decoded)) {
|
||||
unset($tenant[$dataColumn]);
|
||||
}
|
||||
|
||||
return $tenant;
|
||||
}
|
||||
|
||||
public function getFromData(string $key)
|
||||
{
|
||||
$this->dataArray = $this->dataArray ?? json_decode($this->{$this->dataColumn()}, true);
|
||||
|
||||
return $this->dataArray[$key] ?? null;
|
||||
}
|
||||
|
||||
public function get(string $key)
|
||||
{
|
||||
return $this->attributes[$key] ?? $this->getFromData($key) ?? null;
|
||||
}
|
||||
|
||||
public function getMany(array $keys): array
|
||||
{
|
||||
return array_reduce($keys, function ($result, $key) {
|
||||
$result[$key] = $this->get($key);
|
||||
|
||||
return $result;
|
||||
}, []);
|
||||
}
|
||||
|
||||
public function put(string $key, $value)
|
||||
{
|
||||
if (in_array($key, $this->customColumns())) {
|
||||
$this->update([$key => $value]);
|
||||
} else {
|
||||
$obj = json_decode($this->{$this->dataColumn()});
|
||||
$obj->$key = $value;
|
||||
|
||||
$this->update([$this->dataColumn() => json_encode($obj)]);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function putMany(array $kvPairs)
|
||||
{
|
||||
$customColumns = [];
|
||||
$jsonObj = json_decode($this->{$this->dataColumn()});
|
||||
|
||||
foreach ($kvPairs as $key => $value) {
|
||||
if (in_array($key, $this->customColumns())) {
|
||||
$customColumns[$key] = $value;
|
||||
continue;
|
||||
}
|
||||
|
||||
$jsonObj->$key = $value;
|
||||
}
|
||||
|
||||
$this->update(array_merge($customColumns, [
|
||||
$this->dataColumn() => json_encode($jsonObj),
|
||||
]));
|
||||
}
|
||||
}
|
||||
186
src/StorageDrivers/Database/TenantRepository.php
Normal file
186
src/StorageDrivers/Database/TenantRepository.php
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
<?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())->data, 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())->data, 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';
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ namespace Stancl\Tenancy\StorageDrivers;
|
|||
|
||||
use Illuminate\Contracts\Redis\Factory as Redis;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Stancl\Tenancy\Contracts\Future\CanDeleteKeys;
|
||||
use Stancl\Tenancy\Contracts\StorageDriver;
|
||||
use Stancl\Tenancy\Exceptions\DomainsOccupiedByOtherTenantException;
|
||||
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedException;
|
||||
|
|
@ -13,7 +14,7 @@ use Stancl\Tenancy\Exceptions\TenantDoesNotExistException;
|
|||
use Stancl\Tenancy\Exceptions\TenantWithThisIdAlreadyExistsException;
|
||||
use Stancl\Tenancy\Tenant;
|
||||
|
||||
class RedisStorageDriver implements StorageDriver
|
||||
class RedisStorageDriver implements StorageDriver, CanDeleteKeys
|
||||
{
|
||||
/** @var Application */
|
||||
protected $app;
|
||||
|
|
@ -230,4 +231,11 @@ class RedisStorageDriver implements StorageDriver
|
|||
|
||||
$this->redis->hmset("tenants:{$tenant->id}", $kvPairs);
|
||||
}
|
||||
|
||||
public function deleteMany(array $keys, Tenant $tenant = null): void
|
||||
{
|
||||
$tenant = $tenant ?? $this->tenant();
|
||||
|
||||
$this->redis->hdel("tenants:{$tenant->id}", ...$keys);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ namespace Stancl\Tenancy\TenancyBootstrappers;
|
|||
|
||||
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
|
||||
use Stancl\Tenancy\DatabaseManager;
|
||||
use Stancl\Tenancy\Exceptions\TenantDatabaseDoesNotExistException;
|
||||
use Stancl\Tenancy\Tenant;
|
||||
|
||||
class DatabaseTenancyBootstrapper implements TenancyBootstrapper
|
||||
|
|
@ -20,6 +21,11 @@ class DatabaseTenancyBootstrapper implements TenancyBootstrapper
|
|||
|
||||
public function start(Tenant $tenant)
|
||||
{
|
||||
$database = $tenant->getDatabaseName();
|
||||
if (! $this->database->getTenantDatabaseManager($tenant)->databaseExists($database)) {
|
||||
throw new TenantDatabaseDoesNotExistException($database);
|
||||
}
|
||||
|
||||
$this->database->connect($tenant);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,8 +9,10 @@ use Closure;
|
|||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Traits\ForwardsCalls;
|
||||
use Stancl\Tenancy\Contracts\Future\CanDeleteKeys;
|
||||
use Stancl\Tenancy\Contracts\StorageDriver;
|
||||
use Stancl\Tenancy\Contracts\UniqueIdentifierGenerator;
|
||||
use Stancl\Tenancy\Exceptions\NotImplementedException;
|
||||
use Stancl\Tenancy\Exceptions\TenantStorageException;
|
||||
|
||||
/**
|
||||
|
|
@ -349,6 +351,39 @@ class Tenant implements ArrayAccess
|
|||
return $this->put($key, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a key from the tenant's storage.
|
||||
*
|
||||
* @param string $key
|
||||
* @return self
|
||||
*/
|
||||
public function deleteKey(string $key): self
|
||||
{
|
||||
return $this->deleteKeys([$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete keys from the tenant's storage.
|
||||
*
|
||||
* @param string[] $keys
|
||||
* @return self
|
||||
*/
|
||||
public function deleteKeys(array $keys): self
|
||||
{
|
||||
if (! $this->storage instanceof CanDeleteKeys) {
|
||||
throw new NotImplementedException(get_class($this->storage), 'deleteMany',
|
||||
'This method was added to storage drivers provided by the package in 2.2.0 and will be part of the StorageDriver contract in 3.0.0.'
|
||||
);
|
||||
} else {
|
||||
$this->storage->deleteMany($keys);
|
||||
foreach ($keys as $key) {
|
||||
unset($this->data[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a value.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -4,10 +4,15 @@ declare(strict_types=1);
|
|||
|
||||
namespace Stancl\Tenancy;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Console\Kernel as ConsoleKernel;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Traits\ForwardsCalls;
|
||||
use Stancl\Tenancy\Contracts\Future\CanFindByAnyKey;
|
||||
use Stancl\Tenancy\Contracts\TenantCannotBeCreatedException;
|
||||
use Stancl\Tenancy\Exceptions\NotImplementedException;
|
||||
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedException;
|
||||
use Stancl\Tenancy\Jobs\QueuedTenantDatabaseMigrator;
|
||||
use Stancl\Tenancy\Jobs\QueuedTenantDatabaseSeeder;
|
||||
|
|
@ -17,6 +22,8 @@ use Stancl\Tenancy\Jobs\QueuedTenantDatabaseSeeder;
|
|||
*/
|
||||
class TenantManager
|
||||
{
|
||||
use ForwardsCalls;
|
||||
|
||||
/**
|
||||
* The current tenant.
|
||||
*
|
||||
|
|
@ -47,7 +54,7 @@ class TenantManager
|
|||
$this->app = $app;
|
||||
$this->storage = $storage;
|
||||
$this->artisan = $artisan;
|
||||
$this->database = $database;
|
||||
$this->database = $database->withTenantManager($this);
|
||||
|
||||
$this->bootstrapFeatures();
|
||||
}
|
||||
|
|
@ -60,6 +67,8 @@ class TenantManager
|
|||
*/
|
||||
public function createTenant(Tenant $tenant): self
|
||||
{
|
||||
$this->event('tenant.creating', $tenant);
|
||||
|
||||
$this->ensureTenantCanBeCreated($tenant);
|
||||
|
||||
$this->storage->createTenant($tenant);
|
||||
|
|
@ -89,6 +98,8 @@ class TenantManager
|
|||
|
||||
$this->database->createDatabase($tenant, $afterCreating);
|
||||
|
||||
$this->event('tenant.created', $tenant);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
@ -100,12 +111,16 @@ class TenantManager
|
|||
*/
|
||||
public function deleteTenant(Tenant $tenant): self
|
||||
{
|
||||
$this->event('tenant.deleting', $tenant);
|
||||
|
||||
$this->storage->deleteTenant($tenant);
|
||||
|
||||
if ($this->shouldDeleteDatabase()) {
|
||||
$this->database->deleteDatabase($tenant);
|
||||
}
|
||||
|
||||
$this->event('tenant.deleted', $tenant);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
@ -198,6 +213,34 @@ class TenantManager
|
|||
return $this->storage->findByDomain($domain);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a tenant using an arbitrary key.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @return Tenant
|
||||
* @throws TenantCouldNotBeIdentifiedException
|
||||
* @throws NotImplementedException
|
||||
*/
|
||||
public function findBy(string $key, $value): Tenant
|
||||
{
|
||||
if ($key === null) {
|
||||
throw new Exception('No key supplied.');
|
||||
}
|
||||
|
||||
if ($value === null) {
|
||||
throw new Exception('No value supplied.');
|
||||
}
|
||||
|
||||
if (! $this->storage instanceof CanFindByAnyKey) {
|
||||
throw new NotImplementedException(get_class($this->storage), 'findBy',
|
||||
'This method was added to the DB storage driver provided by the package in 2.2.0 and might be part of the StorageDriver contract in 3.0.0.'
|
||||
);
|
||||
}
|
||||
|
||||
return $this->storage->findBy($key, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all tenants.
|
||||
*
|
||||
|
|
@ -246,13 +289,13 @@ class TenantManager
|
|||
*/
|
||||
public function bootstrapTenancy(Tenant $tenant): self
|
||||
{
|
||||
$prevented = $this->event('bootstrapping');
|
||||
$prevented = $this->event('bootstrapping', $tenant);
|
||||
|
||||
foreach ($this->tenancyBootstrappers($prevented) as $bootstrapper) {
|
||||
$this->app[$bootstrapper]->start($tenant);
|
||||
}
|
||||
|
||||
$this->event('bootstrapped');
|
||||
$this->event('bootstrapped', $tenant);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
|
@ -263,7 +306,7 @@ class TenantManager
|
|||
return $this;
|
||||
}
|
||||
|
||||
$prevented = $this->event('ending');
|
||||
$prevented = $this->event('ending', $this->getTenant());
|
||||
|
||||
foreach ($this->tenancyBootstrappers($prevented) as $bootstrapper) {
|
||||
$this->app[$bootstrapper]->end();
|
||||
|
|
@ -383,17 +426,27 @@ class TenantManager
|
|||
}
|
||||
|
||||
/**
|
||||
* Execute event listeners.
|
||||
* Trigger an event and execute event listeners.
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed ...$args
|
||||
* @return string[]
|
||||
*/
|
||||
protected function event(string $name): array
|
||||
public function event(string $name, ...$args): array
|
||||
{
|
||||
return array_reduce($this->eventListeners[$name] ?? [], function ($prevented, $listener) {
|
||||
$prevented = array_merge($prevented, $listener($this) ?? []);
|
||||
return array_reduce($this->eventListeners[$name] ?? [], function ($results, $listener) use ($args) {
|
||||
$results = array_merge($results, $listener($this, ...$args) ?? []);
|
||||
|
||||
return $prevented;
|
||||
return $results;
|
||||
}, []);
|
||||
}
|
||||
|
||||
public function __call($method, $parameters)
|
||||
{
|
||||
if (Str::startsWith($method, 'findBy')) {
|
||||
return $this->findBy(Str::snake(substr($method, 6)), $parameters[0]);
|
||||
}
|
||||
|
||||
static::throwBadMethodCallException($method);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,15 +8,19 @@ use Stancl\Tenancy\Tenant;
|
|||
|
||||
class CacheManagerTest extends TestCase
|
||||
{
|
||||
public $autoInitTenancy = false;
|
||||
|
||||
/** @test */
|
||||
public function default_tag_is_automatically_applied()
|
||||
{
|
||||
$this->initTenancy();
|
||||
$this->assertArrayIsSubset([config('tenancy.cache.tag_base') . tenant('id')], cache()->tags('foo')->getTags()->getNames());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function tags_are_merged_when_array_is_passed()
|
||||
{
|
||||
$this->initTenancy();
|
||||
$expected = [config('tenancy.cache.tag_base') . tenant('id'), 'foo', 'bar'];
|
||||
$this->assertEquals($expected, cache()->tags(['foo', 'bar'])->getTags()->getNames());
|
||||
}
|
||||
|
|
@ -24,6 +28,7 @@ class CacheManagerTest extends TestCase
|
|||
/** @test */
|
||||
public function tags_are_merged_when_string_is_passed()
|
||||
{
|
||||
$this->initTenancy();
|
||||
$expected = [config('tenancy.cache.tag_base') . tenant('id'), 'foo'];
|
||||
$this->assertEquals($expected, cache()->tags('foo')->getTags()->getNames());
|
||||
}
|
||||
|
|
@ -31,6 +36,7 @@ class CacheManagerTest extends TestCase
|
|||
/** @test */
|
||||
public function exception_is_thrown_when_zero_arguments_are_passed_to_tags_method()
|
||||
{
|
||||
$this->initTenancy();
|
||||
$this->expectException(\Exception::class);
|
||||
cache()->tags();
|
||||
}
|
||||
|
|
@ -38,6 +44,7 @@ class CacheManagerTest extends TestCase
|
|||
/** @test */
|
||||
public function exception_is_thrown_when_more_than_one_argument_is_passed_to_tags_method()
|
||||
{
|
||||
$this->initTenancy();
|
||||
$this->expectException(\Exception::class);
|
||||
cache()->tags(1, 2);
|
||||
}
|
||||
|
|
|
|||
45
tests/FutureTest.php
Normal file
45
tests/FutureTest.php
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Stancl\Tenancy\Tests;
|
||||
|
||||
use Stancl\Tenancy\Contracts\Future\CanFindByAnyKey;
|
||||
use Stancl\Tenancy\Tenant;
|
||||
|
||||
class FutureTest extends TestCase
|
||||
{
|
||||
public $autoCreateTenant = false;
|
||||
public $autoInitTenancy = false;
|
||||
|
||||
/** @test */
|
||||
public function keys_can_be_deleted_from_tenant_storage()
|
||||
{
|
||||
$tenant = Tenant::new()->withData(['email' => 'foo@example.com', 'role' => 'admin'])->save();
|
||||
|
||||
$this->assertArrayHasKey('email', $tenant->data);
|
||||
$tenant->deleteKey('email');
|
||||
$this->assertArrayNotHasKey('email', $tenant->data);
|
||||
$this->assertArrayNotHasKey('email', tenancy()->all()->first()->data);
|
||||
|
||||
$tenant->put(['foo' => 'bar', 'abc' => 'xyz']);
|
||||
$this->assertArrayHasKey('foo', $tenant->data);
|
||||
$this->assertArrayHasKey('abc', $tenant->data);
|
||||
|
||||
$tenant->deleteKeys(['foo', 'abc']);
|
||||
$this->assertArrayNotHasKey('foo', $tenant->data);
|
||||
$this->assertArrayNotHasKey('abc', $tenant->data);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function tenant_can_be_identified_using_an_arbitrary_string()
|
||||
{
|
||||
if (! tenancy()->storage instanceof CanFindByAnyKey) {
|
||||
$this->markTestSkipped(get_class(tenancy()->storage) . ' does not implement the CanFindByAnyKey interface.');
|
||||
}
|
||||
|
||||
$tenant = Tenant::new()->withData(['email' => 'foo@example.com'])->save();
|
||||
|
||||
$this->assertSame($tenant->id, tenancy()->findByEmail('foo@example.com')->id);
|
||||
}
|
||||
}
|
||||
|
|
@ -320,4 +320,14 @@ class TenantManagerTest extends TestCase
|
|||
$this->expectException(TenantDoesNotExistException::class);
|
||||
tenancy()->find('gjnfdgf');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function event_listeners_can_accept_arguments()
|
||||
{
|
||||
tenancy()->hook('tenant.creating', function ($tenantManager, $tenant) {
|
||||
$this->assertSame('bar', $tenant->foo);
|
||||
});
|
||||
|
||||
Tenant::new()->withData(['foo' => 'bar'])->save();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Stancl\Tenancy\Tests;
|
||||
|
||||
use Stancl\Tenancy\StorageDrivers\Database\TenantModel;
|
||||
use Stancl\Tenancy\StorageDrivers\Database\TenantRepository;
|
||||
use Stancl\Tenancy\Tenant;
|
||||
|
||||
class TenantStorageTest extends TestCase
|
||||
|
|
@ -112,10 +112,11 @@ class TenantStorageTest extends TestCase
|
|||
}
|
||||
|
||||
/** @test */
|
||||
public function tenant_model_uses_correct_connection()
|
||||
public function tenant_repository_uses_correct_connection()
|
||||
{
|
||||
config(['database.connections.foo' => config('database.connections.sqlite')]);
|
||||
config(['tenancy.storage_drivers.db.connection' => 'foo']);
|
||||
$this->assertSame('foo', (new TenantModel)->getConnectionName());
|
||||
$this->assertSame('foo', app(TenantRepository::class)->database->getName());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
|
|
@ -156,6 +157,9 @@ class TenantStorageTest extends TestCase
|
|||
tenancy()->create(['foo.localhost']);
|
||||
tenancy()->init('foo.localhost');
|
||||
|
||||
tenant()->put('foo', '111');
|
||||
$this->assertSame('111', tenant()->get('foo'));
|
||||
|
||||
tenant()->put(['foo' => 'bar', 'abc' => 'xyz']);
|
||||
$this->assertSame(['foo' => 'bar', 'abc' => 'xyz'], tenant()->get(['foo', 'abc']));
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue