From 435d8528a75acdbdc2994962bc47e90f4288aed1 Mon Sep 17 00:00:00 2001
From: Stefan Ninic
Date: Sat, 25 Dec 2021 22:10:34 +0100
Subject: [PATCH 01/30] Fixed array to string conversion (#718)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Fixed array to string conversion
Previous code would give this warning before actually showing exception message
`PHP Warning: Array to string conversion in .../vendor/stancl/tenancy/src/CacheManager.php on line 24`
* Update variable & syntax
Co-authored-by: Samuel Štancl
---
src/CacheManager.php | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/CacheManager.php b/src/CacheManager.php
index f7190842..88428353 100644
--- a/src/CacheManager.php
+++ b/src/CacheManager.php
@@ -20,8 +20,10 @@ class CacheManager extends BaseCacheManager
$tags = [config('tenancy.cache.tag_base') . tenant()->getTenantKey()];
if ($method === 'tags') {
- if (count($parameters) !== 1) {
- throw new \Exception("Method tags() takes exactly 1 argument. {count($parameters)} passed.");
+ $count = count($parameters);
+
+ if ($count !== 1) {
+ throw new \Exception("Method tags() takes exactly 1 argument. $count passed.");
}
$names = $parameters[0];
From 73a4a3018cadca2ba0fb5f2130fca1718a2b3670 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Samuel=20=C5=A0tancl?=
Date: Fri, 31 Dec 2021 18:10:03 +0100
Subject: [PATCH 02/30] Improve queue tenancy
---
docker-compose.yml | 2 +-
.../DatabaseTenancyBootstrapper.php | 1 +
.../QueueTenancyBootstrapper.php | 80 +++++++--
src/Database/DatabaseManager.php | 23 ++-
.../TenantCouldNotBeIdentifiedById.php | 2 +-
src/Tenancy.php | 4 +-
tests/QueueTest.php | 152 ++++++++++++++++--
tests/TenantDatabaseManagerTest.php | 51 ++++++
8 files changed, 279 insertions(+), 36 deletions(-)
diff --git a/docker-compose.yml b/docker-compose.yml
index 30d87dfd..e8e8d418 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -4,7 +4,7 @@ services:
build:
context: .
args:
- PHP_VERSION: ${PHP_VERSION}
+ PHP_VERSION: ${PHP_VERSION:-8.1}
depends_on:
mysql:
condition: service_healthy
diff --git a/src/Bootstrappers/DatabaseTenancyBootstrapper.php b/src/Bootstrappers/DatabaseTenancyBootstrapper.php
index a107fc0d..59ee0aec 100644
--- a/src/Bootstrappers/DatabaseTenancyBootstrapper.php
+++ b/src/Bootstrappers/DatabaseTenancyBootstrapper.php
@@ -6,6 +6,7 @@ namespace Stancl\Tenancy\Bootstrappers;
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
use Stancl\Tenancy\Contracts\Tenant;
+use Stancl\Tenancy\Contracts\TenantWithDatabase;
use Stancl\Tenancy\Database\DatabaseManager;
use Stancl\Tenancy\Exceptions\TenantDatabaseDoesNotExistException;
diff --git a/src/Bootstrappers/QueueTenancyBootstrapper.php b/src/Bootstrappers/QueueTenancyBootstrapper.php
index 6fefaad2..5706963e 100644
--- a/src/Bootstrappers/QueueTenancyBootstrapper.php
+++ b/src/Bootstrappers/QueueTenancyBootstrapper.php
@@ -7,7 +7,10 @@ namespace Stancl\Tenancy\Bootstrappers;
use Illuminate\Config\Repository;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Contracts\Foundation\Application;
+use Illuminate\Queue\Events\JobFailed;
+use Illuminate\Queue\Events\JobProcessed;
use Illuminate\Queue\Events\JobProcessing;
+use Illuminate\Queue\Events\JobRetryRequested;
use Illuminate\Queue\QueueManager;
use Illuminate\Support\Testing\Fakes\QueueFake;
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
@@ -28,7 +31,7 @@ class QueueTenancyBootstrapper implements TenancyBootstrapper
*/
public static function __constructStatic(Application $app)
{
- static::setUpJobListener($app->make(Dispatcher::class));
+ static::setUpJobListener($app->make(Dispatcher::class), $app->runningUnitTests());
}
public function __construct(Repository $config, QueueManager $queue)
@@ -39,25 +42,70 @@ class QueueTenancyBootstrapper implements TenancyBootstrapper
$this->setUpPayloadGenerator();
}
- protected static function setUpJobListener($dispatcher)
+ protected static function setUpJobListener($dispatcher, $runningTests)
{
- $dispatcher->listen(JobProcessing::class, function ($event) {
- $tenantId = $event->job->payload()['tenant_id'] ?? null;
+ $previousTenant = null;
- // The job is not tenant-aware
- if (! $tenantId) {
- return;
- }
+ $dispatcher->listen(JobProcessing::class, function ($event) use (&$previousTenant) {
+ $previousTenant = tenant();
- // Tenancy is already initialized for the tenant (e.g. dispatchNow was used)
- if (tenancy()->initialized && tenant()->getTenantKey() === $tenantId) {
- return;
- }
-
- // Tenancy was either not initialized, or initialized for a different tenant.
- // Therefore, we initialize it for the correct tenant.
- tenancy()->initialize(tenancy()->find($tenantId));
+ static::initializeTenancyForQueue($event->job->payload()['tenant_id'] ?? null);
});
+
+ $dispatcher->listen(JobRetryRequested::class, function ($event) use (&$previousTenant) {
+ $previousTenant = tenant();
+
+ 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) {
+ if ($runningTests) {
+ static::revertToPreviousState($event, $previousTenant);
+ }
+ };
+
+ $dispatcher->listen(JobProcessed::class, $revertToPreviousState); // artisan('queue:work') which succeeds
+ $dispatcher->listen(JobFailed::class, $revertToPreviousState); // artisan('queue:work') which fails
+ }
+
+ protected static function initializeTenancyForQueue($tenantId)
+ {
+ // The job is not tenant-aware
+ if (! $tenantId) {
+ return;
+ }
+
+ if (tenancy()->initialized) {
+ if (tenant()->getTenantKey() === $tenantId) {
+ // Tenancy is already initialized for the tenant (e.g. dispatchNow was used)
+ return;
+ }
+ }
+
+ // Tenancy was either not initialized, or initialized for a different tenant.
+ // Therefore, we initialize it for the correct tenant.
+ tenancy()->initialize(tenancy()->find($tenantId));
+ }
+
+ protected static function revertToPreviousState($event, &$previousTenant)
+ {
+ $tenantId = $event->job->payload()['tenant_id'] ?? null;
+
+ // The job was not tenant-aware
+ if (! $tenantId) {
+ return;
+ }
+
+ // Revert back to the previous tenant
+ if (tenant() && $previousTenant && $previousTenant->isNot(tenant())) {
+ tenancy()->initialize($previousTenant);
+ }
+
+ // End tenancy
+ if (tenant() && (! $previousTenant)) {
+ tenancy()->end();
+ }
}
protected function setUpPayloadGenerator()
diff --git a/src/Database/DatabaseManager.php b/src/Database/DatabaseManager.php
index dd30f443..e85fd659 100644
--- a/src/Database/DatabaseManager.php
+++ b/src/Database/DatabaseManager.php
@@ -38,7 +38,7 @@ class DatabaseManager
*/
public function connectToTenant(TenantWithDatabase $tenant)
{
- $this->database->purge('tenant');
+ $this->purgeTenantConnection();
$this->createTenantConnection($tenant);
$this->setDefaultConnection('tenant');
}
@@ -48,10 +48,7 @@ class DatabaseManager
*/
public function reconnectToCentral()
{
- if (tenancy()->initialized) {
- $this->database->purge('tenant');
- }
-
+ $this->purgeTenantConnection();
$this->setDefaultConnection($this->config->get('tenancy.database.central_connection'));
}
@@ -60,7 +57,7 @@ class DatabaseManager
*/
public function setDefaultConnection(string $connection)
{
- $this->app['config']['database.default'] = $connection;
+ $this->config['database.default'] = $connection;
$this->database->setDefaultConnection($connection);
}
@@ -69,7 +66,19 @@ class DatabaseManager
*/
public function createTenantConnection(TenantWithDatabase $tenant)
{
- $this->app['config']['database.connections.tenant'] = $tenant->database()->connection();
+ $this->config['database.connections.tenant'] = $tenant->database()->connection();
+ }
+
+ /**
+ * Purge the tenant database connection.
+ */
+ public function purgeTenantConnection()
+ {
+ if (array_key_exists('tenant', $this->database->getConnections())) {
+ $this->database->purge('tenant');
+ }
+
+ unset($this->config['database.connections.tenant']);
}
/**
diff --git a/src/Exceptions/TenantCouldNotBeIdentifiedById.php b/src/Exceptions/TenantCouldNotBeIdentifiedById.php
index 8fa103ea..5c2e562c 100644
--- a/src/Exceptions/TenantCouldNotBeIdentifiedById.php
+++ b/src/Exceptions/TenantCouldNotBeIdentifiedById.php
@@ -9,7 +9,7 @@ use Facade\IgnitionContracts\ProvidesSolution;
use Facade\IgnitionContracts\Solution;
use Stancl\Tenancy\Contracts\TenantCouldNotBeIdentifiedException;
-// todo: in v3 this should be suffixed with Exception
+// todo: in v4 this should be suffixed with Exception
class TenantCouldNotBeIdentifiedById extends TenantCouldNotBeIdentifiedException implements ProvidesSolution
{
public function __construct($tenant_id)
diff --git a/src/Tenancy.php b/src/Tenancy.php
index 864c00f0..30f138e3 100644
--- a/src/Tenancy.php
+++ b/src/Tenancy.php
@@ -66,10 +66,10 @@ class Tenancy
return;
}
- $this->initialized = false;
-
event(new Events\TenancyEnded($this));
+ $this->initialized = false;
+
$this->tenant = null;
}
diff --git a/tests/QueueTest.php b/tests/QueueTest.php
index 41d71320..75c727ce 100644
--- a/tests/QueueTest.php
+++ b/tests/QueueTest.php
@@ -4,18 +4,30 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Tests;
+use Exception;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
+use Illuminate\Database\Schema\Blueprint;
use Illuminate\Foundation\Bus\Dispatchable;
+use Illuminate\Queue\Events\JobProcessed;
use Illuminate\Queue\Events\JobProcessing;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
+use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Event;
+use Illuminate\Support\Facades\Schema;
use Spatie\Valuestore\Valuestore;
+use Stancl\JobPipeline\JobPipeline;
+use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
use Stancl\Tenancy\Bootstrappers\QueueTenancyBootstrapper;
+use Stancl\Tenancy\Events\TenancyEnded;
use Stancl\Tenancy\Events\TenancyInitialized;
+use Stancl\Tenancy\Events\TenantCreated;
+use Stancl\Tenancy\Jobs\CreateDatabase;
use Stancl\Tenancy\Listeners\BootstrapTenancy;
+use Stancl\Tenancy\Listeners\RevertToCentralContext;
use Stancl\Tenancy\Tests\Etc\Tenant;
+use Stancl\Tenancy\Tests\Etc\User;
class QueueTest extends TestCase
{
@@ -31,15 +43,49 @@ class QueueTest extends TestCase
config([
'tenancy.bootstrappers' => [
QueueTenancyBootstrapper::class,
+ DatabaseTenancyBootstrapper::class,
],
'queue.default' => 'redis',
]);
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
+ Event::listen(TenancyEnded::class, RevertToCentralContext::class);
$this->valuestore = Valuestore::make(__DIR__ . '/Etc/tmp/queuetest.json')->flush();
}
+ protected function withFailedJobs()
+ {
+ Schema::connection('central')->create('failed_jobs', function (Blueprint $table) {
+ $table->id();
+ $table->string('uuid')->unique();
+ $table->text('connection');
+ $table->text('queue');
+ $table->longText('payload');
+ $table->longText('exception');
+ $table->timestamp('failed_at')->useCurrent();
+ });
+ }
+
+ protected function withUsers()
+ {
+ Schema::create('users', function (Blueprint $table) {
+ $table->increments('id');
+ $table->string('name');
+ $table->string('email')->unique();
+ $table->string('password');
+ $table->rememberToken();
+ $table->timestamps();
+ });
+ }
+
+ protected function withTenantDatabases()
+ {
+ Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
+ return $event->tenant;
+ })->toListener());
+ }
+
/** @test */
public function tenant_id_is_passed_to_tenant_queues()
{
@@ -49,7 +95,7 @@ class QueueTest extends TestCase
tenancy()->initialize($tenant);
- Event::fake([JobProcessing::class]);
+ Event::fake([JobProcessing::class, JobProcessed::class]);
dispatch(new TestJob($this->valuestore));
@@ -79,21 +125,91 @@ class QueueTest extends TestCase
});
}
- /** @test */
- public function tenancy_is_initialized_inside_queues()
+ /**
+ * @test
+ *
+ * @testWith [true]
+ * [false]
+ */
+ public function tenancy_is_initialized_inside_queues(bool $shouldEndTenancy)
{
- $tenant = Tenant::create([
- 'id' => 'acme',
- ]);
+ $this->withTenantDatabases();
+ $this->withFailedJobs();
+
+ $tenant = Tenant::create();
tenancy()->initialize($tenant);
- dispatch(new TestJob($this->valuestore));
+ $this->withUsers();
+
+ $user = User::create(['name' => 'Foo', 'email' => 'foo@bar.com', 'password' => 'secret']);
+
+ $this->valuestore->put('userName', 'Bar');
+
+ dispatch(new TestJob($this->valuestore, $user));
$this->assertFalse($this->valuestore->has('tenant_id'));
+
+ if ($shouldEndTenancy) {
+ tenancy()->end();
+ }
+
$this->artisan('queue:work --once');
- $this->assertSame('The current tenant id is: acme', $this->valuestore->get('tenant_id'));
+ $this->assertSame(0, DB::connection('central')->table('failed_jobs')->count());
+
+ $this->assertSame('The current tenant id is: ' . $tenant->id, $this->valuestore->get('tenant_id'));
+
+ $tenant->run(function () use ($user) {
+ $this->assertSame('Bar', $user->fresh()->name);
+ });
+ }
+
+ /**
+ * @test
+ *
+ * @testWith [true]
+ * [false]
+ */
+ public function tenancy_is_initialized_when_retrying_jobs(bool $shouldEndTenancy)
+ {
+ $this->withFailedJobs();
+ $this->withTenantDatabases();
+
+ $tenant = Tenant::create();
+
+ tenancy()->initialize($tenant);
+
+ $this->withUsers();
+
+ $user = User::create(['name' => 'Foo', 'email' => 'foo@bar.com', 'password' => 'secret']);
+
+ $this->valuestore->put('userName', 'Bar');
+ $this->valuestore->put('shouldFail', true);
+
+ dispatch(new TestJob($this->valuestore, $user));
+
+ $this->assertFalse($this->valuestore->has('tenant_id'));
+
+ if ($shouldEndTenancy) {
+ tenancy()->end();
+ }
+
+ $this->artisan('queue:work --once');
+
+ $this->assertSame(1, DB::connection('central')->table('failed_jobs')->count());
+ $this->assertNull($this->valuestore->get('tenant_id')); // job failed
+
+ $this->artisan('queue:retry all');
+ $this->artisan('queue:work --once');
+
+ $this->assertSame(0, DB::connection('central')->table('failed_jobs')->count());
+
+ $this->assertSame('The current tenant id is: ' . $tenant->id, $this->valuestore->get('tenant_id')); // job succeeded
+
+ $tenant->run(function () use ($user) {
+ $this->assertSame('Bar', $user->fresh()->name);
+ });
}
/** @test */
@@ -127,13 +243,31 @@ class TestJob implements ShouldQueue
/** @var Valuestore */
protected $valuestore;
- public function __construct(Valuestore $valuestore)
+ /** @var User|null */
+ protected $user;
+
+ public function __construct(Valuestore $valuestore, User $user = null)
{
$this->valuestore = $valuestore;
+ $this->user = $user;
}
public function handle()
{
+ if ($this->valuestore->get('shouldFail')) {
+ $this->valuestore->put('shouldFail', false);
+
+ throw new Exception('failing');
+ }
+
+ if ($this->user) {
+ assert($this->user->getConnectionName() === 'tenant');
+ }
+
$this->valuestore->put('tenant_id', 'The current tenant id is: ' . tenant('id'));
+
+ if ($userName = $this->valuestore->get('userName')) {
+ $this->user->update(['name' => $userName]);
+ }
}
}
diff --git a/tests/TenantDatabaseManagerTest.php b/tests/TenantDatabaseManagerTest.php
index ead2bba8..f64770b1 100644
--- a/tests/TenantDatabaseManagerTest.php
+++ b/tests/TenantDatabaseManagerTest.php
@@ -4,17 +4,21 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Tests;
+use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Event;
+use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
use PDO;
use Stancl\JobPipeline\JobPipeline;
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
use Stancl\Tenancy\Database\DatabaseManager;
+use Stancl\Tenancy\Events\TenancyEnded;
use Stancl\Tenancy\Events\TenancyInitialized;
use Stancl\Tenancy\Events\TenantCreated;
use Stancl\Tenancy\Exceptions\TenantDatabaseAlreadyExistsException;
use Stancl\Tenancy\Jobs\CreateDatabase;
use Stancl\Tenancy\Listeners\BootstrapTenancy;
+use Stancl\Tenancy\Listeners\RevertToCentralContext;
use Stancl\Tenancy\TenantDatabaseManagers\MySQLDatabaseManager;
use Stancl\Tenancy\TenantDatabaseManagers\PermissionControlledMySQLDatabaseManager;
use Stancl\Tenancy\TenantDatabaseManagers\PostgreSQLDatabaseManager;
@@ -102,6 +106,52 @@ class TenantDatabaseManagerTest extends TestCase
];
}
+ /** @test */
+ public function the_tenant_connection_is_fully_removed()
+ {
+ config([
+ 'tenancy.boostrappers' => [
+ DatabaseTenancyBootstrapper::class,
+ ],
+ ]);
+
+ Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
+ return $event->tenant;
+ })->toListener());
+
+ Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
+ Event::listen(TenancyEnded::class, RevertToCentralContext::class);
+
+ $tenant = Tenant::create();
+
+ $this->assertSame(['central'], array_keys(app('db')->getConnections()));
+ $this->assertArrayNotHasKey('tenant', config('database.connections'));
+
+ tenancy()->initialize($tenant);
+
+ $this->createUsersTable();
+
+ $this->assertSame(['central', 'tenant'], array_keys(app('db')->getConnections()));
+ $this->assertArrayHasKey('tenant', config('database.connections'));
+
+ tenancy()->end();
+
+ $this->assertSame(['central'], array_keys(app('db')->getConnections()));
+ $this->assertNull(config('database.connections.tenant'));
+ }
+
+ protected function createUsersTable()
+ {
+ Schema::create('users', function (Blueprint $table) {
+ $table->increments('id');
+ $table->string('name');
+ $table->string('email')->unique();
+ $table->string('password');
+ $table->rememberToken();
+ $table->timestamps();
+ });
+ }
+
/** @test */
public function db_name_is_prefixed_with_db_path_when_sqlite_is_used()
{
@@ -217,5 +267,6 @@ class TenantDatabaseManagerTest extends TestCase
/** @test */
public function path_used_by_sqlite_manager_can_be_customized()
{
+ $this->markTestIncomplete();
}
}
From 49ef28da059bb0423f9ccd8f9f88b43cd58cfb34 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Samuel=20=C5=A0tancl?=
Date: Fri, 31 Dec 2021 18:19:53 +0100
Subject: [PATCH 03/30] 6.x support
---
tests/QueueTest.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/QueueTest.php b/tests/QueueTest.php
index 75c727ce..9758c9c8 100644
--- a/tests/QueueTest.php
+++ b/tests/QueueTest.php
@@ -57,7 +57,7 @@ class QueueTest extends TestCase
protected function withFailedJobs()
{
Schema::connection('central')->create('failed_jobs', function (Blueprint $table) {
- $table->id();
+ $table->increments('id');
$table->string('uuid')->unique();
$table->text('connection');
$table->text('queue');
From a83568ded280ebca364ad898f28db2ec047f87a6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Samuel=20=C5=A0tancl?=
Date: Fri, 31 Dec 2021 18:28:37 +0100
Subject: [PATCH 04/30] Only use JobRetryRequested in Laravel 8
---
.../QueueTenancyBootstrapper.php | 20 ++++++----
tests/Etc/tmp/queuetest.json | 2 +-
tests/QueueTest.php | 37 +++++++++++--------
3 files changed, 34 insertions(+), 25 deletions(-)
diff --git a/src/Bootstrappers/QueueTenancyBootstrapper.php b/src/Bootstrappers/QueueTenancyBootstrapper.php
index 5706963e..c94d6749 100644
--- a/src/Bootstrappers/QueueTenancyBootstrapper.php
+++ b/src/Bootstrappers/QueueTenancyBootstrapper.php
@@ -4,17 +4,18 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Bootstrappers;
+use Illuminate\Support\Str;
use Illuminate\Config\Repository;
-use Illuminate\Contracts\Events\Dispatcher;
-use Illuminate\Contracts\Foundation\Application;
+use Illuminate\Queue\QueueManager;
+use Stancl\Tenancy\Contracts\Tenant;
use Illuminate\Queue\Events\JobFailed;
use Illuminate\Queue\Events\JobProcessed;
use Illuminate\Queue\Events\JobProcessing;
+use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Queue\Events\JobRetryRequested;
-use Illuminate\Queue\QueueManager;
use Illuminate\Support\Testing\Fakes\QueueFake;
+use Illuminate\Contracts\Foundation\Application;
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
-use Stancl\Tenancy\Contracts\Tenant;
class QueueTenancyBootstrapper implements TenancyBootstrapper
{
@@ -52,11 +53,14 @@ class QueueTenancyBootstrapper implements TenancyBootstrapper
static::initializeTenancyForQueue($event->job->payload()['tenant_id'] ?? null);
});
- $dispatcher->listen(JobRetryRequested::class, function ($event) use (&$previousTenant) {
- $previousTenant = tenant();
+ if (Str::startsWith(app()->version(), '8')) {
+ // queue:retry tenancy is only supported in Laravel 8
+ $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) {
diff --git a/tests/Etc/tmp/queuetest.json b/tests/Etc/tmp/queuetest.json
index 00cf7c37..dc158c6b 100644
--- a/tests/Etc/tmp/queuetest.json
+++ b/tests/Etc/tmp/queuetest.json
@@ -1 +1 @@
-{"tenant_id":"The current tenant id is: acme"}
\ No newline at end of file
+{"userName":"Bar","shouldFail":false,"tenant_id":"The current tenant id is: a7f73c10-9879-40ae-b7b0-1ded7c1f7b1b"}
\ No newline at end of file
diff --git a/tests/QueueTest.php b/tests/QueueTest.php
index 9758c9c8..158ad56b 100644
--- a/tests/QueueTest.php
+++ b/tests/QueueTest.php
@@ -5,29 +5,30 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Tests;
use Exception;
+use Illuminate\Support\Str;
use Illuminate\Bus\Queueable;
-use Illuminate\Contracts\Queue\ShouldQueue;
+use Spatie\Valuestore\Valuestore;
+use Illuminate\Support\Facades\DB;
+use Stancl\Tenancy\Tests\Etc\User;
+use Stancl\JobPipeline\JobPipeline;
+use Stancl\Tenancy\Tests\Etc\Tenant;
+use Illuminate\Support\Facades\Event;
+use Illuminate\Queue\SerializesModels;
+use Illuminate\Support\Facades\Schema;
+use Stancl\Tenancy\Events\TenancyEnded;
+use Stancl\Tenancy\Jobs\CreateDatabase;
+use Illuminate\Queue\InteractsWithQueue;
+use Stancl\Tenancy\Events\TenantCreated;
use Illuminate\Database\Schema\Blueprint;
-use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\Events\JobProcessed;
use Illuminate\Queue\Events\JobProcessing;
-use Illuminate\Queue\InteractsWithQueue;
-use Illuminate\Queue\SerializesModels;
-use Illuminate\Support\Facades\DB;
-use Illuminate\Support\Facades\Event;
-use Illuminate\Support\Facades\Schema;
-use Spatie\Valuestore\Valuestore;
-use Stancl\JobPipeline\JobPipeline;
-use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
-use Stancl\Tenancy\Bootstrappers\QueueTenancyBootstrapper;
-use Stancl\Tenancy\Events\TenancyEnded;
+use Illuminate\Contracts\Queue\ShouldQueue;
+use Illuminate\Foundation\Bus\Dispatchable;
use Stancl\Tenancy\Events\TenancyInitialized;
-use Stancl\Tenancy\Events\TenantCreated;
-use Stancl\Tenancy\Jobs\CreateDatabase;
use Stancl\Tenancy\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Listeners\RevertToCentralContext;
-use Stancl\Tenancy\Tests\Etc\Tenant;
-use Stancl\Tenancy\Tests\Etc\User;
+use Stancl\Tenancy\Bootstrappers\QueueTenancyBootstrapper;
+use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
class QueueTest extends TestCase
{
@@ -173,6 +174,10 @@ class QueueTest extends TestCase
*/
public function tenancy_is_initialized_when_retrying_jobs(bool $shouldEndTenancy)
{
+ if (! Str::startsWith(app()->version(), '8')) {
+ $this->markTestSkipped('queue:retry tenancy is only supported in Laravel 8');
+ }
+
$this->withFailedJobs();
$this->withTenantDatabases();
From e442bdb64419038758d4fd6e10946735050b5a7d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Samuel=20=C5=A0tancl?=
Date: Fri, 31 Dec 2021 18:29:05 +0100
Subject: [PATCH 05/30] Only use JobRetryRequested in Laravel 8
---
src/Bootstrappers/QueueTenancyBootstrapper.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Bootstrappers/QueueTenancyBootstrapper.php b/src/Bootstrappers/QueueTenancyBootstrapper.php
index c94d6749..d2ce52d0 100644
--- a/src/Bootstrappers/QueueTenancyBootstrapper.php
+++ b/src/Bootstrappers/QueueTenancyBootstrapper.php
@@ -54,7 +54,7 @@ class QueueTenancyBootstrapper implements TenancyBootstrapper
});
if (Str::startsWith(app()->version(), '8')) {
- // queue:retry tenancy is only supported in Laravel 8
+ // JobRetryRequested only exists since Laravel 8
$dispatcher->listen(JobRetryRequested::class, function ($event) use (&$previousTenant) {
$previousTenant = tenant();
From 96d9ad13d821b1619062e930c2bce54642b1ffd3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Samuel=20=C5=A0tancl?=
Date: Thu, 6 Jan 2022 16:57:01 +0100
Subject: [PATCH 06/30] Add a note about 'tenant' connection being reserved
(fixes #774)
---
assets/config.php | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/assets/config.php b/assets/config.php
index 029591ad..85592d14 100644
--- a/assets/config.php
+++ b/assets/config.php
@@ -42,7 +42,8 @@ return [
'central_connection' => env('DB_CONNECTION', 'central'),
/**
- * Connection used as a "template" for the tenant database connection.
+ * Connection used as a "template" for the dynamically created tenant database connection.
+ * Note: don't name your template connection tenant. That name is reserved by package.
*/
'template_tenant_connection' => null,
From f08e33afd80d14d99eb0595261cb100f981d4b52 Mon Sep 17 00:00:00 2001
From: Jori Stein <44996807+stein-j@users.noreply.github.com>
Date: Thu, 6 Jan 2022 21:35:56 +0100
Subject: [PATCH 07/30] Remove redondant initialization (#775)
---
src/Commands/Run.php | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/Commands/Run.php b/src/Commands/Run.php
index 4216d1c6..aa518d7a 100644
--- a/src/Commands/Run.php
+++ b/src/Commands/Run.php
@@ -34,7 +34,6 @@ class Run extends Command
{
tenancy()->runForMultiple($this->option('tenants'), function ($tenant) {
$this->line("Tenant: {$tenant->getTenantKey()}");
- tenancy()->initialize($tenant);
$callback = function ($prefix = '') {
return function ($arguments, $argument) use ($prefix) {
From 9c79267e2444b5df4c32123f62387b71953f0a26 Mon Sep 17 00:00:00 2001
From: Erik Gaal
Date: Mon, 14 Feb 2022 14:31:01 +0100
Subject: [PATCH 08/30] Fix .env loading in development (#799)
* Upgrade vlucas/phpdotenv to ^5.0
`Dotenv::create($paths)` was the syntax for releases before v4
* Remove vlucas/phpdotenv dependency and make it work with newer versions.
---
composer.json | 1 -
tests/TestCase.php | 6 +++++-
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/composer.json b/composer.json
index b10f2d16..bf66e1f2 100644
--- a/composer.json
+++ b/composer.json
@@ -18,7 +18,6 @@
"stancl/virtualcolumn": "^1.0"
},
"require-dev": {
- "vlucas/phpdotenv": "^3.3|^4.0|^5.0",
"laravel/framework": "^6.0|^7.0|^8.0",
"orchestra/testbench-browser-kit": "^4.0|^5.0|^6.0",
"league/flysystem-aws-s3-v3": "~1.0",
diff --git a/tests/TestCase.php b/tests/TestCase.php
index bbc42489..d3e42ea1 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -48,7 +48,11 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
protected function getEnvironmentSetUp($app)
{
if (file_exists(__DIR__ . '/../.env')) {
- \Dotenv\Dotenv::create(__DIR__ . '/..')->load();
+ if (method_exists(\Dotenv\Dotenv::class, 'createImmutable')) {
+ \Dotenv\Dotenv::createImmutable(__DIR__ . '/..')->load();
+ } else {
+ \Dotenv\Dotenv::create(__DIR__ . '/..')->load();
+ }
}
$app['config']->set([
From 27f916c3239fb4dbad44f1cfa74fbde4bd9d656d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Samuel=20=C5=A0tancl?=
Date: Sat, 19 Feb 2022 16:12:38 +0100
Subject: [PATCH 09/30] end tenancy in queue if the next job is not tenant
aware
---
src/Bootstrappers/QueueTenancyBootstrapper.php | 10 ++++++++--
tests/Etc/tmp/queuetest.json | 1 -
tests/QueueTest.php | 5 +++++
3 files changed, 13 insertions(+), 3 deletions(-)
delete mode 100644 tests/Etc/tmp/queuetest.json
diff --git a/src/Bootstrappers/QueueTenancyBootstrapper.php b/src/Bootstrappers/QueueTenancyBootstrapper.php
index d2ce52d0..2e9aa051 100644
--- a/src/Bootstrappers/QueueTenancyBootstrapper.php
+++ b/src/Bootstrappers/QueueTenancyBootstrapper.php
@@ -75,14 +75,20 @@ class QueueTenancyBootstrapper implements TenancyBootstrapper
protected static function initializeTenancyForQueue($tenantId)
{
- // The job is not tenant-aware
if (! $tenantId) {
+ // The job is not tenant-aware
+ if (tenancy()->initialized) {
+ // Tenancy was initialized, so we revert back to the central context
+ tenancy()->end();
+ }
+
return;
}
if (tenancy()->initialized) {
+ // Tenancy is already initialized
if (tenant()->getTenantKey() === $tenantId) {
- // Tenancy is already initialized for the tenant (e.g. dispatchNow was used)
+ // It's initialized for the same tenant (e.g. dispatchNow was used, or the previous job also ran for this tenant)
return;
}
}
diff --git a/tests/Etc/tmp/queuetest.json b/tests/Etc/tmp/queuetest.json
deleted file mode 100644
index dc158c6b..00000000
--- a/tests/Etc/tmp/queuetest.json
+++ /dev/null
@@ -1 +0,0 @@
-{"userName":"Bar","shouldFail":false,"tenant_id":"The current tenant id is: a7f73c10-9879-40ae-b7b0-1ded7c1f7b1b"}
\ No newline at end of file
diff --git a/tests/QueueTest.php b/tests/QueueTest.php
index 158ad56b..afe64fea 100644
--- a/tests/QueueTest.php
+++ b/tests/QueueTest.php
@@ -55,6 +55,11 @@ class QueueTest extends TestCase
$this->valuestore = Valuestore::make(__DIR__ . '/Etc/tmp/queuetest.json')->flush();
}
+ public function tearDown(): void
+ {
+ $this->valuestore->flush();
+ }
+
protected function withFailedJobs()
{
Schema::connection('central')->create('failed_jobs', function (Blueprint $table) {
From 368d3cc99f7916af5e8290cddabd4968eeec73ab Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Samuel=20=C5=A0tancl?=
Date: Sat, 19 Feb 2022 16:21:27 +0100
Subject: [PATCH 10/30] add forceRefresh option to QueueTenancyBootstrapper
---
.../QueueTenancyBootstrapper.php | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/src/Bootstrappers/QueueTenancyBootstrapper.php b/src/Bootstrappers/QueueTenancyBootstrapper.php
index 2e9aa051..6a88f701 100644
--- a/src/Bootstrappers/QueueTenancyBootstrapper.php
+++ b/src/Bootstrappers/QueueTenancyBootstrapper.php
@@ -25,6 +25,14 @@ class QueueTenancyBootstrapper implements TenancyBootstrapper
/** @var QueueManager */
protected $queue;
+ /**
+ * Don't persist the same tenant across multiple jobs even if they have the same tenant ID.
+ *
+ * This is useful when you're changing the tenant's state (e.g. properties in the `data` column) and want the next job to initialize tenancy again
+ * with the new data. Features like the Tenant Config are only executed when tenancy is initialized, so the re-initialization is needed in some cases.
+ */
+ public static bool $forceRefresh = false;
+
/**
* The normal constructor is only executed after tenancy is bootstrapped.
* However, we're registering a hook to initialize tenancy. Therefore,
@@ -85,6 +93,17 @@ class QueueTenancyBootstrapper implements TenancyBootstrapper
return;
}
+ if (static::$forceRefresh) {
+ // Re-initialize tenancy between all jobs
+ if (tenancy()->initialized) {
+ tenancy()->end();
+ }
+
+ tenancy()->initialize(tenancy()->find($tenantId));
+
+ return;
+ }
+
if (tenancy()->initialized) {
// Tenancy is already initialized
if (tenant()->getTenantKey() === $tenantId) {
From 8e9485f9b1f51d066a791d5cf94834738383ab76 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Samuel=20=C5=A0tancl?=
Date: Sat, 19 Feb 2022 16:31:31 +0100
Subject: [PATCH 11/30] add empty queuetest.json
---
tests/Etc/tmp/queuetest.json | 0
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 tests/Etc/tmp/queuetest.json
diff --git a/tests/Etc/tmp/queuetest.json b/tests/Etc/tmp/queuetest.json
new file mode 100644
index 00000000..e69de29b
From 5249ec7c82013a5122f076d9fac6ed2a04b8f532 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Samuel=20=C5=A0tancl?=
Date: Sat, 19 Feb 2022 16:31:45 +0100
Subject: [PATCH 12/30] ignore changes to queuetest.json
---
.gitignore | 1 +
1 file changed, 1 insertion(+)
diff --git a/.gitignore b/.gitignore
index 1d03dbec..b3223156 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,4 @@ psysh
phpunit_var_*.xml
coverage/
clover.xml
+tests/Etc/tmp/queuetest.json
From 5b9b3845261fcd5f24181f2a92911db62b26364f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Samuel=20=C5=A0tancl?=
Date: Sat, 19 Feb 2022 16:33:59 +0100
Subject: [PATCH 13/30] Remove codecov
---
.github/workflows/ci.yml | 4 ----
README.md | 1 -
2 files changed, 5 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index efb8ad02..f7b64d2d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -26,7 +26,3 @@ jobs:
run: docker-compose exec -T test composer require --no-interaction "laravel/framework:${{ matrix.laravel }}"
- name: Run tests
run: ./test
- - name: Send code coverage to codecov
- env:
- CODECOV_TOKEN: 24382d15-84e7-4a55-bea4-c4df96a24a9b
- run: bash <(curl -s https://codecov.io/bash)
diff --git a/README.md b/README.md
index 46f1b097..f4d28288 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,6 @@
-
From b4a4eab949481d2f96b0b2c1c21aa8db92a2147e Mon Sep 17 00:00:00 2001
From: masiorama
Date: Tue, 22 Feb 2022 16:26:07 +0100
Subject: [PATCH 14/30] Add drop of db views on migrate fresh command (#812)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Optionally handle drop of table views on MigrateFresh
@stancl I managed to make the modification discussed here #811
Afaik (and I can understand) this is the easiest way to handle it, but I'm open to discuss.
* Remove redundant store variable
* code style
Co-authored-by: Samuel Štancl
---
src/Commands/MigrateFresh.php | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/Commands/MigrateFresh.php b/src/Commands/MigrateFresh.php
index f50e2f5f..4d003db0 100644
--- a/src/Commands/MigrateFresh.php
+++ b/src/Commands/MigrateFresh.php
@@ -7,6 +7,7 @@ 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
{
@@ -22,6 +23,8 @@ final class MigrateFresh extends Command
public function __construct()
{
parent::__construct();
+
+ $this->addOption('--drop-views', null, InputOption::VALUE_NONE, 'Drop views along with tenant tables.', null);
$this->setName('tenants:migrate-fresh');
}
@@ -37,6 +40,7 @@ final class MigrateFresh extends Command
$this->info('Dropping tables.');
$this->call('db:wipe', array_filter([
'--database' => 'tenant',
+ '--drop-views' => $this->option('drop-views'),
'--force' => true,
]));
From 79e3d53b06f33aa6e30ee4454177e1d798918704 Mon Sep 17 00:00:00 2001
From: Erik Gaal
Date: Tue, 8 Mar 2022 01:50:25 +0100
Subject: [PATCH 15/30] [3.x] Compatibility with Laravel 9 (#802)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Test on Laravel 9
* Don't extend final Kernel class
* Make FilesystemTenancyBootstrapper compatible with Flysystem v3
Co-authored-by: George
* Update tenant maintenance mode te be in line with Laravel
* Exclude PHP 7.4 <> L9 combination from testing
* add root_override-related assertions
* getPrefix -> getPathPrefix
* handle / inconsistency in s3 prefix
* Refactor Storage facade changes
Co-authored-by: George
Co-authored-by: Samuel Štancl
---
.github/workflows/ci.yml | 5 +-
composer.json | 8 +--
.../FilesystemTenancyBootstrapper.php | 31 ++++-----
.../CheckTenantForMaintenanceMode.php | 9 ++-
tests/BootstrapperTest.php | 69 +++++++++++--------
tests/Etc/ConsoleKernel.php | 2 +-
tests/MaintenanceModeTest.php | 4 +-
tests/TestCase.php | 1 +
8 files changed, 74 insertions(+), 55 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index f7b64d2d..1303061a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -16,7 +16,10 @@ jobs:
strategy:
matrix:
php: ["7.4", "8.0"]
- laravel: ["^6.0", "^8.0"]
+ laravel: ["^6.0", "^8.0", "^9.0"]
+ exclude:
+ - laravel: "^9.0"
+ php: "7.4"
steps:
- uses: actions/checkout@v2
diff --git a/composer.json b/composer.json
index bf66e1f2..88bfea29 100644
--- a/composer.json
+++ b/composer.json
@@ -11,16 +11,16 @@
],
"require": {
"ext-json": "*",
- "illuminate/support": "^6.0|^7.0|^8.0",
+ "illuminate/support": "^6.0|^7.0|^8.0|^9.0",
"facade/ignition-contracts": "^1.0",
"ramsey/uuid": "^3.7|^4.0",
"stancl/jobpipeline": "^1.0",
"stancl/virtualcolumn": "^1.0"
},
"require-dev": {
- "laravel/framework": "^6.0|^7.0|^8.0",
- "orchestra/testbench-browser-kit": "^4.0|^5.0|^6.0",
- "league/flysystem-aws-s3-v3": "~1.0",
+ "laravel/framework": "^6.0|^7.0|^8.0|^9.0",
+ "orchestra/testbench": "^4.0|^5.0|^6.0|^7.0",
+ "league/flysystem-aws-s3-v3": "^1.0|^3.0",
"doctrine/dbal": "^2.10",
"spatie/valuestore": "^1.2.5"
},
diff --git a/src/Bootstrappers/FilesystemTenancyBootstrapper.php b/src/Bootstrappers/FilesystemTenancyBootstrapper.php
index d5ae2d50..418be93f 100644
--- a/src/Bootstrappers/FilesystemTenancyBootstrapper.php
+++ b/src/Bootstrappers/FilesystemTenancyBootstrapper.php
@@ -54,20 +54,20 @@ class FilesystemTenancyBootstrapper implements TenancyBootstrapper
}
// Storage facade
- foreach ($this->app['config']['tenancy.filesystem.disks'] as $disk) {
- /** @var FilesystemAdapter $filesystemDisk */
- $filesystemDisk = Storage::disk($disk);
- $this->originalPaths['disks'][$disk] = $filesystemDisk->getAdapter()->getPathPrefix();
+ Storage::forgetDisk($this->app['config']['tenancy.filesystem.disks']);
- if ($root = str_replace(
+ foreach ($this->app['config']['tenancy.filesystem.disks'] as $disk) {
+ $originalRoot = $this->app['config']["filesystems.disks.{$disk}.root"];
+ $this->originalPaths['disks'][$disk] = $originalRoot;
+
+ $finalPrefix = str_replace(
'%storage_path%',
storage_path(),
- $this->app['config']["tenancy.filesystem.root_override.{$disk}"] ?? ''
- )) {
- $filesystemDisk->getAdapter()->setPathPrefix($finalPrefix = $root);
- } else {
- $root = $this->app['config']["filesystems.disks.{$disk}.root"];
- $filesystemDisk->getAdapter()->setPathPrefix($finalPrefix = $root . "/{$suffix}");
+ $this->app['config']["tenancy.filesystem.root_override.{$disk}"] ?? '',
+ );
+
+ if (! $finalPrefix) {
+ $finalPrefix = $originalRoot . '/'. $suffix;
}
$this->app['config']["filesystems.disks.{$disk}.root"] = $finalPrefix;
@@ -84,14 +84,9 @@ class FilesystemTenancyBootstrapper implements TenancyBootstrapper
$this->app['url']->setAssetRoot($this->app['config']['app.asset_url']);
// Storage facade
+ Storage::forgetDisk($this->app['config']['tenancy.filesystem.disks']);
foreach ($this->app['config']['tenancy.filesystem.disks'] as $disk) {
- /** @var FilesystemAdapter $filesystemDisk */
- $filesystemDisk = Storage::disk($disk);
-
- $root = $this->originalPaths['disks'][$disk];
-
- $filesystemDisk->getAdapter()->setPathPrefix($root);
- $this->app['config']["filesystems.disks.{$disk}.root"] = $root;
+ $this->app['config']["filesystems.disks.{$disk}.root"] = $this->originalPaths['disks'][$disk];
}
}
}
diff --git a/src/Middleware/CheckTenantForMaintenanceMode.php b/src/Middleware/CheckTenantForMaintenanceMode.php
index 5554663f..8e29a31e 100644
--- a/src/Middleware/CheckTenantForMaintenanceMode.php
+++ b/src/Middleware/CheckTenantForMaintenanceMode.php
@@ -5,7 +5,7 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Middleware;
use Closure;
-use Illuminate\Foundation\Http\Exceptions\MaintenanceModeException;
+use Symfony\Component\HttpKernel\Exception\HttpException;
use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode;
use Stancl\Tenancy\Exceptions\TenancyNotInitializedException;
use Symfony\Component\HttpFoundation\IpUtils;
@@ -29,7 +29,12 @@ class CheckTenantForMaintenanceMode extends CheckForMaintenanceMode
return $next($request);
}
- throw new MaintenanceModeException($data['time'], $data['retry'], $data['message']);
+ throw new HttpException(
+ 503,
+ 'Service Unavailable',
+ null,
+ isset($data['retry']) ? ['Retry-After' => $data['retry']] : []
+ );
}
return $next($request);
diff --git a/tests/BootstrapperTest.php b/tests/BootstrapperTest.php
index 1b0c880d..29aa7dc9 100644
--- a/tests/BootstrapperTest.php
+++ b/tests/BootstrapperTest.php
@@ -4,23 +4,27 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Tests;
-use Illuminate\Support\Facades\Cache;
+use Illuminate\Filesystem\FilesystemAdapter;
+use ReflectionObject;
+use ReflectionProperty;
+use Illuminate\Support\Str;
use Illuminate\Support\Facades\DB;
+use Stancl\JobPipeline\JobPipeline;
+use Stancl\Tenancy\Tests\Etc\Tenant;
+use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Storage;
-use Stancl\JobPipeline\JobPipeline;
-use Stancl\Tenancy\Bootstrappers\CacheTenancyBootstrapper;
-use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
-use Stancl\Tenancy\Bootstrappers\FilesystemTenancyBootstrapper;
-use Stancl\Tenancy\Bootstrappers\RedisTenancyBootstrapper;
use Stancl\Tenancy\Events\TenancyEnded;
-use Stancl\Tenancy\Events\TenancyInitialized;
-use Stancl\Tenancy\Events\TenantCreated;
use Stancl\Tenancy\Jobs\CreateDatabase;
+use Stancl\Tenancy\Events\TenantCreated;
+use Stancl\Tenancy\Events\TenancyInitialized;
use Stancl\Tenancy\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Listeners\RevertToCentralContext;
-use Stancl\Tenancy\Tests\Etc\Tenant;
+use Stancl\Tenancy\Bootstrappers\CacheTenancyBootstrapper;
+use Stancl\Tenancy\Bootstrappers\RedisTenancyBootstrapper;
+use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
+use Stancl\Tenancy\Bootstrappers\FilesystemTenancyBootstrapper;
class BootstrapperTest extends TestCase
{
@@ -165,6 +169,7 @@ class BootstrapperTest extends TestCase
$tenant2 = Tenant::create();
tenancy()->initialize($tenant1);
+
Storage::disk('public')->put('foo', 'bar');
$this->assertSame('bar', Storage::disk('public')->get('foo'));
@@ -184,30 +189,38 @@ class BootstrapperTest extends TestCase
$this->assertFalse(Storage::disk('public')->exists('foo'));
$this->assertFalse(Storage::disk('public')->exists('abc'));
+ $expected_storage_path = $old_storage_path . '/tenant' . tenant('id'); // /tenant = suffix base
+
+ // Check that disk prefixes respect the root_override logic
+ $this->assertSame($expected_storage_path . '/app/', $this->getDiskPrefix('local'));
+ $this->assertSame($expected_storage_path . '/app/public/', $this->getDiskPrefix('public'));
+ $this->assertSame('tenant' . tenant('id') . '/', ltrim($this->getDiskPrefix('s3'), '/'));
+
// Check suffixing logic
$new_storage_path = storage_path();
- $this->assertEquals($old_storage_path . '/' . config('tenancy.filesystem.suffix_base') . tenant('id'), $new_storage_path);
+ $this->assertEquals($expected_storage_path, $new_storage_path);
+ }
- foreach (config('tenancy.filesystem.disks') as $disk) {
- $suffix = config('tenancy.filesystem.suffix_base') . tenant('id');
+ protected function getDiskPrefix(string $disk): string
+ {
+ /** @var FilesystemAdapter $disk */
+ $disk = Storage::disk($disk);
+ $adapter = $disk->getAdapter();
- /** @var FilesystemAdapter $filesystemDisk */
- $filesystemDisk = Storage::disk($disk);
-
- $current_path_prefix = $filesystemDisk->getAdapter()->getPathPrefix();
-
- if ($override = config("tenancy.filesystem.root_override.{$disk}")) {
- $correct_path_prefix = str_replace('%storage_path%', storage_path(), $override);
- } else {
- if ($base = $old_storage_facade_roots[$disk]) {
- $correct_path_prefix = $base . "/$suffix/";
- } else {
- $correct_path_prefix = "$suffix/";
- }
- }
-
- $this->assertSame($correct_path_prefix, $current_path_prefix);
+ if (! Str::startsWith(app()->version(), '9.')) {
+ return $adapter->getPathPrefix();
}
+
+ $prefixer = (new ReflectionObject($adapter))->getProperty('prefixer');
+ $prefixer->setAccessible(true);
+
+ // reflection -> instance
+ $prefixer = $prefixer->getValue($adapter);
+
+ $prefix = (new ReflectionProperty($prefixer, 'prefix'));
+ $prefix->setAccessible(true);
+
+ return $prefix->getValue($prefixer);
}
// for queues see QueueTest
diff --git a/tests/Etc/ConsoleKernel.php b/tests/Etc/ConsoleKernel.php
index 1bc66365..9d37d3c6 100644
--- a/tests/Etc/ConsoleKernel.php
+++ b/tests/Etc/ConsoleKernel.php
@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Tests\Etc;
-use Orchestra\Testbench\Console\Kernel;
+use Orchestra\Testbench\Foundation\Console\Kernel;
class ConsoleKernel extends Kernel
{
diff --git a/tests/MaintenanceModeTest.php b/tests/MaintenanceModeTest.php
index a8ecb064..4a8d8d0c 100644
--- a/tests/MaintenanceModeTest.php
+++ b/tests/MaintenanceModeTest.php
@@ -4,12 +4,14 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Tests;
+use Symfony\Component\HttpKernel\Exception\HttpException;
use Illuminate\Foundation\Http\Exceptions\MaintenanceModeException;
use Illuminate\Support\Facades\Route;
use Stancl\Tenancy\Database\Concerns\MaintenanceMode;
use Stancl\Tenancy\Middleware\CheckTenantForMaintenanceMode;
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
use Stancl\Tenancy\Tests\Etc\Tenant;
+use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
class MaintenanceModeTest extends TestCase
{
@@ -32,7 +34,7 @@ class MaintenanceModeTest extends TestCase
$tenant->putDownForMaintenance();
- $this->expectException(MaintenanceModeException::class);
+ $this->expectException(HttpException::class);
$this->withoutExceptionHandling()
->get('http://acme.localhost/foo');
}
diff --git a/tests/TestCase.php b/tests/TestCase.php
index d3e42ea1..cea669a1 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -87,6 +87,7 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
'public',
's3',
],
+ 'filesystems.disks.s3.bucket' => 'foo',
'tenancy.redis.tenancy' => env('TENANCY_TEST_REDIS_TENANCY', true),
'database.redis.client' => env('TENANCY_TEST_REDIS_CLIENT', 'phpredis'),
'tenancy.redis.prefixed_connections' => ['default'],
From eb1a2ebe32a3dd9e5941c81602d55aab2268db06 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Samuel=20=C5=A0tancl?=
Date: Thu, 17 Mar 2022 12:24:57 +0100
Subject: [PATCH 16/30] Use 7.2 instead of 7.4
---
.github/workflows/ci.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1303061a..3d1698b4 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -15,11 +15,11 @@ jobs:
strategy:
matrix:
- php: ["7.4", "8.0"]
+ php: ["7.2", "8.0"]
laravel: ["^6.0", "^8.0", "^9.0"]
exclude:
- laravel: "^9.0"
- php: "7.4"
+ php: "7.2"
steps:
- uses: actions/checkout@v2
From fa2a61fcd74126587312567d5e07e273c8ea43b4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Samuel=20=C5=A0tancl?=
Date: Thu, 17 Mar 2022 12:30:14 +0100
Subject: [PATCH 17/30] Use PHP 7.3 instead of 7.2
---
.github/workflows/ci.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 3d1698b4..cc8ad985 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -15,11 +15,11 @@ jobs:
strategy:
matrix:
- php: ["7.2", "8.0"]
+ php: ["7.3", "8.0"]
laravel: ["^6.0", "^8.0", "^9.0"]
exclude:
- laravel: "^9.0"
- php: "7.2"
+ php: "7.3"
steps:
- uses: actions/checkout@v2
From 49ebb75f007d649465fc3f847ddc76f6396b0337 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Samuel=20=C5=A0tancl?=
Date: Thu, 17 Mar 2022 12:46:49 +0100
Subject: [PATCH 18/30] Fixes #827
---
src/Bootstrappers/QueueTenancyBootstrapper.php | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/Bootstrappers/QueueTenancyBootstrapper.php b/src/Bootstrappers/QueueTenancyBootstrapper.php
index 6a88f701..666e29ed 100644
--- a/src/Bootstrappers/QueueTenancyBootstrapper.php
+++ b/src/Bootstrappers/QueueTenancyBootstrapper.php
@@ -30,8 +30,10 @@ class QueueTenancyBootstrapper implements TenancyBootstrapper
*
* This is useful when you're changing the tenant's state (e.g. properties in the `data` column) and want the next job to initialize tenancy again
* with the new data. Features like the Tenant Config are only executed when tenancy is initialized, so the re-initialization is needed in some cases.
+ *
+ * @var bool
*/
- public static bool $forceRefresh = false;
+ public static $forceRefresh = false;
/**
* The normal constructor is only executed after tenancy is bootstrapped.
From 5026f54a6d4482226951d3a8196218ea41434db4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Samuel=20=C5=A0tancl?=
Date: Wed, 23 Mar 2022 20:48:55 +0100
Subject: [PATCH 19/30] fix path prefixing
---
CONTRIBUTING.md | 13 +++++++++++++
Dockerfile | 2 +-
docker-compose.override.yml | 5 +++++
src/Bootstrappers/FilesystemTenancyBootstrapper.php | 4 +++-
tests/BootstrapperTest.php | 2 +-
tests/Etc/tmp/queuetest.json | 0
6 files changed, 23 insertions(+), 3 deletions(-)
create mode 100644 docker-compose.override.yml
delete mode 100644 tests/Etc/tmp/queuetest.json
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 7dce1b82..a5a6ec3f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -9,3 +9,16 @@ StyleCI will flag code style violations in your pull requests.
Run `docker-compose up -d` to start the containers. Then run `./test` to run the tests.
When you're done testing, run `docker-compose down` to shut down the containers.
+
+### Docker on M1
+
+You can add:
+```yaml
+services:
+ mysql:
+ platform: linux/amd64
+ mysql2:
+ platform: linux/amd64
+```
+
+to `docker-compose.override.yml` to make `docker-compose up-d` work on M1.
diff --git a/Dockerfile b/Dockerfile
index 06d97aea..36f52d6a 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -30,7 +30,7 @@ RUN docker-php-ext-configure pgsql -with-pgsql=/usr/local/pgsql \
# && if [ "${PHP_VERSION}" = "7.4" ]; then docker-php-ext-configure gd --with-freetype --with-jpeg; else docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/; fi \
&& docker-php-ext-install -j$(nproc) gd pdo pdo_mysql pdo_pgsql pdo_sqlite pgsql zip gmp bcmath pcntl ldap sysvmsg exif \
# install the redis php extension
- && pecl install redis-5.3.2 \
+ && pecl install redis-5.3.7 \
&& docker-php-ext-enable redis \
# install the pcov extention
&& pecl install pcov \
diff --git a/docker-compose.override.yml b/docker-compose.override.yml
new file mode 100644
index 00000000..29e9fb37
--- /dev/null
+++ b/docker-compose.override.yml
@@ -0,0 +1,5 @@
+services:
+ mysql:
+ platform: linux/amd64
+ mysql2:
+ platform: linux/amd64
diff --git a/src/Bootstrappers/FilesystemTenancyBootstrapper.php b/src/Bootstrappers/FilesystemTenancyBootstrapper.php
index 418be93f..dcd7299e 100644
--- a/src/Bootstrappers/FilesystemTenancyBootstrapper.php
+++ b/src/Bootstrappers/FilesystemTenancyBootstrapper.php
@@ -67,7 +67,9 @@ class FilesystemTenancyBootstrapper implements TenancyBootstrapper
);
if (! $finalPrefix) {
- $finalPrefix = $originalRoot . '/'. $suffix;
+ $finalPrefix = $originalRoot
+ ? $originalRoot . '/'. $suffix
+ : $suffix;
}
$this->app['config']["filesystems.disks.{$disk}.root"] = $finalPrefix;
diff --git a/tests/BootstrapperTest.php b/tests/BootstrapperTest.php
index 29aa7dc9..588fadd8 100644
--- a/tests/BootstrapperTest.php
+++ b/tests/BootstrapperTest.php
@@ -194,7 +194,7 @@ class BootstrapperTest extends TestCase
// Check that disk prefixes respect the root_override logic
$this->assertSame($expected_storage_path . '/app/', $this->getDiskPrefix('local'));
$this->assertSame($expected_storage_path . '/app/public/', $this->getDiskPrefix('public'));
- $this->assertSame('tenant' . tenant('id') . '/', ltrim($this->getDiskPrefix('s3'), '/'));
+ $this->assertSame('tenant' . tenant('id') . '/', $this->getDiskPrefix('s3'), '/');
// Check suffixing logic
$new_storage_path = storage_path();
diff --git a/tests/Etc/tmp/queuetest.json b/tests/Etc/tmp/queuetest.json
deleted file mode 100644
index e69de29b..00000000
From 600bb823de185ef02c3ff5aeba0c75f41409bafa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Samuel=20=C5=A0tancl?=
Date: Wed, 23 Mar 2022 20:49:25 +0100
Subject: [PATCH 20/30] avoid double // in prefix
---
src/Bootstrappers/FilesystemTenancyBootstrapper.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Bootstrappers/FilesystemTenancyBootstrapper.php b/src/Bootstrappers/FilesystemTenancyBootstrapper.php
index dcd7299e..da1e5e2a 100644
--- a/src/Bootstrappers/FilesystemTenancyBootstrapper.php
+++ b/src/Bootstrappers/FilesystemTenancyBootstrapper.php
@@ -68,7 +68,7 @@ class FilesystemTenancyBootstrapper implements TenancyBootstrapper
if (! $finalPrefix) {
$finalPrefix = $originalRoot
- ? $originalRoot . '/'. $suffix
+ ? rtrim($originalRoot, '/') . '/'. $suffix
: $suffix;
}
From e1ae6f4380bf2062adfb44bc1acb83e603b95ed9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Samuel=20=C5=A0tancl?=
Date: Wed, 23 Mar 2022 20:57:15 +0100
Subject: [PATCH 21/30] re-add queuetest.json
---
.gitignore | 1 -
tests/Etc/tmp/queuetest.json | 0
2 files changed, 1 deletion(-)
create mode 100644 tests/Etc/tmp/queuetest.json
diff --git a/.gitignore b/.gitignore
index b3223156..1d03dbec 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,4 +8,3 @@ psysh
phpunit_var_*.xml
coverage/
clover.xml
-tests/Etc/tmp/queuetest.json
diff --git a/tests/Etc/tmp/queuetest.json b/tests/Etc/tmp/queuetest.json
new file mode 100644
index 00000000..e69de29b
From 4e717236f9c1aa5d4e4aa588bace783d4d7bcde2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Samuel=20=C5=A0tancl?=
Date: Wed, 23 Mar 2022 20:57:32 +0100
Subject: [PATCH 22/30] revert gitignore
---
.gitignore | 1 +
1 file changed, 1 insertion(+)
diff --git a/.gitignore b/.gitignore
index 1d03dbec..b3223156 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,4 @@ psysh
phpunit_var_*.xml
coverage/
clover.xml
+tests/Etc/tmp/queuetest.json
From 4f196097979653fa7b7522ac339116d88b5baae4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Samuel=20=C5=A0tancl?=
Date: Wed, 23 Mar 2022 20:58:47 +0100
Subject: [PATCH 23/30] remove docker-compose.override.yml
---
.gitignore | 1 +
docker-compose.override.yml | 5 -----
2 files changed, 1 insertion(+), 5 deletions(-)
delete mode 100644 docker-compose.override.yml
diff --git a/.gitignore b/.gitignore
index b3223156..f470ba75 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,4 @@ phpunit_var_*.xml
coverage/
clover.xml
tests/Etc/tmp/queuetest.json
+docker-compose.override.yml
diff --git a/docker-compose.override.yml b/docker-compose.override.yml
deleted file mode 100644
index 29e9fb37..00000000
--- a/docker-compose.override.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-services:
- mysql:
- platform: linux/amd64
- mysql2:
- platform: linux/amd64
From 349125c02ebe71216bfbbb4ea0c3b955e03ba474 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Samuel=20=C5=A0tancl?=
Date: Wed, 30 Mar 2022 18:00:55 +0200
Subject: [PATCH 24/30] Merge hotfix branch (#834)
* try specifying the signature in __construct
* constructor doesn't work since Reflection is used, try specifying getDefaultName() instead
* Fixed: make migration commands compatible
* Fix failing tests
* Fix username generation
* Re-create tmp dir as well if needed
* wip
---
src/Commands/Migrate.php | 20 ++++++----------
src/Commands/MigrateFresh.php | 2 +-
src/Commands/Rollback.php | 11 ++++++---
src/Concerns/ExtendsLaravelCommand.php | 23 +++++++++++++++++++
src/Database/DatabaseManager.php | 10 +++++++-
src/Jobs/CreateDatabase.php | 2 +-
...rmissionControlledMySQLDatabaseManager.php | 5 ----
tests/DatabaseUsersTest.php | 9 ++++++--
tests/Etc/tmp/queuetest.json | 0
tests/QueueTest.php | 20 +++++++++++++++-
10 files changed, 75 insertions(+), 27 deletions(-)
create mode 100644 src/Concerns/ExtendsLaravelCommand.php
delete mode 100644 tests/Etc/tmp/queuetest.json
diff --git a/src/Commands/Migrate.php b/src/Commands/Migrate.php
index bf92dfcd..c67d3598 100644
--- a/src/Commands/Migrate.php
+++ b/src/Commands/Migrate.php
@@ -8,32 +8,26 @@ use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Database\Console\Migrations\MigrateCommand;
use Illuminate\Database\Migrations\Migrator;
use Stancl\Tenancy\Concerns\DealsWithMigrations;
+use Stancl\Tenancy\Concerns\ExtendsLaravelCommand;
use Stancl\Tenancy\Concerns\HasATenantsOption;
use Stancl\Tenancy\Events\DatabaseMigrated;
use Stancl\Tenancy\Events\MigratingDatabase;
class Migrate extends MigrateCommand
{
- use HasATenantsOption, DealsWithMigrations;
+ use HasATenantsOption, DealsWithMigrations, ExtendsLaravelCommand;
- /**
- * The console command description.
- *
- * @var string
- */
protected $description = 'Run migrations for tenant(s)';
- /**
- * Create a new command instance.
- *
- * @param Migrator $migrator
- * @param Dispatcher $dispatcher
- */
+ protected static function getTenantCommandName(): string
+ {
+ return 'tenants:migrate';
+ }
+
public function __construct(Migrator $migrator, Dispatcher $dispatcher)
{
parent::__construct($migrator, $dispatcher);
- $this->setName('tenants:migrate');
$this->specifyParameters();
}
diff --git a/src/Commands/MigrateFresh.php b/src/Commands/MigrateFresh.php
index 4d003db0..283d70b0 100644
--- a/src/Commands/MigrateFresh.php
+++ b/src/Commands/MigrateFresh.php
@@ -23,7 +23,7 @@ final class MigrateFresh extends Command
public function __construct()
{
parent::__construct();
-
+
$this->addOption('--drop-views', null, InputOption::VALUE_NONE, 'Drop views along with tenant tables.', null);
$this->setName('tenants:migrate-fresh');
diff --git a/src/Commands/Rollback.php b/src/Commands/Rollback.php
index 081872c8..e60d974b 100644
--- a/src/Commands/Rollback.php
+++ b/src/Commands/Rollback.php
@@ -7,13 +7,19 @@ 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;
use Stancl\Tenancy\Events\RollingBackDatabase;
class Rollback extends RollbackCommand
{
- use HasATenantsOption, DealsWithMigrations;
+ use HasATenantsOption, DealsWithMigrations, ExtendsLaravelCommand;
+
+ protected static function getTenantCommandName(): string
+ {
+ return 'tenants:rollback';
+ }
/**
* The console command description.
@@ -31,8 +37,7 @@ class Rollback extends RollbackCommand
{
parent::__construct($migrator);
- $this->setName('tenants:rollback');
- $this->specifyParameters();
+ $this->specifyTenantSignature();
}
/**
diff --git a/src/Concerns/ExtendsLaravelCommand.php b/src/Concerns/ExtendsLaravelCommand.php
new file mode 100644
index 00000000..bdafc8f7
--- /dev/null
+++ b/src/Concerns/ExtendsLaravelCommand.php
@@ -0,0 +1,23 @@
+specifyParameters();
+ }
+
+ public function getName(): ?string
+ {
+ return static::getTenantCommandName();
+ }
+
+ public static function getDefaultName(): ?string
+ {
+ return static::getTenantCommandName();
+ }
+
+ abstract protected static function getTenantCommandName(): string;
+}
diff --git a/src/Database/DatabaseManager.php b/src/Database/DatabaseManager.php
index e85fd659..6242ffa9 100644
--- a/src/Database/DatabaseManager.php
+++ b/src/Database/DatabaseManager.php
@@ -7,10 +7,12 @@ namespace Stancl\Tenancy\Database;
use Illuminate\Config\Repository;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Database\DatabaseManager as BaseDatabaseManager;
+use Stancl\Tenancy\Contracts\ManagesDatabaseUsers;
use Stancl\Tenancy\Contracts\TenantCannotBeCreatedException;
use Stancl\Tenancy\Contracts\TenantWithDatabase;
use Stancl\Tenancy\Exceptions\DatabaseManagerNotRegisteredException;
use Stancl\Tenancy\Exceptions\TenantDatabaseAlreadyExistsException;
+use Stancl\Tenancy\Exceptions\TenantDatabaseUserAlreadyExistsException;
/**
* @internal Class is subject to breaking changes in minor and patch versions.
@@ -90,8 +92,14 @@ class DatabaseManager
*/
public function ensureTenantCanBeCreated(TenantWithDatabase $tenant): void
{
- if ($tenant->database()->manager()->databaseExists($database = $tenant->database()->getName())) {
+ $manager = $tenant->database()->manager();
+
+ if ($manager->databaseExists($database = $tenant->database()->getName())) {
throw new TenantDatabaseAlreadyExistsException($database);
}
+
+ if ($manager instanceof ManagesDatabaseUsers && $manager->userExists($username = $tenant->database()->getUsername())) {
+ throw new TenantDatabaseUserAlreadyExistsException($username);
+ }
}
}
diff --git a/src/Jobs/CreateDatabase.php b/src/Jobs/CreateDatabase.php
index 3a74534d..3cb2a6b4 100644
--- a/src/Jobs/CreateDatabase.php
+++ b/src/Jobs/CreateDatabase.php
@@ -36,8 +36,8 @@ class CreateDatabase implements ShouldQueue
return false;
}
- $databaseManager->ensureTenantCanBeCreated($this->tenant);
$this->tenant->database()->makeCredentials();
+ $databaseManager->ensureTenantCanBeCreated($this->tenant);
$this->tenant->database()->manager()->createDatabase($this->tenant);
event(new DatabaseCreated($this->tenant));
diff --git a/src/TenantDatabaseManagers/PermissionControlledMySQLDatabaseManager.php b/src/TenantDatabaseManagers/PermissionControlledMySQLDatabaseManager.php
index f8bedc97..918601a8 100644
--- a/src/TenantDatabaseManagers/PermissionControlledMySQLDatabaseManager.php
+++ b/src/TenantDatabaseManagers/PermissionControlledMySQLDatabaseManager.php
@@ -7,7 +7,6 @@ namespace Stancl\Tenancy\TenantDatabaseManagers;
use Stancl\Tenancy\Concerns\CreatesDatabaseUsers;
use Stancl\Tenancy\Contracts\ManagesDatabaseUsers;
use Stancl\Tenancy\DatabaseConfig;
-use Stancl\Tenancy\Exceptions\TenantDatabaseUserAlreadyExistsException;
class PermissionControlledMySQLDatabaseManager extends MySQLDatabaseManager implements ManagesDatabaseUsers
{
@@ -26,10 +25,6 @@ class PermissionControlledMySQLDatabaseManager extends MySQLDatabaseManager impl
$hostname = $databaseConfig->connection()['host'];
$password = $databaseConfig->getPassword();
- if ($this->userExists($username)) {
- throw new TenantDatabaseUserAlreadyExistsException($username);
- }
-
$this->database()->statement("CREATE USER `{$username}`@`%` IDENTIFIED BY '{$password}'");
$grants = implode(', ', static::$grants);
diff --git a/tests/DatabaseUsersTest.php b/tests/DatabaseUsersTest.php
index 0b095024..344239d1 100644
--- a/tests/DatabaseUsersTest.php
+++ b/tests/DatabaseUsersTest.php
@@ -10,6 +10,7 @@ use Illuminate\Support\Str;
use Stancl\JobPipeline\JobPipeline;
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
use Stancl\Tenancy\Contracts\ManagesDatabaseUsers;
+use Stancl\Tenancy\Events\DatabaseCreated;
use Stancl\Tenancy\Events\TenancyInitialized;
use Stancl\Tenancy\Events\TenantCreated;
use Stancl\Tenancy\Exceptions\TenantDatabaseUserAlreadyExistsException;
@@ -67,14 +68,18 @@ class DatabaseUsersTest extends TestCase
$this->assertTrue($manager->databaseExists($tenant->database()->getName()));
$this->expectException(TenantDatabaseUserAlreadyExistsException::class);
+ Event::fake([DatabaseCreated::class]);
+
$tenant2 = Tenant::create([
'tenancy_db_username' => $username,
]);
/** @var ManagesDatabaseUsers $manager */
- $manager = $tenant2->database()->manager();
+ $manager2 = $tenant2->database()->manager();
+
// database was not created because of DB transaction
- $this->assertFalse($manager->databaseExists($tenant2->database()->getName()));
+ $this->assertFalse($manager2->databaseExists($tenant2->database()->getName()));
+ Event::assertNotDispatched(DatabaseCreated::class);
}
/** @test */
diff --git a/tests/Etc/tmp/queuetest.json b/tests/Etc/tmp/queuetest.json
deleted file mode 100644
index e69de29b..00000000
diff --git a/tests/QueueTest.php b/tests/QueueTest.php
index afe64fea..a3df9cd7 100644
--- a/tests/QueueTest.php
+++ b/tests/QueueTest.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Tests;
+use Closure;
use Exception;
use Illuminate\Support\Str;
use Illuminate\Bus\Queueable;
@@ -24,6 +25,7 @@ use Illuminate\Queue\Events\JobProcessed;
use Illuminate\Queue\Events\JobProcessing;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
+use PDO;
use Stancl\Tenancy\Events\TenancyInitialized;
use Stancl\Tenancy\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Listeners\RevertToCentralContext;
@@ -52,7 +54,7 @@ class QueueTest extends TestCase
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
Event::listen(TenancyEnded::class, RevertToCentralContext::class);
- $this->valuestore = Valuestore::make(__DIR__ . '/Etc/tmp/queuetest.json')->flush();
+ $this->createValueStore();
}
public function tearDown(): void
@@ -60,6 +62,22 @@ class QueueTest extends TestCase
$this->valuestore->flush();
}
+ protected function createValueStore(): void
+ {
+ $valueStorePath = __DIR__ . '/Etc/tmp/queuetest.json';
+
+ if (! file_exists($valueStorePath)) {
+ // The directory sometimes goes missing as well when the file is deleted in git
+ if (! is_dir(__DIR__ . '/Etc/tmp')) {
+ mkdir(__DIR__ . '/Etc/tmp');
+ }
+
+ file_put_contents($valueStorePath, '');
+ }
+
+ $this->valuestore = Valuestore::make($valueStorePath)->flush();
+ }
+
protected function withFailedJobs()
{
Schema::connection('central')->create('failed_jobs', function (Blueprint $table) {
From f065ea60b0e56c6a22cd75c476ca448649f8ffe2 Mon Sep 17 00:00:00 2001
From: Roy de Vos Burchart
Date: Fri, 1 Apr 2022 22:53:09 +0200
Subject: [PATCH 25/30] Update QueueTenancyBootstrapper.php (#836)
---
src/Bootstrappers/QueueTenancyBootstrapper.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/Bootstrappers/QueueTenancyBootstrapper.php b/src/Bootstrappers/QueueTenancyBootstrapper.php
index 666e29ed..790e1344 100644
--- a/src/Bootstrappers/QueueTenancyBootstrapper.php
+++ b/src/Bootstrappers/QueueTenancyBootstrapper.php
@@ -63,8 +63,8 @@ class QueueTenancyBootstrapper implements TenancyBootstrapper
static::initializeTenancyForQueue($event->job->payload()['tenant_id'] ?? null);
});
- if (Str::startsWith(app()->version(), '8')) {
- // JobRetryRequested only exists since Laravel 8
+ if (version_compare(app()->version(), '8.64', '>=')) {
+ // JobRetryRequested only exists since Laravel 8.64
$dispatcher->listen(JobRetryRequested::class, function ($event) use (&$previousTenant) {
$previousTenant = tenant();
From 40bf576e0087f3350fa5f91c2c73962a7dcdff50 Mon Sep 17 00:00:00 2001
From: Nathan Dunn
Date: Fri, 8 Apr 2022 02:13:29 +0100
Subject: [PATCH 26/30] [3.x] Update PostgreSQLSchemaManager to set correct
config key value (#840)
* Update PostgreSQLSchemaManager to set correct config key value
* Update to use version_compare
* Update TenantDatabaseManagerTest
* Improve TenantDatabaseManagerTest
* Update TenantDatabaseManager
---
src/TenantDatabaseManagers/PostgreSQLSchemaManager.php | 6 +++++-
tests/TenantDatabaseManagerTest.php | 6 +++++-
2 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/src/TenantDatabaseManagers/PostgreSQLSchemaManager.php b/src/TenantDatabaseManagers/PostgreSQLSchemaManager.php
index 9d815b25..55f049d0 100644
--- a/src/TenantDatabaseManagers/PostgreSQLSchemaManager.php
+++ b/src/TenantDatabaseManagers/PostgreSQLSchemaManager.php
@@ -46,7 +46,11 @@ class PostgreSQLSchemaManager implements TenantDatabaseManager
public function makeConnectionConfig(array $baseConfig, string $databaseName): array
{
- $baseConfig['schema'] = $databaseName;
+ if (version_compare(app()->version(), '9.0', '>=')) {
+ $baseConfig['search_path'] = $databaseName;
+ } else {
+ $baseConfig['schema'] = $databaseName;
+ }
return $baseConfig;
}
diff --git a/tests/TenantDatabaseManagerTest.php b/tests/TenantDatabaseManagerTest.php
index f64770b1..3d45d96f 100644
--- a/tests/TenantDatabaseManagerTest.php
+++ b/tests/TenantDatabaseManagerTest.php
@@ -194,7 +194,11 @@ class TenantDatabaseManagerTest extends TestCase
]);
tenancy()->initialize($tenant);
- $this->assertSame($tenant->database()->getName(), config('database.connections.' . config('database.default') . '.schema'));
+ $schemaConfig = version_compare(app()->version(), '9.0', '>=') ?
+ config('database.connections.' . config('database.default') . '.search_path') :
+ config('database.connections.' . config('database.default') . '.schema');
+
+ $this->assertSame($tenant->database()->getName(), $schemaConfig);
$this->assertSame($originalDatabaseName, config(['database.connections.pgsql.database']));
}
From 0569bf5a3495a194079540f8c7096ab6aac68117 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Samuel=20=C5=A0tancl?=
Date: Sun, 1 May 2022 12:56:25 +0200
Subject: [PATCH 27/30] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index f4d28288..95fb7c60 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
-
+
From a1c34421488b8eb4c5776cbf457f0719c79d742e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Samuel=20=C5=A0tancl?=
Date: Sun, 15 May 2022 13:32:09 +0200
Subject: [PATCH 28/30] Resolve #854
---
src/Database/Concerns/BelongsToTenant.php | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/Database/Concerns/BelongsToTenant.php b/src/Database/Concerns/BelongsToTenant.php
index 5410758d..fc899411 100644
--- a/src/Database/Concerns/BelongsToTenant.php
+++ b/src/Database/Concerns/BelongsToTenant.php
@@ -16,7 +16,7 @@ trait BelongsToTenant
public function tenant()
{
- return $this->belongsTo(config('tenancy.tenant_model'), BelongsToTenant::$tenantIdColumn);
+ return $this->belongsTo(config('tenancy.tenant_model'), static::$tenantIdColumn);
}
public static function bootBelongsToTenant()
@@ -24,9 +24,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());
}
}
From 4d95e88e272d5c3f4beebd10a8e549d17259a079 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Samuel=20=C5=A0tancl?=
Date: Sun, 15 May 2022 13:45:54 +0200
Subject: [PATCH 29/30] Revert "Resolve #854"
This reverts commit a1c34421488b8eb4c5776cbf457f0719c79d742e.
---
src/Database/Concerns/BelongsToTenant.php | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/Database/Concerns/BelongsToTenant.php b/src/Database/Concerns/BelongsToTenant.php
index fc899411..5410758d 100644
--- a/src/Database/Concerns/BelongsToTenant.php
+++ b/src/Database/Concerns/BelongsToTenant.php
@@ -16,7 +16,7 @@ trait BelongsToTenant
public function tenant()
{
- return $this->belongsTo(config('tenancy.tenant_model'), static::$tenantIdColumn);
+ return $this->belongsTo(config('tenancy.tenant_model'), BelongsToTenant::$tenantIdColumn);
}
public static function bootBelongsToTenant()
@@ -24,9 +24,9 @@ trait BelongsToTenant
static::addGlobalScope(new TenantScope);
static::creating(function ($model) {
- if (! $model->getAttribute(static::$tenantIdColumn) && ! $model->relationLoaded('tenant')) {
+ if (! $model->getAttribute(BelongsToTenant::$tenantIdColumn) && ! $model->relationLoaded('tenant')) {
if (tenancy()->initialized) {
- $model->setAttribute(static::$tenantIdColumn, tenant()->getTenantKey());
+ $model->setAttribute(BelongsToTenant::$tenantIdColumn, tenant()->getTenantKey());
$model->setRelation('tenant', tenant());
}
}
From 51228defc68a6362e31f486a04e3117105abf3e0 Mon Sep 17 00:00:00 2001
From: Vincent GS
Date: Thu, 26 May 2022 04:51:27 -0500
Subject: [PATCH 30/30] [3.x][Filesystem] Provide an additional argument for
tenant name path (#817)
* Let the user pass the tenant suffix by %tenant%
In this PR we let the user pass an additional parameter using `%tenant%` so the user can additionally pass the folder corresponding to each tenant.
This is my proposal, because if I try to use %storage_path% within Linux, I get the full path to the project when I use Google Cloud Storage
* Missing missing updates
Moving from $subject to $root when %storage_path% has been replaced
---
src/Bootstrappers/FilesystemTenancyBootstrapper.php | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/src/Bootstrappers/FilesystemTenancyBootstrapper.php b/src/Bootstrappers/FilesystemTenancyBootstrapper.php
index da1e5e2a..346892b3 100644
--- a/src/Bootstrappers/FilesystemTenancyBootstrapper.php
+++ b/src/Bootstrappers/FilesystemTenancyBootstrapper.php
@@ -5,7 +5,6 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Bootstrappers;
use Illuminate\Contracts\Foundation\Application;
-use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Facades\Storage;
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
use Stancl\Tenancy\Contracts\Tenant;
@@ -61,8 +60,8 @@ class FilesystemTenancyBootstrapper implements TenancyBootstrapper
$this->originalPaths['disks'][$disk] = $originalRoot;
$finalPrefix = str_replace(
- '%storage_path%',
- storage_path(),
+ ['%storage_path%', '%tenant%'],
+ [storage_path(), $tenant->getTenantKey()],
$this->app['config']["tenancy.filesystem.root_override.{$disk}"] ?? '',
);