mirror of
https://github.com/archtechx/tenancy.git
synced 2026-02-05 21:14:03 +00:00
Merge branch 'master' of https://github.com/archtechx/tenancy into stein-j-readied-tenant
This commit is contained in:
commit
6222a72a2f
54 changed files with 404 additions and 299 deletions
|
|
@ -150,16 +150,8 @@ class TenancyServiceProvider extends ServiceProvider
|
||||||
|
|
||||||
protected function makeTenancyMiddlewareHighestPriority()
|
protected function makeTenancyMiddlewareHighestPriority()
|
||||||
{
|
{
|
||||||
$tenancyMiddleware = [
|
// PreventAccessFromCentralDomains has even higher priority than the identification middleware
|
||||||
// Even higher priority than the initialization middleware
|
$tenancyMiddleware = array_merge([Middleware\PreventAccessFromCentralDomains::class], config('tenancy.identification.middleware'));
|
||||||
Middleware\PreventAccessFromCentralDomains::class,
|
|
||||||
|
|
||||||
Middleware\InitializeTenancyByDomain::class,
|
|
||||||
Middleware\InitializeTenancyBySubdomain::class,
|
|
||||||
Middleware\InitializeTenancyByDomainOrSubdomain::class,
|
|
||||||
Middleware\InitializeTenancyByPath::class,
|
|
||||||
Middleware\InitializeTenancyByRequestData::class,
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach (array_reverse($tenancyMiddleware) as $middleware) {
|
foreach (array_reverse($tenancyMiddleware) as $middleware) {
|
||||||
$this->app[\Illuminate\Contracts\Http\Kernel::class]->prependToMiddlewarePriority($middleware);
|
$this->app[\Illuminate\Contracts\Http\Kernel::class]->prependToMiddlewarePriority($middleware);
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ declare(strict_types=1);
|
||||||
|
|
||||||
use Stancl\Tenancy\Database\Models\Domain;
|
use Stancl\Tenancy\Database\Models\Domain;
|
||||||
use Stancl\Tenancy\Database\Models\Tenant;
|
use Stancl\Tenancy\Database\Models\Tenant;
|
||||||
|
use Stancl\Tenancy\Middleware;
|
||||||
|
use Stancl\Tenancy\Resolvers;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'tenant_model' => Tenant::class,
|
'tenant_model' => Tenant::class,
|
||||||
|
|
@ -21,6 +23,56 @@ return [
|
||||||
'localhost',
|
'localhost',
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'identification' => [
|
||||||
|
/**
|
||||||
|
* The default middleware used for tenant identification.
|
||||||
|
*
|
||||||
|
* If you use multiple forms of identification, you can set this to the "main" approach you use.
|
||||||
|
*/
|
||||||
|
'default_middleware' => Middleware\InitializeTenancyByDomain::class,// todo@identification add this to a 'tenancy' mw group
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All of the identification middleware used by the package.
|
||||||
|
*
|
||||||
|
* If you write your own, make sure to add them to this array.
|
||||||
|
*/
|
||||||
|
'middleware' => [
|
||||||
|
Middleware\InitializeTenancyByDomain::class,
|
||||||
|
Middleware\InitializeTenancyBySubdomain::class,
|
||||||
|
Middleware\InitializeTenancyByDomainOrSubdomain::class,
|
||||||
|
Middleware\InitializeTenancyByPath::class,
|
||||||
|
Middleware\InitializeTenancyByRequestData::class,
|
||||||
|
],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tenant resolvers used by the package.
|
||||||
|
*
|
||||||
|
* Resolvers which implement the CachedTenantResolver contract have options for configuring the caching details.
|
||||||
|
* If you add your own resolvers, do not add the 'cache' key unless your resolver is based on CachedTenantResolver.
|
||||||
|
*/
|
||||||
|
'resolvers' => [
|
||||||
|
Resolvers\DomainTenantResolver::class => [
|
||||||
|
'cache' => false,
|
||||||
|
'cache_ttl' => 3600, // seconds
|
||||||
|
'cache_store' => null, // default
|
||||||
|
],
|
||||||
|
Resolvers\PathTenantResolver::class => [
|
||||||
|
'tenant_parameter_name' => 'tenant',
|
||||||
|
|
||||||
|
'cache' => false,
|
||||||
|
'cache_ttl' => 3600, // seconds
|
||||||
|
'cache_store' => null, // default
|
||||||
|
],
|
||||||
|
Resolvers\RequestDataTenantResolver::class => [
|
||||||
|
'cache' => false,
|
||||||
|
'cache_ttl' => 3600, // seconds
|
||||||
|
'cache_store' => null, // default
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
// todo@docs update integration guides to use Stancl\Tenancy::defaultMiddleware()
|
||||||
|
],
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tenancy bootstrappers are executed when tenancy is initialized.
|
* Tenancy bootstrappers are executed when tenancy is initialized.
|
||||||
* Their responsibility is making Laravel features tenant-aware.
|
* Their responsibility is making Laravel features tenant-aware.
|
||||||
|
|
@ -234,4 +286,12 @@ return [
|
||||||
'--class' => 'DatabaseSeeder', // root seeder class
|
'--class' => 'DatabaseSeeder', // root seeder class
|
||||||
// '--force' => true,
|
// '--force' => true,
|
||||||
],
|
],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Single-database tenancy config.
|
||||||
|
*/
|
||||||
|
'single_db' => [
|
||||||
|
/** The name of the column used by models with the BelongsToTenant trait. */
|
||||||
|
'tenant_id_column' => 'tenant_id',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,8 @@
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
use Stancl\Tenancy\Controllers\TenantAssetController;
|
||||||
|
|
||||||
Route::get('/tenancy/assets/{path?}', 'Stancl\Tenancy\Controllers\TenantAssetsController@asset')
|
Route::get('/tenancy/assets/{path?}', [TenantAssetController::class, 'asset'])
|
||||||
->where('path', '(.*)')
|
->where('path', '(.*)')
|
||||||
->name('stancl.tenancy.asset');
|
->name('stancl.tenancy.asset');
|
||||||
|
|
|
||||||
22
phpstan.neon
22
phpstan.neon
|
|
@ -13,14 +13,16 @@ parameters:
|
||||||
- Illuminate\Database\Eloquent\Model
|
- Illuminate\Database\Eloquent\Model
|
||||||
|
|
||||||
ignoreErrors:
|
ignoreErrors:
|
||||||
-
|
- '#Cannot access offset (.*?) on Illuminate\\Contracts\\Foundation\\Application#'
|
||||||
message: '#Cannot access offset (.*?) on Illuminate\\Contracts\\Foundation\\Application#'
|
- '#Cannot access offset (.*?) on Illuminate\\Contracts\\Config\\Repository#'
|
||||||
paths:
|
|
||||||
- src/TenancyServiceProvider.php
|
|
||||||
-
|
-
|
||||||
message: '#invalid type Laravel\\Telescope\\IncomingEntry#'
|
message: '#invalid type Laravel\\Telescope\\IncomingEntry#'
|
||||||
paths:
|
paths:
|
||||||
- src/Features/TelescopeTags.php
|
- src/Features/TelescopeTags.php
|
||||||
|
-
|
||||||
|
message: '#Call to an undefined method Illuminate\\Database\\Eloquent\\Model::getRelationshipToPrimaryModel\(\)#'
|
||||||
|
paths:
|
||||||
|
- src/Database/ParentModelScope.php
|
||||||
-
|
-
|
||||||
message: '#Parameter \#1 \$key of method Illuminate\\Contracts\\Cache\\Repository::put\(\) expects string#'
|
message: '#Parameter \#1 \$key of method Illuminate\\Contracts\\Cache\\Repository::put\(\) expects string#'
|
||||||
paths:
|
paths:
|
||||||
|
|
@ -29,6 +31,18 @@ parameters:
|
||||||
message: '#PHPDoc tag \@param has invalid value \(dynamic#'
|
message: '#PHPDoc tag \@param has invalid value \(dynamic#'
|
||||||
paths:
|
paths:
|
||||||
- src/helpers.php
|
- src/helpers.php
|
||||||
|
-
|
||||||
|
message: '#Illuminate\\Routing\\UrlGenerator#'
|
||||||
|
paths:
|
||||||
|
- src/Bootstrappers/FilesystemTenancyBootstrapper.php
|
||||||
|
-
|
||||||
|
message: '#select\(\) expects string, Illuminate\\Database\\Query\\Expression given#'
|
||||||
|
paths:
|
||||||
|
- src/Database/TenantDatabaseManagers/PermissionControlledMySQLDatabaseManager.php
|
||||||
|
-
|
||||||
|
message: '#Trying to invoke Closure\|null but it might not be a callable#'
|
||||||
|
paths:
|
||||||
|
- src/Database/DatabaseConfig.php
|
||||||
|
|
||||||
checkMissingIterableValueType: false
|
checkMissingIterableValueType: false
|
||||||
treatPhpDocTypesAsCertain: false
|
treatPhpDocTypesAsCertain: false
|
||||||
|
|
|
||||||
|
|
@ -35,4 +35,4 @@
|
||||||
<log type="coverage-clover" target="coverage/phpunit/clover.xml" showUncoveredFiles="true"/>
|
<log type="coverage-clover" target="coverage/phpunit/clover.xml" showUncoveredFiles="true"/>
|
||||||
<log type="coverage-html" target="coverage/phpunit/html" lowUpperBound="35" highLowerBound="70"/>
|
<log type="coverage-html" target="coverage/phpunit/html" lowUpperBound="35" highLowerBound="70"/>
|
||||||
</logging>
|
</logging>
|
||||||
</phpunit>
|
</phpunit>
|
||||||
|
|
|
||||||
|
|
@ -23,14 +23,14 @@ class BatchTenancyBootstrapper implements TenancyBootstrapper
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function bootstrap(Tenant $tenant)
|
public function bootstrap(Tenant $tenant): void
|
||||||
{
|
{
|
||||||
// Update batch repository connection to use the tenant connection
|
// Update batch repository connection to use the tenant connection
|
||||||
$this->previousConnection = $this->batchRepository->getConnection();
|
$this->previousConnection = $this->batchRepository->getConnection();
|
||||||
$this->batchRepository->setConnection($this->databaseManager->connection('tenant'));
|
$this->batchRepository->setConnection($this->databaseManager->connection('tenant'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function revert()
|
public function revert(): void
|
||||||
{
|
{
|
||||||
if ($this->previousConnection) {
|
if ($this->previousConnection) {
|
||||||
// Replace batch repository connection with the previously replaced one
|
// Replace batch repository connection with the previously replaced one
|
||||||
|
|
|
||||||
|
|
@ -13,18 +13,14 @@ use Stancl\Tenancy\Contracts\Tenant;
|
||||||
|
|
||||||
class CacheTenancyBootstrapper implements TenancyBootstrapper
|
class CacheTenancyBootstrapper implements TenancyBootstrapper
|
||||||
{
|
{
|
||||||
/** @var CacheManager */
|
protected ?CacheManager $originalCache = null;
|
||||||
protected $originalCache;
|
|
||||||
|
|
||||||
/** @var Application */
|
public function __construct(
|
||||||
protected $app;
|
protected Application $app
|
||||||
|
) {
|
||||||
public function __construct(Application $app)
|
|
||||||
{
|
|
||||||
$this->app = $app;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function bootstrap(Tenant $tenant)
|
public function bootstrap(Tenant $tenant): void
|
||||||
{
|
{
|
||||||
$this->resetFacadeCache();
|
$this->resetFacadeCache();
|
||||||
|
|
||||||
|
|
@ -34,7 +30,7 @@ class CacheTenancyBootstrapper implements TenancyBootstrapper
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function revert()
|
public function revert(): void
|
||||||
{
|
{
|
||||||
$this->resetFacadeCache();
|
$this->resetFacadeCache();
|
||||||
|
|
||||||
|
|
@ -50,7 +46,7 @@ class CacheTenancyBootstrapper implements TenancyBootstrapper
|
||||||
* facade has been made prior to bootstrapping tenancy. The
|
* facade has been made prior to bootstrapping tenancy. The
|
||||||
* facade has its own cache, separate from the container.
|
* facade has its own cache, separate from the container.
|
||||||
*/
|
*/
|
||||||
public function resetFacadeCache()
|
public function resetFacadeCache(): void
|
||||||
{
|
{
|
||||||
Cache::clearResolvedInstances();
|
Cache::clearResolvedInstances();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use Stancl\Tenancy\Contracts\TenancyBootstrapper;
|
||||||
use Stancl\Tenancy\Contracts\Tenant;
|
use Stancl\Tenancy\Contracts\Tenant;
|
||||||
use Stancl\Tenancy\Database\Contracts\TenantWithDatabase;
|
use Stancl\Tenancy\Database\Contracts\TenantWithDatabase;
|
||||||
use Stancl\Tenancy\Database\DatabaseManager;
|
use Stancl\Tenancy\Database\DatabaseManager;
|
||||||
use Stancl\Tenancy\Exceptions\TenantDatabaseDoesNotExistException;
|
use Stancl\Tenancy\Database\Exceptions\TenantDatabaseDoesNotExistException;
|
||||||
|
|
||||||
class DatabaseTenancyBootstrapper implements TenancyBootstrapper
|
class DatabaseTenancyBootstrapper implements TenancyBootstrapper
|
||||||
{
|
{
|
||||||
|
|
@ -20,7 +20,7 @@ class DatabaseTenancyBootstrapper implements TenancyBootstrapper
|
||||||
$this->database = $database;
|
$this->database = $database;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function bootstrap(Tenant $tenant)
|
public function bootstrap(Tenant $tenant): void
|
||||||
{
|
{
|
||||||
/** @var TenantWithDatabase $tenant */
|
/** @var TenantWithDatabase $tenant */
|
||||||
|
|
||||||
|
|
@ -35,7 +35,7 @@ class DatabaseTenancyBootstrapper implements TenancyBootstrapper
|
||||||
$this->database->connectToTenant($tenant);
|
$this->database->connectToTenant($tenant);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function revert()
|
public function revert(): void
|
||||||
{
|
{
|
||||||
$this->database->reconnectToCentral();
|
$this->database->reconnectToCentral();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||||
namespace Stancl\Tenancy\Bootstrappers;
|
namespace Stancl\Tenancy\Bootstrappers;
|
||||||
|
|
||||||
use Illuminate\Contracts\Foundation\Application;
|
use Illuminate\Contracts\Foundation\Application;
|
||||||
|
use Illuminate\Routing\UrlGenerator;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
|
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
|
||||||
use Stancl\Tenancy\Contracts\Tenant;
|
use Stancl\Tenancy\Contracts\Tenant;
|
||||||
|
|
@ -27,13 +28,14 @@ class FilesystemTenancyBootstrapper implements TenancyBootstrapper
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->app['url']->macro('setAssetRoot', function ($root) {
|
$this->app['url']->macro('setAssetRoot', function ($root) {
|
||||||
|
/** @var UrlGenerator $this */
|
||||||
$this->assetRoot = $root;
|
$this->assetRoot = $root;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function bootstrap(Tenant $tenant)
|
public function bootstrap(Tenant $tenant): void
|
||||||
{
|
{
|
||||||
$suffix = $this->app['config']['tenancy.filesystem.suffix_base'] . $tenant->getTenantKey();
|
$suffix = $this->app['config']['tenancy.filesystem.suffix_base'] . $tenant->getTenantKey();
|
||||||
|
|
||||||
|
|
@ -45,7 +47,7 @@ class FilesystemTenancyBootstrapper implements TenancyBootstrapper
|
||||||
// asset()
|
// asset()
|
||||||
if ($this->app['config']['tenancy.filesystem.asset_helper_tenancy'] ?? true) {
|
if ($this->app['config']['tenancy.filesystem.asset_helper_tenancy'] ?? true) {
|
||||||
if ($this->originalPaths['asset_url']) {
|
if ($this->originalPaths['asset_url']) {
|
||||||
$this->app['config']['app.asset_url'] = ($this->originalPaths['asset_url'] ?? $this->app['config']['app.url']) . "/$suffix";
|
$this->app['config']['app.asset_url'] = $this->originalPaths['asset_url'] . "/$suffix";
|
||||||
$this->app['url']->setAssetRoot($this->app['config']['app.asset_url']);
|
$this->app['url']->setAssetRoot($this->app['config']['app.asset_url']);
|
||||||
} else {
|
} else {
|
||||||
$this->app['url']->setAssetRoot($this->app['url']->route('stancl.tenancy.asset', ['path' => '']));
|
$this->app['url']->setAssetRoot($this->app['url']->route('stancl.tenancy.asset', ['path' => '']));
|
||||||
|
|
@ -82,7 +84,7 @@ class FilesystemTenancyBootstrapper implements TenancyBootstrapper
|
||||||
|
|
||||||
if ($url = str_replace(
|
if ($url = str_replace(
|
||||||
'%tenant_id%',
|
'%tenant_id%',
|
||||||
$tenant->getTenantKey(),
|
(string) $tenant->getTenantKey(),
|
||||||
$this->app['config']["tenancy.filesystem.url_override.{$disk}"] ?? ''
|
$this->app['config']["tenancy.filesystem.url_override.{$disk}"] ?? ''
|
||||||
)) {
|
)) {
|
||||||
$this->app['config']["filesystems.disks.{$disk}.url"] = url($url);
|
$this->app['config']["filesystems.disks.{$disk}.url"] = url($url);
|
||||||
|
|
@ -91,7 +93,7 @@ class FilesystemTenancyBootstrapper implements TenancyBootstrapper
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function revert()
|
public function revert(): void
|
||||||
{
|
{
|
||||||
// storage_path()
|
// storage_path()
|
||||||
$this->app->useStoragePath($this->originalPaths['storage']);
|
$this->app->useStoragePath($this->originalPaths['storage']);
|
||||||
|
|
|
||||||
|
|
@ -10,27 +10,23 @@ use Stancl\Tenancy\Contracts\Tenant;
|
||||||
|
|
||||||
class ScoutTenancyBootstrapper implements TenancyBootstrapper
|
class ScoutTenancyBootstrapper implements TenancyBootstrapper
|
||||||
{
|
{
|
||||||
/** @var Repository */
|
protected ?string $originalScoutPrefix = null;
|
||||||
protected $config;
|
|
||||||
|
|
||||||
/** @var string */
|
public function __construct(
|
||||||
protected $originalScoutPrefix;
|
protected Repository $config,
|
||||||
|
) {
|
||||||
public function __construct(Repository $config)
|
|
||||||
{
|
|
||||||
$this->config = $config;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function bootstrap(Tenant $tenant)
|
public function bootstrap(Tenant $tenant): void
|
||||||
{
|
{
|
||||||
if (! isset($this->originalScoutPrefix)) {
|
if ($this->originalScoutPrefix !== null) {
|
||||||
$this->originalScoutPrefix = $this->config->get('scout.prefix');
|
$this->originalScoutPrefix = $this->config->get('scout.prefix');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->config->set('scout.prefix', $this->getTenantPrefix($tenant));
|
$this->config->set('scout.prefix', $this->getTenantPrefix($tenant));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function revert()
|
public function revert(): void
|
||||||
{
|
{
|
||||||
$this->config->set('scout.prefix', $this->originalScoutPrefix);
|
$this->config->set('scout.prefix', $this->originalScoutPrefix);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ class QueueTenancyBootstrapper implements TenancyBootstrapper
|
||||||
* However, we're registering a hook to initialize tenancy. Therefore,
|
* However, we're registering a hook to initialize tenancy. Therefore,
|
||||||
* we need to register the hook at service provider execution time.
|
* we need to register the hook at service provider execution time.
|
||||||
*/
|
*/
|
||||||
public static function __constructStatic(Application $app)
|
public static function __constructStatic(Application $app): void
|
||||||
{
|
{
|
||||||
static::setUpJobListener($app->make(Dispatcher::class), $app->runningUnitTests());
|
static::setUpJobListener($app->make(Dispatcher::class), $app->runningUnitTests());
|
||||||
}
|
}
|
||||||
|
|
@ -52,7 +52,7 @@ class QueueTenancyBootstrapper implements TenancyBootstrapper
|
||||||
$this->setUpPayloadGenerator();
|
$this->setUpPayloadGenerator();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function setUpJobListener($dispatcher, $runningTests)
|
protected static function setUpJobListener(Dispatcher $dispatcher, bool $runningTests): void
|
||||||
{
|
{
|
||||||
$previousTenant = null;
|
$previousTenant = null;
|
||||||
|
|
||||||
|
|
@ -62,14 +62,11 @@ class QueueTenancyBootstrapper implements TenancyBootstrapper
|
||||||
static::initializeTenancyForQueue($event->job->payload()['tenant_id'] ?? null);
|
static::initializeTenancyForQueue($event->job->payload()['tenant_id'] ?? null);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (version_compare(app()->version(), '8.64', '>=')) {
|
$dispatcher->listen(JobRetryRequested::class, function ($event) use (&$previousTenant) {
|
||||||
// JobRetryRequested only exists since Laravel 8.64
|
$previousTenant = tenant();
|
||||||
$dispatcher->listen(JobRetryRequested::class, function ($event) use (&$previousTenant) {
|
|
||||||
$previousTenant = tenant();
|
|
||||||
|
|
||||||
static::initializeTenancyForQueue($event->payload()['tenant_id'] ?? null);
|
static::initializeTenancyForQueue($event->payload()['tenant_id'] ?? null);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// If we're running tests, we make sure to clean up after any artisan('queue:work') calls
|
// If we're running tests, we make sure to clean up after any artisan('queue:work') calls
|
||||||
$revertToPreviousState = function ($event) use (&$previousTenant, $runningTests) {
|
$revertToPreviousState = function ($event) use (&$previousTenant, $runningTests) {
|
||||||
|
|
@ -82,7 +79,7 @@ class QueueTenancyBootstrapper implements TenancyBootstrapper
|
||||||
$dispatcher->listen(JobFailed::class, $revertToPreviousState); // artisan('queue:work') which fails
|
$dispatcher->listen(JobFailed::class, $revertToPreviousState); // artisan('queue:work') which fails
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function initializeTenancyForQueue($tenantId)
|
protected static function initializeTenancyForQueue(string|int $tenantId): void
|
||||||
{
|
{
|
||||||
if (! $tenantId) {
|
if (! $tenantId) {
|
||||||
// The job is not tenant-aware
|
// The job is not tenant-aware
|
||||||
|
|
@ -100,7 +97,9 @@ class QueueTenancyBootstrapper implements TenancyBootstrapper
|
||||||
tenancy()->end();
|
tenancy()->end();
|
||||||
}
|
}
|
||||||
|
|
||||||
tenancy()->initialize(tenancy()->find($tenantId));
|
/** @var Tenant $tenant */
|
||||||
|
$tenant = tenancy()->find($tenantId);
|
||||||
|
tenancy()->initialize($tenant);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -115,10 +114,13 @@ class QueueTenancyBootstrapper implements TenancyBootstrapper
|
||||||
|
|
||||||
// Tenancy was either not initialized, or initialized for a different tenant.
|
// Tenancy was either not initialized, or initialized for a different tenant.
|
||||||
// Therefore, we initialize it for the correct tenant.
|
// Therefore, we initialize it for the correct tenant.
|
||||||
tenancy()->initialize(tenancy()->find($tenantId));
|
|
||||||
|
/** @var Tenant $tenant */
|
||||||
|
$tenant = tenancy()->find($tenantId);
|
||||||
|
tenancy()->initialize($tenant);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function revertToPreviousState($event, &$previousTenant)
|
protected static function revertToPreviousState(JobProcessed|JobFailed $event, ?Tenant &$previousTenant): void
|
||||||
{
|
{
|
||||||
$tenantId = $event->job->payload()['tenant_id'] ?? null;
|
$tenantId = $event->job->payload()['tenant_id'] ?? null;
|
||||||
|
|
||||||
|
|
@ -138,7 +140,7 @@ class QueueTenancyBootstrapper implements TenancyBootstrapper
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function setUpPayloadGenerator()
|
protected function setUpPayloadGenerator(): void
|
||||||
{
|
{
|
||||||
$bootstrapper = &$this;
|
$bootstrapper = &$this;
|
||||||
|
|
||||||
|
|
@ -149,17 +151,17 @@ class QueueTenancyBootstrapper implements TenancyBootstrapper
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function bootstrap(Tenant $tenant)
|
public function bootstrap(Tenant $tenant): void
|
||||||
{
|
{
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
public function revert()
|
public function revert(): void
|
||||||
{
|
{
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPayload(string $connection)
|
public function getPayload(string $connection): array
|
||||||
{
|
{
|
||||||
if (! tenancy()->initialized) {
|
if (! tenancy()->initialized) {
|
||||||
return [];
|
return [];
|
||||||
|
|
|
||||||
|
|
@ -22,18 +22,21 @@ class RedisTenancyBootstrapper implements TenancyBootstrapper
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function bootstrap(Tenant $tenant)
|
public function bootstrap(Tenant $tenant): void
|
||||||
{
|
{
|
||||||
foreach ($this->prefixedConnections() as $connection) {
|
foreach ($this->prefixedConnections() as $connection) {
|
||||||
$prefix = $this->config['tenancy.redis.prefix_base'] . $tenant->getTenantKey();
|
$prefix = $this->config['tenancy.redis.prefix_base'] . $tenant->getTenantKey();
|
||||||
$client = Redis::connection($connection)->client();
|
$client = Redis::connection($connection)->client();
|
||||||
|
|
||||||
$this->originalPrefixes[$connection] = $client->getOption($client::OPT_PREFIX);
|
/** @var string $originalPrefix */
|
||||||
|
$originalPrefix = $client->getOption($client::OPT_PREFIX);
|
||||||
|
|
||||||
|
$this->originalPrefixes[$connection] = $originalPrefix;
|
||||||
$client->setOption($client::OPT_PREFIX, $prefix);
|
$client->setOption($client::OPT_PREFIX, $prefix);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function revert()
|
public function revert(): void
|
||||||
{
|
{
|
||||||
foreach ($this->prefixedConnections() as $connection) {
|
foreach ($this->prefixedConnections() as $connection) {
|
||||||
$client = Redis::connection($connection)->client();
|
$client = Redis::connection($connection)->client();
|
||||||
|
|
@ -44,7 +47,8 @@ class RedisTenancyBootstrapper implements TenancyBootstrapper
|
||||||
$this->originalPrefixes = [];
|
$this->originalPrefixes = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function prefixedConnections()
|
/** @return string[] */
|
||||||
|
protected function prefixedConnections(): array
|
||||||
{
|
{
|
||||||
return $this->config['tenancy.redis.prefixed_connections'];
|
return $this->config['tenancy.redis.prefixed_connections'];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ class Down extends DownCommand
|
||||||
|
|
||||||
protected $description = 'Put tenants into maintenance mode.';
|
protected $description = 'Put tenants into maintenance mode.';
|
||||||
|
|
||||||
public function handle(): void
|
public function handle(): int
|
||||||
{
|
{
|
||||||
// The base down command is heavily used. Instead of saving the data inside a file,
|
// The base down command is heavily used. Instead of saving the data inside a file,
|
||||||
// the data is stored the tenant database, which means some Laravel features
|
// the data is stored the tenant database, which means some Laravel features
|
||||||
|
|
@ -29,16 +29,18 @@ class Down extends DownCommand
|
||||||
$payload = $this->getDownDatabasePayload();
|
$payload = $this->getDownDatabasePayload();
|
||||||
|
|
||||||
// This runs for all tenants if no --tenants are specified
|
// This runs for all tenants if no --tenants are specified
|
||||||
tenancy()->runForMultiple($this->option('tenants'), function ($tenant) use ($payload) {
|
tenancy()->runForMultiple($this->getTenants(), function ($tenant) use ($payload) {
|
||||||
$this->line("Tenant: {$tenant['id']}");
|
$this->line("Tenant: {$tenant['id']}");
|
||||||
$tenant->putDownForMaintenance($payload);
|
$tenant->putDownForMaintenance($payload);
|
||||||
});
|
});
|
||||||
|
|
||||||
$this->comment('Tenants are now in maintenance mode.');
|
$this->comment('Tenants are now in maintenance mode.');
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get the payload to be placed in the "down" file. */
|
/** Get the payload to be placed in the "down" file. */
|
||||||
protected function getDownDatabasePayload()
|
protected function getDownDatabasePayload(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'except' => $this->excludedPaths(),
|
'except' => $this->excludedPaths(),
|
||||||
|
|
@ -46,7 +48,7 @@ class Down extends DownCommand
|
||||||
'retry' => $this->getRetryTime(),
|
'retry' => $this->getRetryTime(),
|
||||||
'refresh' => $this->option('refresh'),
|
'refresh' => $this->option('refresh'),
|
||||||
'secret' => $this->option('secret'),
|
'secret' => $this->option('secret'),
|
||||||
'status' => (int) $this->option('status', 503),
|
'status' => (int) ($this->option('status') ?? 503),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,8 +49,8 @@ class Link extends Command
|
||||||
{
|
{
|
||||||
CreateStorageSymlinksAction::handle(
|
CreateStorageSymlinksAction::handle(
|
||||||
$tenants,
|
$tenants,
|
||||||
$this->option('relative') ?? false,
|
(bool) ($this->option('relative') ?? false),
|
||||||
$this->option('force') ?? false,
|
(bool) ($this->option('force') ?? false),
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->info('The links have been created.');
|
$this->info('The links have been created.');
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,7 @@ use Symfony\Component\Console\Output\ConsoleOutput;
|
||||||
class Run extends Command
|
class Run extends Command
|
||||||
{
|
{
|
||||||
use HasTenantOptions;
|
use HasTenantOptions;
|
||||||
/**
|
|
||||||
* The console command description.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $description = 'Run a command for tenant(s)';
|
protected $description = 'Run a command for tenant(s)';
|
||||||
|
|
||||||
protected $signature = 'tenants:run {commandname : The artisan command.}
|
protected $signature = 'tenants:run {commandname : The artisan command.}
|
||||||
|
|
@ -25,10 +21,9 @@ class Run extends Command
|
||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
$argvInput = $this->ArgvInput();
|
$argvInput = $this->argvInput();
|
||||||
$tenants = $this->getTenants();
|
|
||||||
|
|
||||||
tenancy()->runForMultiple($tenants, function ($tenant) use ($argvInput) {
|
tenancy()->runForMultiple($this->getTenants(), function ($tenant) use ($argvInput) {
|
||||||
$this->line("Tenant: {$tenant->getTenantKey()}");
|
$this->line("Tenant: {$tenant->getTenantKey()}");
|
||||||
|
|
||||||
$this->getLaravel()
|
$this->getLaravel()
|
||||||
|
|
@ -39,12 +34,15 @@ class Run extends Command
|
||||||
|
|
||||||
protected function argvInput(): ArgvInput
|
protected function argvInput(): ArgvInput
|
||||||
{
|
{
|
||||||
|
/** @var string $commandname */
|
||||||
|
$commandname = $this->argument('commandname');
|
||||||
|
|
||||||
// Convert string command to array
|
// Convert string command to array
|
||||||
$subCommand = explode(' ', $this->argument('commandname'));
|
$subcommand = explode(' ', $commandname);
|
||||||
|
|
||||||
// Add "artisan" as first parameter because ArgvInput expects "artisan" as first parameter and later removes it
|
// Add "artisan" as first parameter because ArgvInput expects "artisan" as first parameter and later removes it
|
||||||
array_unshift($subCommand, 'artisan');
|
array_unshift($subcommand, 'artisan');
|
||||||
|
|
||||||
return new ArgvInput($subCommand);
|
return new ArgvInput($subcommand);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||||
namespace Stancl\Tenancy\Commands;
|
namespace Stancl\Tenancy\Commands;
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Stancl\Tenancy\Contracts\Tenant;
|
use Stancl\Tenancy\Contracts\Tenant;
|
||||||
|
|
||||||
class TenantList extends Command
|
class TenantList extends Command
|
||||||
|
|
@ -16,15 +17,16 @@ class TenantList extends Command
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
$this->info('Listing all tenants.');
|
$this->info('Listing all tenants.');
|
||||||
tenancy()
|
|
||||||
->query()
|
$tenants = tenancy()->query()->cursor();
|
||||||
->cursor()
|
|
||||||
->each(function (Tenant $tenant) {
|
foreach ($tenants as $tenant) {
|
||||||
if ($tenant->domains) {
|
/** @var Model&Tenant $tenant */
|
||||||
$this->line("[Tenant] {$tenant->getTenantKeyName()}: {$tenant->getTenantKey()} @ " . implode('; ', $tenant->domains->pluck('domain')->toArray() ?? []));
|
if ($tenant->domains) {
|
||||||
} else {
|
$this->line("[Tenant] {$tenant->getTenantKeyName()}: {$tenant->getTenantKey()} @ " . implode('; ', $tenant->domains->pluck('domain')->toArray() ?? []));
|
||||||
$this->line("[Tenant] {$tenant->getTenantKeyName()}: {$tenant->getTenantKey()}");
|
} else {
|
||||||
}
|
$this->line("[Tenant] {$tenant->getTenantKeyName()}: {$tenant->getTenantKey()}");
|
||||||
});
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||||
namespace Stancl\Tenancy\Concerns;
|
namespace Stancl\Tenancy\Concerns;
|
||||||
|
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Stancl\Tenancy\Database\Models\Tenant;
|
use Stancl\Tenancy\Contracts\Tenant;
|
||||||
|
|
||||||
trait DealsWithTenantSymlinks
|
trait DealsWithTenantSymlinks
|
||||||
{
|
{
|
||||||
|
|
@ -23,12 +23,14 @@ trait DealsWithTenantSymlinks
|
||||||
$diskUrls = config('tenancy.filesystem.url_override');
|
$diskUrls = config('tenancy.filesystem.url_override');
|
||||||
$disks = config('tenancy.filesystem.root_override');
|
$disks = config('tenancy.filesystem.root_override');
|
||||||
$suffixBase = config('tenancy.filesystem.suffix_base');
|
$suffixBase = config('tenancy.filesystem.suffix_base');
|
||||||
$symlinks = collect();
|
|
||||||
$tenantKey = $tenant->getTenantKey();
|
$tenantKey = $tenant->getTenantKey();
|
||||||
|
|
||||||
|
/** @var Collection<array<string, string>> $symlinks */
|
||||||
|
$symlinks = collect([]);
|
||||||
|
|
||||||
foreach ($diskUrls as $disk => $publicPath) {
|
foreach ($diskUrls as $disk => $publicPath) {
|
||||||
$storagePath = str_replace('%storage_path%', $suffixBase . $tenantKey, $disks[$disk]);
|
$storagePath = str_replace('%storage_path%', $suffixBase . $tenantKey, $disks[$disk]);
|
||||||
$publicPath = str_replace('%tenant_id%', $tenantKey, $publicPath);
|
$publicPath = str_replace('%tenant_id%', (string) $tenantKey, $publicPath);
|
||||||
|
|
||||||
tenancy()->central(function () use ($symlinks, $publicPath, $storagePath) {
|
tenancy()->central(function () use ($symlinks, $publicPath, $storagePath) {
|
||||||
$symlinks->push([public_path($publicPath) => storage_path($storagePath)]);
|
$symlinks->push([public_path($publicPath) => storage_path($storagePath)]);
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Contracts;
|
namespace Stancl\Tenancy\Contracts;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property-read Tenant $tenant
|
* @property-read Tenant $tenant
|
||||||
*
|
*
|
||||||
|
|
@ -15,5 +17,5 @@ namespace Stancl\Tenancy\Contracts;
|
||||||
*/
|
*/
|
||||||
interface Domain
|
interface Domain
|
||||||
{
|
{
|
||||||
public function tenant();
|
public function tenant(): BelongsTo;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ namespace Stancl\Tenancy\Contracts;
|
||||||
*/
|
*/
|
||||||
interface TenancyBootstrapper
|
interface TenancyBootstrapper
|
||||||
{
|
{
|
||||||
public function bootstrap(Tenant $tenant);
|
public function bootstrap(Tenant $tenant): void;
|
||||||
|
|
||||||
public function revert();
|
public function revert(): void;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ abstract class TenantCannotBeCreatedException extends \Exception
|
||||||
{
|
{
|
||||||
abstract public function reason(): string;
|
abstract public function reason(): string;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
protected $message;
|
protected $message;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
|
|
|
||||||
32
src/Controllers/TenantAssetController.php
Normal file
32
src/Controllers/TenantAssetController.php
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Stancl\Tenancy\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Routing\Controller;
|
||||||
|
use Stancl\Tenancy\Tenancy;
|
||||||
|
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
class TenantAssetController extends Controller // todo@docs this was renamed from TenantAssetsController
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->middleware(Tenancy::defaultMiddleware());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
|
||||||
|
*/
|
||||||
|
public function asset(string $path = null): BinaryFileResponse
|
||||||
|
{
|
||||||
|
abort_if($path === null, 404);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return response()->file(storage_path("app/public/$path"));
|
||||||
|
} catch (Throwable) {
|
||||||
|
abort(404);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Controllers;
|
|
||||||
|
|
||||||
use Closure;
|
|
||||||
use Illuminate\Routing\Controller;
|
|
||||||
use Throwable;
|
|
||||||
|
|
||||||
class TenantAssetsController extends Controller // todo rename this to TenantAssetController & update references in docs
|
|
||||||
{
|
|
||||||
public static string|array|Closure $tenancyMiddleware = Stancl\Tenancy\Middleware\InitializeTenancyByDomain::class;
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->middleware(static::$tenancyMiddleware);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function asset(string $path = null)
|
|
||||||
{
|
|
||||||
abort_if($path === null, 404);
|
|
||||||
|
|
||||||
try {
|
|
||||||
return response()->file(storage_path("app/public/$path"));
|
|
||||||
} catch (Throwable) {
|
|
||||||
abort(404);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -12,11 +12,14 @@ use Stancl\Tenancy\Database\TenantScope;
|
||||||
*/
|
*/
|
||||||
trait BelongsToTenant
|
trait BelongsToTenant
|
||||||
{
|
{
|
||||||
public static $tenantIdColumn = 'tenant_id';
|
|
||||||
|
|
||||||
public function tenant()
|
public function tenant()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(config('tenancy.tenant_model'), BelongsToTenant::$tenantIdColumn);
|
return $this->belongsTo(config('tenancy.tenant_model'), static::tenantIdColumn());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function tenantIdColumn(): string
|
||||||
|
{
|
||||||
|
return config('tenancy.single_db.tenant_id_column');
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function bootBelongsToTenant(): void
|
public static function bootBelongsToTenant(): void
|
||||||
|
|
@ -24,9 +27,9 @@ trait BelongsToTenant
|
||||||
static::addGlobalScope(new TenantScope);
|
static::addGlobalScope(new TenantScope);
|
||||||
|
|
||||||
static::creating(function ($model) {
|
static::creating(function ($model) {
|
||||||
if (! $model->getAttribute(BelongsToTenant::$tenantIdColumn) && ! $model->relationLoaded('tenant')) {
|
if (! $model->getAttribute(static::tenantIdColumn()) && ! $model->relationLoaded('tenant')) {
|
||||||
if (tenancy()->initialized) {
|
if (tenancy()->initialized) {
|
||||||
$model->setAttribute(BelongsToTenant::$tenantIdColumn, tenant()->getTenantKey());
|
$model->setAttribute(static::tenantIdColumn(), tenant()->getTenantKey());
|
||||||
$model->setRelation('tenant', tenant());
|
$model->setRelation('tenant', tenant());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,11 @@ trait HasScopedValidationRules
|
||||||
{
|
{
|
||||||
public function unique($table, $column = 'NULL')
|
public function unique($table, $column = 'NULL')
|
||||||
{
|
{
|
||||||
return (new Unique($table, $column))->where(BelongsToTenant::$tenantIdColumn, $this->getTenantKey());
|
return (new Unique($table, $column))->where(BelongsToTenant::tenantIdColumn(), $this->getTenantKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function exists($table, $column = 'NULL')
|
public function exists($table, $column = 'NULL')
|
||||||
{
|
{
|
||||||
return (new Exists($table, $column))->where(BelongsToTenant::$tenantIdColumn, $this->getTenantKey());
|
return (new Exists($table, $column))->where(BelongsToTenant::tenantIdColumn(), $this->getTenantKey());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,21 +5,15 @@ declare(strict_types=1);
|
||||||
namespace Stancl\Tenancy\Database\Concerns;
|
namespace Stancl\Tenancy\Database\Concerns;
|
||||||
|
|
||||||
use Stancl\Tenancy\Contracts\Tenant;
|
use Stancl\Tenancy\Contracts\Tenant;
|
||||||
use Stancl\Tenancy\Resolvers;
|
|
||||||
use Stancl\Tenancy\Resolvers\Contracts\CachedTenantResolver;
|
use Stancl\Tenancy\Resolvers\Contracts\CachedTenantResolver;
|
||||||
|
use Stancl\Tenancy\Tenancy;
|
||||||
|
|
||||||
trait InvalidatesResolverCache
|
trait InvalidatesResolverCache
|
||||||
{
|
{
|
||||||
public static $resolvers = [
|
|
||||||
Resolvers\DomainTenantResolver::class,
|
|
||||||
Resolvers\PathTenantResolver::class,
|
|
||||||
Resolvers\RequestDataTenantResolver::class,
|
|
||||||
];
|
|
||||||
|
|
||||||
public static function bootInvalidatesResolverCache(): void
|
public static function bootInvalidatesResolverCache(): void
|
||||||
{
|
{
|
||||||
static::saved(function (Tenant $tenant) {
|
static::saved(function (Tenant $tenant) {
|
||||||
foreach (static::$resolvers as $resolver) {
|
foreach (Tenancy::cachedResolvers() as $resolver) {
|
||||||
/** @var CachedTenantResolver $resolver */
|
/** @var CachedTenantResolver $resolver */
|
||||||
$resolver = app($resolver);
|
$resolver = app($resolver);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,24 +5,18 @@ declare(strict_types=1);
|
||||||
namespace Stancl\Tenancy\Database\Concerns;
|
namespace Stancl\Tenancy\Database\Concerns;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Stancl\Tenancy\Resolvers;
|
|
||||||
use Stancl\Tenancy\Resolvers\Contracts\CachedTenantResolver;
|
use Stancl\Tenancy\Resolvers\Contracts\CachedTenantResolver;
|
||||||
|
use Stancl\Tenancy\Tenancy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Meant to be used on models that belong to tenants.
|
* Meant to be used on models that belong to tenants.
|
||||||
*/
|
*/
|
||||||
trait InvalidatesTenantsResolverCache
|
trait InvalidatesTenantsResolverCache
|
||||||
{
|
{
|
||||||
public static $resolvers = [
|
|
||||||
Resolvers\DomainTenantResolver::class,
|
|
||||||
Resolvers\PathTenantResolver::class,
|
|
||||||
Resolvers\RequestDataTenantResolver::class,
|
|
||||||
];
|
|
||||||
|
|
||||||
public static function bootInvalidatesTenantsResolverCache(): void
|
public static function bootInvalidatesTenantsResolverCache(): void
|
||||||
{
|
{
|
||||||
static::saved(function (Model $model) {
|
static::saved(function (Model $model) {
|
||||||
foreach (static::$resolvers as $resolver) {
|
foreach (Tenancy::cachedResolvers() as $resolver) {
|
||||||
/** @var CachedTenantResolver $resolver */
|
/** @var CachedTenantResolver $resolver */
|
||||||
$resolver = app($resolver);
|
$resolver = app($resolver);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,5 +9,15 @@ use Stancl\Tenancy\Database\DatabaseConfig;
|
||||||
|
|
||||||
interface TenantWithDatabase extends Tenant
|
interface TenantWithDatabase extends Tenant
|
||||||
{
|
{
|
||||||
|
/** Get the tenant's database config. */
|
||||||
public function database(): DatabaseConfig;
|
public function database(): DatabaseConfig;
|
||||||
|
|
||||||
|
/** Get the internal prefix. */
|
||||||
|
public static function internalPrefix(): string;
|
||||||
|
|
||||||
|
/** Get an internal key. */
|
||||||
|
public function getInternal(string $key): mixed;
|
||||||
|
|
||||||
|
/** Set internal key. */
|
||||||
|
public function setInternal(string $key, mixed $value): static;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,20 +26,20 @@ class DatabaseConfig
|
||||||
|
|
||||||
public static function __constructStatic(): void
|
public static function __constructStatic(): void
|
||||||
{
|
{
|
||||||
static::$usernameGenerator = static::$usernameGenerator ?? function (Tenant $tenant) {
|
static::$usernameGenerator = static::$usernameGenerator ?? function (Model&Tenant $tenant) {
|
||||||
return Str::random(16);
|
return Str::random(16);
|
||||||
};
|
};
|
||||||
|
|
||||||
static::$passwordGenerator = static::$passwordGenerator ?? function (Tenant $tenant) {
|
static::$passwordGenerator = static::$passwordGenerator ?? function (Model&Tenant $tenant) {
|
||||||
return Hash::make(Str::random(32));
|
return Hash::make(Str::random(32));
|
||||||
};
|
};
|
||||||
|
|
||||||
static::$databaseNameGenerator = static::$databaseNameGenerator ?? function (Tenant $tenant) {
|
static::$databaseNameGenerator = static::$databaseNameGenerator ?? function (Model&Tenant $tenant) {
|
||||||
return config('tenancy.database.prefix') . $tenant->getTenantKey() . config('tenancy.database.suffix');
|
return config('tenancy.database.prefix') . $tenant->getTenantKey() . config('tenancy.database.suffix');
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __construct(Tenant $tenant)
|
public function __construct(Model&Tenant $tenant)
|
||||||
{
|
{
|
||||||
static::__constructStatic();
|
static::__constructStatic();
|
||||||
|
|
||||||
|
|
@ -61,7 +61,7 @@ class DatabaseConfig
|
||||||
static::$passwordGenerator = $passwordGenerator;
|
static::$passwordGenerator = $passwordGenerator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getName(): ?string
|
public function getName(): string
|
||||||
{
|
{
|
||||||
return $this->tenant->getInternal('db_name') ?? (static::$databaseNameGenerator)($this->tenant);
|
return $this->tenant->getInternal('db_name') ?? (static::$databaseNameGenerator)($this->tenant);
|
||||||
}
|
}
|
||||||
|
|
@ -81,7 +81,7 @@ class DatabaseConfig
|
||||||
*/
|
*/
|
||||||
public function makeCredentials(): void
|
public function makeCredentials(): void
|
||||||
{
|
{
|
||||||
$this->tenant->setInternal('db_name', $this->getName() ?? (static::$databaseNameGenerator)($this->tenant));
|
$this->tenant->setInternal('db_name', $this->getName());
|
||||||
|
|
||||||
if ($this->manager() instanceof Contracts\ManagesDatabaseUsers) {
|
if ($this->manager() instanceof Contracts\ManagesDatabaseUsers) {
|
||||||
$this->tenant->setInternal('db_username', $this->getUsername() ?? (static::$usernameGenerator)($this->tenant));
|
$this->tenant->setInternal('db_username', $this->getUsername() ?? (static::$usernameGenerator)($this->tenant));
|
||||||
|
|
|
||||||
|
|
@ -15,25 +15,14 @@ use Stancl\Tenancy\Database\Contracts\TenantWithDatabase;
|
||||||
*/
|
*/
|
||||||
class DatabaseManager
|
class DatabaseManager
|
||||||
{
|
{
|
||||||
/** @var Application */
|
public function __construct(
|
||||||
protected $app;
|
protected Application $app,
|
||||||
|
protected BaseDatabaseManager $database,
|
||||||
/** @var BaseDatabaseManager */
|
protected Repository $config,
|
||||||
protected $database;
|
) {
|
||||||
|
|
||||||
/** @var Repository */
|
|
||||||
protected $config;
|
|
||||||
|
|
||||||
public function __construct(Application $app, BaseDatabaseManager $database, Repository $config)
|
|
||||||
{
|
|
||||||
$this->app = $app;
|
|
||||||
$this->database = $database;
|
|
||||||
$this->config = $config;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Connect to a tenant's database. */
|
||||||
* Connect to a tenant's database.
|
|
||||||
*/
|
|
||||||
public function connectToTenant(TenantWithDatabase $tenant): void
|
public function connectToTenant(TenantWithDatabase $tenant): void
|
||||||
{
|
{
|
||||||
$this->purgeTenantConnection();
|
$this->purgeTenantConnection();
|
||||||
|
|
@ -41,35 +30,27 @@ class DatabaseManager
|
||||||
$this->setDefaultConnection('tenant');
|
$this->setDefaultConnection('tenant');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Reconnect to the default non-tenant connection. */
|
||||||
* Reconnect to the default non-tenant connection.
|
|
||||||
*/
|
|
||||||
public function reconnectToCentral(): void
|
public function reconnectToCentral(): void
|
||||||
{
|
{
|
||||||
$this->purgeTenantConnection();
|
$this->purgeTenantConnection();
|
||||||
$this->setDefaultConnection($this->config->get('tenancy.database.central_connection'));
|
$this->setDefaultConnection($this->config->get('tenancy.database.central_connection'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Change the default database connection config. */
|
||||||
* Change the default database connection config.
|
|
||||||
*/
|
|
||||||
public function setDefaultConnection(string $connection): void
|
public function setDefaultConnection(string $connection): void
|
||||||
{
|
{
|
||||||
$this->config['database.default'] = $connection;
|
$this->config['database.default'] = $connection;
|
||||||
$this->database->setDefaultConnection($connection);
|
$this->database->setDefaultConnection($connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Create the tenant database connection. */
|
||||||
* Create the tenant database connection.
|
|
||||||
*/
|
|
||||||
public function createTenantConnection(TenantWithDatabase $tenant): void
|
public function createTenantConnection(TenantWithDatabase $tenant): void
|
||||||
{
|
{
|
||||||
$this->config['database.connections.tenant'] = $tenant->database()->connection();
|
$this->config['database.connections.tenant'] = $tenant->database()->connection();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Purge the tenant database connection. */
|
||||||
* Purge the tenant database connection.
|
|
||||||
*/
|
|
||||||
public function purgeTenantConnection(): void
|
public function purgeTenantConnection(): void
|
||||||
{
|
{
|
||||||
if (array_key_exists('tenant', $this->database->getConnections())) {
|
if (array_key_exists('tenant', $this->database->getConnections())) {
|
||||||
|
|
@ -83,8 +64,8 @@ class DatabaseManager
|
||||||
* Check if a tenant can be created.
|
* Check if a tenant can be created.
|
||||||
*
|
*
|
||||||
* @throws TenantCannotBeCreatedException
|
* @throws TenantCannotBeCreatedException
|
||||||
* @throws DatabaseManagerNotRegisteredException
|
* @throws Exceptions\DatabaseManagerNotRegisteredException
|
||||||
* @throws TenantDatabaseAlreadyExistsException
|
* @throws Exceptions\TenantDatabaseAlreadyExistsException
|
||||||
*/
|
*/
|
||||||
public function ensureTenantCanBeCreated(TenantWithDatabase $tenant): void
|
public function ensureTenantCanBeCreated(TenantWithDatabase $tenant): void
|
||||||
{
|
{
|
||||||
|
|
@ -94,8 +75,13 @@ class DatabaseManager
|
||||||
throw new Exceptions\TenantDatabaseAlreadyExistsException($database);
|
throw new Exceptions\TenantDatabaseAlreadyExistsException($database);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($manager instanceof Contracts\ManagesDatabaseUsers && $manager->userExists($username = $tenant->database()->getUsername())) {
|
if ($manager instanceof Contracts\ManagesDatabaseUsers) {
|
||||||
throw new Exceptions\TenantDatabaseUserAlreadyExistsException($username);
|
/** @var string $username */
|
||||||
|
$username = $tenant->database()->getUsername();
|
||||||
|
|
||||||
|
if ($manager->userExists($username)) {
|
||||||
|
throw new Exceptions\TenantDatabaseUserAlreadyExistsException($username);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ class ParentModelScope implements Scope
|
||||||
$builder->whereHas($builder->getModel()->getRelationshipToPrimaryModel());
|
$builder->whereHas($builder->getModel()->getRelationshipToPrimaryModel());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function extend(Builder $builder)
|
public function extend(Builder $builder): void
|
||||||
{
|
{
|
||||||
$builder->macro('withoutParentModel', function (Builder $builder) {
|
$builder->macro('withoutParentModel', function (Builder $builder) {
|
||||||
return $builder->withoutGlobalScope($this);
|
return $builder->withoutGlobalScope($this);
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,8 @@ class PermissionControlledMySQLDatabaseManager extends MySQLDatabaseManager impl
|
||||||
{
|
{
|
||||||
use CreatesDatabaseUsers;
|
use CreatesDatabaseUsers;
|
||||||
|
|
||||||
public static $grants = [
|
/** @var string[] */
|
||||||
|
public static array $grants = [
|
||||||
'ALTER', 'ALTER ROUTINE', 'CREATE', 'CREATE ROUTINE', 'CREATE TEMPORARY TABLES', 'CREATE VIEW',
|
'ALTER', 'ALTER ROUTINE', 'CREATE', 'CREATE ROUTINE', 'CREATE TEMPORARY TABLES', 'CREATE VIEW',
|
||||||
'DELETE', 'DROP', 'EVENT', 'EXECUTE', 'INDEX', 'INSERT', 'LOCK TABLES', 'REFERENCES', 'SELECT',
|
'DELETE', 'DROP', 'EVENT', 'EXECUTE', 'INDEX', 'INSERT', 'LOCK TABLES', 'REFERENCES', 'SELECT',
|
||||||
'SHOW VIEW', 'TRIGGER', 'UPDATE',
|
'SHOW VIEW', 'TRIGGER', 'UPDATE',
|
||||||
|
|
|
||||||
|
|
@ -25,11 +25,7 @@ class PostgreSQLSchemaManager extends TenantDatabaseManager
|
||||||
|
|
||||||
public function makeConnectionConfig(array $baseConfig, string $databaseName): array
|
public function makeConnectionConfig(array $baseConfig, string $databaseName): array
|
||||||
{
|
{
|
||||||
if (version_compare(app()->version(), '9.0', '>=')) {
|
$baseConfig['search_path'] = $databaseName;
|
||||||
$baseConfig['search_path'] = $databaseName;
|
|
||||||
} else {
|
|
||||||
$baseConfig['schema'] = $databaseName;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $baseConfig;
|
return $baseConfig;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,10 @@ class TenantScope implements Scope
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$builder->where($model->qualifyColumn(BelongsToTenant::$tenantIdColumn), tenant()->getTenantKey());
|
$builder->where($model->qualifyColumn(BelongsToTenant::tenantIdColumn()), tenant()->getTenantKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function extend(Builder $builder)
|
public function extend(Builder $builder): void
|
||||||
{
|
{
|
||||||
$builder->macro('withoutTenancy', function (Builder $builder) {
|
$builder->macro('withoutTenancy', function (Builder $builder) {
|
||||||
return $builder->withoutGlobalScope($this);
|
return $builder->withoutGlobalScope($this);
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ class RouteIsMissingTenantParameterException extends Exception
|
||||||
{
|
{
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$parameter = PathTenantResolver::$tenantParameterName;
|
$parameter = PathTenantResolver::tenantParameterName();
|
||||||
|
|
||||||
parent::__construct("The route's first argument is not the tenant id (configured paramter name: $parameter).");
|
parent::__construct("The route's first argument is not the tenant id (configured paramter name: $parameter).");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,24 +16,25 @@ use Stancl\Tenancy\Tenancy;
|
||||||
|
|
||||||
class TenantConfig implements Feature
|
class TenantConfig implements Feature
|
||||||
{
|
{
|
||||||
/** @var Repository */
|
|
||||||
protected $config;
|
|
||||||
|
|
||||||
public array $originalConfig = [];
|
public array $originalConfig = [];
|
||||||
|
|
||||||
public static $storageToConfigMap = [
|
/** @var array<string, string|array> */
|
||||||
|
public static array $storageToConfigMap = [
|
||||||
// 'paypal_api_key' => 'services.paypal.api_key',
|
// 'paypal_api_key' => 'services.paypal.api_key',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function __construct(Repository $config)
|
public function __construct(
|
||||||
{
|
protected Repository $config,
|
||||||
$this->config = $config;
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function bootstrap(Tenancy $tenancy): void
|
public function bootstrap(Tenancy $tenancy): void
|
||||||
{
|
{
|
||||||
Event::listen(TenancyBootstrapped::class, function (TenancyBootstrapped $event) {
|
Event::listen(TenancyBootstrapped::class, function (TenancyBootstrapped $event) {
|
||||||
$this->setTenantConfig($event->tenancy->tenant);
|
/** @var Tenant $tenant */
|
||||||
|
$tenant = $event->tenancy->tenant;
|
||||||
|
|
||||||
|
$this->setTenantConfig($tenant);
|
||||||
});
|
});
|
||||||
|
|
||||||
Event::listen(RevertedToCentralContext::class, function () {
|
Event::listen(RevertedToCentralContext::class, function () {
|
||||||
|
|
@ -43,8 +44,8 @@ class TenantConfig implements Feature
|
||||||
|
|
||||||
public function setTenantConfig(Tenant $tenant): void
|
public function setTenantConfig(Tenant $tenant): void
|
||||||
{
|
{
|
||||||
/** @var Tenant|Model $tenant */
|
|
||||||
foreach (static::$storageToConfigMap as $storageKey => $configKey) {
|
foreach (static::$storageToConfigMap as $storageKey => $configKey) {
|
||||||
|
/** @var Tenant&Model $tenant */
|
||||||
$override = Arr::get($tenant, $storageKey);
|
$override = Arr::get($tenant, $storageKey);
|
||||||
|
|
||||||
if (! is_null($override)) {
|
if (! is_null($override)) {
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ class UniversalRoutes implements Feature
|
||||||
public static string $middlewareGroup = 'universal';
|
public static string $middlewareGroup = 'universal';
|
||||||
|
|
||||||
// todo docblock
|
// todo docblock
|
||||||
|
/** @var array<class-string<\Stancl\Tenancy\Middleware\IdentificationMiddleware>> */
|
||||||
public static array $identificationMiddlewares = [
|
public static array $identificationMiddlewares = [
|
||||||
Middleware\InitializeTenancyByDomain::class,
|
Middleware\InitializeTenancyByDomain::class,
|
||||||
Middleware\InitializeTenancyBySubdomain::class,
|
Middleware\InitializeTenancyBySubdomain::class,
|
||||||
|
|
@ -42,7 +43,10 @@ class UniversalRoutes implements Feature
|
||||||
|
|
||||||
public static function routeHasMiddleware(Route $route, string $middleware): bool
|
public static function routeHasMiddleware(Route $route, string $middleware): bool
|
||||||
{
|
{
|
||||||
if (in_array($middleware, $route->middleware(), true)) {
|
/** @var array $routeMiddleware */
|
||||||
|
$routeMiddleware = $route->middleware();
|
||||||
|
|
||||||
|
if (in_array($middleware, $routeMiddleware, true)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Listeners;
|
namespace Stancl\Tenancy\Listeners;
|
||||||
|
|
||||||
|
use Stancl\Tenancy\Contracts\Tenant;
|
||||||
use Stancl\Tenancy\Events\BootstrappingTenancy;
|
use Stancl\Tenancy\Events\BootstrappingTenancy;
|
||||||
use Stancl\Tenancy\Events\TenancyBootstrapped;
|
use Stancl\Tenancy\Events\TenancyBootstrapped;
|
||||||
use Stancl\Tenancy\Events\TenancyInitialized;
|
use Stancl\Tenancy\Events\TenancyInitialized;
|
||||||
|
|
@ -15,7 +16,10 @@ class BootstrapTenancy
|
||||||
event(new BootstrappingTenancy($event->tenancy));
|
event(new BootstrappingTenancy($event->tenancy));
|
||||||
|
|
||||||
foreach ($event->tenancy->getBootstrappers() as $bootstrapper) {
|
foreach ($event->tenancy->getBootstrappers() as $bootstrapper) {
|
||||||
$bootstrapper->bootstrap($event->tenancy->tenant);
|
/** @var Tenant $tenant */
|
||||||
|
$tenant = $event->tenancy->tenant;
|
||||||
|
|
||||||
|
$bootstrapper->bootstrap($tenant);
|
||||||
}
|
}
|
||||||
|
|
||||||
event(new TenancyBootstrapped($event->tenancy));
|
event(new TenancyBootstrapped($event->tenancy));
|
||||||
|
|
|
||||||
|
|
@ -4,21 +4,22 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Listeners;
|
namespace Stancl\Tenancy\Listeners;
|
||||||
|
|
||||||
|
use Stancl\Tenancy\Database\Contracts\TenantWithDatabase;
|
||||||
use Stancl\Tenancy\Database\DatabaseManager;
|
use Stancl\Tenancy\Database\DatabaseManager;
|
||||||
use Stancl\Tenancy\Events\Contracts\TenantEvent;
|
use Stancl\Tenancy\Events\Contracts\TenantEvent;
|
||||||
|
|
||||||
class CreateTenantConnection
|
class CreateTenantConnection
|
||||||
{
|
{
|
||||||
/** @var DatabaseManager */
|
public function __construct(
|
||||||
protected $database;
|
protected DatabaseManager $database,
|
||||||
|
) {
|
||||||
public function __construct(DatabaseManager $database)
|
|
||||||
{
|
|
||||||
$this->database = $database;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handle(TenantEvent $event): void
|
public function handle(TenantEvent $event): void
|
||||||
{
|
{
|
||||||
$this->database->createTenantConnection($event->tenant);
|
/** @var TenantWithDatabase */
|
||||||
|
$tenant = $event->tenant;
|
||||||
|
|
||||||
|
$this->database->createTenantConnection($tenant);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ abstract class QueueableListener implements ShouldQueue
|
||||||
{
|
{
|
||||||
public static bool $shouldQueue = false;
|
public static bool $shouldQueue = false;
|
||||||
|
|
||||||
public function shouldQueue($event): bool
|
public function shouldQueue(object $event): bool
|
||||||
{
|
{
|
||||||
if (static::$shouldQueue) {
|
if (static::$shouldQueue) {
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ use Illuminate\Http\Request;
|
||||||
use Illuminate\Routing\Route;
|
use Illuminate\Routing\Route;
|
||||||
use Illuminate\Support\Facades\Event;
|
use Illuminate\Support\Facades\Event;
|
||||||
use Illuminate\Support\Facades\URL;
|
use Illuminate\Support\Facades\URL;
|
||||||
|
use Stancl\Tenancy\Contracts\Tenant;
|
||||||
use Stancl\Tenancy\Events\InitializingTenancy;
|
use Stancl\Tenancy\Events\InitializingTenancy;
|
||||||
use Stancl\Tenancy\Exceptions\RouteIsMissingTenantParameterException;
|
use Stancl\Tenancy\Exceptions\RouteIsMissingTenantParameterException;
|
||||||
use Stancl\Tenancy\Resolvers\PathTenantResolver;
|
use Stancl\Tenancy\Resolvers\PathTenantResolver;
|
||||||
|
|
@ -33,11 +34,8 @@ class InitializeTenancyByPath extends IdentificationMiddleware
|
||||||
// Only initialize tenancy if tenant is the first parameter
|
// Only initialize tenancy if tenant is the first parameter
|
||||||
// We don't want to initialize tenancy if the tenant is
|
// We don't want to initialize tenancy if the tenant is
|
||||||
// simply injected into some route controller action.
|
// simply injected into some route controller action.
|
||||||
if ($route->parameterNames()[0] === PathTenantResolver::$tenantParameterName) {
|
if ($route->parameterNames()[0] === PathTenantResolver::tenantParameterName()) {
|
||||||
// Set tenant as a default parameter for the URLs in the current request
|
$this->setDefaultTenantForRouteParametersWhenTenancyIsInitialized();
|
||||||
Event::listen(InitializingTenancy::class, function (InitializingTenancy $event) {
|
|
||||||
URL::defaults([PathTenantResolver::$tenantParameterName => $event->tenancy->tenant->getTenantKey()]);
|
|
||||||
});
|
|
||||||
|
|
||||||
return $this->initializeTenancy(
|
return $this->initializeTenancy(
|
||||||
$request,
|
$request,
|
||||||
|
|
@ -50,4 +48,16 @@ class InitializeTenancyByPath extends IdentificationMiddleware
|
||||||
|
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function setDefaultTenantForRouteParametersWhenTenancyIsInitialized(): void
|
||||||
|
{
|
||||||
|
Event::listen(InitializingTenancy::class, function (InitializingTenancy $event) {
|
||||||
|
/** @var Tenant $tenant */
|
||||||
|
$tenant = $event->tenancy->tenant;
|
||||||
|
|
||||||
|
URL::defaults([
|
||||||
|
PathTenantResolver::tenantParameterName() => $tenant->getTenantKey(),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,23 +11,17 @@ use Stancl\Tenancy\Contracts\TenantResolver;
|
||||||
|
|
||||||
abstract class CachedTenantResolver implements TenantResolver
|
abstract class CachedTenantResolver implements TenantResolver
|
||||||
{
|
{
|
||||||
public static bool $shouldCache = false; // todo docblocks for these
|
|
||||||
|
|
||||||
public static int $cacheTTL = 3600; // seconds
|
|
||||||
|
|
||||||
public static string|null $cacheStore = null; // default
|
|
||||||
|
|
||||||
/** @var Repository */
|
/** @var Repository */
|
||||||
protected $cache;
|
protected $cache;
|
||||||
|
|
||||||
public function __construct(Factory $cache)
|
public function __construct(Factory $cache)
|
||||||
{
|
{
|
||||||
$this->cache = $cache->store(static::$cacheStore);
|
$this->cache = $cache->store(static::cacheStore());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function resolve(mixed ...$args): Tenant
|
public function resolve(mixed ...$args): Tenant
|
||||||
{
|
{
|
||||||
if (! static::$shouldCache) {
|
if (! static::shouldCache()) {
|
||||||
return $this->resolveWithoutCache(...$args);
|
return $this->resolveWithoutCache(...$args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -42,14 +36,14 @@ abstract class CachedTenantResolver implements TenantResolver
|
||||||
}
|
}
|
||||||
|
|
||||||
$tenant = $this->resolveWithoutCache(...$args);
|
$tenant = $this->resolveWithoutCache(...$args);
|
||||||
$this->cache->put($key, $tenant, static::$cacheTTL);
|
$this->cache->put($key, $tenant, static::cacheTTL());
|
||||||
|
|
||||||
return $tenant;
|
return $tenant;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function invalidateCache(Tenant $tenant): void
|
public function invalidateCache(Tenant $tenant): void
|
||||||
{
|
{
|
||||||
if (! static::$shouldCache) {
|
if (! static::shouldCache()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -75,4 +69,19 @@ abstract class CachedTenantResolver implements TenantResolver
|
||||||
* @return array[]
|
* @return array[]
|
||||||
*/
|
*/
|
||||||
abstract public function getArgsForTenant(Tenant $tenant): array;
|
abstract public function getArgsForTenant(Tenant $tenant): array;
|
||||||
|
|
||||||
|
public static function shouldCache(): bool
|
||||||
|
{
|
||||||
|
return config('tenancy.identification.resolvers.' . static::class . '.cache') ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function cacheTTL(): int
|
||||||
|
{
|
||||||
|
return config('tenancy.identification.resolvers.' . static::class . '.cache_ttl') ?? 3600;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function cacheStore(): string|null
|
||||||
|
{
|
||||||
|
return config('tenancy.identification.resolvers.' . static::class . '.cache_store');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,6 @@ class DomainTenantResolver extends Contracts\CachedTenantResolver
|
||||||
/** The model representing the domain that the tenant was identified on. */
|
/** The model representing the domain that the tenant was identified on. */
|
||||||
public static Domain $currentDomain; // todo |null?
|
public static Domain $currentDomain; // todo |null?
|
||||||
|
|
||||||
public static bool $shouldCache = false;
|
|
||||||
|
|
||||||
public static int $cacheTTL = 3600; // seconds
|
|
||||||
|
|
||||||
public static string|null $cacheStore = null; // default
|
|
||||||
|
|
||||||
public function resolveWithoutCache(mixed ...$args): Tenant
|
public function resolveWithoutCache(mixed ...$args): Tenant
|
||||||
{
|
{
|
||||||
$domain = $args[0];
|
$domain = $args[0];
|
||||||
|
|
|
||||||
|
|
@ -10,21 +10,13 @@ use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedByPathException;
|
||||||
|
|
||||||
class PathTenantResolver extends Contracts\CachedTenantResolver
|
class PathTenantResolver extends Contracts\CachedTenantResolver
|
||||||
{
|
{
|
||||||
public static string $tenantParameterName = 'tenant';
|
|
||||||
|
|
||||||
public static bool $shouldCache = false;
|
|
||||||
|
|
||||||
public static int $cacheTTL = 3600; // seconds
|
|
||||||
|
|
||||||
public static string|null $cacheStore = null; // default
|
|
||||||
|
|
||||||
public function resolveWithoutCache(mixed ...$args): Tenant
|
public function resolveWithoutCache(mixed ...$args): Tenant
|
||||||
{
|
{
|
||||||
/** @var Route $route */
|
/** @var Route $route */
|
||||||
$route = $args[0];
|
$route = $args[0];
|
||||||
|
|
||||||
if ($id = (string) $route->parameter(static::$tenantParameterName)) {
|
if ($id = (string) $route->parameter(static::tenantParameterName())) {
|
||||||
$route->forgetParameter(static::$tenantParameterName);
|
$route->forgetParameter(static::tenantParameterName());
|
||||||
|
|
||||||
if ($tenant = tenancy()->find($id)) {
|
if ($tenant = tenancy()->find($id)) {
|
||||||
return $tenant;
|
return $tenant;
|
||||||
|
|
@ -40,4 +32,9 @@ class PathTenantResolver extends Contracts\CachedTenantResolver
|
||||||
[$tenant->getTenantKey()],
|
[$tenant->getTenantKey()],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function tenantParameterName(): string
|
||||||
|
{
|
||||||
|
return config('tenancy.identification.resolvers.' . static::class . '.tenant_parameter_name') ?? 'tenant';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ class Tenancy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo0 for phpstan this should be $this->tenant?, but first I want to clean up the $initialized logic and explore removing the property
|
// todo1 for phpstan this should be $this->tenant?, but first I want to clean up the $initialized logic and explore removing the property
|
||||||
if ($this->initialized && $this->tenant->getTenantKey() === $tenant->getTenantKey()) {
|
if ($this->initialized && $this->tenant->getTenantKey() === $tenant->getTenantKey()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -99,19 +99,30 @@ class Tenancy
|
||||||
{
|
{
|
||||||
$class = config('tenancy.tenant_model');
|
$class = config('tenancy.tenant_model');
|
||||||
|
|
||||||
return new $class;
|
/** @var Tenant&Model $model */
|
||||||
|
$model = new $class;
|
||||||
|
|
||||||
|
return $model;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to find a tenant using an ID.
|
||||||
|
*
|
||||||
|
* @return (Tenant&Model)|null
|
||||||
|
*/
|
||||||
public static function find(int|string $id): Tenant|null
|
public static function find(int|string $id): Tenant|null
|
||||||
{
|
{
|
||||||
return static::model()->where(static::model()->getTenantKeyName(), $id)->first();
|
/** @var (Tenant&Model)|null */
|
||||||
|
$tenant = static::model()->where(static::model()->getTenantKeyName(), $id)->first();
|
||||||
|
|
||||||
|
return $tenant;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run a callback in the central context.
|
* Run a callback in the central context.
|
||||||
* Atomic, safely reverts to previous context.
|
* Atomic, safely reverts to previous context.
|
||||||
*/
|
*/
|
||||||
public function central(Closure $callback)
|
public function central(Closure $callback): mixed
|
||||||
{
|
{
|
||||||
$previousTenant = $this->tenant;
|
$previousTenant = $this->tenant;
|
||||||
|
|
||||||
|
|
@ -132,7 +143,7 @@ class Tenancy
|
||||||
* Run a callback for multiple tenants.
|
* Run a callback for multiple tenants.
|
||||||
* More performant than running $tenant->run() one by one.
|
* More performant than running $tenant->run() one by one.
|
||||||
*
|
*
|
||||||
* @param Tenant[]|\Traversable|string[]|null $tenants
|
* @param array<Tenant>|array<string|int>|\Traversable|string|int|null $tenants
|
||||||
*/
|
*/
|
||||||
public function runForMultiple($tenants, Closure $callback): void
|
public function runForMultiple($tenants, Closure $callback): void
|
||||||
{
|
{
|
||||||
|
|
@ -146,7 +157,7 @@ class Tenancy
|
||||||
$tenants = is_string($tenants) ? [$tenants] : $tenants;
|
$tenants = is_string($tenants) ? [$tenants] : $tenants;
|
||||||
|
|
||||||
// Use all tenants if $tenants is falsy
|
// Use all tenants if $tenants is falsy
|
||||||
$tenants = $tenants ?: $this->model()->cursor(); // todo0 phpstan thinks this isn't needed, but tests fail without it
|
$tenants = $tenants ?: $this->model()->cursor(); // todo1 phpstan thinks this isn't needed, but tests fail without it
|
||||||
|
|
||||||
$originalTenant = $this->tenant;
|
$originalTenant = $this->tenant;
|
||||||
|
|
||||||
|
|
@ -155,6 +166,7 @@ class Tenancy
|
||||||
$tenant = $this->find($tenant);
|
$tenant = $this->find($tenant);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @var Tenant $tenant */
|
||||||
$this->initialize($tenant);
|
$this->initialize($tenant);
|
||||||
$callback($tenant);
|
$callback($tenant);
|
||||||
}
|
}
|
||||||
|
|
@ -165,4 +177,41 @@ class Tenancy
|
||||||
$this->end();
|
$this->end();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cached tenant resolvers used by the package.
|
||||||
|
*
|
||||||
|
* @return array<class-string<Resolvers\Contracts\CachedTenantResolver>>
|
||||||
|
*/
|
||||||
|
public static function cachedResolvers(): array
|
||||||
|
{
|
||||||
|
$resolvers = config('tenancy.identification.resolvers', []);
|
||||||
|
|
||||||
|
$cachedResolvers = array_filter($resolvers, function (array $options) {
|
||||||
|
// Resolvers based on CachedTenantResolver have the 'cache' option in the resolver config
|
||||||
|
return isset($options['cache']);
|
||||||
|
});
|
||||||
|
|
||||||
|
return array_keys($cachedResolvers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tenant identification middleware used by the package.
|
||||||
|
*
|
||||||
|
* @return array<class-string<Middleware\IdentificationMiddleware>>
|
||||||
|
*/
|
||||||
|
public static function middleware(): array
|
||||||
|
{
|
||||||
|
return config('tenancy.identification.middleware', []);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default tenant identification middleware used by the package.
|
||||||
|
*
|
||||||
|
* @return class-string<Middleware\IdentificationMiddleware>
|
||||||
|
*/
|
||||||
|
public static function defaultMiddleware(): string
|
||||||
|
{
|
||||||
|
return config('tenancy.identification.default_middleware', Middleware\InitializeTenancyByDomain::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,7 @@ class TenancyServiceProvider extends ServiceProvider
|
||||||
if ($event instanceof TenancyEvent) {
|
if ($event instanceof TenancyEvent) {
|
||||||
match (tenancy()->logMode()) {
|
match (tenancy()->logMode()) {
|
||||||
LogMode::SILENT => tenancy()->logEvent($event),
|
LogMode::SILENT => tenancy()->logEvent($event),
|
||||||
LogMode::INSTANT => dump($event), // todo0 perhaps still log
|
LogMode::INSTANT => dump($event), // todo1 perhaps still log
|
||||||
default => null,
|
default => null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -107,12 +107,12 @@ function contextIsSwitchedWhenTenancyInitialized()
|
||||||
|
|
||||||
class MyBootstrapper implements TenancyBootstrapper
|
class MyBootstrapper implements TenancyBootstrapper
|
||||||
{
|
{
|
||||||
public function bootstrap(\Stancl\Tenancy\Contracts\Tenant $tenant)
|
public function bootstrap(\Stancl\Tenancy\Contracts\Tenant $tenant): void
|
||||||
{
|
{
|
||||||
app()->instance('tenancy_initialized_for_tenant', $tenant->getTenantKey());
|
app()->instance('tenancy_initialized_for_tenant', $tenant->getTenantKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function revert()
|
public function revert(): void
|
||||||
{
|
{
|
||||||
app()->instance('tenancy_ended', true);
|
app()->instance('tenancy_ended', true);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,13 +30,13 @@ test('batch repository is set to tenant connection and reverted', function () {
|
||||||
|
|
||||||
tenancy()->initialize($tenant);
|
tenancy()->initialize($tenant);
|
||||||
expect(getBatchRepositoryConnectionName())->toBe('tenant');
|
expect(getBatchRepositoryConnectionName())->toBe('tenant');
|
||||||
|
|
||||||
tenancy()->initialize($tenant2);
|
tenancy()->initialize($tenant2);
|
||||||
expect(getBatchRepositoryConnectionName())->toBe('tenant');
|
expect(getBatchRepositoryConnectionName())->toBe('tenant');
|
||||||
|
|
||||||
tenancy()->end();
|
tenancy()->end();
|
||||||
expect(getBatchRepositoryConnectionName())->toBe('central');
|
expect(getBatchRepositoryConnectionName())->toBe('central');
|
||||||
})->skip(fn() => version_compare(app()->version(), '8.0', '<'), 'Job batches are only supported in Laravel 8+');
|
});
|
||||||
|
|
||||||
function getBatchRepositoryConnectionName()
|
function getBatchRepositoryConnectionName()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -332,10 +332,6 @@ function getDiskPrefix(string $disk): string
|
||||||
$disk = Storage::disk($disk);
|
$disk = Storage::disk($disk);
|
||||||
$adapter = $disk->getAdapter();
|
$adapter = $disk->getAdapter();
|
||||||
|
|
||||||
if (! Str::startsWith(app()->version(), '9.')) {
|
|
||||||
return $adapter->getPathPrefix();
|
|
||||||
}
|
|
||||||
|
|
||||||
$prefixer = (new ReflectionObject($adapter))->getProperty('prefixer');
|
$prefixer = (new ReflectionObject($adapter))->getProperty('prefixer');
|
||||||
$prefixer->setAccessible(true);
|
$prefixer->setAccessible(true);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,7 @@ use Illuminate\Support\Facades\DB;
|
||||||
use Stancl\Tenancy\Resolvers\DomainTenantResolver;
|
use Stancl\Tenancy\Resolvers\DomainTenantResolver;
|
||||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||||
|
|
||||||
afterEach(function () {
|
// todo@v4 test this with other resolvers as well?
|
||||||
DomainTenantResolver::$shouldCache = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
test('tenants can be resolved using the cached resolver', function () {
|
test('tenants can be resolved using the cached resolver', function () {
|
||||||
$tenant = Tenant::create();
|
$tenant = Tenant::create();
|
||||||
|
|
@ -27,14 +25,14 @@ test('the underlying resolver is not touched when using the cached resolver', fu
|
||||||
|
|
||||||
DB::enableQueryLog();
|
DB::enableQueryLog();
|
||||||
|
|
||||||
DomainTenantResolver::$shouldCache = false;
|
config(['tenancy.identification.resolvers.' . DomainTenantResolver::class . '.cache' => false]);
|
||||||
|
|
||||||
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
|
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
|
||||||
DB::flushQueryLog();
|
DB::flushQueryLog();
|
||||||
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
|
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
|
||||||
pest()->assertNotEmpty(DB::getQueryLog()); // not empty
|
pest()->assertNotEmpty(DB::getQueryLog()); // not empty
|
||||||
|
|
||||||
DomainTenantResolver::$shouldCache = true;
|
config(['tenancy.identification.resolvers.' . DomainTenantResolver::class . '.cache' => true]);
|
||||||
|
|
||||||
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
|
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
|
||||||
DB::flushQueryLog();
|
DB::flushQueryLog();
|
||||||
|
|
@ -50,7 +48,7 @@ test('cache is invalidated when the tenant is updated', function () {
|
||||||
|
|
||||||
DB::enableQueryLog();
|
DB::enableQueryLog();
|
||||||
|
|
||||||
DomainTenantResolver::$shouldCache = true;
|
config(['tenancy.identification.resolvers.' . DomainTenantResolver::class . '.cache' => true]);
|
||||||
|
|
||||||
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
|
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
|
||||||
DB::flushQueryLog();
|
DB::flushQueryLog();
|
||||||
|
|
@ -74,7 +72,7 @@ test('cache is invalidated when a tenants domain is changed', function () {
|
||||||
|
|
||||||
DB::enableQueryLog();
|
DB::enableQueryLog();
|
||||||
|
|
||||||
DomainTenantResolver::$shouldCache = true;
|
config(['tenancy.identification.resolvers.' . DomainTenantResolver::class . '.cache' => true]);
|
||||||
|
|
||||||
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
|
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
|
||||||
DB::flushQueryLog();
|
DB::flushQueryLog();
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,6 @@ use Stancl\Tenancy\Resolvers\PathTenantResolver;
|
||||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
PathTenantResolver::$tenantParameterName = 'tenant';
|
|
||||||
|
|
||||||
Route::group([
|
Route::group([
|
||||||
'prefix' => '/{tenant}',
|
'prefix' => '/{tenant}',
|
||||||
'middleware' => InitializeTenancyByPath::class,
|
'middleware' => InitializeTenancyByPath::class,
|
||||||
|
|
@ -26,11 +24,6 @@ beforeEach(function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function () {
|
|
||||||
// Global state cleanup
|
|
||||||
PathTenantResolver::$tenantParameterName = 'tenant';
|
|
||||||
});
|
|
||||||
|
|
||||||
test('tenant can be identified by path', function () {
|
test('tenant can be identified by path', function () {
|
||||||
Tenant::create([
|
Tenant::create([
|
||||||
'id' => 'acme',
|
'id' => 'acme',
|
||||||
|
|
@ -101,7 +94,7 @@ test('an exception is thrown when the routes first parameter is not tenant', fun
|
||||||
});
|
});
|
||||||
|
|
||||||
test('tenant parameter name can be customized', function () {
|
test('tenant parameter name can be customized', function () {
|
||||||
PathTenantResolver::$tenantParameterName = 'team';
|
config(['tenancy.identification.resolvers.' . PathTenantResolver::class . '.tenant_parameter_name' => 'team']);
|
||||||
|
|
||||||
Route::group([
|
Route::group([
|
||||||
'prefix' => '/{team}',
|
'prefix' => '/{team}',
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,6 @@ use Stancl\Tenancy\Database\Concerns\HasScopedValidationRules;
|
||||||
use Stancl\Tenancy\Tests\Etc\Tenant as TestTenant;
|
use Stancl\Tenancy\Tests\Etc\Tenant as TestTenant;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
BelongsToTenant::$tenantIdColumn = 'tenant_id';
|
|
||||||
|
|
||||||
Schema::create('posts', function (Blueprint $table) {
|
Schema::create('posts', function (Blueprint $table) {
|
||||||
$table->increments('id');
|
$table->increments('id');
|
||||||
$table->string('text');
|
$table->string('text');
|
||||||
|
|
@ -144,7 +142,7 @@ test('tenant id is not auto added when creating primary resources in central con
|
||||||
});
|
});
|
||||||
|
|
||||||
test('tenant id column name can be customized', function () {
|
test('tenant id column name can be customized', function () {
|
||||||
BelongsToTenant::$tenantIdColumn = 'team_id';
|
config(['tenancy.single_db.tenant_id_column' => 'team_id']);
|
||||||
|
|
||||||
Schema::drop('comments');
|
Schema::drop('comments');
|
||||||
Schema::drop('posts');
|
Schema::drop('posts');
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,8 @@ use Illuminate\Support\Facades\Event;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Stancl\Tenancy\Bootstrappers\FilesystemTenancyBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\FilesystemTenancyBootstrapper;
|
||||||
use Stancl\Tenancy\Controllers\TenantAssetsController;
|
|
||||||
use Stancl\Tenancy\Events\TenancyInitialized;
|
use Stancl\Tenancy\Events\TenancyInitialized;
|
||||||
use Stancl\Tenancy\Listeners\BootstrapTenancy;
|
use Stancl\Tenancy\Listeners\BootstrapTenancy;
|
||||||
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
|
|
||||||
use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData;
|
use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData;
|
||||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||||
|
|
||||||
|
|
@ -21,13 +19,8 @@ beforeEach(function () {
|
||||||
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
|
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function () {
|
|
||||||
// Cleanup
|
|
||||||
TenantAssetsController::$tenancyMiddleware = InitializeTenancyByDomain::class;
|
|
||||||
});
|
|
||||||
|
|
||||||
test('asset can be accessed using the url returned by the tenant asset helper', function () {
|
test('asset can be accessed using the url returned by the tenant asset helper', function () {
|
||||||
TenantAssetsController::$tenancyMiddleware = InitializeTenancyByRequestData::class;
|
config(['tenancy.identification.default_middleware' => InitializeTenancyByRequestData::class]);
|
||||||
|
|
||||||
$tenant = Tenant::create();
|
$tenant = Tenant::create();
|
||||||
tenancy()->initialize($tenant);
|
tenancy()->initialize($tenant);
|
||||||
|
|
@ -95,7 +88,7 @@ test('asset helper tenancy can be disabled', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('test asset controller returns a 404 when no path is provided', function () {
|
test('test asset controller returns a 404 when no path is provided', function () {
|
||||||
TenantAssetsController::$tenancyMiddleware = InitializeTenancyByRequestData::class;
|
config(['tenancy.identification.default_middleware' => InitializeTenancyByRequestData::class]);
|
||||||
|
|
||||||
$tenant = Tenant::create();
|
$tenant = Tenant::create();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -154,9 +154,7 @@ test('schema manager uses schema to separate tenant dbs', function () {
|
||||||
]);
|
]);
|
||||||
tenancy()->initialize($tenant);
|
tenancy()->initialize($tenant);
|
||||||
|
|
||||||
$schemaConfig = version_compare(app()->version(), '9.0', '>=') ?
|
$schemaConfig = config('database.connections.' . config('database.default') . '.search_path');
|
||||||
config('database.connections.' . config('database.default') . '.search_path') :
|
|
||||||
config('database.connections.' . config('database.default') . '.schema');
|
|
||||||
|
|
||||||
expect($schemaConfig)->toBe($tenant->database()->getName());
|
expect($schemaConfig)->toBe($tenant->database()->getName());
|
||||||
expect(config(['database.connections.pgsql.database']))->toBe($originalDatabaseName);
|
expect(config(['database.connections.pgsql.database']))->toBe($originalDatabaseName);
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
|
||||||
'--realpath' => true,
|
'--realpath' => true,
|
||||||
'--force' => true,
|
'--force' => true,
|
||||||
],
|
],
|
||||||
'tenancy.bootstrappers.redis' => RedisTenancyBootstrapper::class, // todo0 change this to []? two tests in TenantDatabaseManagerTest are failing with that
|
'tenancy.bootstrappers.redis' => RedisTenancyBootstrapper::class, // todo1 change this to []? two tests in TenantDatabaseManagerTest are failing with that
|
||||||
'queue.connections.central' => [
|
'queue.connections.central' => [
|
||||||
'driver' => 'sync',
|
'driver' => 'sync',
|
||||||
'central' => true,
|
'central' => true,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue