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:
62
src/Framework/Filesystem/Traits/AppendableStorageTrait.php
Normal file
62
src/Framework/Filesystem/Traits/AppendableStorageTrait.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Filesystem\Traits;
|
||||
|
||||
use App\Framework\Filesystem\Exceptions\FileWriteException;
|
||||
|
||||
/**
|
||||
* Trait für Append Storage-Operationen
|
||||
*
|
||||
* Implementiert Append-Funktionalität für Logs,
|
||||
* Analytics und Stream-artige Daten.
|
||||
*/
|
||||
trait AppendableStorageTrait
|
||||
{
|
||||
public function append(string $path, string $content): void
|
||||
{
|
||||
$resolvedPath = $this->resolvePath($path);
|
||||
$dir = dirname($resolvedPath);
|
||||
if (! is_dir($dir)) {
|
||||
$this->createDirectory($path);
|
||||
}
|
||||
|
||||
if (@file_put_contents($resolvedPath, $content, FILE_APPEND | LOCK_EX) === false) {
|
||||
throw new FileWriteException($path);
|
||||
}
|
||||
}
|
||||
|
||||
public function appendLine(string $path, string $line): void
|
||||
{
|
||||
$this->append($path, $line . PHP_EOL);
|
||||
}
|
||||
|
||||
public function appendJson(string $path, array $data): void
|
||||
{
|
||||
$this->appendLine($path, json_encode($data));
|
||||
}
|
||||
|
||||
public function appendCsv(string $path, array $row): void
|
||||
{
|
||||
$resolvedPath = $this->resolvePath($path);
|
||||
$handle = @fopen($resolvedPath, 'a');
|
||||
if ($handle === false) {
|
||||
throw new FileWriteException($path);
|
||||
}
|
||||
|
||||
if (@fputcsv($handle, $row) === false) {
|
||||
@fclose($handle);
|
||||
|
||||
throw new FileWriteException($path);
|
||||
}
|
||||
|
||||
@fclose($handle);
|
||||
}
|
||||
|
||||
public function appendWithTimestamp(string $path, string $content): void
|
||||
{
|
||||
$timestampedContent = '[' . date('Y-m-d H:i:s') . '] ' . $content;
|
||||
$this->appendLine($path, $timestampedContent);
|
||||
}
|
||||
}
|
||||
45
src/Framework/Filesystem/Traits/AtomicStorageTrait.php
Normal file
45
src/Framework/Filesystem/Traits/AtomicStorageTrait.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Filesystem\Traits;
|
||||
|
||||
use App\Framework\Filesystem\Exceptions\FileWriteException;
|
||||
|
||||
/**
|
||||
* Trait für Atomic Storage-Operationen
|
||||
*
|
||||
* Implementiert atomare Schreiboperationen durch temporäre Dateien
|
||||
* und rename()-Operation für Data Consistency.
|
||||
*/
|
||||
trait AtomicStorageTrait
|
||||
{
|
||||
public function putAtomic(string $path, string $content): void
|
||||
{
|
||||
$tempPath = $path . '.tmp.' . uniqid();
|
||||
$this->put($tempPath, $content);
|
||||
|
||||
$resolvedPath = $this->resolvePath($path);
|
||||
$resolvedTempPath = $this->resolvePath($tempPath);
|
||||
|
||||
if (! @rename($resolvedTempPath, $resolvedPath)) {
|
||||
@unlink($resolvedTempPath);
|
||||
|
||||
throw new FileWriteException($path);
|
||||
}
|
||||
}
|
||||
|
||||
public function updateAtomic(string $path, callable $updateCallback): void
|
||||
{
|
||||
$originalContent = $this->exists($path) ? $this->get($path) : '';
|
||||
$newContent = $updateCallback($originalContent);
|
||||
$this->putAtomic($path, $newContent);
|
||||
}
|
||||
|
||||
public function updateJsonAtomic(string $path, callable $updateCallback): void
|
||||
{
|
||||
$originalData = $this->exists($path) ? json_decode($this->get($path), true) ?? [] : [];
|
||||
$newData = $updateCallback($originalData);
|
||||
$this->putAtomic($path, json_encode($newData, JSON_PRETTY_PRINT));
|
||||
}
|
||||
}
|
||||
125
src/Framework/Filesystem/Traits/CompressibleStorageTrait.php
Normal file
125
src/Framework/Filesystem/Traits/CompressibleStorageTrait.php
Normal file
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Filesystem\Traits;
|
||||
|
||||
use App\Framework\Filesystem\Exceptions\FileNotFoundException;
|
||||
use App\Framework\Filesystem\Exceptions\FileReadException;
|
||||
use App\Framework\Filesystem\Exceptions\FileWriteException;
|
||||
|
||||
/**
|
||||
* Trait für Compression Storage-Operationen
|
||||
*
|
||||
* Implementiert automatische Komprimierung für große Dateien,
|
||||
* ideal für Analytics-Daten, Logs und Archive.
|
||||
*/
|
||||
trait CompressibleStorageTrait
|
||||
{
|
||||
public function putCompressed(string $path, string $content, string $algorithm = 'gzip'): void
|
||||
{
|
||||
$compressedContent = match ($algorithm) {
|
||||
'gzip' => gzencode($content),
|
||||
'deflate' => gzdeflate($content),
|
||||
'bzip2' => bzcompress($content),
|
||||
default => throw new \InvalidArgumentException("Unsupported compression algorithm: {$algorithm}")
|
||||
};
|
||||
|
||||
if ($compressedContent === false) {
|
||||
throw new FileWriteException($path);
|
||||
}
|
||||
|
||||
$this->put($path . '.' . $algorithm, $compressedContent);
|
||||
}
|
||||
|
||||
public function getCompressed(string $path): string
|
||||
{
|
||||
// Automatische Erkennung der Komprimierung basierend auf Dateiendung
|
||||
$pathInfo = pathinfo($path);
|
||||
$algorithm = $pathInfo['extension'] ?? '';
|
||||
|
||||
if (! in_array($algorithm, ['gzip', 'gz', 'deflate', 'bzip2', 'bz2'])) {
|
||||
throw new \InvalidArgumentException("Cannot detect compression algorithm from path: {$path}");
|
||||
}
|
||||
|
||||
if (! $this->exists($path)) {
|
||||
throw new FileNotFoundException($path);
|
||||
}
|
||||
|
||||
$compressedContent = $this->get($path);
|
||||
|
||||
$content = match ($algorithm) {
|
||||
'gzip', 'gz' => gzdecode($compressedContent),
|
||||
'deflate' => gzinflate($compressedContent),
|
||||
'bzip2', 'bz2' => bzdecompress($compressedContent),
|
||||
default => throw new \InvalidArgumentException("Unsupported compression algorithm: {$algorithm}")
|
||||
};
|
||||
|
||||
if ($content === false) {
|
||||
throw new FileReadException($path);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
public function compress(string $path, string $algorithm = 'gzip'): void
|
||||
{
|
||||
if (! $this->exists($path)) {
|
||||
throw new FileNotFoundException($path);
|
||||
}
|
||||
|
||||
$content = $this->get($path);
|
||||
$compressedPath = $path . '.' . $algorithm;
|
||||
|
||||
$this->putCompressed($path, $content, $algorithm);
|
||||
$this->delete($path);
|
||||
|
||||
// Komprimierte Datei zurück zum ursprünglichen Pfad verschieben
|
||||
$this->copy($compressedPath, $path);
|
||||
$this->delete($compressedPath);
|
||||
}
|
||||
|
||||
public function decompress(string $path): void
|
||||
{
|
||||
$content = $this->getCompressed($path);
|
||||
|
||||
// Entferne Komprimierungs-Extension
|
||||
$pathInfo = pathinfo($path);
|
||||
$algorithm = $pathInfo['extension'] ?? '';
|
||||
$decompressedPath = $pathInfo['dirname'] . '/' . $pathInfo['filename'];
|
||||
|
||||
$this->put($decompressedPath, $content);
|
||||
$this->delete($path);
|
||||
}
|
||||
|
||||
public function isCompressed(string $path): bool
|
||||
{
|
||||
if (! $this->exists($path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$pathInfo = pathinfo($path);
|
||||
$extension = $pathInfo['extension'] ?? '';
|
||||
|
||||
return in_array($extension, ['gzip', 'gz', 'deflate', 'bzip2', 'bz2']);
|
||||
}
|
||||
|
||||
public function getAvailableAlgorithms(): array
|
||||
{
|
||||
$algorithms = [];
|
||||
|
||||
if (function_exists('gzencode')) {
|
||||
$algorithms[] = 'gzip';
|
||||
}
|
||||
|
||||
if (function_exists('gzdeflate')) {
|
||||
$algorithms[] = 'deflate';
|
||||
}
|
||||
|
||||
if (function_exists('bzcompress')) {
|
||||
$algorithms[] = 'bzip2';
|
||||
}
|
||||
|
||||
return $algorithms;
|
||||
}
|
||||
}
|
||||
151
src/Framework/Filesystem/Traits/LockableStorageTrait.php
Normal file
151
src/Framework/Filesystem/Traits/LockableStorageTrait.php
Normal file
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Filesystem\Traits;
|
||||
|
||||
use App\Framework\Filesystem\Exceptions\FileNotFoundException;
|
||||
use App\Framework\Filesystem\Exceptions\FileReadException;
|
||||
use App\Framework\Filesystem\Exceptions\FileWriteException;
|
||||
|
||||
/**
|
||||
* Trait für File-Locking Storage-Operationen
|
||||
*
|
||||
* Implementiert File-Locking für kritische Operationen
|
||||
* und verhindert Data Corruption bei Concurrent Access.
|
||||
*/
|
||||
trait LockableStorageTrait
|
||||
{
|
||||
public function withExclusiveLock(string $path, callable $operation): mixed
|
||||
{
|
||||
$resolvedPath = $this->resolvePath($path);
|
||||
$handle = @fopen($resolvedPath, 'c+');
|
||||
if ($handle === false) {
|
||||
throw new FileWriteException($path);
|
||||
}
|
||||
|
||||
if (! @flock($handle, LOCK_EX)) {
|
||||
@fclose($handle);
|
||||
|
||||
throw new FileWriteException($path);
|
||||
}
|
||||
|
||||
try {
|
||||
return $operation($handle);
|
||||
} finally {
|
||||
@flock($handle, LOCK_UN);
|
||||
@fclose($handle);
|
||||
}
|
||||
}
|
||||
|
||||
public function withSharedLock(string $path, callable $operation): mixed
|
||||
{
|
||||
if (! $this->exists($path)) {
|
||||
throw new FileNotFoundException($path);
|
||||
}
|
||||
|
||||
$resolvedPath = $this->resolvePath($path);
|
||||
$handle = @fopen($resolvedPath, 'r');
|
||||
if ($handle === false) {
|
||||
throw new FileReadException($path);
|
||||
}
|
||||
|
||||
if (! @flock($handle, LOCK_SH)) {
|
||||
@fclose($handle);
|
||||
|
||||
throw new FileReadException($path);
|
||||
}
|
||||
|
||||
try {
|
||||
return $operation($handle);
|
||||
} finally {
|
||||
@flock($handle, LOCK_UN);
|
||||
@fclose($handle);
|
||||
}
|
||||
}
|
||||
|
||||
public function isLocked(string $path): bool
|
||||
{
|
||||
if (! $this->exists($path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$resolvedPath = $this->resolvePath($path);
|
||||
$handle = @fopen($resolvedPath, 'r');
|
||||
if ($handle === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$isLocked = ! @flock($handle, LOCK_EX | LOCK_NB);
|
||||
|
||||
if (! $isLocked) {
|
||||
@flock($handle, LOCK_UN);
|
||||
}
|
||||
|
||||
@fclose($handle);
|
||||
|
||||
return $isLocked;
|
||||
}
|
||||
|
||||
public function putWithLock(string $path, string $content): void
|
||||
{
|
||||
$this->withExclusiveLock($path, function ($handle) use ($content) {
|
||||
if (@ftruncate($handle, 0) === false || @rewind($handle) === false) {
|
||||
throw new FileWriteException('Lock operation failed');
|
||||
}
|
||||
|
||||
if (@fwrite($handle, $content) === false) {
|
||||
throw new FileWriteException('Lock operation failed');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function updateWithLock(string $path, callable $updateCallback): void
|
||||
{
|
||||
$this->withExclusiveLock($path, function ($handle) use ($updateCallback) {
|
||||
$originalContent = '';
|
||||
if (@rewind($handle) !== false) {
|
||||
$originalContent = @stream_get_contents($handle) ?: '';
|
||||
}
|
||||
|
||||
$newContent = $updateCallback($originalContent);
|
||||
|
||||
if (@ftruncate($handle, 0) === false || @rewind($handle) === false) {
|
||||
throw new FileWriteException('Lock operation failed');
|
||||
}
|
||||
|
||||
if (@fwrite($handle, $newContent) === false) {
|
||||
throw new FileWriteException('Lock operation failed');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function appendWithLock(string $path, string $content): void
|
||||
{
|
||||
$resolvedPath = $this->resolvePath($path);
|
||||
$dir = dirname($resolvedPath);
|
||||
if (! is_dir($dir)) {
|
||||
$this->createDirectory($path);
|
||||
}
|
||||
|
||||
$handle = @fopen($resolvedPath, 'a');
|
||||
if ($handle === false) {
|
||||
throw new FileWriteException($path);
|
||||
}
|
||||
|
||||
if (! @flock($handle, LOCK_EX)) {
|
||||
@fclose($handle);
|
||||
|
||||
throw new FileWriteException($path);
|
||||
}
|
||||
|
||||
try {
|
||||
if (@fwrite($handle, $content) === false) {
|
||||
throw new FileWriteException($path);
|
||||
}
|
||||
} finally {
|
||||
@flock($handle, LOCK_UN);
|
||||
@fclose($handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
98
src/Framework/Filesystem/Traits/StreamableStorageTrait.php
Normal file
98
src/Framework/Filesystem/Traits/StreamableStorageTrait.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Filesystem\Traits;
|
||||
|
||||
use App\Framework\Filesystem\Exceptions\FileNotFoundException;
|
||||
use App\Framework\Filesystem\Exceptions\FileReadException;
|
||||
use App\Framework\Filesystem\Exceptions\FileWriteException;
|
||||
|
||||
/**
|
||||
* Trait für Stream-basierte Storage-Operationen
|
||||
*
|
||||
* Implementiert Stream-Funktionalität für große Dateien
|
||||
* ohne komplettes Laden in den Speicher.
|
||||
*/
|
||||
trait StreamableStorageTrait
|
||||
{
|
||||
public function readStream(string $path)
|
||||
{
|
||||
if (! $this->exists($path)) {
|
||||
throw new FileNotFoundException($path);
|
||||
}
|
||||
|
||||
$resolvedPath = $this->resolvePath($path);
|
||||
$stream = @fopen($resolvedPath, 'r');
|
||||
if ($stream === false) {
|
||||
throw new FileReadException($path);
|
||||
}
|
||||
|
||||
return $stream;
|
||||
}
|
||||
|
||||
public function writeStream(string $path)
|
||||
{
|
||||
$resolvedPath = $this->resolvePath($path);
|
||||
$dir = dirname($resolvedPath);
|
||||
if (! is_dir($dir)) {
|
||||
$this->createDirectory($path);
|
||||
}
|
||||
|
||||
$stream = @fopen($resolvedPath, 'w');
|
||||
if ($stream === false) {
|
||||
throw new FileWriteException($path);
|
||||
}
|
||||
|
||||
return $stream;
|
||||
}
|
||||
|
||||
public function appendStream(string $path)
|
||||
{
|
||||
$resolvedPath = $this->resolvePath($path);
|
||||
$dir = dirname($resolvedPath);
|
||||
if (! is_dir($dir)) {
|
||||
$this->createDirectory($path);
|
||||
}
|
||||
|
||||
$stream = @fopen($resolvedPath, 'a');
|
||||
if ($stream === false) {
|
||||
throw new FileWriteException($path);
|
||||
}
|
||||
|
||||
return $stream;
|
||||
}
|
||||
|
||||
public function putStream(string $path, $stream): void
|
||||
{
|
||||
$writeStream = $this->writeStream($path);
|
||||
|
||||
if (@stream_copy_to_stream($stream, $writeStream) === false) {
|
||||
@fclose($writeStream);
|
||||
|
||||
throw new FileWriteException($path);
|
||||
}
|
||||
|
||||
@fclose($writeStream);
|
||||
}
|
||||
|
||||
public function copyStream(string $source, string $destination): void
|
||||
{
|
||||
$sourceStream = $this->readStream($source);
|
||||
$this->putStream($destination, $sourceStream);
|
||||
@fclose($sourceStream);
|
||||
}
|
||||
|
||||
public function readLines(string $path): \Generator
|
||||
{
|
||||
$stream = $this->readStream($path);
|
||||
|
||||
try {
|
||||
while (($line = @fgets($stream)) !== false) {
|
||||
yield rtrim($line, "\r\n");
|
||||
}
|
||||
} finally {
|
||||
@fclose($stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user