docs(cache): add comprehensive cache configuration and permission handling guides

- Introduce `cache-configuration.md` for detailed instructions on cache setup, permission troubleshooting, and best practices.
- Add `cache-permissions-quick-fix.md` for concise resolutions to common permission errors.
- Include a detailed `FILECACHE_PERMISSION_FIX_PLAN.md` outlining solutions for permission-related issues.
- Enhance `docker-entrypoint.sh` with permission fixes for multi-user caches.
- Update `Makefile` with cache clear commands for local and staging environments.
- Improve `FileCache` for graceful degradation on permission errors, ensuring reliability under multi-user scenarios.
This commit is contained in:
2025-11-03 23:54:27 +01:00
parent a1242f776e
commit 56f09b5001
8 changed files with 610 additions and 11 deletions

View File

@@ -190,8 +190,16 @@ final readonly class FileCache implements CacheDriver, Scannable
// File expired - try to delete it
try {
$this->fileSystem->delete($fullPath);
} catch (\App\Framework\Filesystem\Exceptions\FilePermissionException $e) {
// Permission denied - continue (graceful degradation)
// Datei wird beim n?chsten Cleanup oder manuell gel?scht
continue;
} catch (\App\Framework\Filesystem\Exceptions\FileNotFoundException $e) {
// File already deleted - continue
continue;
} catch (\Throwable $e) {
// Continue even if deletion fails
// Any other error - continue (graceful degradation)
continue;
}
continue;
@@ -221,10 +229,15 @@ final readonly class FileCache implements CacheDriver, Scannable
}
if ($content === null || $content === '') {
// Empty file - try to delete it
try {
$this->fileSystem->delete($bestFile);
} catch (\App\Framework\Filesystem\Exceptions\FilePermissionException $e) {
// Permission denied - treat as miss (graceful degradation)
} catch (\App\Framework\Filesystem\Exceptions\FileNotFoundException $e) {
// File already deleted - treat as miss
} catch (\Throwable $e) {
// Continue even if deletion fails
// Any other error - treat as miss (graceful degradation)
}
return CacheItem::miss($key);
@@ -259,8 +272,21 @@ final readonly class FileCache implements CacheDriver, Scannable
$ttlSeconds = $item->ttl !== null ? $item->ttl->toCacheSeconds() : null;
$expiresAt = $ttlSeconds ? (time() + $ttlSeconds) : null;
// Delete old cache files for this key (graceful degradation)
foreach ($this->getFilesForKey($item->key) as $file) {
$this->fileSystem->delete($file);
try {
$this->fileSystem->delete($file);
} catch (\App\Framework\Filesystem\Exceptions\FilePermissionException $e) {
// Permission denied - continue (graceful degradation)
// Alte Datei bleibt, wird beim n?chsten Cleanup gel?scht
continue;
} catch (\App\Framework\Filesystem\Exceptions\FileNotFoundException $e) {
// File already deleted - continue
continue;
} catch (\Throwable $e) {
// Any other error - continue (graceful degradation)
continue;
}
}
$file = $this->getFileName($item->key, $expiresAt);
@@ -302,10 +328,25 @@ final readonly class FileCache implements CacheDriver, Scannable
$success = true;
foreach ($keys as $key) {
$result = $this->withKeyLock($key, function () use ($key) {
$deletedAny = false;
foreach ($this->getFilesForKey($key) as $file) {
$this->fileSystem->delete($file);
try {
$this->fileSystem->delete($file);
$deletedAny = true;
} catch (\App\Framework\Filesystem\Exceptions\FilePermissionException $e) {
// Permission denied - continue (graceful degradation)
// Datei bleibt, wird beim n?chsten Cleanup gel?scht
continue;
} catch (\App\Framework\Filesystem\Exceptions\FileNotFoundException $e) {
// File already deleted - continue
continue;
} catch (\Throwable $e) {
// Any other error - continue (graceful degradation)
continue;
}
}
// Return true if at least one file was deleted or no files existed
return true;
});
@@ -329,14 +370,21 @@ final readonly class FileCache implements CacheDriver, Scannable
return str_ends_with($file, '.cache.php');
});
// Delete all cache files
// Delete all cache files (graceful degradation)
foreach ($cacheFiles as $file) {
try {
// Convert to absolute path if needed
$fullPath = str_starts_with($file, '/') ? $file : self::CACHE_PATH . DIRECTORY_SEPARATOR . $file;
$this->fileSystem->delete($fullPath);
} catch (\App\Framework\Filesystem\Exceptions\FilePermissionException $e) {
// Permission denied - continue with other files (graceful degradation)
// Diese Dateien bleiben, werden beim n?chsten Cleanup gel?scht
continue;
} catch (\App\Framework\Filesystem\Exceptions\FileNotFoundException $e) {
// File already deleted - continue
continue;
} catch (\Throwable $e) {
// Continue with other files even if one fails
// Any other error - continue with other files (graceful degradation)
continue;
}
}

View File

@@ -213,15 +213,18 @@ final readonly class FileStorage implements Storage, AtomicStorage, AppendableSt
}
// Prüfe Directory-Permissions
// WICHTIG: Für unlink() braucht man nur Schreibrechte im Verzeichnis, nicht auf der Datei selbst!
$dir = dirname($resolvedPath);
if (! is_writable($dir)) {
throw FilePermissionException::delete($path, 'Directory is not writable: ' . $dir);
}
// Prüfe File-Permissions
if (! is_writable($resolvedPath)) {
throw FilePermissionException::delete($path, 'File is not writable');
}
// ENTFERNT: Prüfe File-Permissions
// Für unlink() benötigt man nur Schreibrechte im Parent-Verzeichnis, nicht auf der Datei selbst.
// Dies ist ein Unix-Standard-Verhalten: unlink() entfernt einen Directory-Eintrag, nicht die Datei selbst.
// if (! is_writable($resolvedPath)) {
// throw FilePermissionException::delete($path, 'File is not writable');
// }
if (! @unlink($resolvedPath)) {
$error = error_get_last();