diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php
index 589838bc..7e649ea5 100644
--- a/.php-cs-fixer.php
+++ b/.php-cs-fixer.php
@@ -10,6 +10,7 @@ $rules = [
'operators' => [
'=>' => null,
'|' => 'no_space',
+ '&' => 'no_space',
]
],
'blank_line_after_namespace' => true,
diff --git a/assets/TenancyServiceProvider.stub.php b/assets/TenancyServiceProvider.stub.php
index a3626225..75784361 100644
--- a/assets/TenancyServiceProvider.stub.php
+++ b/assets/TenancyServiceProvider.stub.php
@@ -144,16 +144,8 @@ class TenancyServiceProvider extends ServiceProvider
protected function makeTenancyMiddlewareHighestPriority()
{
- $tenancyMiddleware = [
- // Even higher priority than the initialization middleware
- Middleware\PreventAccessFromCentralDomains::class,
-
- Middleware\InitializeTenancyByDomain::class,
- Middleware\InitializeTenancyBySubdomain::class,
- Middleware\InitializeTenancyByDomainOrSubdomain::class,
- Middleware\InitializeTenancyByPath::class,
- Middleware\InitializeTenancyByRequestData::class,
- ];
+ // PreventAccessFromCentralDomains has even higher priority than the identification middleware
+ $tenancyMiddleware = array_merge([Middleware\PreventAccessFromCentralDomains::class], config('tenancy.identification.middleware'));
foreach (array_reverse($tenancyMiddleware) as $middleware) {
$this->app[\Illuminate\Contracts\Http\Kernel::class]->prependToMiddlewarePriority($middleware);
diff --git a/assets/config.php b/assets/config.php
index 7aff2b65..6130bade 100644
--- a/assets/config.php
+++ b/assets/config.php
@@ -4,6 +4,8 @@ declare(strict_types=1);
use Stancl\Tenancy\Database\Models\Domain;
use Stancl\Tenancy\Database\Models\Tenant;
+use Stancl\Tenancy\Middleware;
+use Stancl\Tenancy\Resolvers;
return [
'tenant_model' => Tenant::class,
@@ -21,6 +23,56 @@ return [
'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.
* Their responsibility is making Laravel features tenant-aware.
@@ -215,4 +267,12 @@ return [
'--class' => 'DatabaseSeeder', // root seeder class
// '--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',
+ ],
];
diff --git a/assets/routes.php b/assets/routes.php
index 9223c099..a27f782d 100644
--- a/assets/routes.php
+++ b/assets/routes.php
@@ -3,7 +3,8 @@
declare(strict_types=1);
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', '(.*)')
->name('stancl.tenancy.asset');
diff --git a/composer.json b/composer.json
index cc213add..0dc9df09 100644
--- a/composer.json
+++ b/composer.json
@@ -63,6 +63,7 @@
"docker-m1": "ln -s docker-compose-m1.override.yml docker-compose.override.yml",
"coverage": "open coverage/phpunit/html/index.html",
"phpstan": "vendor/bin/phpstan",
+ "cs": "php-cs-fixer fix --config=.php-cs-fixer.php",
"test": "PHP_VERSION=8.1 ./test --no-coverage",
"test-full": "PHP_VERSION=8.1 ./test"
},
diff --git a/phpstan.neon b/phpstan.neon
index 9ff082dd..3e9ba51d 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -10,16 +10,39 @@ parameters:
universalObjectCratesClasses:
- Illuminate\Routing\Route
+ - Illuminate\Database\Eloquent\Model
ignoreErrors:
- -
- message: '#Cannot access offset (.*?) on Illuminate\\Contracts\\Foundation\\Application#'
- paths:
- - src/TenancyServiceProvider.php
+ - '#Cannot access offset (.*?) on Illuminate\\Contracts\\Foundation\\Application#'
+ - '#Cannot access offset (.*?) on Illuminate\\Contracts\\Config\\Repository#'
-
message: '#invalid type Laravel\\Telescope\\IncomingEntry#'
paths:
- 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#'
+ paths:
+ - src/helpers.php
+ -
+ message: '#PHPDoc tag \@param has invalid value \(dynamic#'
+ paths:
+ - 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
treatPhpDocTypesAsCertain: false
diff --git a/phpunit.xml b/phpunit.xml
index 28fc8a08..9d2b9339 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -35,4 +35,4 @@
-
\ No newline at end of file
+
diff --git a/src/Actions/CreateStorageSymlinksAction.php b/src/Actions/CreateStorageSymlinksAction.php
index 779a42af..eac5d933 100644
--- a/src/Actions/CreateStorageSymlinksAction.php
+++ b/src/Actions/CreateStorageSymlinksAction.php
@@ -8,7 +8,7 @@ use Exception;
use Illuminate\Support\Collection;
use Illuminate\Support\LazyCollection;
use Stancl\Tenancy\Concerns\DealsWithTenantSymlinks;
-use Stancl\Tenancy\Database\Models\Tenant;
+use Stancl\Tenancy\Contracts\Tenant;
use Stancl\Tenancy\Events\CreatingStorageSymlink;
use Stancl\Tenancy\Events\StorageSymlinkCreated;
diff --git a/src/Actions/RemoveStorageSymlinksAction.php b/src/Actions/RemoveStorageSymlinksAction.php
index bfbcfa0a..a3660e7a 100644
--- a/src/Actions/RemoveStorageSymlinksAction.php
+++ b/src/Actions/RemoveStorageSymlinksAction.php
@@ -7,7 +7,7 @@ namespace Stancl\Tenancy\Actions;
use Illuminate\Support\Collection;
use Illuminate\Support\LazyCollection;
use Stancl\Tenancy\Concerns\DealsWithTenantSymlinks;
-use Stancl\Tenancy\Database\Models\Tenant;
+use Stancl\Tenancy\Contracts\Tenant;
use Stancl\Tenancy\Events\RemovingStorageSymlink;
use Stancl\Tenancy\Events\StorageSymlinkRemoved;
diff --git a/src/Bootstrappers/BatchTenancyBootstrapper.php b/src/Bootstrappers/BatchTenancyBootstrapper.php
index ccd1c00a..589bdac0 100644
--- a/src/Bootstrappers/BatchTenancyBootstrapper.php
+++ b/src/Bootstrappers/BatchTenancyBootstrapper.php
@@ -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
$this->previousConnection = $this->batchRepository->getConnection();
$this->batchRepository->setConnection($this->databaseManager->connection('tenant'));
}
- public function revert()
+ public function revert(): void
{
if ($this->previousConnection) {
// Replace batch repository connection with the previously replaced one
diff --git a/src/Bootstrappers/CacheTenancyBootstrapper.php b/src/Bootstrappers/CacheTenancyBootstrapper.php
index bef156d2..29547fae 100644
--- a/src/Bootstrappers/CacheTenancyBootstrapper.php
+++ b/src/Bootstrappers/CacheTenancyBootstrapper.php
@@ -13,18 +13,14 @@ use Stancl\Tenancy\Contracts\Tenant;
class CacheTenancyBootstrapper implements TenancyBootstrapper
{
- /** @var CacheManager */
- protected $originalCache;
+ protected ?CacheManager $originalCache = null;
- /** @var Application */
- protected $app;
-
- public function __construct(Application $app)
- {
- $this->app = $app;
+ public function __construct(
+ protected Application $app
+ ) {
}
- public function bootstrap(Tenant $tenant)
+ public function bootstrap(Tenant $tenant): void
{
$this->resetFacadeCache();
@@ -34,7 +30,7 @@ class CacheTenancyBootstrapper implements TenancyBootstrapper
});
}
- public function revert()
+ public function revert(): void
{
$this->resetFacadeCache();
@@ -50,7 +46,7 @@ class CacheTenancyBootstrapper implements TenancyBootstrapper
* facade has been made prior to bootstrapping tenancy. The
* facade has its own cache, separate from the container.
*/
- public function resetFacadeCache()
+ public function resetFacadeCache(): void
{
Cache::clearResolvedInstances();
}
diff --git a/src/Bootstrappers/DatabaseTenancyBootstrapper.php b/src/Bootstrappers/DatabaseTenancyBootstrapper.php
index 0c3396c4..f058dc43 100644
--- a/src/Bootstrappers/DatabaseTenancyBootstrapper.php
+++ b/src/Bootstrappers/DatabaseTenancyBootstrapper.php
@@ -20,7 +20,7 @@ class DatabaseTenancyBootstrapper implements TenancyBootstrapper
$this->database = $database;
}
- public function bootstrap(Tenant $tenant)
+ public function bootstrap(Tenant $tenant): void
{
/** @var TenantWithDatabase $tenant */
@@ -35,7 +35,7 @@ class DatabaseTenancyBootstrapper implements TenancyBootstrapper
$this->database->connectToTenant($tenant);
}
- public function revert()
+ public function revert(): void
{
$this->database->reconnectToCentral();
}
diff --git a/src/Bootstrappers/FilesystemTenancyBootstrapper.php b/src/Bootstrappers/FilesystemTenancyBootstrapper.php
index d90d36d0..e9e0d93d 100644
--- a/src/Bootstrappers/FilesystemTenancyBootstrapper.php
+++ b/src/Bootstrappers/FilesystemTenancyBootstrapper.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Bootstrappers;
use Illuminate\Contracts\Foundation\Application;
+use Illuminate\Routing\UrlGenerator;
use Illuminate\Support\Facades\Storage;
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
use Stancl\Tenancy\Contracts\Tenant;
@@ -27,13 +28,14 @@ class FilesystemTenancyBootstrapper implements TenancyBootstrapper
];
$this->app['url']->macro('setAssetRoot', function ($root) {
+ /** @var UrlGenerator $this */
$this->assetRoot = $root;
return $this;
});
}
- public function bootstrap(Tenant $tenant)
+ public function bootstrap(Tenant $tenant): void
{
$suffix = $this->app['config']['tenancy.filesystem.suffix_base'] . $tenant->getTenantKey();
@@ -45,7 +47,7 @@ class FilesystemTenancyBootstrapper implements TenancyBootstrapper
// asset()
if ($this->app['config']['tenancy.filesystem.asset_helper_tenancy'] ?? true) {
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']);
} else {
$this->app['url']->setAssetRoot($this->app['url']->route('stancl.tenancy.asset', ['path' => '']));
@@ -82,7 +84,7 @@ class FilesystemTenancyBootstrapper implements TenancyBootstrapper
if ($url = str_replace(
'%tenant_id%',
- $tenant->getTenantKey(),
+ (string) $tenant->getTenantKey(),
$this->app['config']["tenancy.filesystem.url_override.{$disk}"] ?? ''
)) {
$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()
$this->app->useStoragePath($this->originalPaths['storage']);
diff --git a/src/Bootstrappers/Integrations/ScoutTenancyBootstrapper.php b/src/Bootstrappers/Integrations/ScoutTenancyBootstrapper.php
index 49869bb5..da5a921a 100644
--- a/src/Bootstrappers/Integrations/ScoutTenancyBootstrapper.php
+++ b/src/Bootstrappers/Integrations/ScoutTenancyBootstrapper.php
@@ -10,27 +10,23 @@ use Stancl\Tenancy\Contracts\Tenant;
class ScoutTenancyBootstrapper implements TenancyBootstrapper
{
- /** @var Repository */
- protected $config;
+ protected ?string $originalScoutPrefix = null;
- /** @var string */
- protected $originalScoutPrefix;
-
- public function __construct(Repository $config)
- {
- $this->config = $config;
+ public function __construct(
+ protected Repository $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->config->set('scout.prefix', $this->getTenantPrefix($tenant));
}
- public function revert()
+ public function revert(): void
{
$this->config->set('scout.prefix', $this->originalScoutPrefix);
}
diff --git a/src/Bootstrappers/QueueTenancyBootstrapper.php b/src/Bootstrappers/QueueTenancyBootstrapper.php
index 2f859ecd..5b6ef4d8 100644
--- a/src/Bootstrappers/QueueTenancyBootstrapper.php
+++ b/src/Bootstrappers/QueueTenancyBootstrapper.php
@@ -39,7 +39,7 @@ class QueueTenancyBootstrapper implements TenancyBootstrapper
* However, we're registering a hook to initialize tenancy. Therefore,
* 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());
}
@@ -52,7 +52,7 @@ class QueueTenancyBootstrapper implements TenancyBootstrapper
$this->setUpPayloadGenerator();
}
- protected static function setUpJobListener($dispatcher, $runningTests)
+ protected static function setUpJobListener(Dispatcher $dispatcher, bool $runningTests): void
{
$previousTenant = null;
@@ -62,14 +62,11 @@ class QueueTenancyBootstrapper implements TenancyBootstrapper
static::initializeTenancyForQueue($event->job->payload()['tenant_id'] ?? null);
});
- if (version_compare(app()->version(), '8.64', '>=')) {
- // JobRetryRequested only exists since Laravel 8.64
- $dispatcher->listen(JobRetryRequested::class, function ($event) use (&$previousTenant) {
- $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
$revertToPreviousState = function ($event) use (&$previousTenant, $runningTests) {
@@ -82,7 +79,7 @@ class QueueTenancyBootstrapper implements TenancyBootstrapper
$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) {
// The job is not tenant-aware
@@ -100,7 +97,9 @@ class QueueTenancyBootstrapper implements TenancyBootstrapper
tenancy()->end();
}
- tenancy()->initialize(tenancy()->find($tenantId));
+ /** @var Tenant $tenant */
+ $tenant = tenancy()->find($tenantId);
+ tenancy()->initialize($tenant);
return;
}
@@ -115,10 +114,13 @@ class QueueTenancyBootstrapper implements TenancyBootstrapper
// Tenancy was either not initialized, or initialized for a different 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;
@@ -138,7 +140,7 @@ class QueueTenancyBootstrapper implements TenancyBootstrapper
}
}
- protected function setUpPayloadGenerator()
+ protected function setUpPayloadGenerator(): void
{
$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) {
return [];
diff --git a/src/Bootstrappers/RedisTenancyBootstrapper.php b/src/Bootstrappers/RedisTenancyBootstrapper.php
index 7536984e..975a37d5 100644
--- a/src/Bootstrappers/RedisTenancyBootstrapper.php
+++ b/src/Bootstrappers/RedisTenancyBootstrapper.php
@@ -22,18 +22,21 @@ class RedisTenancyBootstrapper implements TenancyBootstrapper
$this->config = $config;
}
- public function bootstrap(Tenant $tenant)
+ public function bootstrap(Tenant $tenant): void
{
foreach ($this->prefixedConnections() as $connection) {
$prefix = $this->config['tenancy.redis.prefix_base'] . $tenant->getTenantKey();
$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);
}
}
- public function revert()
+ public function revert(): void
{
foreach ($this->prefixedConnections() as $connection) {
$client = Redis::connection($connection)->client();
@@ -44,7 +47,8 @@ class RedisTenancyBootstrapper implements TenancyBootstrapper
$this->originalPrefixes = [];
}
- protected function prefixedConnections()
+ /** @return string[] */
+ protected function prefixedConnections(): array
{
return $this->config['tenancy.redis.prefixed_connections'];
}
diff --git a/src/Commands/Down.php b/src/Commands/Down.php
new file mode 100644
index 00000000..6b390957
--- /dev/null
+++ b/src/Commands/Down.php
@@ -0,0 +1,54 @@
+getDownDatabasePayload();
+
+ // This runs for all tenants if no --tenants are specified
+ tenancy()->runForMultiple($this->getTenants(), function ($tenant) use ($payload) {
+ $this->line("Tenant: {$tenant['id']}");
+ $tenant->putDownForMaintenance($payload);
+ });
+
+ $this->comment('Tenants are now in maintenance mode.');
+
+ return 0;
+ }
+
+ /** Get the payload to be placed in the "down" file. */
+ protected function getDownDatabasePayload(): array
+ {
+ return [
+ 'except' => $this->excludedPaths(),
+ 'redirect' => $this->redirectPath(),
+ 'retry' => $this->getRetryTime(),
+ 'refresh' => $this->option('refresh'),
+ 'secret' => $this->option('secret'),
+ 'status' => (int) ($this->option('status') ?? 503),
+ ];
+ }
+}
diff --git a/src/Commands/Install.php b/src/Commands/Install.php
index 41492b26..12a2c2c9 100644
--- a/src/Commands/Install.php
+++ b/src/Commands/Install.php
@@ -8,24 +8,11 @@ use Illuminate\Console\Command;
class Install extends Command
{
- /**
- * The name and signature of the console command.
- *
- * @var string
- */
protected $signature = 'tenancy:install';
- /**
- * The console command description.
- *
- * @var string
- */
protected $description = 'Install stancl/tenancy.';
- /**
- * Execute the console command.
- */
- public function handle()
+ public function handle(): void
{
$this->comment('Installing stancl/tenancy...');
$this->callSilent('vendor:publish', [
diff --git a/src/Commands/Link.php b/src/Commands/Link.php
index 061f2d3d..53f3cf6f 100644
--- a/src/Commands/Link.php
+++ b/src/Commands/Link.php
@@ -15,30 +15,15 @@ class Link extends Command
{
use HasATenantsOption;
- /**
- * The console command signature.
- *
- * @var string
- */
protected $signature = 'tenants:link
{--tenants=* : The tenant(s) to run the command for. Default: all}
{--relative : Create the symbolic link using relative paths}
{--force : Recreate existing symbolic links}
{--remove : Remove symbolic links}';
- /**
- * The console command description.
- *
- * @var string
- */
protected $description = 'Create or remove tenant symbolic links.';
- /**
- * Execute the console command.
- *
- * @return void
- */
- public function handle()
+ public function handle(): void
{
$tenants = $this->getTenants();
@@ -64,8 +49,8 @@ class Link extends Command
{
CreateStorageSymlinksAction::handle(
$tenants,
- $this->option('relative') ?? false,
- $this->option('force') ?? false,
+ (bool) ($this->option('relative') ?? false),
+ (bool) ($this->option('force') ?? false),
);
$this->info('The links have been created.');
diff --git a/src/Commands/Migrate.php b/src/Commands/Migrate.php
index 995d28e0..fed6cc67 100644
--- a/src/Commands/Migrate.php
+++ b/src/Commands/Migrate.php
@@ -17,7 +17,7 @@ use Stancl\Tenancy\Events\MigratingDatabase;
class Migrate extends MigrateCommand
{
- use HasATenantsOption, DealsWithMigrations, ExtendsLaravelCommand;
+ use HasATenantsOption, ExtendsLaravelCommand;
protected $description = 'Run migrations for tenant(s)';
@@ -35,10 +35,7 @@ class Migrate extends MigrateCommand
$this->specifyParameters();
}
- /**
- * Execute the console command.
- */
- public function handle()
+ public function handle(): int
{
foreach (config('tenancy.migration_parameters') as $parameter => $value) {
if (! $this->input->hasParameterOption($parameter)) {
@@ -47,7 +44,7 @@ class Migrate extends MigrateCommand
}
if (! $this->confirmToProceed()) {
- return;
+ return 1;
}
try {
diff --git a/src/Commands/MigrateFresh.php b/src/Commands/MigrateFresh.php
index 63860153..56a6047f 100644
--- a/src/Commands/MigrateFresh.php
+++ b/src/Commands/MigrateFresh.php
@@ -5,19 +5,13 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Commands;
use Illuminate\Console\Command;
-use Stancl\Tenancy\Concerns\DealsWithMigrations;
use Stancl\Tenancy\Concerns\HasATenantsOption;
use Symfony\Component\Console\Input\InputOption;
final class MigrateFresh extends Command
{
- use HasATenantsOption, DealsWithMigrations;
+ use HasATenantsOption;
- /**
- * The console command description.
- *
- * @var string
- */
protected $description = 'Drop all tables and re-run all migrations for tenant(s)';
public function __construct()
@@ -29,12 +23,9 @@ final class MigrateFresh extends Command
$this->setName('tenants:migrate-fresh');
}
- /**
- * Execute the console command.
- */
- public function handle()
+ public function handle(): void
{
- tenancy()->runForMultiple($this->option('tenants'), function ($tenant) {
+ tenancy()->runForMultiple($this->getTenants(), function ($tenant) {
$this->info('Dropping tables.');
$this->call('db:wipe', array_filter([
'--database' => 'tenant',
diff --git a/src/Commands/Rollback.php b/src/Commands/Rollback.php
index 1c434189..d3989cc0 100644
--- a/src/Commands/Rollback.php
+++ b/src/Commands/Rollback.php
@@ -6,7 +6,6 @@ namespace Stancl\Tenancy\Commands;
use Illuminate\Database\Console\Migrations\RollbackCommand;
use Illuminate\Database\Migrations\Migrator;
-use Stancl\Tenancy\Concerns\DealsWithMigrations;
use Stancl\Tenancy\Concerns\ExtendsLaravelCommand;
use Stancl\Tenancy\Concerns\HasATenantsOption;
use Stancl\Tenancy\Events\DatabaseRolledBack;
@@ -14,25 +13,10 @@ use Stancl\Tenancy\Events\RollingBackDatabase;
class Rollback extends RollbackCommand
{
- use HasATenantsOption, DealsWithMigrations, ExtendsLaravelCommand;
+ use HasATenantsOption, ExtendsLaravelCommand;
- protected static function getTenantCommandName(): string
- {
- return 'tenants:rollback';
- }
-
- /**
- * The console command description.
- *
- * @var string
- */
protected $description = 'Rollback migrations for tenant(s).';
- /**
- * Create a new command instance.
- *
- * @return void
- */
public function __construct(Migrator $migrator)
{
parent::__construct($migrator);
@@ -40,10 +24,7 @@ class Rollback extends RollbackCommand
$this->specifyTenantSignature();
}
- /**
- * Execute the console command.
- */
- public function handle()
+ public function handle(): int
{
foreach (config('tenancy.migration_parameters') as $parameter => $value) {
if (! $this->input->hasParameterOption($parameter)) {
@@ -52,10 +33,10 @@ class Rollback extends RollbackCommand
}
if (! $this->confirmToProceed()) {
- return;
+ return 1;
}
- tenancy()->runForMultiple($this->option('tenants'), function ($tenant) {
+ tenancy()->runForMultiple($this->getTenants(), function ($tenant) {
$this->line("Tenant: {$tenant->getTenantKey()}");
event(new RollingBackDatabase($tenant));
@@ -65,5 +46,12 @@ class Rollback extends RollbackCommand
event(new DatabaseRolledBack($tenant));
});
+
+ return 0;
+ }
+
+ protected static function getTenantCommandName(): string
+ {
+ return 'tenants:rollback';
}
}
diff --git a/src/Commands/Run.php b/src/Commands/Run.php
index a24fb9c7..9bb04716 100644
--- a/src/Commands/Run.php
+++ b/src/Commands/Run.php
@@ -6,33 +6,24 @@ namespace Stancl\Tenancy\Commands;
use Illuminate\Console\Command;
use Illuminate\Contracts\Console\Kernel;
+use Stancl\Tenancy\Concerns\HasATenantsOption;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\ConsoleOutput;
class Run extends Command
{
- /**
- * The console command description.
- *
- * @var string
- */
+ use HasATenantsOption;
+
protected $description = 'Run a command for tenant(s)';
- /**
- * The name and signature of the console command.
- *
- * @var string
- */
protected $signature = 'tenants:run {commandname : The artisan command.}
{--tenants=* : The tenant(s) to run the command for. Default: all}';
- /**
- * Execute the console command.
- */
- public function handle()
+ public function handle(): void
{
- $argvInput = $this->ArgvInput();
- tenancy()->runForMultiple($this->option('tenants'), function ($tenant) use ($argvInput) {
+ $argvInput = $this->argvInput();
+
+ tenancy()->runForMultiple($this->getTenants(), function ($tenant) use ($argvInput) {
$this->line("Tenant: {$tenant->getTenantKey()}");
$this->getLaravel()
@@ -41,17 +32,17 @@ class Run extends Command
});
}
- /**
- * Get command as ArgvInput instance.
- */
- protected function ArgvInput(): ArgvInput
+ protected function argvInput(): ArgvInput
{
+ /** @var string $commandname */
+ $commandname = $this->argument('commandname');
+
// 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
- array_unshift($subCommand, 'artisan');
+ array_unshift($subcommand, 'artisan');
- return new ArgvInput($subCommand);
+ return new ArgvInput($subcommand);
}
}
diff --git a/src/Commands/Seed.php b/src/Commands/Seed.php
index 8c525208..496c04e6 100644
--- a/src/Commands/Seed.php
+++ b/src/Commands/Seed.php
@@ -14,29 +14,16 @@ class Seed extends SeedCommand
{
use HasATenantsOption;
- /**
- * The console command description.
- *
- * @var string
- */
protected $description = 'Seed tenant database(s).';
protected $name = 'tenants:seed';
- /**
- * Create a new command instance.
- *
- * @return void
- */
public function __construct(ConnectionResolverInterface $resolver)
{
parent::__construct($resolver);
}
- /**
- * Execute the console command.
- */
- public function handle()
+ public function handle(): int
{
foreach (config('tenancy.seeder_parameters') as $parameter => $value) {
if (! $this->input->hasParameterOption($parameter)) {
@@ -45,10 +32,10 @@ class Seed extends SeedCommand
}
if (! $this->confirmToProceed()) {
- return;
+ return 1;
}
- tenancy()->runForMultiple($this->option('tenants'), function ($tenant) {
+ tenancy()->runForMultiple($this->getTenants(), function ($tenant) {
$this->line("Tenant: {$tenant->getTenantKey()}");
event(new SeedingDatabase($tenant));
@@ -58,5 +45,7 @@ class Seed extends SeedCommand
event(new DatabaseSeeded($tenant));
});
+
+ return 0;
}
}
diff --git a/src/Commands/TenantList.php b/src/Commands/TenantList.php
index 13775676..9fd3f8bd 100644
--- a/src/Commands/TenantList.php
+++ b/src/Commands/TenantList.php
@@ -5,39 +5,28 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Commands;
use Illuminate\Console\Command;
+use Illuminate\Database\Eloquent\Model;
use Stancl\Tenancy\Contracts\Tenant;
class TenantList extends Command
{
- /**
- * The name and signature of the console command.
- *
- * @var string
- */
protected $signature = 'tenants:list';
- /**
- * The console command description.
- *
- * @var string
- */
protected $description = 'List tenants.';
- /**
- * Execute the console command.
- */
- public function handle()
+ public function handle(): void
{
$this->info('Listing all tenants.');
- tenancy()
- ->query()
- ->cursor()
- ->each(function (Tenant $tenant) {
- if ($tenant->domains) {
- $this->line("[Tenant] {$tenant->getTenantKeyName()}: {$tenant->getTenantKey()} @ " . implode('; ', $tenant->domains->pluck('domain')->toArray() ?? []));
- } else {
- $this->line("[Tenant] {$tenant->getTenantKeyName()}: {$tenant->getTenantKey()}");
- }
- });
+
+ $tenants = tenancy()->query()->cursor();
+
+ foreach ($tenants as $tenant) {
+ /** @var Model&Tenant $tenant */
+ if ($tenant->domains) {
+ $this->line("[Tenant] {$tenant->getTenantKeyName()}: {$tenant->getTenantKey()} @ " . implode('; ', $tenant->domains->pluck('domain')->toArray() ?? []));
+ } else {
+ $this->line("[Tenant] {$tenant->getTenantKeyName()}: {$tenant->getTenantKey()}");
+ }
+ }
}
}
diff --git a/src/Commands/Up.php b/src/Commands/Up.php
new file mode 100644
index 00000000..a3f690c2
--- /dev/null
+++ b/src/Commands/Up.php
@@ -0,0 +1,27 @@
+runForMultiple($this->getTenants(), function ($tenant) {
+ $this->line("Tenant: {$tenant['id']}");
+ $tenant->bringUpFromMaintenance();
+ });
+
+ $this->comment('Tenants are now out of maintenance mode.');
+ }
+}
diff --git a/src/Concerns/DealsWithMigrations.php b/src/Concerns/DealsWithMigrations.php
index 4bb6b44c..3129c68d 100644
--- a/src/Concerns/DealsWithMigrations.php
+++ b/src/Concerns/DealsWithMigrations.php
@@ -6,12 +6,12 @@ namespace Stancl\Tenancy\Concerns;
trait DealsWithMigrations
{
- protected function getMigrationPaths()
+ protected function getMigrationPaths(): array
{
if ($this->input->hasOption('path') && $this->input->getOption('path')) {
return parent::getMigrationPaths();
}
- return database_path('migrations/tenant');
+ return [database_path('migrations/tenant')];
}
}
diff --git a/src/Concerns/DealsWithTenantSymlinks.php b/src/Concerns/DealsWithTenantSymlinks.php
index a4c972bb..5f3baf5b 100644
--- a/src/Concerns/DealsWithTenantSymlinks.php
+++ b/src/Concerns/DealsWithTenantSymlinks.php
@@ -5,7 +5,7 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Concerns;
use Illuminate\Support\Collection;
-use Stancl\Tenancy\Database\Models\Tenant;
+use Stancl\Tenancy\Contracts\Tenant;
trait DealsWithTenantSymlinks
{
@@ -15,25 +15,29 @@ trait DealsWithTenantSymlinks
* Tenants can have a symlink for each disk registered in the tenancy.filesystem.url_override config.
*
* This is used for creating all possible tenant symlinks and removing all existing tenant symlinks.
+ *
+ * @return Collection
*/
protected static function possibleTenantSymlinks(Tenant $tenant): Collection
{
$diskUrls = config('tenancy.filesystem.url_override');
$disks = config('tenancy.filesystem.root_override');
$suffixBase = config('tenancy.filesystem.suffix_base');
- $symlinks = collect();
$tenantKey = $tenant->getTenantKey();
+ /** @var Collection> $symlinks */
+ $symlinks = collect([]);
+
foreach ($diskUrls as $disk => $publicPath) {
$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) {
$symlinks->push([public_path($publicPath) => storage_path($storagePath)]);
});
}
- return $symlinks->mapWithKeys(fn ($item) => $item);
+ return $symlinks->mapWithKeys(fn ($item) => $item); // [[a => b], [c => d]] -> [a => b, c => d]
}
/** Determine if the provided path is an existing symlink. */
diff --git a/src/Contracts/Domain.php b/src/Contracts/Domain.php
index 2c02089e..a9a19a50 100644
--- a/src/Contracts/Domain.php
+++ b/src/Contracts/Domain.php
@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Contracts;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
+
/**
* @property-read Tenant $tenant
*
@@ -15,5 +17,5 @@ namespace Stancl\Tenancy\Contracts;
*/
interface Domain
{
- public function tenant();
+ public function tenant(): BelongsTo;
}
diff --git a/src/Contracts/TenancyBootstrapper.php b/src/Contracts/TenancyBootstrapper.php
index 8b43755f..6da5c537 100644
--- a/src/Contracts/TenancyBootstrapper.php
+++ b/src/Contracts/TenancyBootstrapper.php
@@ -9,7 +9,7 @@ namespace Stancl\Tenancy\Contracts;
*/
interface TenancyBootstrapper
{
- public function bootstrap(Tenant $tenant);
+ public function bootstrap(Tenant $tenant): void;
- public function revert();
+ public function revert(): void;
}
diff --git a/src/Contracts/TenantCannotBeCreatedException.php b/src/Contracts/TenantCannotBeCreatedException.php
index 19eac15b..53d8589f 100644
--- a/src/Contracts/TenantCannotBeCreatedException.php
+++ b/src/Contracts/TenantCannotBeCreatedException.php
@@ -8,6 +8,7 @@ abstract class TenantCannotBeCreatedException extends \Exception
{
abstract public function reason(): string;
+ /** @var string */
protected $message;
public function __construct()
diff --git a/src/Contracts/UniqueIdentifierGenerator.php b/src/Contracts/UniqueIdentifierGenerator.php
index b21d6028..14d91ae0 100644
--- a/src/Contracts/UniqueIdentifierGenerator.php
+++ b/src/Contracts/UniqueIdentifierGenerator.php
@@ -4,10 +4,12 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Contracts;
+use Illuminate\Database\Eloquent\Model;
+
interface UniqueIdentifierGenerator
{
/**
- * Generate a unique identifier.
+ * Generate a unique identifier for a model.
*/
- public static function generate($resource): string;
+ public static function generate(Model $model): string;
}
diff --git a/src/Controllers/TenantAssetController.php b/src/Controllers/TenantAssetController.php
new file mode 100644
index 00000000..7a95dffe
--- /dev/null
+++ b/src/Controllers/TenantAssetController.php
@@ -0,0 +1,32 @@
+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);
+ }
+ }
+}
diff --git a/src/Controllers/TenantAssetsController.php b/src/Controllers/TenantAssetsController.php
deleted file mode 100644
index 03d600d0..00000000
--- a/src/Controllers/TenantAssetsController.php
+++ /dev/null
@@ -1,30 +0,0 @@
-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);
- }
- }
-}
diff --git a/src/Database/Concerns/BelongsToTenant.php b/src/Database/Concerns/BelongsToTenant.php
index ade966a8..07048a1f 100644
--- a/src/Database/Concerns/BelongsToTenant.php
+++ b/src/Database/Concerns/BelongsToTenant.php
@@ -12,11 +12,14 @@ use Stancl\Tenancy\Database\TenantScope;
*/
trait BelongsToTenant
{
- public static $tenantIdColumn = 'tenant_id';
-
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
@@ -24,9 +27,9 @@ trait BelongsToTenant
static::addGlobalScope(new TenantScope);
static::creating(function ($model) {
- if (! $model->getAttribute(BelongsToTenant::$tenantIdColumn) && ! $model->relationLoaded('tenant')) {
+ if (! $model->getAttribute(static::tenantIdColumn()) && ! $model->relationLoaded('tenant')) {
if (tenancy()->initialized) {
- $model->setAttribute(BelongsToTenant::$tenantIdColumn, tenant()->getTenantKey());
+ $model->setAttribute(static::tenantIdColumn(), tenant()->getTenantKey());
$model->setRelation('tenant', tenant());
}
}
diff --git a/src/Database/Concerns/HasDomains.php b/src/Database/Concerns/HasDomains.php
index 594e9a81..bd512e23 100644
--- a/src/Database/Concerns/HasDomains.php
+++ b/src/Database/Concerns/HasDomains.php
@@ -10,6 +10,8 @@ use Stancl\Tenancy\Contracts\Domain;
/**
* @property-read Domain[]|\Illuminate\Database\Eloquent\Collection $domains
+ * @mixin \Illuminate\Database\Eloquent\Model
+ * @mixin \Stancl\Tenancy\Contracts\Tenant
*/
trait HasDomains
{
diff --git a/src/Database/Concerns/HasScopedValidationRules.php b/src/Database/Concerns/HasScopedValidationRules.php
index ae5c7fc7..7913a215 100644
--- a/src/Database/Concerns/HasScopedValidationRules.php
+++ b/src/Database/Concerns/HasScopedValidationRules.php
@@ -11,11 +11,11 @@ trait HasScopedValidationRules
{
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')
{
- return (new Exists($table, $column))->where(BelongsToTenant::$tenantIdColumn, $this->getTenantKey());
+ return (new Exists($table, $column))->where(BelongsToTenant::tenantIdColumn(), $this->getTenantKey());
}
}
diff --git a/src/Database/Concerns/InitializationHelpers.php b/src/Database/Concerns/InitializationHelpers.php
new file mode 100644
index 00000000..46802df1
--- /dev/null
+++ b/src/Database/Concerns/InitializationHelpers.php
@@ -0,0 +1,21 @@
+initialize($this);
+ }
+
+ public function leave(): void
+ {
+ tenancy()->end();
+ }
+}
diff --git a/src/Database/Concerns/InvalidatesResolverCache.php b/src/Database/Concerns/InvalidatesResolverCache.php
index 3b864789..21894f41 100644
--- a/src/Database/Concerns/InvalidatesResolverCache.php
+++ b/src/Database/Concerns/InvalidatesResolverCache.php
@@ -5,21 +5,15 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Database\Concerns;
use Stancl\Tenancy\Contracts\Tenant;
-use Stancl\Tenancy\Resolvers;
use Stancl\Tenancy\Resolvers\Contracts\CachedTenantResolver;
+use Stancl\Tenancy\Tenancy;
trait InvalidatesResolverCache
{
- public static $resolvers = [
- Resolvers\DomainTenantResolver::class,
- Resolvers\PathTenantResolver::class,
- Resolvers\RequestDataTenantResolver::class,
- ];
-
public static function bootInvalidatesResolverCache(): void
{
static::saved(function (Tenant $tenant) {
- foreach (static::$resolvers as $resolver) {
+ foreach (Tenancy::cachedResolvers() as $resolver) {
/** @var CachedTenantResolver $resolver */
$resolver = app($resolver);
diff --git a/src/Database/Concerns/InvalidatesTenantsResolverCache.php b/src/Database/Concerns/InvalidatesTenantsResolverCache.php
index 8d7c2845..d954567f 100644
--- a/src/Database/Concerns/InvalidatesTenantsResolverCache.php
+++ b/src/Database/Concerns/InvalidatesTenantsResolverCache.php
@@ -5,24 +5,18 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Database\Concerns;
use Illuminate\Database\Eloquent\Model;
-use Stancl\Tenancy\Resolvers;
use Stancl\Tenancy\Resolvers\Contracts\CachedTenantResolver;
+use Stancl\Tenancy\Tenancy;
/**
* Meant to be used on models that belong to tenants.
*/
trait InvalidatesTenantsResolverCache
{
- public static $resolvers = [
- Resolvers\DomainTenantResolver::class,
- Resolvers\PathTenantResolver::class,
- Resolvers\RequestDataTenantResolver::class,
- ];
-
public static function bootInvalidatesTenantsResolverCache(): void
{
static::saved(function (Model $model) {
- foreach (static::$resolvers as $resolver) {
+ foreach (Tenancy::cachedResolvers() as $resolver) {
/** @var CachedTenantResolver $resolver */
$resolver = app($resolver);
diff --git a/src/Database/Concerns/MaintenanceMode.php b/src/Database/Concerns/MaintenanceMode.php
index 55e0e46d..cc4490f6 100644
--- a/src/Database/Concerns/MaintenanceMode.php
+++ b/src/Database/Concerns/MaintenanceMode.php
@@ -4,17 +4,27 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Database\Concerns;
-use Carbon\Carbon;
-
+/**
+ * @mixin \Illuminate\Database\Eloquent\Model
+ */
trait MaintenanceMode
{
- public function putDownForMaintenance($data = [])
+ public function putDownForMaintenance($data = []): void
{
- $this->update(['maintenance_mode' => [
- 'time' => $data['time'] ?? Carbon::now()->getTimestamp(),
- 'message' => $data['message'] ?? null,
- 'retry' => $data['retry'] ?? null,
- 'allowed' => $data['allowed'] ?? [],
- ]]);
+ $this->update([
+ 'maintenance_mode' => [
+ 'except' => $data['except'] ?? null,
+ 'redirect' => $data['redirect'] ?? null,
+ 'retry' => $data['retry'] ?? null,
+ 'refresh' => $data['refresh'] ?? null,
+ 'secret' => $data['secret'] ?? null,
+ 'status' => $data['status'] ?? 503,
+ ],
+ ]);
+ }
+
+ public function bringUpFromMaintenance(): void
+ {
+ $this->update(['maintenance_mode' => null]);
}
}
diff --git a/src/Database/Contracts/TenantWithDatabase.php b/src/Database/Contracts/TenantWithDatabase.php
index c9247d94..76a73340 100644
--- a/src/Database/Contracts/TenantWithDatabase.php
+++ b/src/Database/Contracts/TenantWithDatabase.php
@@ -9,5 +9,15 @@ use Stancl\Tenancy\Database\DatabaseConfig;
interface TenantWithDatabase extends Tenant
{
+ /** Get the tenant's database config. */
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;
}
diff --git a/src/Database/DatabaseConfig.php b/src/Database/DatabaseConfig.php
index a4c79582..6c68f379 100644
--- a/src/Database/DatabaseConfig.php
+++ b/src/Database/DatabaseConfig.php
@@ -26,20 +26,20 @@ class DatabaseConfig
public static function __constructStatic(): void
{
- static::$usernameGenerator = static::$usernameGenerator ?? function (Tenant $tenant) {
+ static::$usernameGenerator = static::$usernameGenerator ?? function (Model&Tenant $tenant) {
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));
};
- 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');
};
}
- public function __construct(Tenant $tenant)
+ public function __construct(Model&Tenant $tenant)
{
static::__constructStatic();
@@ -61,7 +61,7 @@ class DatabaseConfig
static::$passwordGenerator = $passwordGenerator;
}
- public function getName(): ?string
+ public function getName(): string
{
return $this->tenant->getInternal('db_name') ?? (static::$databaseNameGenerator)($this->tenant);
}
@@ -81,7 +81,7 @@ class DatabaseConfig
*/
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) {
$this->tenant->setInternal('db_username', $this->getUsername() ?? (static::$usernameGenerator)($this->tenant));
diff --git a/src/Database/DatabaseManager.php b/src/Database/DatabaseManager.php
index a92ccb7b..ce9219d5 100644
--- a/src/Database/DatabaseManager.php
+++ b/src/Database/DatabaseManager.php
@@ -15,25 +15,14 @@ use Stancl\Tenancy\Database\Contracts\TenantWithDatabase;
*/
class DatabaseManager
{
- /** @var Application */
- protected $app;
-
- /** @var BaseDatabaseManager */
- protected $database;
-
- /** @var Repository */
- protected $config;
-
- public function __construct(Application $app, BaseDatabaseManager $database, Repository $config)
- {
- $this->app = $app;
- $this->database = $database;
- $this->config = $config;
+ public function __construct(
+ protected Application $app,
+ protected BaseDatabaseManager $database,
+ protected Repository $config,
+ ) {
}
- /**
- * Connect to a tenant's database.
- */
+ /** Connect to a tenant's database. */
public function connectToTenant(TenantWithDatabase $tenant): void
{
$this->purgeTenantConnection();
@@ -41,35 +30,27 @@ class DatabaseManager
$this->setDefaultConnection('tenant');
}
- /**
- * Reconnect to the default non-tenant connection.
- */
+ /** Reconnect to the default non-tenant connection. */
public function reconnectToCentral(): void
{
$this->purgeTenantConnection();
$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
{
$this->config['database.default'] = $connection;
$this->database->setDefaultConnection($connection);
}
- /**
- * Create the tenant database connection.
- */
+ /** Create the tenant database connection. */
public function createTenantConnection(TenantWithDatabase $tenant): void
{
$this->config['database.connections.tenant'] = $tenant->database()->connection();
}
- /**
- * Purge the tenant database connection.
- */
+ /** Purge the tenant database connection. */
public function purgeTenantConnection(): void
{
if (array_key_exists('tenant', $this->database->getConnections())) {
@@ -83,8 +64,8 @@ class DatabaseManager
* Check if a tenant can be created.
*
* @throws TenantCannotBeCreatedException
- * @throws DatabaseManagerNotRegisteredException
- * @throws TenantDatabaseAlreadyExistsException
+ * @throws Exceptions\DatabaseManagerNotRegisteredException
+ * @throws Exceptions\TenantDatabaseAlreadyExistsException
*/
public function ensureTenantCanBeCreated(TenantWithDatabase $tenant): void
{
@@ -94,8 +75,13 @@ class DatabaseManager
throw new Exceptions\TenantDatabaseAlreadyExistsException($database);
}
- if ($manager instanceof Contracts\ManagesDatabaseUsers && $manager->userExists($username = $tenant->database()->getUsername())) {
- throw new Exceptions\TenantDatabaseUserAlreadyExistsException($username);
+ if ($manager instanceof Contracts\ManagesDatabaseUsers) {
+ /** @var string $username */
+ $username = $tenant->database()->getUsername();
+
+ if ($manager->userExists($username)) {
+ throw new Exceptions\TenantDatabaseUserAlreadyExistsException($username);
+ }
}
}
}
diff --git a/src/Database/Models/Tenant.php b/src/Database/Models/Tenant.php
index 4518e7b7..88c34146 100644
--- a/src/Database/Models/Tenant.php
+++ b/src/Database/Models/Tenant.php
@@ -26,6 +26,7 @@ class Tenant extends Model implements Contracts\Tenant
Concerns\HasDataColumn,
Concerns\HasInternalKeys,
Concerns\TenantRun,
+ Concerns\InitializationHelpers,
Concerns\InvalidatesResolverCache;
protected $table = 'tenants';
diff --git a/src/Database/ParentModelScope.php b/src/Database/ParentModelScope.php
index 78f5de20..cfc003c1 100644
--- a/src/Database/ParentModelScope.php
+++ b/src/Database/ParentModelScope.php
@@ -19,7 +19,7 @@ class ParentModelScope implements Scope
$builder->whereHas($builder->getModel()->getRelationshipToPrimaryModel());
}
- public function extend(Builder $builder)
+ public function extend(Builder $builder): void
{
$builder->macro('withoutParentModel', function (Builder $builder) {
return $builder->withoutGlobalScope($this);
diff --git a/src/Database/TenantDatabaseManagers/PermissionControlledMySQLDatabaseManager.php b/src/Database/TenantDatabaseManagers/PermissionControlledMySQLDatabaseManager.php
index 337864dc..f7e7440e 100644
--- a/src/Database/TenantDatabaseManagers/PermissionControlledMySQLDatabaseManager.php
+++ b/src/Database/TenantDatabaseManagers/PermissionControlledMySQLDatabaseManager.php
@@ -12,7 +12,8 @@ class PermissionControlledMySQLDatabaseManager extends MySQLDatabaseManager impl
{
use CreatesDatabaseUsers;
- public static $grants = [
+ /** @var string[] */
+ public static array $grants = [
'ALTER', 'ALTER ROUTINE', 'CREATE', 'CREATE ROUTINE', 'CREATE TEMPORARY TABLES', 'CREATE VIEW',
'DELETE', 'DROP', 'EVENT', 'EXECUTE', 'INDEX', 'INSERT', 'LOCK TABLES', 'REFERENCES', 'SELECT',
'SHOW VIEW', 'TRIGGER', 'UPDATE',
diff --git a/src/Database/TenantDatabaseManagers/PostgreSQLSchemaManager.php b/src/Database/TenantDatabaseManagers/PostgreSQLSchemaManager.php
index fa5aa593..a7558e1b 100644
--- a/src/Database/TenantDatabaseManagers/PostgreSQLSchemaManager.php
+++ b/src/Database/TenantDatabaseManagers/PostgreSQLSchemaManager.php
@@ -25,11 +25,7 @@ class PostgreSQLSchemaManager extends TenantDatabaseManager
public function makeConnectionConfig(array $baseConfig, string $databaseName): array
{
- if (version_compare(app()->version(), '9.0', '>=')) {
- $baseConfig['search_path'] = $databaseName;
- } else {
- $baseConfig['schema'] = $databaseName;
- }
+ $baseConfig['search_path'] = $databaseName;
return $baseConfig;
}
diff --git a/src/Database/TenantScope.php b/src/Database/TenantScope.php
index 8592f16c..fdab9d70 100644
--- a/src/Database/TenantScope.php
+++ b/src/Database/TenantScope.php
@@ -17,10 +17,10 @@ class TenantScope implements Scope
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) {
return $builder->withoutGlobalScope($this);
diff --git a/src/Events/Contracts/TenancyEvent.php b/src/Events/Contracts/TenancyEvent.php
index f292049d..85b793f5 100644
--- a/src/Events/Contracts/TenancyEvent.php
+++ b/src/Events/Contracts/TenancyEvent.php
@@ -8,11 +8,8 @@ use Stancl\Tenancy\Tenancy;
abstract class TenancyEvent
{
- /** @var Tenancy */
- public $tenancy;
-
- public function __construct(Tenancy $tenancy)
- {
- $this->tenancy = $tenancy;
+ public function __construct(
+ public Tenancy $tenancy,
+ ) {
}
}
diff --git a/src/Exceptions/DomainOccupiedByOtherTenantException.php b/src/Exceptions/DomainOccupiedByOtherTenantException.php
index 00d42f3e..c0860ca8 100644
--- a/src/Exceptions/DomainOccupiedByOtherTenantException.php
+++ b/src/Exceptions/DomainOccupiedByOtherTenantException.php
@@ -8,7 +8,7 @@ use Exception;
class DomainOccupiedByOtherTenantException extends Exception
{
- public function __construct($domain)
+ public function __construct(string $domain)
{
parent::__construct("The $domain domain is occupied by another tenant.");
}
diff --git a/src/Exceptions/RouteIsMissingTenantParameterException.php b/src/Exceptions/RouteIsMissingTenantParameterException.php
index b979c819..afe56ea7 100644
--- a/src/Exceptions/RouteIsMissingTenantParameterException.php
+++ b/src/Exceptions/RouteIsMissingTenantParameterException.php
@@ -11,7 +11,7 @@ class RouteIsMissingTenantParameterException extends Exception
{
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).");
}
diff --git a/src/Exceptions/TenancyNotInitializedException.php b/src/Exceptions/TenancyNotInitializedException.php
index d2744499..be936747 100644
--- a/src/Exceptions/TenancyNotInitializedException.php
+++ b/src/Exceptions/TenancyNotInitializedException.php
@@ -8,7 +8,7 @@ use Exception;
class TenancyNotInitializedException extends Exception
{
- public function __construct($message = '')
+ public function __construct(string $message = '')
{
parent::__construct($message ?: 'Tenancy is not initialized.');
}
diff --git a/src/Features/TenantConfig.php b/src/Features/TenantConfig.php
index 50756b2c..7b82a7cd 100644
--- a/src/Features/TenantConfig.php
+++ b/src/Features/TenantConfig.php
@@ -16,24 +16,25 @@ use Stancl\Tenancy\Tenancy;
class TenantConfig implements Feature
{
- /** @var Repository */
- protected $config;
-
public array $originalConfig = [];
- public static $storageToConfigMap = [
+ /** @var array */
+ public static array $storageToConfigMap = [
// 'paypal_api_key' => 'services.paypal.api_key',
];
- public function __construct(Repository $config)
- {
- $this->config = $config;
+ public function __construct(
+ protected Repository $config,
+ ) {
}
public function bootstrap(Tenancy $tenancy): void
{
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 () {
@@ -43,8 +44,8 @@ class TenantConfig implements Feature
public function setTenantConfig(Tenant $tenant): void
{
- /** @var Tenant|Model $tenant */
foreach (static::$storageToConfigMap as $storageKey => $configKey) {
+ /** @var Tenant&Model $tenant */
$override = Arr::get($tenant, $storageKey);
if (! is_null($override)) {
diff --git a/src/Features/UniversalRoutes.php b/src/Features/UniversalRoutes.php
index e327b5d3..ad0433fc 100644
--- a/src/Features/UniversalRoutes.php
+++ b/src/Features/UniversalRoutes.php
@@ -16,6 +16,7 @@ class UniversalRoutes implements Feature
public static string $middlewareGroup = 'universal';
// todo docblock
+ /** @var array> */
public static array $identificationMiddlewares = [
Middleware\InitializeTenancyByDomain::class,
Middleware\InitializeTenancyBySubdomain::class,
@@ -42,7 +43,10 @@ class UniversalRoutes implements Feature
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;
}
diff --git a/src/Jobs/CreateDatabase.php b/src/Jobs/CreateDatabase.php
index f143f399..dbc4b097 100644
--- a/src/Jobs/CreateDatabase.php
+++ b/src/Jobs/CreateDatabase.php
@@ -24,7 +24,7 @@ class CreateDatabase implements ShouldQueue
) {
}
- public function handle(DatabaseManager $databaseManager)
+ public function handle(DatabaseManager $databaseManager): bool
{
event(new CreatingDatabase($this->tenant));
@@ -38,5 +38,7 @@ class CreateDatabase implements ShouldQueue
$this->tenant->database()->manager()->createDatabase($this->tenant);
event(new DatabaseCreated($this->tenant));
+
+ return true;
}
}
diff --git a/src/Jobs/CreateStorageSymlinks.php b/src/Jobs/CreateStorageSymlinks.php
index 4f18bb03..fb9a3b0d 100644
--- a/src/Jobs/CreateStorageSymlinks.php
+++ b/src/Jobs/CreateStorageSymlinks.php
@@ -16,24 +16,12 @@ class CreateStorageSymlinks implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
- public Tenant $tenant;
-
- /**
- * Create a new job instance.
- *
- * @return void
- */
- public function __construct(Tenant $tenant)
- {
- $this->tenant = $tenant;
+ public function __construct(
+ public Tenant $tenant,
+ ) {
}
- /**
- * Execute the job.
- *
- * @return void
- */
- public function handle()
+ public function handle(): void
{
CreateStorageSymlinksAction::handle($this->tenant);
}
diff --git a/src/Jobs/DeleteDomains.php b/src/Jobs/DeleteDomains.php
index 8d89ce9e..15fff779 100644
--- a/src/Jobs/DeleteDomains.php
+++ b/src/Jobs/DeleteDomains.php
@@ -9,14 +9,12 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
-use Stancl\Tenancy\Database\Concerns\HasDomains;
use Stancl\Tenancy\Database\Contracts\TenantWithDatabase;
class DeleteDomains
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
- /** @var TenantWithDatabase&Model&HasDomains */ // todo unresolvable type for phpstan
protected TenantWithDatabase&Model $tenant;
public function __construct(TenantWithDatabase&Model $tenant)
diff --git a/src/Listeners/BootstrapTenancy.php b/src/Listeners/BootstrapTenancy.php
index 205efc5f..50f38208 100644
--- a/src/Listeners/BootstrapTenancy.php
+++ b/src/Listeners/BootstrapTenancy.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Listeners;
+use Stancl\Tenancy\Contracts\Tenant;
use Stancl\Tenancy\Events\BootstrappingTenancy;
use Stancl\Tenancy\Events\TenancyBootstrapped;
use Stancl\Tenancy\Events\TenancyInitialized;
@@ -15,7 +16,10 @@ class BootstrapTenancy
event(new BootstrappingTenancy($event->tenancy));
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));
diff --git a/src/Listeners/CreateTenantConnection.php b/src/Listeners/CreateTenantConnection.php
index 01351c08..b4983d32 100644
--- a/src/Listeners/CreateTenantConnection.php
+++ b/src/Listeners/CreateTenantConnection.php
@@ -4,21 +4,22 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Listeners;
+use Stancl\Tenancy\Database\Contracts\TenantWithDatabase;
use Stancl\Tenancy\Database\DatabaseManager;
use Stancl\Tenancy\Events\Contracts\TenantEvent;
class CreateTenantConnection
{
- /** @var DatabaseManager */
- protected $database;
-
- public function __construct(DatabaseManager $database)
- {
- $this->database = $database;
+ public function __construct(
+ protected DatabaseManager $database,
+ ) {
}
public function handle(TenantEvent $event): void
{
- $this->database->createTenantConnection($event->tenant);
+ /** @var TenantWithDatabase */
+ $tenant = $event->tenant;
+
+ $this->database->createTenantConnection($tenant);
}
}
diff --git a/src/Listeners/QueueableListener.php b/src/Listeners/QueueableListener.php
index e10c1e7a..f486873d 100644
--- a/src/Listeners/QueueableListener.php
+++ b/src/Listeners/QueueableListener.php
@@ -13,7 +13,7 @@ abstract class QueueableListener implements ShouldQueue
{
public static bool $shouldQueue = false;
- public function shouldQueue($event): bool
+ public function shouldQueue(object $event): bool
{
if (static::$shouldQueue) {
return true;
diff --git a/src/Middleware/CheckTenantForMaintenanceMode.php b/src/Middleware/CheckTenantForMaintenanceMode.php
index c1c734f5..58fcd184 100644
--- a/src/Middleware/CheckTenantForMaintenanceMode.php
+++ b/src/Middleware/CheckTenantForMaintenanceMode.php
@@ -7,7 +7,6 @@ namespace Stancl\Tenancy\Middleware;
use Closure;
use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode;
use Stancl\Tenancy\Exceptions\TenancyNotInitializedException;
-use Symfony\Component\HttpFoundation\IpUtils;
use Symfony\Component\HttpKernel\Exception\HttpException;
class CheckTenantForMaintenanceMode extends CheckForMaintenanceMode
@@ -21,19 +20,38 @@ class CheckTenantForMaintenanceMode extends CheckForMaintenanceMode
if (tenant('maintenance_mode')) {
$data = tenant('maintenance_mode');
- if (isset($data['allowed']) && IpUtils::checkIp($request->ip(), (array) $data['allowed'])) {
+ if (isset($data['secret']) && $request->path() === $data['secret']) {
+ return $this->bypassResponse($data['secret']);
+ }
+
+ if ($this->hasValidBypassCookie($request, $data) ||
+ $this->inExceptArray($request)) {
return $next($request);
}
- if ($this->inExceptArray($request)) {
- return $next($request);
+ if (isset($data['redirect'])) {
+ $path = $data['redirect'] === '/'
+ ? $data['redirect']
+ : trim($data['redirect'], '/');
+
+ if ($request->path() !== $path) {
+ return redirect($path);
+ }
+ }
+
+ if (isset($data['template'])) {
+ return response(
+ $data['template'],
+ (int) ($data['status'] ?? 503),
+ $this->getHeaders($data)
+ );
}
throw new HttpException(
- 503,
+ (int) ($data['status'] ?? 503),
'Service Unavailable',
null,
- isset($data['retry']) ? ['Retry-After' => $data['retry']] : []
+ $this->getHeaders($data)
);
}
diff --git a/src/Middleware/IdentificationMiddleware.php b/src/Middleware/IdentificationMiddleware.php
index 38f4684d..12aa4a16 100644
--- a/src/Middleware/IdentificationMiddleware.php
+++ b/src/Middleware/IdentificationMiddleware.php
@@ -4,22 +4,22 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Middleware;
+use Closure;
+use Illuminate\Http\Request;
use Stancl\Tenancy\Contracts\TenantCouldNotBeIdentifiedException;
use Stancl\Tenancy\Contracts\TenantResolver;
use Stancl\Tenancy\Tenancy;
+/**
+ * @property Tenancy $tenancy
+ * @property TenantResolver $resolver
+ */
abstract class IdentificationMiddleware
{
- /** @var callable */
- public static $onFail;
+ public static ?Closure $onFail = null;
- /** @var Tenancy */
- protected $tenancy;
-
- /** @var TenantResolver */
- protected $resolver;
-
- public function initializeTenancy($request, $next, ...$resolverArguments)
+ /** @return \Illuminate\Http\Response|mixed */
+ public function initializeTenancy(Request $request, Closure $next, mixed ...$resolverArguments): mixed
{
try {
$this->tenancy->initialize(
diff --git a/src/Middleware/InitializeTenancyByDomain.php b/src/Middleware/InitializeTenancyByDomain.php
index 5a07112d..add5597d 100644
--- a/src/Middleware/InitializeTenancyByDomain.php
+++ b/src/Middleware/InitializeTenancyByDomain.php
@@ -5,32 +5,22 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Middleware;
use Closure;
+use Illuminate\Http\Request;
use Stancl\Tenancy\Resolvers\DomainTenantResolver;
use Stancl\Tenancy\Tenancy;
class InitializeTenancyByDomain extends IdentificationMiddleware
{
- /** @var callable|null */
- public static $onFail;
+ public static ?Closure $onFail = null;
- /** @var Tenancy */
- protected $tenancy;
-
- /** @var DomainTenantResolver */
- protected $resolver;
-
- public function __construct(Tenancy $tenancy, DomainTenantResolver $resolver)
- {
- $this->tenancy = $tenancy;
- $this->resolver = $resolver;
+ public function __construct(
+ protected Tenancy $tenancy,
+ protected DomainTenantResolver $resolver,
+ ) {
}
- /**
- * Handle an incoming request.
- *
- * @param \Illuminate\Http\Request $request
- */
- public function handle($request, Closure $next)
+ /** @return \Illuminate\Http\Response|mixed */
+ public function handle(Request $request, Closure $next): mixed
{
return $this->initializeTenancy(
$request,
diff --git a/src/Middleware/InitializeTenancyByDomainOrSubdomain.php b/src/Middleware/InitializeTenancyByDomainOrSubdomain.php
index 9b153db3..1a30001a 100644
--- a/src/Middleware/InitializeTenancyByDomainOrSubdomain.php
+++ b/src/Middleware/InitializeTenancyByDomainOrSubdomain.php
@@ -5,16 +5,13 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Middleware;
use Closure;
+use Illuminate\Http\Request;
use Illuminate\Support\Str;
class InitializeTenancyByDomainOrSubdomain
{
- /**
- * Handle an incoming request.
- *
- * @param \Illuminate\Http\Request $request
- */
- public function handle($request, Closure $next)
+ /** @return \Illuminate\Http\Response|mixed */
+ public function handle(Request $request, Closure $next): mixed
{
if ($this->isSubdomain($request->getHost())) {
return app(InitializeTenancyBySubdomain::class)->handle($request, $next);
diff --git a/src/Middleware/InitializeTenancyByPath.php b/src/Middleware/InitializeTenancyByPath.php
index ae15323c..3e484f87 100644
--- a/src/Middleware/InitializeTenancyByPath.php
+++ b/src/Middleware/InitializeTenancyByPath.php
@@ -9,6 +9,7 @@ use Illuminate\Http\Request;
use Illuminate\Routing\Route;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\URL;
+use Stancl\Tenancy\Contracts\Tenant;
use Stancl\Tenancy\Events\InitializingTenancy;
use Stancl\Tenancy\Exceptions\RouteIsMissingTenantParameterException;
use Stancl\Tenancy\Resolvers\PathTenantResolver;
@@ -16,22 +17,16 @@ use Stancl\Tenancy\Tenancy;
class InitializeTenancyByPath extends IdentificationMiddleware
{
- /** @var callable|null */
- public static $onFail;
+ public static ?Closure $onFail = null;
- /** @var Tenancy */
- protected $tenancy;
-
- /** @var PathTenantResolver */
- protected $resolver;
-
- public function __construct(Tenancy $tenancy, PathTenantResolver $resolver)
- {
- $this->tenancy = $tenancy;
- $this->resolver = $resolver;
+ public function __construct(
+ protected Tenancy $tenancy,
+ protected PathTenantResolver $resolver,
+ ) {
}
- public function handle(Request $request, Closure $next)
+ /** @return \Illuminate\Http\Response|mixed */
+ public function handle(Request $request, Closure $next): mixed
{
/** @var Route $route */
$route = $request->route();
@@ -39,11 +34,8 @@ class InitializeTenancyByPath extends IdentificationMiddleware
// Only initialize tenancy if tenant is the first parameter
// We don't want to initialize tenancy if the tenant is
// simply injected into some route controller action.
- if ($route->parameterNames()[0] === PathTenantResolver::$tenantParameterName) {
- // Set tenant as a default parameter for the URLs in the current request
- Event::listen(InitializingTenancy::class, function (InitializingTenancy $event) {
- URL::defaults([PathTenantResolver::$tenantParameterName => $event->tenancy->tenant->getTenantKey()]);
- });
+ if ($route->parameterNames()[0] === PathTenantResolver::tenantParameterName()) {
+ $this->setDefaultTenantForRouteParametersWhenTenancyIsInitialized();
return $this->initializeTenancy(
$request,
@@ -56,4 +48,16 @@ class InitializeTenancyByPath extends IdentificationMiddleware
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(),
+ ]);
+ });
+ }
}
diff --git a/src/Middleware/InitializeTenancyByRequestData.php b/src/Middleware/InitializeTenancyByRequestData.php
index 4e1d33ff..ba587d9a 100644
--- a/src/Middleware/InitializeTenancyByRequestData.php
+++ b/src/Middleware/InitializeTenancyByRequestData.php
@@ -11,33 +11,18 @@ use Stancl\Tenancy\Tenancy;
class InitializeTenancyByRequestData extends IdentificationMiddleware
{
- /** @var string|null */
- public static $header = 'X-Tenant';
+ public static string $header = 'X-Tenant';
+ public static string $queryParameter = 'tenant';
+ public static ?Closure $onFail = null;
- /** @var string|null */
- public static $queryParameter = 'tenant';
-
- /** @var callable|null */
- public static $onFail;
-
- /** @var Tenancy */
- protected $tenancy;
-
- /** @var TenantResolver */
- protected $resolver;
-
- public function __construct(Tenancy $tenancy, RequestDataTenantResolver $resolver)
- {
- $this->tenancy = $tenancy;
- $this->resolver = $resolver;
+ public function __construct(
+ protected Tenancy $tenancy,
+ protected RequestDataTenantResolver $resolver,
+ ) {
}
- /**
- * Handle an incoming request.
- *
- * @param \Illuminate\Http\Request $request
- */
- public function handle($request, Closure $next)
+ /** @return \Illuminate\Http\Response|mixed */
+ public function handle(Request $request, Closure $next): mixed
{
if ($request->method() !== 'OPTIONS') {
return $this->initializeTenancy($request, $next, $this->getPayload($request));
diff --git a/src/Middleware/InitializeTenancyBySubdomain.php b/src/Middleware/InitializeTenancyBySubdomain.php
index 76389df7..1bf083f3 100644
--- a/src/Middleware/InitializeTenancyBySubdomain.php
+++ b/src/Middleware/InitializeTenancyBySubdomain.php
@@ -6,6 +6,7 @@ namespace Stancl\Tenancy\Middleware;
use Closure;
use Exception;
+use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Str;
use Stancl\Tenancy\Exceptions\NotASubdomainException;
@@ -21,15 +22,10 @@ class InitializeTenancyBySubdomain extends InitializeTenancyByDomain
*/
public static $subdomainIndex = 0;
- /** @var callable|null */
- public static $onFail;
+ public static ?Closure $onFail = null;
- /**
- * Handle an incoming request.
- *
- * @param \Illuminate\Http\Request $request
- */
- public function handle($request, Closure $next)
+ /** @return Response|mixed */
+ public function handle(Request $request, Closure $next): mixed
{
$subdomain = $this->makeSubdomain($request->getHost());
diff --git a/src/Middleware/PreventAccessFromCentralDomains.php b/src/Middleware/PreventAccessFromCentralDomains.php
index 14b2306d..40718730 100644
--- a/src/Middleware/PreventAccessFromCentralDomains.php
+++ b/src/Middleware/PreventAccessFromCentralDomains.php
@@ -11,12 +11,11 @@ class PreventAccessFromCentralDomains
{
/**
* Set this property if you want to customize the on-fail behavior.
- *
- * @var callable|null
*/
- public static $abortRequest;
+ public static ?Closure $abortRequest;
- public function handle(Request $request, Closure $next)
+ /** @return \Illuminate\Http\Response|mixed */
+ public function handle(Request $request, Closure $next): mixed
{
if (in_array($request->getHost(), config('tenancy.central_domains'))) {
$abortRequest = static::$abortRequest ?? function () {
diff --git a/src/Middleware/ScopeSessions.php b/src/Middleware/ScopeSessions.php
index 8abfcfe8..dc302ee5 100644
--- a/src/Middleware/ScopeSessions.php
+++ b/src/Middleware/ScopeSessions.php
@@ -10,9 +10,10 @@ use Stancl\Tenancy\Exceptions\TenancyNotInitializedException;
class ScopeSessions
{
- public static $tenantIdKey = '_tenant_id';
+ public static string $tenantIdKey = '_tenant_id';
- public function handle(Request $request, Closure $next)
+ /** @return \Illuminate\Http\Response|mixed */
+ public function handle(Request $request, Closure $next): mixed
{
if (! tenancy()->initialized) {
throw new TenancyNotInitializedException('Tenancy needs to be initialized before the session scoping middleware is executed');
diff --git a/src/Resolvers/Contracts/CachedTenantResolver.php b/src/Resolvers/Contracts/CachedTenantResolver.php
index f93d7bb5..b6a4b15c 100644
--- a/src/Resolvers/Contracts/CachedTenantResolver.php
+++ b/src/Resolvers/Contracts/CachedTenantResolver.php
@@ -11,23 +11,17 @@ use Stancl\Tenancy\Contracts\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 */
protected $cache;
public function __construct(Factory $cache)
{
- $this->cache = $cache->store(static::$cacheStore);
+ $this->cache = $cache->store(static::cacheStore());
}
public function resolve(mixed ...$args): Tenant
{
- if (! static::$shouldCache) {
+ if (! static::shouldCache()) {
return $this->resolveWithoutCache(...$args);
}
@@ -42,14 +36,14 @@ abstract class CachedTenantResolver implements TenantResolver
}
$tenant = $this->resolveWithoutCache(...$args);
- $this->cache->put($key, $tenant, static::$cacheTTL);
+ $this->cache->put($key, $tenant, static::cacheTTL());
return $tenant;
}
public function invalidateCache(Tenant $tenant): void
{
- if (! static::$shouldCache) {
+ if (! static::shouldCache()) {
return;
}
@@ -65,7 +59,7 @@ abstract class CachedTenantResolver implements TenantResolver
abstract public function resolveWithoutCache(mixed ...$args): Tenant;
- public function resolved(Tenant $tenant, ...$args): void
+ public function resolved(Tenant $tenant, mixed ...$args): void
{
}
@@ -75,4 +69,19 @@ abstract class CachedTenantResolver implements TenantResolver
* @return 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');
+ }
}
diff --git a/src/Resolvers/DomainTenantResolver.php b/src/Resolvers/DomainTenantResolver.php
index 926c02c0..cf88f579 100644
--- a/src/Resolvers/DomainTenantResolver.php
+++ b/src/Resolvers/DomainTenantResolver.php
@@ -14,17 +14,10 @@ class DomainTenantResolver extends Contracts\CachedTenantResolver
/** The model representing the domain that the tenant was identified on. */
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
{
$domain = $args[0];
- /** @var Tenant|null $tenant */
$tenant = config('tenancy.tenant_model')::query()
->whereHas('domains', fn (Builder $query) => $query->where('domain', $domain))
->with('domains')
@@ -39,7 +32,7 @@ class DomainTenantResolver extends Contracts\CachedTenantResolver
throw new TenantCouldNotBeIdentifiedOnDomainException($args[0]);
}
- public function resolved(Tenant $tenant, ...$args): void
+ public function resolved(Tenant $tenant, mixed ...$args): void
{
$this->setCurrentDomain($tenant, $args[0]);
}
diff --git a/src/Resolvers/PathTenantResolver.php b/src/Resolvers/PathTenantResolver.php
index 2ac2a59f..1359e9c1 100644
--- a/src/Resolvers/PathTenantResolver.php
+++ b/src/Resolvers/PathTenantResolver.php
@@ -10,21 +10,13 @@ use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedByPathException;
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
{
/** @var Route $route */
$route = $args[0];
- if ($id = $route->parameter(static::$tenantParameterName)) {
- $route->forgetParameter(static::$tenantParameterName);
+ if ($id = (string) $route->parameter(static::tenantParameterName())) {
+ $route->forgetParameter(static::tenantParameterName());
if ($tenant = tenancy()->find($id)) {
return $tenant;
@@ -37,7 +29,12 @@ class PathTenantResolver extends Contracts\CachedTenantResolver
public function getArgsForTenant(Tenant $tenant): array
{
return [
- [$tenant->id],
+ [$tenant->getTenantKey()],
];
}
+
+ public static function tenantParameterName(): string
+ {
+ return config('tenancy.identification.resolvers.' . static::class . '.tenant_parameter_name') ?? 'tenant';
+ }
}
diff --git a/src/Resolvers/RequestDataTenantResolver.php b/src/Resolvers/RequestDataTenantResolver.php
index 5ed65495..8a5bbc53 100644
--- a/src/Resolvers/RequestDataTenantResolver.php
+++ b/src/Resolvers/RequestDataTenantResolver.php
@@ -17,7 +17,7 @@ class RequestDataTenantResolver extends Contracts\CachedTenantResolver
public function resolveWithoutCache(mixed ...$args): Tenant
{
- $payload = $args[0];
+ $payload = (string) $args[0];
if ($payload && $tenant = tenancy()->find($payload)) {
return $tenant;
@@ -29,7 +29,7 @@ class RequestDataTenantResolver extends Contracts\CachedTenantResolver
public function getArgsForTenant(Tenant $tenant): array
{
return [
- [$tenant->id],
+ [$tenant->getTenantKey()],
];
}
}
diff --git a/src/Tenancy.php b/src/Tenancy.php
index 0a8d4542..271d36b9 100644
--- a/src/Tenancy.php
+++ b/src/Tenancy.php
@@ -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()) {
return;
}
@@ -99,19 +99,30 @@ class Tenancy
{
$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
{
- 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.
* Atomic, safely reverts to previous context.
*/
- public function central(Closure $callback)
+ public function central(Closure $callback): mixed
{
$previousTenant = $this->tenant;
@@ -132,7 +143,7 @@ class Tenancy
* Run a callback for multiple tenants.
* More performant than running $tenant->run() one by one.
*
- * @param Tenant[]|\Traversable|string[]|null $tenants
+ * @param array|array|\Traversable|string|int|null $tenants
*/
public function runForMultiple($tenants, Closure $callback): void
{
@@ -146,7 +157,7 @@ class Tenancy
$tenants = is_string($tenants) ? [$tenants] : $tenants;
// Use all tenants if $tenants is falsey
- $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;
@@ -155,6 +166,7 @@ class Tenancy
$tenant = $this->find($tenant);
}
+ /** @var Tenant $tenant */
$this->initialize($tenant);
$callback($tenant);
}
@@ -165,4 +177,41 @@ class Tenancy
$this->end();
}
}
+
+ /**
+ * Cached tenant resolvers used by the package.
+ *
+ * @return array>
+ */
+ 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>
+ */
+ public static function middleware(): array
+ {
+ return config('tenancy.identification.middleware', []);
+ }
+
+ /**
+ * Default tenant identification middleware used by the package.
+ *
+ * @return class-string
+ */
+ public static function defaultMiddleware(): string
+ {
+ return config('tenancy.identification.default_middleware', Middleware\InitializeTenancyByDomain::class);
+ }
}
diff --git a/src/TenancyServiceProvider.php b/src/TenancyServiceProvider.php
index b8eee487..7e12a857 100644
--- a/src/TenancyServiceProvider.php
+++ b/src/TenancyServiceProvider.php
@@ -86,6 +86,8 @@ class TenancyServiceProvider extends ServiceProvider
Commands\TenantList::class,
Commands\TenantDump::class,
Commands\MigrateFresh::class,
+ Commands\Down::class,
+ Commands\Up::class,
]);
$this->publishes([
@@ -118,7 +120,7 @@ class TenancyServiceProvider extends ServiceProvider
if ($event instanceof TenancyEvent) {
match (tenancy()->logMode()) {
LogMode::SILENT => tenancy()->logEvent($event),
- LogMode::INSTANT => dump($event), // todo0 perhaps still log
+ LogMode::INSTANT => dump($event), // todo1 perhaps still log
default => null,
};
}
diff --git a/src/UUIDGenerator.php b/src/UUIDGenerator.php
index 736a6924..a0974862 100644
--- a/src/UUIDGenerator.php
+++ b/src/UUIDGenerator.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Stancl\Tenancy;
+use Illuminate\Database\Eloquent\Model;
use Ramsey\Uuid\Uuid;
use Stancl\Tenancy\Contracts\UniqueIdentifierGenerator;
@@ -11,7 +12,7 @@ use Stancl\Tenancy\Contracts\UniqueIdentifierGenerator;
class UUIDGenerator implements UniqueIdentifierGenerator
{
- public static function generate($resource): string
+ public static function generate(Model $model): string
{
return Uuid::uuid4()->toString();
}
diff --git a/src/helpers.php b/src/helpers.php
index ac805aa5..c50da940 100644
--- a/src/helpers.php
+++ b/src/helpers.php
@@ -35,6 +35,7 @@ if (! function_exists('tenant')) {
if (! function_exists('tenant_asset')) {
// todo docblock
+ // todo add an option to generate paths respecting the ASSET_URL
function tenant_asset(string|null $asset): string
{
return route('stancl.tenancy.asset', ['path' => $asset]);
@@ -42,16 +43,42 @@ if (! function_exists('tenant_asset')) {
}
if (! function_exists('global_asset')) {
- function global_asset(string $asset) // todo types, also inside the globalUrl implementation
+ function global_asset(string $asset): string
{
return app('globalUrl')->asset($asset);
}
}
if (! function_exists('global_cache')) {
- function global_cache()
+ /**
+ * Get / set the specified cache value in the global cache store.
+ *
+ * If an array is passed, we'll assume you want to put to the cache.
+ *
+ * @param dynamic key|key,default|data,expiration|null
+ * @return mixed|\Illuminate\Cache\CacheManager
+ *
+ * @throws \InvalidArgumentException
+ */
+ function global_cache(): mixed
{
- return app('globalCache');
+ $arguments = func_get_args();
+
+ if (empty($arguments)) {
+ return app('globalCache');
+ }
+
+ if (is_string($arguments[0])) {
+ return app('globalCache')->get(...$arguments);
+ }
+
+ if (! is_array($arguments[0])) {
+ throw new InvalidArgumentException(
+ 'When setting a value in the cache, you must pass an array of key / value pairs.'
+ );
+ }
+
+ return app('globalCache')->put(key($arguments[0]), reset($arguments[0]), $arguments[1] ?? null);
}
}
diff --git a/tests/AutomaticModeTest.php b/tests/AutomaticModeTest.php
index ab484ccf..fc740fc1 100644
--- a/tests/AutomaticModeTest.php
+++ b/tests/AutomaticModeTest.php
@@ -107,12 +107,12 @@ function contextIsSwitchedWhenTenancyInitialized()
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());
}
- public function revert()
+ public function revert(): void
{
app()->instance('tenancy_ended', true);
}
diff --git a/tests/BatchTest.php b/tests/BatchTest.php
index a168deb2..629a4e61 100644
--- a/tests/BatchTest.php
+++ b/tests/BatchTest.php
@@ -30,13 +30,13 @@ test('batch repository is set to tenant connection and reverted', function () {
tenancy()->initialize($tenant);
expect(getBatchRepositoryConnectionName())->toBe('tenant');
-
+
tenancy()->initialize($tenant2);
expect(getBatchRepositoryConnectionName())->toBe('tenant');
tenancy()->end();
expect(getBatchRepositoryConnectionName())->toBe('central');
-})->skip(fn() => version_compare(app()->version(), '8.0', '<'), 'Job batches are only supported in Laravel 8+');
+});
function getBatchRepositoryConnectionName()
{
diff --git a/tests/BootstrapperTest.php b/tests/BootstrapperTest.php
index a610fbd2..ba4ea41a 100644
--- a/tests/BootstrapperTest.php
+++ b/tests/BootstrapperTest.php
@@ -332,10 +332,6 @@ function getDiskPrefix(string $disk): string
$disk = Storage::disk($disk);
$adapter = $disk->getAdapter();
- if (! Str::startsWith(app()->version(), '9.')) {
- return $adapter->getPathPrefix();
- }
-
$prefixer = (new ReflectionObject($adapter))->getProperty('prefixer');
$prefixer->setAccessible(true);
diff --git a/tests/CachedTenantResolverTest.php b/tests/CachedTenantResolverTest.php
index d71375be..fa624b04 100644
--- a/tests/CachedTenantResolverTest.php
+++ b/tests/CachedTenantResolverTest.php
@@ -6,9 +6,7 @@ use Illuminate\Support\Facades\DB;
use Stancl\Tenancy\Resolvers\DomainTenantResolver;
use Stancl\Tenancy\Tests\Etc\Tenant;
-afterEach(function () {
- DomainTenantResolver::$shouldCache = false;
-});
+// todo@v4 test this with other resolvers as well?
test('tenants can be resolved using the cached resolver', function () {
$tenant = Tenant::create();
@@ -27,14 +25,14 @@ test('the underlying resolver is not touched when using the cached resolver', fu
DB::enableQueryLog();
- DomainTenantResolver::$shouldCache = false;
+ config(['tenancy.identification.resolvers.' . DomainTenantResolver::class . '.cache' => false]);
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
DB::flushQueryLog();
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
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();
DB::flushQueryLog();
@@ -50,7 +48,7 @@ test('cache is invalidated when the tenant is updated', function () {
DB::enableQueryLog();
- DomainTenantResolver::$shouldCache = true;
+ config(['tenancy.identification.resolvers.' . DomainTenantResolver::class . '.cache' => true]);
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
DB::flushQueryLog();
@@ -74,7 +72,7 @@ test('cache is invalidated when a tenants domain is changed', function () {
DB::enableQueryLog();
- DomainTenantResolver::$shouldCache = true;
+ config(['tenancy.identification.resolvers.' . DomainTenantResolver::class . '.cache' => true]);
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
DB::flushQueryLog();
diff --git a/tests/CommandsTest.php b/tests/CommandsTest.php
index e3308236..ebd43e11 100644
--- a/tests/CommandsTest.php
+++ b/tests/CommandsTest.php
@@ -9,7 +9,6 @@ use Stancl\Tenancy\Tests\Etc\Tenant;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Artisan;
-use PHPUnit\Framework\ExceptionWrapper;
use Stancl\Tenancy\Events\TenancyEnded;
use Stancl\Tenancy\Jobs\CreateDatabase;
use Illuminate\Database\DatabaseManager;
@@ -217,8 +216,9 @@ test('run command with array of tenants works', function () {
Artisan::call('tenants:migrate-fresh');
pest()->artisan("tenants:run --tenants=$tenantId1 --tenants=$tenantId2 'foo foo --b=bar --c=xyz'")
- ->expectsOutput('Tenant: ' . $tenantId1)
- ->expectsOutput('Tenant: ' . $tenantId2);
+ ->expectsOutputToContain('Tenant: ' . $tenantId1)
+ ->expectsOutputToContain('Tenant: ' . $tenantId2)
+ ->assertExitCode(0);
});
test('link command works', function() {
diff --git a/tests/DomainTest.php b/tests/DomainTest.php
index 594270e1..6995da24 100644
--- a/tests/DomainTest.php
+++ b/tests/DomainTest.php
@@ -81,7 +81,7 @@ test('tenant can be identified by domain', function () {
test('onfail logic can be customized', function () {
InitializeTenancyByDomain::$onFail = function () {
- return 'foo';
+ return response('foo');
};
pest()
diff --git a/tests/Etc/Tenant.php b/tests/Etc/Tenant.php
index 20a96072..9b59dedb 100644
--- a/tests/Etc/Tenant.php
+++ b/tests/Etc/Tenant.php
@@ -7,9 +7,13 @@ namespace Stancl\Tenancy\Tests\Etc;
use Stancl\Tenancy\Database\Contracts\TenantWithDatabase;
use Stancl\Tenancy\Database\Concerns\HasDatabase;
use Stancl\Tenancy\Database\Concerns\HasDomains;
+use Stancl\Tenancy\Database\Concerns\MaintenanceMode;
use Stancl\Tenancy\Database\Models;
+/**
+ * @method static static create(array $attributes = [])
+ */
class Tenant extends Models\Tenant implements TenantWithDatabase
{
- use HasDatabase, HasDomains;
+ use HasDatabase, HasDomains, MaintenanceMode;
}
diff --git a/tests/GlobalCacheTest.php b/tests/GlobalCacheTest.php
index 8a13395c..ea38341b 100644
--- a/tests/GlobalCacheTest.php
+++ b/tests/GlobalCacheTest.php
@@ -50,3 +50,17 @@ test('global cache manager stores data in global cache', function () {
expect(cache('def'))->toBe('ghi');
});
+test('the global_cache helper supports the same syntax as the cache helper', function () {
+ $tenant = Tenant::create();
+ $tenant->enter();
+
+ expect(cache('foo'))->toBe(null); // tenant cache is empty
+
+ global_cache(['foo' => 'bar']);
+ expect(global_cache('foo'))->toBe('bar');
+
+ global_cache()->set('foo', 'baz');
+ expect(global_cache()->get('foo'))->toBe('baz');
+
+ expect(cache('foo'))->toBe(null); // tenant cache is not affected
+});
diff --git a/tests/MaintenanceModeTest.php b/tests/MaintenanceModeTest.php
index 770dc5f2..6e28d1ab 100644
--- a/tests/MaintenanceModeTest.php
+++ b/tests/MaintenanceModeTest.php
@@ -2,14 +2,14 @@
declare(strict_types=1);
+use Illuminate\Support\Facades\Artisan;
use Stancl\Tenancy\Database\Concerns\MaintenanceMode;
-use Symfony\Component\HttpKernel\Exception\HttpException;
use Illuminate\Support\Facades\Route;
use Stancl\Tenancy\Middleware\CheckTenantForMaintenanceMode;
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
use Stancl\Tenancy\Tests\Etc\Tenant;
-test('tenant can be in maintenance mode', function () {
+test('tenants can be in maintenance mode', function () {
Route::get('/foo', function () {
return 'bar';
})->middleware([InitializeTenancyByDomain::class, CheckTenantForMaintenanceMode::class]);
@@ -19,16 +19,40 @@ test('tenant can be in maintenance mode', function () {
'domain' => 'acme.localhost',
]);
- pest()->get('http://acme.localhost/foo')
- ->assertSuccessful();
-
- tenancy()->end(); // flush stored tenant instance
+ pest()->get('http://acme.localhost/foo')->assertStatus(200);
$tenant->putDownForMaintenance();
- pest()->expectException(HttpException::class);
- pest()->withoutExceptionHandling()
- ->get('http://acme.localhost/foo');
+ tenancy()->end(); // End tenancy before making a request
+ pest()->get('http://acme.localhost/foo')->assertStatus(503);
+
+ $tenant->bringUpFromMaintenance();
+
+ tenancy()->end(); // End tenancy before making a request
+ pest()->get('http://acme.localhost/foo')->assertStatus(200);
+});
+
+test('tenants can be put into maintenance mode using artisan commands', function() {
+ Route::get('/foo', function () {
+ return 'bar';
+ })->middleware([InitializeTenancyByDomain::class, CheckTenantForMaintenanceMode::class]);
+
+ $tenant = MaintenanceTenant::create();
+ $tenant->domains()->create([
+ 'domain' => 'acme.localhost',
+ ]);
+
+ pest()->get('http://acme.localhost/foo')->assertStatus(200);
+
+ Artisan::call('tenants:down');
+
+ tenancy()->end(); // End tenancy before making a request
+ pest()->get('http://acme.localhost/foo')->assertStatus(503);
+
+ Artisan::call('tenants:up');
+
+ tenancy()->end(); // End tenancy before making a request
+ pest()->get('http://acme.localhost/foo')->assertStatus(200);
});
class MaintenanceTenant extends Tenant
diff --git a/tests/PathIdentificationTest.php b/tests/PathIdentificationTest.php
index bfa8f8ad..32880c4f 100644
--- a/tests/PathIdentificationTest.php
+++ b/tests/PathIdentificationTest.php
@@ -10,8 +10,6 @@ use Stancl\Tenancy\Resolvers\PathTenantResolver;
use Stancl\Tenancy\Tests\Etc\Tenant;
beforeEach(function () {
- PathTenantResolver::$tenantParameterName = 'tenant';
-
Route::group([
'prefix' => '/{tenant}',
'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 () {
Tenant::create([
'id' => 'acme',
@@ -71,7 +64,7 @@ test('exception is thrown when tenant cannot be identified by path', function ()
test('onfail logic can be customized', function () {
InitializeTenancyByPath::$onFail = function () {
- return 'foo';
+ return response('foo');
};
pest()
@@ -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 () {
- PathTenantResolver::$tenantParameterName = 'team';
+ config(['tenancy.identification.resolvers.' . PathTenantResolver::class . '.tenant_parameter_name' => 'team']);
Route::group([
'prefix' => '/{team}',
diff --git a/tests/RequestDataIdentificationTest.php b/tests/RequestDataIdentificationTest.php
index 81bdda53..e5a05f65 100644
--- a/tests/RequestDataIdentificationTest.php
+++ b/tests/RequestDataIdentificationTest.php
@@ -37,7 +37,6 @@ test('header identification works', function () {
});
test('query parameter identification works', function () {
- InitializeTenancyByRequestData::$header = null;
InitializeTenancyByRequestData::$queryParameter = 'tenant';
$tenant = Tenant::create();
diff --git a/tests/SingleDatabaseTenancyTest.php b/tests/SingleDatabaseTenancyTest.php
index 8914a6d7..ec0a0edf 100644
--- a/tests/SingleDatabaseTenancyTest.php
+++ b/tests/SingleDatabaseTenancyTest.php
@@ -13,8 +13,6 @@ use Stancl\Tenancy\Database\Concerns\HasScopedValidationRules;
use Stancl\Tenancy\Tests\Etc\Tenant as TestTenant;
beforeEach(function () {
- BelongsToTenant::$tenantIdColumn = 'tenant_id';
-
Schema::create('posts', function (Blueprint $table) {
$table->increments('id');
$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 () {
- BelongsToTenant::$tenantIdColumn = 'team_id';
+ config(['tenancy.single_db.tenant_id_column' => 'team_id']);
Schema::drop('comments');
Schema::drop('posts');
diff --git a/tests/SubdomainTest.php b/tests/SubdomainTest.php
index 00096d8c..0ff52bc0 100644
--- a/tests/SubdomainTest.php
+++ b/tests/SubdomainTest.php
@@ -44,7 +44,7 @@ test('tenant can be identified by subdomain', function () {
test('onfail logic can be customized', function () {
InitializeTenancyBySubdomain::$onFail = function () {
- return 'foo';
+ return response('foo');
};
pest()
diff --git a/tests/TenantAssetTest.php b/tests/TenantAssetTest.php
index d43b7989..a1cd0f5b 100644
--- a/tests/TenantAssetTest.php
+++ b/tests/TenantAssetTest.php
@@ -6,10 +6,8 @@ use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Storage;
use Stancl\Tenancy\Bootstrappers\FilesystemTenancyBootstrapper;
-use Stancl\Tenancy\Controllers\TenantAssetsController;
use Stancl\Tenancy\Events\TenancyInitialized;
use Stancl\Tenancy\Listeners\BootstrapTenancy;
-use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData;
use Stancl\Tenancy\Tests\Etc\Tenant;
@@ -21,13 +19,8 @@ beforeEach(function () {
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 () {
- TenantAssetsController::$tenancyMiddleware = InitializeTenancyByRequestData::class;
+ config(['tenancy.identification.default_middleware' => InitializeTenancyByRequestData::class]);
$tenant = Tenant::create();
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 () {
- TenantAssetsController::$tenancyMiddleware = InitializeTenancyByRequestData::class;
+ config(['tenancy.identification.default_middleware' => InitializeTenancyByRequestData::class]);
$tenant = Tenant::create();
diff --git a/tests/TenantDatabaseManagerTest.php b/tests/TenantDatabaseManagerTest.php
index d6a5b369..b16c06b6 100644
--- a/tests/TenantDatabaseManagerTest.php
+++ b/tests/TenantDatabaseManagerTest.php
@@ -154,9 +154,7 @@ test('schema manager uses schema to separate tenant dbs', function () {
]);
tenancy()->initialize($tenant);
- $schemaConfig = version_compare(app()->version(), '9.0', '>=') ?
- config('database.connections.' . config('database.default') . '.search_path') :
- config('database.connections.' . config('database.default') . '.schema');
+ $schemaConfig = config('database.connections.' . config('database.default') . '.search_path');
expect($schemaConfig)->toBe($tenant->database()->getName());
expect(config(['database.connections.pgsql.database']))->toBe($originalDatabaseName);
diff --git a/tests/TestCase.php b/tests/TestCase.php
index f7f8b9ad..1c0ceb83 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -103,7 +103,7 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
'--realpath' => 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' => [
'driver' => 'sync',
'central' => true,