Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
@@ -1,87 +1,185 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Filesystem;
|
||||
|
||||
use App\Framework\Async\FiberManager;
|
||||
use App\Framework\Filesystem\Exceptions\DirectoryCreateException;
|
||||
use App\Framework\Filesystem\Exceptions\DirectoryListException;
|
||||
use App\Framework\Filesystem\Exceptions\FileCopyException;
|
||||
use App\Framework\Filesystem\Exceptions\FileDeleteException;
|
||||
use App\Framework\Filesystem\Exceptions\FileMetadataException;
|
||||
use App\Framework\Filesystem\Exceptions\FileNotFoundException;
|
||||
use App\Framework\Filesystem\Exceptions\FilePermissionException;
|
||||
use App\Framework\Filesystem\Exceptions\FileReadException;
|
||||
use App\Framework\Filesystem\Exceptions\FileWriteException;
|
||||
use App\Framework\Filesystem\Traits\AppendableStorageTrait;
|
||||
use App\Framework\Filesystem\Traits\AtomicStorageTrait;
|
||||
use App\Framework\Filesystem\Traits\CompressibleStorageTrait;
|
||||
use App\Framework\Filesystem\Traits\LockableStorageTrait;
|
||||
use App\Framework\Filesystem\Traits\StreamableStorageTrait;
|
||||
|
||||
final readonly class FileStorage implements Storage
|
||||
final readonly class FileStorage implements Storage, AtomicStorage, AppendableStorage, StreamableStorage, LockableStorage, CompressibleStorage
|
||||
{
|
||||
use StorageTrait;
|
||||
use AtomicStorageTrait;
|
||||
use AppendableStorageTrait;
|
||||
use StreamableStorageTrait;
|
||||
use LockableStorageTrait;
|
||||
use CompressibleStorageTrait;
|
||||
|
||||
public readonly PermissionChecker $permissions;
|
||||
|
||||
public readonly FiberManager $fiberManager;
|
||||
|
||||
public function __construct(
|
||||
private string $basePath = '/',
|
||||
?FiberManager $fiberManager = null
|
||||
) {
|
||||
$this->permissions = new PermissionChecker($this);
|
||||
$this->fiberManager = $fiberManager ?? $this->createDefaultFiberManager();
|
||||
}
|
||||
|
||||
private function createDefaultFiberManager(): FiberManager
|
||||
{
|
||||
// Create minimal dependencies for FiberManager
|
||||
$clock = new \App\Framework\DateTime\SystemClock();
|
||||
$timer = new \App\Framework\DateTime\SystemTimer($clock);
|
||||
|
||||
return new FiberManager($clock, $timer);
|
||||
}
|
||||
|
||||
private function resolvePath(string $path): string
|
||||
{
|
||||
// Absolute path bleibt unverändert
|
||||
if (str_starts_with($path, '/')) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
// Relative paths werden an basePath gehängt
|
||||
return rtrim($this->basePath, '/') . '/' . ltrim($path, '/');
|
||||
}
|
||||
|
||||
public function get(string $path): string
|
||||
{
|
||||
if (!is_file($path)) {
|
||||
$resolvedPath = $this->resolvePath($path);
|
||||
if (! is_file($resolvedPath)) {
|
||||
throw new FileNotFoundException($path);
|
||||
}
|
||||
|
||||
$content = @file_get_contents($path);
|
||||
if (! is_readable($resolvedPath)) {
|
||||
throw FilePermissionException::read($path, 'File is not readable');
|
||||
}
|
||||
|
||||
$content = @file_get_contents($resolvedPath);
|
||||
if ($content === false) {
|
||||
$error = error_get_last();
|
||||
if ($error && str_contains($error['message'], 'Permission denied')) {
|
||||
throw FilePermissionException::read($path, $error['message']);
|
||||
}
|
||||
|
||||
throw new FileReadException($path);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
public function put(string $path, string $content): void
|
||||
{
|
||||
$dir = dirname($path);
|
||||
if (!is_dir($dir)) {
|
||||
if (!@mkdir($dir, 0777, true) && !is_dir($dir)) {
|
||||
$resolvedPath = $this->resolvePath($path);
|
||||
$dir = dirname($resolvedPath);
|
||||
|
||||
// Prüfe Directory-Permissions
|
||||
if (! is_dir($dir)) {
|
||||
if (! @mkdir($dir, 0777, true) && ! is_dir($dir)) {
|
||||
$error = error_get_last();
|
||||
if ($error && str_contains($error['message'], 'Permission denied')) {
|
||||
throw FilePermissionException::createDirectory($dir, $error['message']);
|
||||
}
|
||||
|
||||
throw new DirectoryCreateException($dir);
|
||||
}
|
||||
} elseif (! is_writable($dir)) {
|
||||
throw FilePermissionException::write($path, 'Directory is not writable: ' . $dir);
|
||||
}
|
||||
if (@file_put_contents($path, $content) === false) {
|
||||
|
||||
// Prüfe File-Permissions wenn Datei bereits existiert
|
||||
if (is_file($resolvedPath) && ! is_writable($resolvedPath)) {
|
||||
throw FilePermissionException::write($path, 'File is not writable');
|
||||
}
|
||||
|
||||
if (@file_put_contents($resolvedPath, $content) === false) {
|
||||
$error = error_get_last();
|
||||
if ($error && str_contains($error['message'], 'Permission denied')) {
|
||||
throw FilePermissionException::write($path, $error['message']);
|
||||
}
|
||||
|
||||
throw new FileWriteException($path);
|
||||
}
|
||||
}
|
||||
|
||||
public function exists(string $path): bool
|
||||
{
|
||||
return is_file($path);
|
||||
return is_file($this->resolvePath($path));
|
||||
}
|
||||
|
||||
public function delete(string $path): void
|
||||
{
|
||||
if (!is_file($path)) {
|
||||
$resolvedPath = $this->resolvePath($path);
|
||||
if (! is_file($resolvedPath)) {
|
||||
throw new FileNotFoundException($path);
|
||||
}
|
||||
|
||||
if (!@unlink($path)) {
|
||||
// Prüfe Directory-Permissions
|
||||
$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');
|
||||
}
|
||||
|
||||
if (! @unlink($resolvedPath)) {
|
||||
$error = error_get_last();
|
||||
if ($error && str_contains($error['message'], 'Permission denied')) {
|
||||
throw FilePermissionException::delete($path, $error['message']);
|
||||
}
|
||||
|
||||
throw new FileDeleteException($path);
|
||||
}
|
||||
}
|
||||
|
||||
public function copy(string $source, string $destination): void
|
||||
{
|
||||
if (!is_file($source)) {
|
||||
$resolvedSource = $this->resolvePath($source);
|
||||
$resolvedDestination = $this->resolvePath($destination);
|
||||
|
||||
if (! is_file($resolvedSource)) {
|
||||
throw new FileNotFoundException($source);
|
||||
}
|
||||
|
||||
// Stelle sicher, dass das Zielverzeichnis existiert
|
||||
$dir = dirname($destination);
|
||||
if (!is_dir($dir)) {
|
||||
$this->createDirectory($dir);
|
||||
$dir = dirname($resolvedDestination);
|
||||
if (! is_dir($dir)) {
|
||||
$this->createDirectory($destination);
|
||||
}
|
||||
|
||||
if (!@copy($source, $destination)) {
|
||||
if (! @copy($resolvedSource, $resolvedDestination)) {
|
||||
throw new FileCopyException($source, $destination);
|
||||
}
|
||||
}
|
||||
|
||||
public function size(string $path): int
|
||||
{
|
||||
if (!is_file($path)) {
|
||||
$resolvedPath = $this->resolvePath($path);
|
||||
if (! is_file($resolvedPath)) {
|
||||
throw new FileNotFoundException($path);
|
||||
}
|
||||
|
||||
$size = @filesize($path);
|
||||
$size = @filesize($resolvedPath);
|
||||
if ($size === false) {
|
||||
throw new FileReadException($path);
|
||||
}
|
||||
@@ -91,11 +189,12 @@ final readonly class FileStorage implements Storage
|
||||
|
||||
public function lastModified(string $path): int
|
||||
{
|
||||
if (!is_file($path)) {
|
||||
$resolvedPath = $this->resolvePath($path);
|
||||
if (! is_file($resolvedPath)) {
|
||||
throw new FileNotFoundException($path);
|
||||
}
|
||||
|
||||
$time = @filemtime($path);
|
||||
$time = @filemtime($resolvedPath);
|
||||
if ($time === false) {
|
||||
throw new FileReadException($path);
|
||||
}
|
||||
@@ -105,11 +204,12 @@ final readonly class FileStorage implements Storage
|
||||
|
||||
public function getMimeType(string $path): string
|
||||
{
|
||||
if (!is_file($path)) {
|
||||
$resolvedPath = $this->resolvePath($path);
|
||||
if (! is_file($resolvedPath)) {
|
||||
throw new FileNotFoundException($path);
|
||||
}
|
||||
|
||||
$mimeType = @mime_content_type($path);
|
||||
$mimeType = @mime_content_type($resolvedPath);
|
||||
if ($mimeType === false) {
|
||||
throw new FileMetadataException($path);
|
||||
}
|
||||
@@ -119,29 +219,32 @@ final readonly class FileStorage implements Storage
|
||||
|
||||
public function isReadable(string $path): bool
|
||||
{
|
||||
if (!is_file($path)) {
|
||||
$resolvedPath = $this->resolvePath($path);
|
||||
if (! is_file($resolvedPath)) {
|
||||
throw new FileNotFoundException($path);
|
||||
}
|
||||
|
||||
return is_readable($path);
|
||||
return is_readable($resolvedPath);
|
||||
}
|
||||
|
||||
public function isWritable(string $path): bool
|
||||
{
|
||||
if (!is_file($path)) {
|
||||
$resolvedPath = $this->resolvePath($path);
|
||||
if (! is_file($resolvedPath)) {
|
||||
throw new FileNotFoundException($path);
|
||||
}
|
||||
|
||||
return is_writable($path);
|
||||
return is_writable($resolvedPath);
|
||||
}
|
||||
|
||||
public function listDirectory(string $directory): array
|
||||
{
|
||||
if (!is_dir($directory)) {
|
||||
$resolvedDirectory = $this->resolvePath($directory);
|
||||
if (! is_dir($resolvedDirectory)) {
|
||||
throw new DirectoryCreateException($directory);
|
||||
}
|
||||
|
||||
$files = @scandir($directory);
|
||||
$files = @scandir($resolvedDirectory);
|
||||
if ($files === false) {
|
||||
throw new DirectoryListException($directory);
|
||||
}
|
||||
@@ -151,10 +254,15 @@ final readonly class FileStorage implements Storage
|
||||
return $file !== '.' && $file !== '..';
|
||||
});
|
||||
|
||||
// Vollständige Pfade erstellen
|
||||
// Relative Pfade zurückgeben (ohne basePath)
|
||||
$result = [];
|
||||
foreach ($files as $file) {
|
||||
$result[] = $directory . DIRECTORY_SEPARATOR . $file;
|
||||
$fullPath = $resolvedDirectory . DIRECTORY_SEPARATOR . $file;
|
||||
// Entferne basePath für relativen Pfad
|
||||
$relativePath = str_starts_with($fullPath, $this->basePath)
|
||||
? substr($fullPath, strlen(rtrim($this->basePath, '/')) + 1)
|
||||
: $fullPath;
|
||||
$result[] = $relativePath;
|
||||
}
|
||||
|
||||
return array_values($result);
|
||||
@@ -162,12 +270,67 @@ final readonly class FileStorage implements Storage
|
||||
|
||||
public function createDirectory(string $path, int $permissions = 0755, bool $recursive = true): void
|
||||
{
|
||||
if (is_dir($path)) {
|
||||
$resolvedPath = $this->resolvePath($path);
|
||||
if (is_dir($resolvedPath)) {
|
||||
return; // Verzeichnis existiert bereits
|
||||
}
|
||||
|
||||
if (!@mkdir($path, $permissions, $recursive) && !is_dir($path)) {
|
||||
// Prüfe Parent-Directory Permissions
|
||||
$parentDir = dirname($resolvedPath);
|
||||
if (is_dir($parentDir) && ! is_writable($parentDir)) {
|
||||
throw FilePermissionException::createDirectory($path, 'Parent directory is not writable: ' . $parentDir);
|
||||
}
|
||||
|
||||
if (! @mkdir($resolvedPath, $permissions, $recursive) && ! is_dir($resolvedPath)) {
|
||||
$error = error_get_last();
|
||||
if ($error && str_contains($error['message'], 'Permission denied')) {
|
||||
throw FilePermissionException::createDirectory($path, $error['message']);
|
||||
}
|
||||
|
||||
throw new DirectoryCreateException($path);
|
||||
}
|
||||
}
|
||||
|
||||
// Batch-Operationen für explizite Parallelverarbeitung
|
||||
|
||||
public function batch(array $operations): array
|
||||
{
|
||||
return $this->fiberManager->batch($operations);
|
||||
}
|
||||
|
||||
public function getMultiple(array $paths): array
|
||||
{
|
||||
$operations = [];
|
||||
foreach ($paths as $path) {
|
||||
$operations[$path] = fn () => $this->get($path);
|
||||
}
|
||||
|
||||
return $this->batch($operations);
|
||||
}
|
||||
|
||||
public function putMultiple(array $files): void
|
||||
{
|
||||
$operations = [];
|
||||
foreach ($files as $path => $content) {
|
||||
$operations[$path] = fn () => $this->put($path, $content);
|
||||
}
|
||||
|
||||
$this->batch($operations);
|
||||
}
|
||||
|
||||
public function getMetadataMultiple(array $paths): array
|
||||
{
|
||||
$operations = [];
|
||||
foreach ($paths as $path) {
|
||||
$operations[$path] = fn () => new FileMetadata(
|
||||
size: $this->size($path),
|
||||
lastModified: $this->lastModified($path),
|
||||
mimeType: $this->getMimeType($path),
|
||||
isReadable: $this->isReadable($path),
|
||||
isWritable: $this->isWritable($path)
|
||||
);
|
||||
}
|
||||
|
||||
return $this->batch($operations);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user