chore: complete update
This commit is contained in:
122
src/Framework/Filesystem/Directory.php
Normal file
122
src/Framework/Filesystem/Directory.php
Normal file
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Filesystem;
|
||||
|
||||
/**
|
||||
* Repräsentiert ein Verzeichnis im Dateisystem mit Lazy-Loading-Unterstützung.
|
||||
*
|
||||
* @property-read string $path Pfad zum Verzeichnis
|
||||
* @property-read Storage $storage Storage-Implementierung
|
||||
* @property-read array $contents Verzeichnisinhalt (lazy geladen)
|
||||
*/
|
||||
final readonly class Directory
|
||||
{
|
||||
public function __construct(
|
||||
public string $path,
|
||||
public Storage $storage,
|
||||
public array $contents = []
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Prüft, ob das Verzeichnis existiert
|
||||
*/
|
||||
public function exists(): bool
|
||||
{
|
||||
return is_dir($this->path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt das Verzeichnis, falls es nicht existiert
|
||||
*/
|
||||
public function create(int $permissions = 0755, bool $recursive = true): void
|
||||
{
|
||||
$this->storage->createDirectory($this->path, $permissions, $recursive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt alle Dateien im Verzeichnis zurück
|
||||
*
|
||||
* @return File[] Liste von Dateiobjekten
|
||||
*/
|
||||
public function getFiles(): array
|
||||
{
|
||||
$paths = $this->storage->listDirectory($this->path);
|
||||
$files = [];
|
||||
|
||||
foreach ($paths as $path) {
|
||||
if (is_file($path)) {
|
||||
$files[] = FilesystemFactory::createFile($path, $this->storage);
|
||||
}
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt alle Unterverzeichnisse zurück
|
||||
*
|
||||
* @return Directory[] Liste von Verzeichnisobjekten
|
||||
*/
|
||||
public function getDirectories(): array
|
||||
{
|
||||
$paths = $this->storage->listDirectory($this->path);
|
||||
$directories = [];
|
||||
|
||||
foreach ($paths as $path) {
|
||||
if (is_dir($path)) {
|
||||
$directories[] = FilesystemFactory::createDirectory($path, $this->storage);
|
||||
}
|
||||
}
|
||||
|
||||
return $directories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt alle Dateien und Unterverzeichnisse in einem Durchgang zurück
|
||||
*
|
||||
* @return array{files: File[], directories: Directory[]}
|
||||
*/
|
||||
public function getContents(): array
|
||||
{
|
||||
$paths = $this->storage->listDirectory($this->path);
|
||||
$files = [];
|
||||
$directories = [];
|
||||
|
||||
foreach ($paths as $path) {
|
||||
if (is_file($path)) {
|
||||
$files[] = FilesystemFactory::createFile($path, $this->storage);
|
||||
} elseif (is_dir($path)) {
|
||||
$directories[] = FilesystemFactory::createDirectory($path, $this->storage);
|
||||
}
|
||||
}
|
||||
|
||||
return ['files' => $files, 'directories' => $directories];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt eine Datei im Verzeichnis zurück
|
||||
*/
|
||||
public function getFile(string $filename): File
|
||||
{
|
||||
$path = $this->path . DIRECTORY_SEPARATOR . $filename;
|
||||
return FilesystemFactory::createFile($path, $this->storage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt ein Unterverzeichnis zurück
|
||||
*/
|
||||
public function getDirectory(string $name): Directory
|
||||
{
|
||||
$path = $this->path . DIRECTORY_SEPARATOR . $name;
|
||||
return FilesystemFactory::createDirectory($path, $this->storage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lädt den Verzeichnisinhalt neu
|
||||
*/
|
||||
public function refresh(): Directory
|
||||
{
|
||||
return FilesystemFactory::createDirectory($this->path, $this->storage);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Filesystem\Exceptions;
|
||||
|
||||
use App\Framework\Exception\FrameworkException;
|
||||
|
||||
final class DirectoryCreateException extends FrameworkException
|
||||
{
|
||||
public function __construct(
|
||||
string $directory,
|
||||
int $code = 0,
|
||||
?\Throwable $previous = null
|
||||
) {
|
||||
parent::__construct(
|
||||
message: "Ordner '$directory' konnte nicht angelegt werden.",
|
||||
code: $code,
|
||||
previous: $previous,
|
||||
context: ['directory' => $directory]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Filesystem\Exceptions;
|
||||
|
||||
use App\Framework\Exception\FrameworkException;
|
||||
|
||||
final class DirectoryListException extends FrameworkException
|
||||
{
|
||||
public function __construct(
|
||||
string $directory,
|
||||
int $code = 0,
|
||||
?\Throwable $previous = null
|
||||
) {
|
||||
parent::__construct(
|
||||
message: "Fehler beim Auslesen des Verzeichnisses '$directory'.",
|
||||
code: $code,
|
||||
previous: $previous,
|
||||
context: ['directory' => $directory]
|
||||
);
|
||||
}
|
||||
}
|
||||
27
src/Framework/Filesystem/Exceptions/FileCopyException.php
Normal file
27
src/Framework/Filesystem/Exceptions/FileCopyException.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Filesystem\Exceptions;
|
||||
|
||||
use App\Framework\Exception\FrameworkException;
|
||||
|
||||
final class FileCopyException extends FrameworkException
|
||||
{
|
||||
public function __construct(
|
||||
string $source,
|
||||
string $destination,
|
||||
int $code = 0,
|
||||
?\Throwable $previous = null
|
||||
) {
|
||||
parent::__construct(
|
||||
message: "Fehler beim Kopieren von '$source' nach '$destination'.",
|
||||
code: $code,
|
||||
previous: $previous,
|
||||
context: [
|
||||
'source' => $source,
|
||||
'destination' => $destination
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
23
src/Framework/Filesystem/Exceptions/FileDeleteException.php
Normal file
23
src/Framework/Filesystem/Exceptions/FileDeleteException.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Filesystem\Exceptions;
|
||||
|
||||
use App\Framework\Exception\FrameworkException;
|
||||
|
||||
final class FileDeleteException extends FrameworkException
|
||||
{
|
||||
public function __construct(
|
||||
string $path,
|
||||
int $code = 0,
|
||||
?\Throwable $previous = null
|
||||
) {
|
||||
parent::__construct(
|
||||
message: "Fehler beim Löschen der Datei '$path'.",
|
||||
code: $code,
|
||||
previous: $previous,
|
||||
context: ['path' => $path]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Filesystem\Exceptions;
|
||||
|
||||
use App\Framework\Exception\FrameworkException;
|
||||
|
||||
final class FileMetadataException extends FrameworkException
|
||||
{
|
||||
public function __construct(string $path, ?\Throwable $previous = null)
|
||||
{
|
||||
parent::__construct("Konnte Metadaten für Datei '{$path}' nicht lesen", 0, $previous);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Filesystem\Exceptions;
|
||||
|
||||
use App\Framework\Exception\FrameworkException;
|
||||
|
||||
final class FileNotFoundException extends FrameworkException
|
||||
{
|
||||
public function __construct(
|
||||
string $path,
|
||||
int $code = 0,
|
||||
?\Throwable $previous = null
|
||||
) {
|
||||
parent::__construct(
|
||||
message: "Datei '$path' nicht gefunden.",
|
||||
code: $code,
|
||||
previous: $previous,
|
||||
context: ['path' => $path]
|
||||
);
|
||||
}
|
||||
}
|
||||
23
src/Framework/Filesystem/Exceptions/FileReadException.php
Normal file
23
src/Framework/Filesystem/Exceptions/FileReadException.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Filesystem\Exceptions;
|
||||
|
||||
use App\Framework\Exception\FrameworkException;
|
||||
|
||||
final class FileReadException extends FrameworkException
|
||||
{
|
||||
public function __construct(
|
||||
string $path,
|
||||
int $code = 0,
|
||||
?\Throwable $previous = null
|
||||
) {
|
||||
parent::__construct(
|
||||
message: "Lesefehler bei '$path'.",
|
||||
code: $code,
|
||||
previous: $previous,
|
||||
context: ['path' => $path]
|
||||
);
|
||||
}
|
||||
}
|
||||
23
src/Framework/Filesystem/Exceptions/FileWriteException.php
Normal file
23
src/Framework/Filesystem/Exceptions/FileWriteException.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Filesystem\Exceptions;
|
||||
|
||||
use App\Framework\Exception\FrameworkException;
|
||||
|
||||
final class FileWriteException extends FrameworkException
|
||||
{
|
||||
public function __construct(
|
||||
string $path,
|
||||
int $code = 0,
|
||||
?\Throwable $previous = null
|
||||
) {
|
||||
parent::__construct(
|
||||
message: "Fehler beim Schreiben in '$path'.",
|
||||
code: $code,
|
||||
previous: $previous,
|
||||
context: ['path' => $path]
|
||||
);
|
||||
}
|
||||
}
|
||||
71
src/Framework/Filesystem/File.php
Normal file
71
src/Framework/Filesystem/File.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Filesystem;
|
||||
|
||||
/**
|
||||
* Repräsentiert eine Datei im Dateisystem mit Lazy-Loading-Unterstützung.
|
||||
*
|
||||
* @property-read string $path Pfad zur Datei
|
||||
* @property-read Storage $storage Storage-Implementierung
|
||||
* @property-read string $contents Dateiinhalt (lazy geladen)
|
||||
* @property-read int $size Dateigröße in Bytes (lazy geladen)
|
||||
* @property-read int $lastModified Zeitstempel der letzten Änderung (lazy geladen)
|
||||
*/
|
||||
final readonly class File
|
||||
{
|
||||
public function __construct(
|
||||
public string $path,
|
||||
public Storage $storage,
|
||||
public string $contents = '',
|
||||
public int $size = 0,
|
||||
public int $lastModified = 0
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Prüft, ob die Datei existiert
|
||||
*/
|
||||
public function exists(): bool
|
||||
{
|
||||
return $this->storage->exists($this->path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Löscht die Datei
|
||||
*/
|
||||
public function delete(): void
|
||||
{
|
||||
$this->storage->delete($this->path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Kopiert die Datei an einen neuen Ort
|
||||
*/
|
||||
public function copyTo(string $destination): File
|
||||
{
|
||||
$this->storage->copy($this->path, $destination);
|
||||
return FilesystemFactory::createFile($destination, $this->storage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lädt die Dateieigenschaften neu
|
||||
*/
|
||||
public function refresh(): File
|
||||
{
|
||||
return FilesystemFactory::createFile($this->path, $this->storage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Holt erweiterte Metadaten der Datei
|
||||
*/
|
||||
public function getMetadata(): FileMetadata
|
||||
{
|
||||
return new FileMetadata(
|
||||
size: $this->size,
|
||||
lastModified: $this->lastModified,
|
||||
mimeType: $this->storage->getMimeType($this->path),
|
||||
isReadable: $this->storage->isReadable($this->path),
|
||||
isWritable: $this->storage->isWritable($this->path)
|
||||
);
|
||||
}
|
||||
}
|
||||
18
src/Framework/Filesystem/FileMetadata.php
Normal file
18
src/Framework/Filesystem/FileMetadata.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Filesystem;
|
||||
|
||||
/**
|
||||
* Kapselt Metadaten einer Datei.
|
||||
*/
|
||||
final readonly class FileMetadata
|
||||
{
|
||||
public function __construct(
|
||||
public int $size = 0,
|
||||
public int $lastModified = 0,
|
||||
public string $mimeType = '',
|
||||
public bool $isReadable = false,
|
||||
public bool $isWritable = false
|
||||
) {}
|
||||
}
|
||||
173
src/Framework/Filesystem/FileStorage.php
Normal file
173
src/Framework/Filesystem/FileStorage.php
Normal file
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Filesystem;
|
||||
|
||||
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\FileReadException;
|
||||
use App\Framework\Filesystem\Exceptions\FileWriteException;
|
||||
|
||||
final readonly class FileStorage implements Storage
|
||||
{
|
||||
use StorageTrait;
|
||||
|
||||
public function get(string $path): string
|
||||
{
|
||||
if (!is_file($path)) {
|
||||
throw new FileNotFoundException($path);
|
||||
}
|
||||
|
||||
$content = @file_get_contents($path);
|
||||
if ($content === false) {
|
||||
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)) {
|
||||
throw new DirectoryCreateException($dir);
|
||||
}
|
||||
}
|
||||
if (@file_put_contents($path, $content) === false) {
|
||||
throw new FileWriteException($path);
|
||||
}
|
||||
}
|
||||
|
||||
public function exists(string $path): bool
|
||||
{
|
||||
return is_file($path);
|
||||
}
|
||||
|
||||
public function delete(string $path): void
|
||||
{
|
||||
if (!is_file($path)) {
|
||||
throw new FileNotFoundException($path);
|
||||
}
|
||||
|
||||
if (!@unlink($path)) {
|
||||
throw new FileDeleteException($path);
|
||||
}
|
||||
}
|
||||
|
||||
public function copy(string $source, string $destination): void
|
||||
{
|
||||
if (!is_file($source)) {
|
||||
throw new FileNotFoundException($source);
|
||||
}
|
||||
|
||||
// Stelle sicher, dass das Zielverzeichnis existiert
|
||||
$dir = dirname($destination);
|
||||
if (!is_dir($dir)) {
|
||||
$this->createDirectory($dir);
|
||||
}
|
||||
|
||||
if (!@copy($source, $destination)) {
|
||||
throw new FileCopyException($source, $destination);
|
||||
}
|
||||
}
|
||||
|
||||
public function size(string $path): int
|
||||
{
|
||||
if (!is_file($path)) {
|
||||
throw new FileNotFoundException($path);
|
||||
}
|
||||
|
||||
$size = @filesize($path);
|
||||
if ($size === false) {
|
||||
throw new FileReadException($path);
|
||||
}
|
||||
|
||||
return $size;
|
||||
}
|
||||
|
||||
public function lastModified(string $path): int
|
||||
{
|
||||
if (!is_file($path)) {
|
||||
throw new FileNotFoundException($path);
|
||||
}
|
||||
|
||||
$time = @filemtime($path);
|
||||
if ($time === false) {
|
||||
throw new FileReadException($path);
|
||||
}
|
||||
|
||||
return $time;
|
||||
}
|
||||
|
||||
public function getMimeType(string $path): string
|
||||
{
|
||||
if (!is_file($path)) {
|
||||
throw new FileNotFoundException($path);
|
||||
}
|
||||
|
||||
$mimeType = @mime_content_type($path);
|
||||
if ($mimeType === false) {
|
||||
throw new FileMetadataException($path);
|
||||
}
|
||||
|
||||
return $mimeType;
|
||||
}
|
||||
|
||||
public function isReadable(string $path): bool
|
||||
{
|
||||
if (!is_file($path)) {
|
||||
throw new FileNotFoundException($path);
|
||||
}
|
||||
|
||||
return is_readable($path);
|
||||
}
|
||||
|
||||
public function isWritable(string $path): bool
|
||||
{
|
||||
if (!is_file($path)) {
|
||||
throw new FileNotFoundException($path);
|
||||
}
|
||||
|
||||
return is_writable($path);
|
||||
}
|
||||
|
||||
public function listDirectory(string $directory): array
|
||||
{
|
||||
if (!is_dir($directory)) {
|
||||
throw new DirectoryCreateException($directory);
|
||||
}
|
||||
|
||||
$files = @scandir($directory);
|
||||
if ($files === false) {
|
||||
throw new DirectoryListException($directory);
|
||||
}
|
||||
|
||||
// Entferne . und .. Einträge
|
||||
$files = array_filter($files, function ($file) {
|
||||
return $file !== '.' && $file !== '..';
|
||||
});
|
||||
|
||||
// Vollständige Pfade erstellen
|
||||
$result = [];
|
||||
foreach ($files as $file) {
|
||||
$result[] = $directory . DIRECTORY_SEPARATOR . $file;
|
||||
}
|
||||
|
||||
return array_values($result);
|
||||
}
|
||||
|
||||
public function createDirectory(string $path, int $permissions = 0755, bool $recursive = true): void
|
||||
{
|
||||
if (is_dir($path)) {
|
||||
return; // Verzeichnis existiert bereits
|
||||
}
|
||||
|
||||
if (!@mkdir($path, $permissions, $recursive) && !is_dir($path)) {
|
||||
throw new DirectoryCreateException($path);
|
||||
}
|
||||
}
|
||||
}
|
||||
157
src/Framework/Filesystem/FilesystemFactory.php
Normal file
157
src/Framework/Filesystem/FilesystemFactory.php
Normal file
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Filesystem;
|
||||
|
||||
use App\Framework\Logging\DefaultLogger;
|
||||
use App\Framework\Logging\LogLevel;
|
||||
use App\Framework\Logging\LoggerFactory;
|
||||
use ReflectionClass;
|
||||
|
||||
/**
|
||||
* Factory für Filesystem-Objekte mit Lazy-Loading-Unterstützung.
|
||||
*/
|
||||
final class FilesystemFactory
|
||||
{
|
||||
/**
|
||||
* Erstellt ein File-Objekt mit Lazy-Loading für schwere Eigenschaften.
|
||||
*
|
||||
* @param string $path Pfad zur Datei
|
||||
* @param Storage $storage Storage-Implementierung
|
||||
* @param int|null $cacheTimeoutSeconds Optional, Zeit in Sekunden, nach der der Cache ungültig wird
|
||||
* @param bool $lazyLoad Optional, ob Lazy-Loading verwendet werden soll
|
||||
* @param DefaultLogger|null $logger Optional, Logger für Debug-Informationen
|
||||
*/
|
||||
public static function createFile(
|
||||
string $path,
|
||||
Storage $storage,
|
||||
?int $cacheTimeoutSeconds = null,
|
||||
bool $lazyLoad = true,
|
||||
?DefaultLogger $logger = null
|
||||
): File {
|
||||
$logger ??= LoggerFactory::getDefaultLogger();
|
||||
|
||||
// Direkte Instanziierung ohne Lazy-Loading
|
||||
if (!$lazyLoad) {
|
||||
$logger->debug("Erstelle File-Objekt ohne Lazy-Loading: {$path}");
|
||||
|
||||
// Nur laden wenn die Datei existiert
|
||||
if (!$storage->exists($path)) {
|
||||
return new File($path, $storage);
|
||||
}
|
||||
|
||||
return new File(
|
||||
path: $path,
|
||||
storage: $storage,
|
||||
contents: $storage->get($path),
|
||||
size: $storage->size($path),
|
||||
lastModified: $storage->lastModified($path)
|
||||
);
|
||||
}
|
||||
|
||||
$reflection = new ReflectionClass(File::class);
|
||||
$loadTime = time();
|
||||
|
||||
// LazyProxy verwenden für individuelle Property-Callbacks
|
||||
return $reflection->newLazyProxy([
|
||||
// Dateiinhalt wird erst beim ersten Zugriff geladen
|
||||
'contents' => function(File $file) use ($loadTime, $cacheTimeoutSeconds, $logger) {
|
||||
$logger->debug("Lazy-Loading contents für {$file->path}");
|
||||
|
||||
// Cache-Invalidierung basierend auf Zeit
|
||||
if ($cacheTimeoutSeconds !== null && time() - $loadTime > $cacheTimeoutSeconds) {
|
||||
$logger->debug("Cache-Timeout erreicht für {$file->path}, lade neu");
|
||||
}
|
||||
|
||||
if (!$file->exists()) {
|
||||
$logger->debug("Datei existiert nicht: {$file->path}");
|
||||
return '';
|
||||
}
|
||||
|
||||
return $file->storage->get($file->path);
|
||||
},
|
||||
|
||||
// Dateigröße wird erst beim ersten Zugriff ermittelt
|
||||
'size' => function(File $file) use ($loadTime, $cacheTimeoutSeconds, $logger) {
|
||||
$logger->debug("Lazy-Loading size für {$file->path}");
|
||||
|
||||
// Cache-Invalidierung basierend auf Zeit
|
||||
if ($cacheTimeoutSeconds !== null && time() - $loadTime > $cacheTimeoutSeconds) {
|
||||
$logger->debug("Cache-Timeout erreicht für {$file->path}, lade neu");
|
||||
}
|
||||
|
||||
if (!$file->exists()) {
|
||||
$logger->debug("Datei existiert nicht: {$file->path}");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $file->storage->size($file->path);
|
||||
},
|
||||
|
||||
// Zeitstempel wird erst beim ersten Zugriff ermittelt
|
||||
'lastModified' => function(File $file) use ($loadTime, $cacheTimeoutSeconds, $logger) {
|
||||
$logger->debug("Lazy-Loading lastModified für {$file->path}");
|
||||
|
||||
// Cache-Invalidierung basierend auf Zeit
|
||||
if ($cacheTimeoutSeconds !== null && time() - $loadTime > $cacheTimeoutSeconds) {
|
||||
$logger->debug("Cache-Timeout erreicht für {$file->path}, lade neu");
|
||||
}
|
||||
|
||||
if (!$file->exists()) {
|
||||
$logger->debug("Datei existiert nicht: {$file->path}");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $file->storage->lastModified($file->path);
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt ein Directory-Objekt mit Lazy-Loading für schwere Eigenschaften.
|
||||
*
|
||||
* @param string $path Pfad zum Verzeichnis
|
||||
* @param Storage $storage Storage-Implementierung
|
||||
* @param bool $lazyLoad Optional, ob Lazy-Loading verwendet werden soll
|
||||
* @param DefaultLogger|null $logger Optional, Logger für Debug-Informationen
|
||||
*/
|
||||
public static function createDirectory(
|
||||
string $path,
|
||||
Storage $storage,
|
||||
bool $lazyLoad = true,
|
||||
?DefaultLogger $logger = null
|
||||
): Directory {
|
||||
$logger ??= LoggerFactory::getDefaultLogger();
|
||||
|
||||
// Direkte Instanziierung ohne Lazy-Loading
|
||||
if (!$lazyLoad) {
|
||||
$logger->debug("Erstelle Directory-Objekt ohne Lazy-Loading: {$path}");
|
||||
|
||||
$contents = [];
|
||||
if (is_dir($path)) {
|
||||
$contents = $storage->listDirectory($path);
|
||||
}
|
||||
|
||||
return new Directory($path, $storage, $contents);
|
||||
}
|
||||
|
||||
$reflection = new ReflectionClass(Directory::class);
|
||||
|
||||
// LazyGhost verwenden - alle Eigenschaften werden beim ersten Zugriff initialisiert
|
||||
$lazyDir = $reflection->newLazyGhost(
|
||||
// Initializer-Callback
|
||||
function(Directory $directory) use ($logger): void {
|
||||
$logger->debug("Lazy-Loading Directory-Inhalt für {$directory->path}");
|
||||
|
||||
// Verzeichnisinhalt wird erst beim ersten Zugriff auf eine Eigenschaft geladen
|
||||
if ($directory->exists()) {
|
||||
$directory->contents = $directory->storage->listDirectory($directory->path);
|
||||
} else {
|
||||
$directory->contents = [];
|
||||
}
|
||||
}
|
||||
);
|
||||
/** @var Directory $lazyDir */
|
||||
return $lazyDir;
|
||||
}
|
||||
}
|
||||
207
src/Framework/Filesystem/InMemoryStorage.php
Normal file
207
src/Framework/Filesystem/InMemoryStorage.php
Normal file
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Filesystem;
|
||||
|
||||
use App\Framework\Filesystem\Exceptions\DirectoryCreateException;
|
||||
use App\Framework\Filesystem\Exceptions\FileNotFoundException;
|
||||
use App\Framework\Filesystem\Exceptions\FileReadException;
|
||||
|
||||
/**
|
||||
* In-Memory-Implementierung des Storage-Interfaces für Tests
|
||||
*/
|
||||
final class InMemoryStorage implements Storage
|
||||
{
|
||||
/** @var array<string, string> */
|
||||
private array $files = [];
|
||||
|
||||
/** @var array<string, bool> */
|
||||
private array $directories = [];
|
||||
|
||||
/** @var array<string, int> */
|
||||
private array $timestamps = [];
|
||||
|
||||
public function get(string $path): string
|
||||
{
|
||||
if (!isset($this->files[$path])) {
|
||||
throw new FileNotFoundException($path);
|
||||
}
|
||||
|
||||
return $this->files[$path];
|
||||
}
|
||||
|
||||
public function put(string $path, string $content): void
|
||||
{
|
||||
$dir = dirname($path);
|
||||
if (!isset($this->directories[$dir])) {
|
||||
$this->createDirectory($dir);
|
||||
}
|
||||
|
||||
$this->files[$path] = $content;
|
||||
$this->timestamps[$path] = time();
|
||||
}
|
||||
|
||||
public function exists(string $path): bool
|
||||
{
|
||||
return isset($this->files[$path]);
|
||||
}
|
||||
|
||||
public function delete(string $path): void
|
||||
{
|
||||
if (!isset($this->files[$path])) {
|
||||
throw new FileNotFoundException($path);
|
||||
}
|
||||
|
||||
unset($this->files[$path]);
|
||||
unset($this->timestamps[$path]);
|
||||
}
|
||||
|
||||
public function copy(string $source, string $destination): void
|
||||
{
|
||||
if (!isset($this->files[$source])) {
|
||||
throw new FileNotFoundException($source);
|
||||
}
|
||||
|
||||
$dir = dirname($destination);
|
||||
if (!isset($this->directories[$dir])) {
|
||||
$this->createDirectory($dir);
|
||||
}
|
||||
|
||||
$this->files[$destination] = $this->files[$source];
|
||||
$this->timestamps[$destination] = time();
|
||||
}
|
||||
|
||||
public function size(string $path): int
|
||||
{
|
||||
if (!isset($this->files[$path])) {
|
||||
throw new FileNotFoundException($path);
|
||||
}
|
||||
|
||||
return strlen($this->files[$path]);
|
||||
}
|
||||
|
||||
public function lastModified(string $path): int
|
||||
{
|
||||
if (!isset($this->files[$path])) {
|
||||
throw new FileNotFoundException($path);
|
||||
}
|
||||
|
||||
return $this->timestamps[$path] ?? time();
|
||||
}
|
||||
|
||||
public function listDirectory(string $directory): array
|
||||
{
|
||||
if (!isset($this->directories[$directory])) {
|
||||
throw new DirectoryCreateException($directory);
|
||||
}
|
||||
|
||||
$result = [];
|
||||
$prefix = $directory . DIRECTORY_SEPARATOR;
|
||||
$prefixLength = strlen($prefix);
|
||||
|
||||
// Dateien im Verzeichnis suchen
|
||||
foreach (array_keys($this->files) as $path) {
|
||||
if (str_starts_with($path, $prefix)) {
|
||||
// Nur direkte Kinder, keine tieferen Ebenen
|
||||
$relativePath = substr($path, $prefixLength);
|
||||
if (!str_contains($relativePath, DIRECTORY_SEPARATOR)) {
|
||||
$result[] = $path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verzeichnisse im Verzeichnis suchen
|
||||
foreach (array_keys($this->directories) as $path) {
|
||||
if ($path !== $directory && strpos($path, $prefix) === 0) {
|
||||
// Nur direkte Kinder
|
||||
$relativePath = substr($path, $prefixLength);
|
||||
if (!str_contains($relativePath, DIRECTORY_SEPARATOR)) {
|
||||
$result[] = $path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function createDirectory(string $path, int $permissions = 0755, bool $recursive = true): void
|
||||
{
|
||||
if (isset($this->directories[$path])) {
|
||||
return; // Verzeichnis existiert bereits
|
||||
}
|
||||
|
||||
if ($recursive) {
|
||||
// Übergeordnete Verzeichnisse erstellen
|
||||
$parent = dirname($path);
|
||||
if ($parent !== '.' && $parent !== '/' && $parent !== $path) {
|
||||
$this->createDirectory($parent, $permissions, true);
|
||||
}
|
||||
}
|
||||
|
||||
$this->directories[$path] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fügt eine Datei direkt zum In-Memory-Speicher hinzu (für Tests)
|
||||
*/
|
||||
public function addFile(string $path, string $content): void
|
||||
{
|
||||
$dir = dirname($path);
|
||||
$this->createDirectory($dir);
|
||||
$this->files[$path] = $content;
|
||||
$this->timestamps[$path] = time();
|
||||
}
|
||||
|
||||
/**
|
||||
* Löscht alle Daten im Speicher (für Tests)
|
||||
*/
|
||||
public function clear(): void
|
||||
{
|
||||
$this->files = [];
|
||||
$this->directories = [];
|
||||
$this->timestamps = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt ein File-Objekt mit Lazy-Loading für teure Eigenschaften
|
||||
*/
|
||||
public function file(string $path): File
|
||||
{
|
||||
return FilesystemFactory::createFile($path, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt ein Directory-Objekt mit Lazy-Loading für teure Eigenschaften
|
||||
*/
|
||||
public function directory(string $path): Directory
|
||||
{
|
||||
return FilesystemFactory::createDirectory($path, $this);
|
||||
}
|
||||
|
||||
public function getMimeType(string $path): string
|
||||
{
|
||||
if (!isset($this->files[$path])) {
|
||||
throw new FileNotFoundException($path);
|
||||
}
|
||||
|
||||
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
||||
if ($finfo === false) {
|
||||
throw new FileReadException($path);
|
||||
}
|
||||
|
||||
$mimeType = finfo_buffer($finfo, $this->files[$path]);
|
||||
finfo_close($finfo);
|
||||
|
||||
return $mimeType ?: 'application/octet-stream';
|
||||
}
|
||||
|
||||
public function isReadable(string $path): bool
|
||||
{
|
||||
return true; // Alle Dateien sind im In-Memory-Speicher lesbar
|
||||
}
|
||||
|
||||
public function isWritable(string $path): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
102
src/Framework/Filesystem/LoggableStorage.php
Normal file
102
src/Framework/Filesystem/LoggableStorage.php
Normal file
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Filesystem;
|
||||
|
||||
use App\Framework\Logging\DefaultLogger;
|
||||
use App\Framework\Logging\LoggerFactory;
|
||||
|
||||
/**
|
||||
* Decorator für Storage-Implementierungen mit Logging-Unterstützung.
|
||||
*/
|
||||
final class LoggableStorage implements Storage
|
||||
{
|
||||
use StorageTrait;
|
||||
|
||||
public function __construct(
|
||||
private readonly Storage $storage,
|
||||
private ?DefaultLogger $logger = null
|
||||
) {
|
||||
$this->logger ??= LoggerFactory::getDefaultLogger();
|
||||
}
|
||||
|
||||
public function get(string $path): string
|
||||
{
|
||||
$this->logger->debug("Lese Datei: {$path}");
|
||||
return $this->storage->get($path);
|
||||
}
|
||||
|
||||
public function put(string $path, string $content): void
|
||||
{
|
||||
$this->logger->debug("Schreibe Datei: {$path}");
|
||||
$this->storage->put($path, $content);
|
||||
}
|
||||
|
||||
public function exists(string $path): bool
|
||||
{
|
||||
$exists = $this->storage->exists($path);
|
||||
$this->logger->debug("Prüfe Existenz: {$path} - " . ($exists ? 'existiert' : 'existiert nicht'));
|
||||
return $exists;
|
||||
}
|
||||
|
||||
public function delete(string $path): void
|
||||
{
|
||||
$this->logger->debug("Lösche Datei: {$path}");
|
||||
$this->storage->delete($path);
|
||||
}
|
||||
|
||||
public function copy(string $source, string $destination): void
|
||||
{
|
||||
$this->logger->debug("Kopiere Datei: {$source} -> {$destination}");
|
||||
$this->storage->copy($source, $destination);
|
||||
}
|
||||
|
||||
public function size(string $path): int
|
||||
{
|
||||
$size = $this->storage->size($path);
|
||||
$this->logger->debug("Dateigröße: {$path} - {$size} Bytes");
|
||||
return $size;
|
||||
}
|
||||
|
||||
public function lastModified(string $path): int
|
||||
{
|
||||
$lastModified = $this->storage->lastModified($path);
|
||||
$this->logger->debug("Letzte Änderung: {$path} - " . date('Y-m-d H:i:s', $lastModified));
|
||||
return $lastModified;
|
||||
}
|
||||
|
||||
public function getMimeType(string $path): string
|
||||
{
|
||||
$mimeType = $this->storage->getMimeType($path);
|
||||
$this->logger->debug("MIME-Typ: {$path} - {$mimeType}");
|
||||
return $mimeType;
|
||||
}
|
||||
|
||||
public function isReadable(string $path): bool
|
||||
{
|
||||
$isReadable = $this->storage->isReadable($path);
|
||||
$this->logger->debug("Lesbar: {$path} - " . ($isReadable ? 'ja' : 'nein'));
|
||||
return $isReadable;
|
||||
}
|
||||
|
||||
public function isWritable(string $path): bool
|
||||
{
|
||||
$isWritable = $this->storage->isWritable($path);
|
||||
$this->logger->debug("Schreibbar: {$path} - " . ($isWritable ? 'ja' : 'nein'));
|
||||
return $isWritable;
|
||||
}
|
||||
|
||||
public function listDirectory(string $directory): array
|
||||
{
|
||||
$this->logger->debug("Liste Verzeichnis: {$directory}");
|
||||
$files = $this->storage->listDirectory($directory);
|
||||
$this->logger->debug("Gefundene Dateien: " . count($files));
|
||||
return $files;
|
||||
}
|
||||
|
||||
public function createDirectory(string $path, int $permissions = 0755, bool $recursive = true): void
|
||||
{
|
||||
$this->logger->debug("Erstelle Verzeichnis: {$path}");
|
||||
$this->storage->createDirectory($path, $permissions, $recursive);
|
||||
}
|
||||
}
|
||||
68
src/Framework/Filesystem/README.md
Normal file
68
src/Framework/Filesystem/README.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# Filesystem-Komponente mit Lazy-Loading
|
||||
|
||||
Diese Komponente bietet eine einfache Abstraktion für Dateisystem-Operationen mit Lazy-Loading-Unterstützung für PHP 8.4.
|
||||
|
||||
## Funktionen
|
||||
|
||||
- **Abstraktion**: Einheitliches Interface für Dateisystem-Operationen
|
||||
- **Lazy-Loading**: Optimierte Leistung durch verzögertes Laden von Eigenschaften
|
||||
- **Testbarkeit**: In-Memory-Implementierung für Tests
|
||||
|
||||
## Verwendungsbeispiele
|
||||
|
||||
### Beispiel 1: Datei mit Lazy-Loading verwenden
|
||||
|
||||
```php
|
||||
// Storage-Implementierung holen
|
||||
$storage = new FileStorage();
|
||||
|
||||
// Datei-Objekt mit Lazy-Loading erstellen
|
||||
$file = $storage->file('/pfad/zur/datei.txt');
|
||||
|
||||
// Hier passiert noch kein Disk-I/O
|
||||
echo "Datei: {$file->path}\n";
|
||||
|
||||
// Erst hier wird der Inhalt tatsächlich geladen
|
||||
echo "Inhalt: {$file->contents}\n";
|
||||
|
||||
// Und hier die Dateigröße
|
||||
echo "Größe: {$file->size} Bytes\n";
|
||||
```
|
||||
|
||||
### Beispiel 2: Mit Verzeichnissen arbeiten
|
||||
|
||||
```php
|
||||
// Verzeichnis-Objekt mit Lazy-Loading erstellen
|
||||
$dir = $storage->directory('/pfad/zum/verzeichnis');
|
||||
|
||||
// Verzeichnis erstellen, falls es nicht existiert
|
||||
if (!$dir->exists()) {
|
||||
$dir->create();
|
||||
}
|
||||
|
||||
// Alle Dateien auflisten (lazy geladen)
|
||||
foreach ($dir->getFiles() as $file) {
|
||||
echo "{$file->path}: {$file->size} Bytes\n";
|
||||
}
|
||||
```
|
||||
|
||||
### Beispiel 3: Dateien kopieren und löschen
|
||||
|
||||
```php
|
||||
$file = $storage->file('/quelle.txt');
|
||||
|
||||
// Datei kopieren
|
||||
$neueFile = $file->copyTo('/ziel.txt');
|
||||
|
||||
// Quelldatei löschen
|
||||
$file->delete();
|
||||
```
|
||||
|
||||
## Technische Details
|
||||
|
||||
Die Komponente nutzt die neuen PHP 8.4 Lazy-Loading-Features:
|
||||
|
||||
- **ReflectionClass::newLazyProxy()**: Für File-Objekte mit individuellen Property-Callbacks
|
||||
- **ReflectionClass::newLazyGhost()**: Für Directory-Objekte mit einheitlichem Initializer
|
||||
|
||||
Die Properties werden erst geladen, wenn sie tatsächlich verwendet werden, was besonders bei Verzeichnislisten und großen Dateien zu erheblichen Leistungsverbesserungen führt.
|
||||
78
src/Framework/Filesystem/Storage.php
Normal file
78
src/Framework/Filesystem/Storage.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace App\Framework\Filesystem;
|
||||
|
||||
interface Storage
|
||||
{
|
||||
/**
|
||||
* Liest den Inhalt einer Datei
|
||||
*/
|
||||
public function get(string $path): string;
|
||||
|
||||
/**
|
||||
* Speichert Inhalt in einer Datei
|
||||
*/
|
||||
public function put(string $path, string $content): void;
|
||||
|
||||
/**
|
||||
* Prüft, ob eine Datei existiert
|
||||
*/
|
||||
public function exists(string $path): bool;
|
||||
|
||||
/**
|
||||
* Löscht eine Datei
|
||||
*/
|
||||
public function delete(string $path): void;
|
||||
|
||||
/**
|
||||
* Kopiert eine Datei
|
||||
*/
|
||||
public function copy(string $source, string $destination): void;
|
||||
|
||||
/**
|
||||
* Ermittelt die Größe einer Datei in Bytes
|
||||
*/
|
||||
public function size(string $path): int;
|
||||
|
||||
/**
|
||||
* Ermittelt das letzte Änderungsdatum einer Datei
|
||||
*/
|
||||
public function lastModified(string $path): int;
|
||||
|
||||
/**
|
||||
* Ermittelt MIME-Typ einer Datei
|
||||
*/
|
||||
public function getMimeType(string $path): string;
|
||||
|
||||
/**
|
||||
* Prüft, ob eine Datei lesbar ist
|
||||
*/
|
||||
public function isReadable(string $path): bool;
|
||||
|
||||
/**
|
||||
* Prüft, ob eine Datei schreibbar ist
|
||||
*/
|
||||
public function isWritable(string $path): bool;
|
||||
|
||||
/**
|
||||
* Liest ein Verzeichnis aus
|
||||
*
|
||||
* @return array<string> Liste von Dateipfaden
|
||||
*/
|
||||
public function listDirectory(string $directory): array;
|
||||
|
||||
/**
|
||||
* Erstellt ein Verzeichnis
|
||||
*/
|
||||
public function createDirectory(string $path, int $permissions = 0755, bool $recursive = true): void;
|
||||
|
||||
/**
|
||||
* Erstellt ein File-Objekt mit Lazy-Loading
|
||||
*/
|
||||
public function file(string $path): File;
|
||||
|
||||
/**
|
||||
* Erstellt ein Directory-Objekt mit Lazy-Loading
|
||||
*/
|
||||
public function directory(string $path): Directory;
|
||||
}
|
||||
26
src/Framework/Filesystem/StorageTrait.php
Normal file
26
src/Framework/Filesystem/StorageTrait.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Filesystem;
|
||||
|
||||
/**
|
||||
* Stellt Basis-Implementierungen für Storage-Klassen bereit.
|
||||
*/
|
||||
trait StorageTrait
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function file(string $path): File
|
||||
{
|
||||
return FilesystemFactory::createFile($path, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function directory(string $path): Directory
|
||||
{
|
||||
return FilesystemFactory::createDirectory($path, $this);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user