mirror of
https://github.com/archtechx/tenancy.git
synced 2026-03-21 21:04:05 +00:00
[4.x] Laravel 13 support (#1443)
- Update ci.yml and composer.json - Wrap single database tenancy trait scopes in whenBooted() - Update SessionSeparationTest to use laravel-cache- prefix in L13 and laravel_cache_ in <=L12. Our own prefix remains tenant_%tenant%_ (as configured in tenancy.cache.prefix). We could update this to be tenant-%tenant%- from now on for consistency with Laravel's prefixes (changed in https://github.com/laravel/framework/pull/56172) but I'm not sure yet. _ seems to read a bit better but perhaps consistency is more important. We may change this later and it can be adjusted in userland easily (since it's just a config option).
This commit is contained in:
parent
8f3ea6297f
commit
c4960b76cb
5 changed files with 52 additions and 18 deletions
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
|
|
@ -17,6 +17,7 @@ jobs:
|
|||
matrix:
|
||||
include:
|
||||
- laravel: "^12.0"
|
||||
- laravel: "^13.0"
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
|
|
|||
|
|
@ -18,17 +18,17 @@
|
|||
"require": {
|
||||
"php": "^8.4",
|
||||
"ext-json": "*",
|
||||
"illuminate/support": "^12.0",
|
||||
"laravel/tinker": "^2.0",
|
||||
"illuminate/support": "^12.0|^13.0",
|
||||
"laravel/tinker": "^2.0|^3.0",
|
||||
"ramsey/uuid": "^4.7.3",
|
||||
"stancl/jobpipeline": "2.0.0-rc6",
|
||||
"stancl/jobpipeline": "2.0.0-rc7",
|
||||
"stancl/virtualcolumn": "^1.5.0",
|
||||
"spatie/invade": "*",
|
||||
"laravel/prompts": "0.*"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/framework": "^12.0",
|
||||
"orchestra/testbench": "^10.0",
|
||||
"laravel/framework": "^13.0",
|
||||
"orchestra/testbench": "^10.0|^11.0",
|
||||
"league/flysystem-aws-s3-v3": "^3.12.2",
|
||||
"doctrine/dbal": "^3.6.0",
|
||||
"spatie/valuestore": "^1.2.5",
|
||||
|
|
|
|||
|
|
@ -12,6 +12,17 @@ trait BelongsToPrimaryModel
|
|||
abstract public function getRelationshipToPrimaryModel(): string;
|
||||
|
||||
public static function bootBelongsToPrimaryModel(): void
|
||||
{
|
||||
if (method_exists(static::class, 'whenBooted')) {
|
||||
// Laravel 13
|
||||
// For context see https://github.com/calebporzio/sushi/commit/62ff7f432cac736cb1da9f46d8f471cb78914b92
|
||||
static::whenBooted(fn () => static::configureBelongsToPrimaryModelScope());
|
||||
} else {
|
||||
static::configureBelongsToPrimaryModelScope();
|
||||
}
|
||||
}
|
||||
|
||||
protected static function configureBelongsToPrimaryModelScope()
|
||||
{
|
||||
$implicitRLS = config('tenancy.rls.manager') === TraitRLSManager::class && TraitRLSManager::$implicitRLS;
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,17 @@ trait BelongsToTenant
|
|||
}
|
||||
|
||||
public static function bootBelongsToTenant(): void
|
||||
{
|
||||
if (method_exists(static::class, 'whenBooted')) {
|
||||
// Laravel 13
|
||||
// For context see https://github.com/calebporzio/sushi/commit/62ff7f432cac736cb1da9f46d8f471cb78914b92
|
||||
static::whenBooted(fn () => static::configureBelongsToTenantScope());
|
||||
} else {
|
||||
static::configureBelongsToTenantScope();
|
||||
}
|
||||
}
|
||||
|
||||
protected static function configureBelongsToTenantScope(): void
|
||||
{
|
||||
// If TraitRLSManager::$implicitRLS is true or this model implements RLSModel
|
||||
// Postgres RLS is used for scoping, so we don't enable the scope used with single-database tenancy.
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ use Stancl\Tenancy\Listeners\RevertToCentralContext;
|
|||
use Stancl\Tenancy\Middleware\InitializeTenancyByPath;
|
||||
use Stancl\Tenancy\Middleware\PreventAccessFromUnwantedDomains;
|
||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
// todo@tests write similar low-level tests for the cache bootstrapper? including the database driver in a single-db setup
|
||||
|
|
@ -100,7 +101,7 @@ test('redis sessions are separated using the redis bootstrapper', function (bool
|
|||
expect($redisClient->getOption($redisClient::OPT_PREFIX) === "tenant_{$tenant->id}_")->toBe($bootstrappedEnabled);
|
||||
|
||||
expect(array_filter(Redis::keys('*'), function (string $key) use ($tenant) {
|
||||
return str($key)->startsWith("tenant_{$tenant->id}_laravel_cache_");
|
||||
return str($key)->startsWith(formatLaravelCacheKey(prefix: "tenant_{$tenant->id}_"));
|
||||
}))->toHaveCount($bootstrappedEnabled ? 1 : 0);
|
||||
})->with([true, false]);
|
||||
|
||||
|
|
@ -118,13 +119,13 @@ test('redis sessions are separated using the cache bootstrapper', function (bool
|
|||
Route::middleware(StartSession::class, InitializeTenancyByPath::class)->get('/{tenant}/foo', fn () => 'bar');
|
||||
pest()->get("/{$tenant->id}/foo");
|
||||
|
||||
expect(app('session')->driver()->getHandler()->getCache()->getStore()->getPrefix() === "laravel_cache_tenant_{$tenant->id}_")->toBe($scopeSessions);
|
||||
expect(app('session')->driver()->getHandler()->getCache()->getStore()->getPrefix() === formatLaravelCacheKey("tenant_{$tenant->id}_"))->toBe($scopeSessions);
|
||||
|
||||
tenancy()->end();
|
||||
expect(app('session')->driver()->getHandler()->getCache()->getStore()->getPrefix())->toBe('laravel_cache_');
|
||||
expect(app('session')->driver()->getHandler()->getCache()->getStore()->getPrefix())->toBe(formatLaravelCacheKey());
|
||||
|
||||
expect(array_filter(Redis::keys('*'), function (string $key) use ($tenant) {
|
||||
return str($key)->startsWith("foolaravel_cache_tenant_{$tenant->id}");
|
||||
return str($key)->startsWith(formatLaravelCacheKey(prefix: 'foo', suffix: "tenant_{$tenant->id}"));
|
||||
}))->toHaveCount($scopeSessions ? 1 : 0);
|
||||
})->with([true, false]);
|
||||
|
||||
|
|
@ -148,14 +149,14 @@ test('memcached sessions are separated using the cache bootstrapper', function (
|
|||
Route::middleware(StartSession::class, InitializeTenancyByPath::class)->get('/{tenant}/foo', fn () => 'bar');
|
||||
pest()->get("/{$tenant->id}/foo");
|
||||
|
||||
expect(app('session')->driver()->getHandler()->getCache()->getStore()->getPrefix() === "laravel_cache_tenant_{$tenant->id}_")->toBe($scopeSessions);
|
||||
expect(app('session')->driver()->getHandler()->getCache()->getStore()->getPrefix() === formatLaravelCacheKey("tenant_{$tenant->id}_"))->toBe($scopeSessions);
|
||||
|
||||
tenancy()->end();
|
||||
expect(app('session')->driver()->getHandler()->getCache()->getStore()->getPrefix())->toBe('laravel_cache_');
|
||||
expect(app('session')->driver()->getHandler()->getCache()->getStore()->getPrefix())->toBe(formatLaravelCacheKey());
|
||||
|
||||
sleep(1.1); // 1s+ sleep is necessary for getAllKeys() to work. if this causes race conditions or we want to avoid the delay, we can refactor this to some type of a mock
|
||||
expect(array_filter($allMemcachedKeys(), function (string $key) use ($tenant) {
|
||||
return str($key)->startsWith("laravel_cache_tenant_{$tenant->id}");
|
||||
return str($key)->startsWith(formatLaravelCacheKey("tenant_{$tenant->id}"));
|
||||
}))->toHaveCount($scopeSessions ? 1 : 0);
|
||||
|
||||
Artisan::call('cache:clear memcached');
|
||||
|
|
@ -177,13 +178,13 @@ test('dynamodb sessions are separated using the cache bootstrapper', function (b
|
|||
Route::middleware(StartSession::class, InitializeTenancyByPath::class)->get('/{tenant}/foo', fn () => 'bar');
|
||||
pest()->get("/{$tenant->id}/foo");
|
||||
|
||||
expect(app('session')->driver()->getHandler()->getCache()->getStore()->getPrefix() === "laravel_cache_tenant_{$tenant->id}_")->toBe($scopeSessions);
|
||||
expect(app('session')->driver()->getHandler()->getCache()->getStore()->getPrefix() === formatLaravelCacheKey("tenant_{$tenant->id}_"))->toBe($scopeSessions);
|
||||
|
||||
tenancy()->end();
|
||||
expect(app('session')->driver()->getHandler()->getCache()->getStore()->getPrefix())->toBe('laravel_cache_');
|
||||
expect(app('session')->driver()->getHandler()->getCache()->getStore()->getPrefix())->toBe(formatLaravelCacheKey());
|
||||
|
||||
expect(array_filter($allDynamodbKeys(), function (string $key) use ($tenant) {
|
||||
return str($key)->startsWith("laravel_cache_tenant_{$tenant->id}");
|
||||
return str($key)->startsWith(formatLaravelCacheKey("tenant_{$tenant->id}"));
|
||||
}))->toHaveCount($scopeSessions ? 1 : 0);
|
||||
})->with([true, false]);
|
||||
|
||||
|
|
@ -202,13 +203,13 @@ test('apc sessions are separated using the cache bootstrapper', function (bool $
|
|||
Route::middleware(StartSession::class, InitializeTenancyByPath::class)->get('/{tenant}/foo', fn () => 'bar');
|
||||
pest()->get("/{$tenant->id}/foo");
|
||||
|
||||
expect(app('session')->driver()->getHandler()->getCache()->getStore()->getPrefix() === "laravel_cache_tenant_{$tenant->id}_")->toBe($scopeSessions);
|
||||
expect(app('session')->driver()->getHandler()->getCache()->getStore()->getPrefix() === formatLaravelCacheKey("tenant_{$tenant->id}_"))->toBe($scopeSessions);
|
||||
|
||||
tenancy()->end();
|
||||
expect(app('session')->driver()->getHandler()->getCache()->getStore()->getPrefix())->toBe('laravel_cache_');
|
||||
expect(app('session')->driver()->getHandler()->getCache()->getStore()->getPrefix())->toBe(formatLaravelCacheKey());
|
||||
|
||||
expect(array_filter($allApcuKeys(), function (string $key) use ($tenant) {
|
||||
return str($key)->startsWith("laravel_cache_tenant_{$tenant->id}");
|
||||
return str($key)->startsWith(formatLaravelCacheKey("tenant_{$tenant->id}"));
|
||||
}))->toHaveCount($scopeSessions ? 1 : 0);
|
||||
})->with([true, false]);
|
||||
|
||||
|
|
@ -250,3 +251,13 @@ test('database sessions are separated regardless of whether the session bootstra
|
|||
// [false, true], // when the connection IS set, the session bootstrapper becomes necessary
|
||||
[false, false],
|
||||
]);
|
||||
|
||||
function formatLaravelCacheKey(string $suffix = '', string $prefix = ''): string
|
||||
{
|
||||
// todo@release if we drop Laravel 12 support we can just switch to - syntax everywhere
|
||||
if (version_compare(app()->version(), '13.0.0') >= 0) {
|
||||
return $prefix . 'laravel-cache-' . $suffix;
|
||||
} else {
|
||||
return $prefix . 'laravel_cache_' . $suffix;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue