1
0
Fork 0
mirror of https://github.com/archtechx/tenancy.git synced 2026-08-06 10:44:04 +00:00

Fix scopeCache() discarding configured cache store paths

scopeCache() rewrote path and lock_path for every file-driver store to a hardcoded '<storage>/framework/cache/data' path, completely ignoring the store's config. Now, scopeCache() remembers each store's original path and lock_path, scopes these paths for the tenant, and restores them to the stored originals on revert.

The store's lock_path was always overwritten by the same hardcoded path. But lock_path is configurable too, AND it's actually optional (unlike path). If it's not configured at all (= it's null or just unset), Laravel automatically falls back to the store's path. So in that case, leave lock_path null instead of assigning the path to it. This is not a *huge* change, assigning path to lock_path would essentially achieve the same thing, BUT if someone explicitly sets lock_path to null in the config, we should just respect that and let Laravel fall back to the path instead of setting the lock_path ourselves.

Also, on revert(), the same hardcoded path was used in scopeCache(). So if someone used a custom file driver-based store, cached something in central context, initialized and ended tenancy, the central cache got corrupt (see the 'central cache is not lost when tenancy ends' test).
This commit is contained in:
lukinovec 2026-07-30 15:57:40 +02:00
parent 04ab6c85c7
commit 483a3ec802

View file

@ -14,6 +14,21 @@ use Stancl\Tenancy\Contracts\Tenant;
class FilesystemTenancyBootstrapper implements TenancyBootstrapper
{
public array $originalDisks = [];
/**
* The path and lock_path each file cache store had in the central context, keyed by store name.
*
* For example:
* [
* 'file' => [
* 'path' => storage_path('framework/cache/data'),
* 'lock_path' => storage_path('framework/cache/data'),
* ],
* ]
*
* Used to scope the store to a tenant, and to restore it back to this when tenancy ends.
*/
protected array $originalCachePaths = [];
public string|null $originalAssetUrl;
public string $originalStoragePath;
@ -191,10 +206,6 @@ class FilesystemTenancyBootstrapper implements TenancyBootstrapper
return;
}
$storagePath = $suffix
? $this->tenantStoragePath($suffix)
: $this->originalStoragePath;
$stores = array_filter($this->app['config']['tenancy.cache.stores'], function ($name) {
$store = $this->app['config']["cache.stores.{$name}"];
@ -206,17 +217,62 @@ class FilesystemTenancyBootstrapper implements TenancyBootstrapper
});
foreach ($stores as $name) {
$path = $storagePath . '/framework/cache/data';
// Store the original configured cache paths, unless they're already stored --
// scopeCache() is also called in revert(), by which point the originals were
// already captured in bootstrap().
$this->originalCachePaths[$name] ??= [
'path' => $this->app['config']["cache.stores.{$name}.path"],
'lock_path' => $this->app['config']["cache.stores.{$name}.lock_path"],
];
$path = $this->scopeCachePath($this->originalCachePaths[$name]['path'], $suffix);
$lockPath = $this->originalCachePaths[$name]['lock_path'];
if ($lockPath !== null) {
// Unlike path, lock_path is optional -- if it's not set, FileStore::lock() falls
// back to path itself (see `$this->lockDirectory ?? $this->directory` in FileStore).
// Leave it null here rather than hardcoding it to $path ourselves, so a store that didn't
// configure a separate lock_path doesn't end up with one.
$lockPath = $this->scopeCachePath($lockPath, $suffix);
}
$this->app['config']["cache.stores.{$name}.path"] = $path;
$this->app['config']["cache.stores.{$name}.lock_path"] = $path;
$this->app['config']["cache.stores.{$name}.lock_path"] = $lockPath;
/** @var \Illuminate\Cache\FileStore $store */
$store = $this->app['cache']->store($name)->getStore();
$store->setDirectory($path);
$store->setLockDirectory($path);
$store->setLockDirectory($lockPath);
}
}
/**
* Return a configured path ($configuredPath is e.g. storage_path('framework/cache/data')),
* scoped to the current tenant when called from bootstrap(),
* or unchanged when called from revert() (= when $suffix is false).
*/
protected function scopeCachePath(string $configuredPath, string|false $suffix): string
{
if ($suffix === false) {
return $configuredPath;
}
if (str_starts_with($configuredPath, $this->originalStoragePath . '/')) {
// Swap the central storage path prefix for the tenant's.
// For example, storage_path('framework/cache/data') becomes storage_path('tenant1/framework/cache/data').
$scopedPath = str($configuredPath)
->after($this->originalStoragePath . '/')
->prepend($this->tenantStoragePath($suffix) . '/')
->toString();
} else {
// Append the tenant suffix so tenants don't share the same cache directory. $configuredPath
// isn't guaranteed to be storage_path()-based (e.g. it could point to a shared network
// mount used to keep the file cache off each server's local disk in a multi-server setup).
$scopedPath = rtrim($configuredPath, '/') . '/' . $suffix;
}
return $scopedPath;
}
public function scopeSessions(string|false $suffix): void
{
if (! $this->app['config']['tenancy.filesystem.scope_sessions']) {