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:
2025-08-11 20:13:26 +02:00
parent 59fd3dd3b1
commit 55a330b223
3683 changed files with 2956207 additions and 16948 deletions

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace App\Domain\Media;
@@ -98,6 +99,7 @@ final readonly class GdImageProcessor implements ImageProcessorInterface
private function generateVariantFilename(string $originalFilename, string $type, string $size, string $format): string
{
$pathInfo = pathinfo($originalFilename);
return $pathInfo['filename'] . "_{$type}_{$size}.{$format}";
}
@@ -138,6 +140,7 @@ final readonly class GdImageProcessor implements ImageProcessorInterface
imagedestroy($source);
$this->saveImageInFormat($dst, $destination, $format);
imagedestroy($dst);
return ['width' => $newWidth, 'height' => $newHeight];
}
@@ -204,7 +207,7 @@ final readonly class GdImageProcessor implements ImageProcessorInterface
$sharpenMatrix = [
[-1, -1, -1],
[-1, 16, -1],
[-1, -1, -1]
[-1, -1, -1],
];
$divisor = 8;
$offset = 0;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace App\Domain\Media;
@@ -37,5 +38,6 @@ final readonly class Image
public string $path,
#[Column(name: 'alt_text')]
public string $altText,
){}
) {
}
}

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Domain\Media;
enum ImageFormat: string

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Domain\Media;
use App\Framework\Database\Attributes\Entity;
@@ -7,5 +9,4 @@ use App\Framework\Database\Attributes\Entity;
#[Entity(tableName: 'image_galleries')]
class ImageGallery
{
}

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace App\Domain\Media;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace App\Domain\Media;
@@ -21,6 +22,7 @@ final readonly class ImageProcessorFactory
// Test ob ImageMagick funktioniert
$test = new \Imagick();
$test->clear();
return new ImagickImageProcessor();
} catch (\Exception $e) {
error_log('ImageMagick is installed but not working: ' . $e->getMessage());

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace App\Domain\Media;

View File

@@ -1,19 +1,17 @@
<?php
declare(strict_types=1);
namespace App\Domain\Media;
use App\Framework\Database\ConnectionInterface;
use App\Framework\Database\EntityManager;
use App\Framework\Database\Transaction;
final readonly class ImageRepository
{
public function __construct(
private EntityManager $entityManager,
private ConnectionInterface $connection,
) {}
) {
}
public function save(Image $image, string $tempPath): void
{
@@ -27,7 +25,6 @@ final readonly class ImageRepository
return $this->entityManager->findOneBy(ImageSlot::class, ['slot_name' => $slotName])->image;
}
public function findById(string $id): ?Image
{
return $this->entityManager->find(Image::class, $id);
@@ -43,8 +40,90 @@ final readonly class ImageRepository
return $this->entityManager->findOneBy(Image::class, ['hash' => $hash]);
}
public function findAll(): array
public function findAll(int $limit = 50, int $offset = 0, ?string $search = null): array
{
return $this->entityManager->findAll(Image::class);
// Simplified version - use EntityManager::findAll for now
$allImages = $this->entityManager->findAll(Image::class);
// Apply search filter if provided
if ($search) {
$allImages = array_filter($allImages, function ($image) use ($search) {
return stripos($image->originalFilename, $search) !== false ||
stripos($image->altText, $search) !== false;
});
}
// Apply offset and limit
return array_slice($allImages, $offset, $limit);
}
public function count(?string $search = null): int
{
$allImages = $this->entityManager->findAll(Image::class);
if ($search) {
$allImages = array_filter($allImages, function ($image) use ($search) {
return stripos($image->originalFilename, $search) !== false ||
stripos($image->altText, $search) !== false;
});
}
return count($allImages);
}
public function findByUlid(string $ulid): ?Image
{
return $this->entityManager->find(Image::class, $ulid);
}
public function updateAltText(string $ulid, string $altText): void
{
$image = $this->findByUlid($ulid);
if ($image) {
$image->altText = $altText;
$this->entityManager->save($image);
}
}
public function updateFilename(string $ulid, string $filename): void
{
$image = $this->findByUlid($ulid);
if ($image) {
$image->filename = $filename;
$this->entityManager->save($image);
}
}
public function search(string $query, ?string $type = null, int $minWidth = 0, int $minHeight = 0): array
{
$allImages = $this->entityManager->findAll(Image::class);
$filteredImages = array_filter($allImages, function ($image) use ($query, $type, $minWidth, $minHeight) {
// Text search
if ($query && ! (stripos($image->originalFilename, $query) !== false || stripos($image->altText, $query) !== false)) {
return false;
}
// Type filter
if ($type && $image->mimeType !== 'image/' . $type) {
return false;
}
// Size filters
if ($minWidth > 0 && $image->width < $minWidth) {
return false;
}
if ($minHeight > 0 && $image->height < $minHeight) {
return false;
}
return true;
});
// Limit to 100 results
return array_slice($filteredImages, 0, 100);
}
}

View File

@@ -1,23 +1,24 @@
<?php
declare(strict_types=1);
namespace App\Domain\Media;
use App\Framework\DateTime\SystemClock;
use App\Framework\Ulid\StringConverter;
use App\Framework\Ulid\Ulid;
final readonly class ImageResizer
{
public function __construct() {}
public function __construct()
{
}
public function __invoke(
Image $image,
int $maxWidth,
int $maxHeight,
int $quality = 9
): ImageVariant
{
): ImageVariant {
$sourcePath = $image->path . $image->filename;
$filename = str_replace('original', 'thumbnail', $image->filename);
@@ -26,7 +27,7 @@ final readonly class ImageResizer
$imageInfo = getimagesize($sourcePath);
if($imageInfo === false) {
if ($imageInfo === false) {
throw new \RuntimeException('Could not get image info');
}
@@ -71,8 +72,8 @@ final readonly class ImageResizer
);
}
private function createImageFromFile(string $sourcePath, mixed $imageType) {
private function createImageFromFile(string $sourcePath, mixed $imageType): \GdImage
{
return match ($imageType) {
IMAGETYPE_PNG => imagecreatefrompng($sourcePath),
IMAGETYPE_JPEG => imagecreatefromjpeg($sourcePath),

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Domain\Media;
enum ImageSize: string

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Domain\Media;
use App\Framework\Database\Attributes\Column;
@@ -17,5 +19,6 @@ final readonly class ImageSlot
public string $slotName,
#[Column(name: 'image_id')]
public string $imageId,
){}
) {
}
}

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Domain\Media;
use App\Framework\Database\EntityManager;
@@ -8,7 +10,8 @@ final readonly class ImageSlotRepository
{
public function __construct(
private EntityManager $entityManager
){}
) {
}
public function getSlots(): array
{
@@ -29,4 +32,48 @@ final readonly class ImageSlotRepository
{
return $this->entityManager->save($imageSlot);
}
public function findAllWithImages(): array
{
$slots = $this->entityManager->findAll(ImageSlot::class);
return array_map(function (ImageSlot $slot) {
$image = null;
if ($slot->imageId) {
$image = $this->entityManager->find(Image::class, $slot->imageId);
}
return ImageSlotView::fromSlot($slot, $image);
}, $slots);
}
public function findByIdWithImage(string $id): ImageSlotView
{
$slot = $this->entityManager->find(ImageSlot::class, $id);
if (! $slot) {
throw new \RuntimeException("ImageSlot with ID {$id} not found");
}
$image = null;
if ($slot->imageId) {
$image = $this->entityManager->find(Image::class, $slot->imageId);
}
return ImageSlotView::fromSlot($slot, $image);
}
public function updateImageId(string $slotId, string $imageId): void
{
$slot = $this->findById($slotId);
#$slot->imageId = $imageId;
$slot = new ImageSlot(
$slot->id,
$slot->slotName,
$imageId
);
$this->entityManager->save($slot);
}
}

View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace App\Domain\Media;
final readonly class ImageSlotView
{
public function __construct(
public int $id,
public string $slotName,
public string $imageId,
public ?Image $image
) {
}
public static function fromSlot(ImageSlot $slot, ?Image $image = null): self
{
return new self(
$slot->id,
$slot->slotName,
$slot->imageId,
$image
);
}
}

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace App\Domain\Media;
@@ -36,7 +37,7 @@ final readonly class ImageSourceSetGenerator
$attributes['width'] = $fallbackImage->width;
$attributes['height'] = $fallbackImage->height;
if (!isset($attributes['alt'])) {
if (! isset($attributes['alt'])) {
$attributes['alt'] = '';
}
@@ -114,6 +115,11 @@ final readonly class ImageSourceSetGenerator
{
$variantsByFormat = [];
// Check if variants are loaded to avoid uninitialized readonly property error
if (! isset($image->variants)) {
return [];
}
foreach ($image->variants as $variant) {
if ($variant->variantType === $variantType) {
$variantsByFormat[$variant->format][] = $variant;
@@ -133,15 +139,16 @@ final readonly class ImageSourceSetGenerator
private function getFallbackImage(array $variantsByFormat, Image $image): ImageVariant
{
// Bevorzugen JPEG als Fallback
if (isset($variantsByFormat['jpeg']) && !empty($variantsByFormat['jpeg'])) {
if (! empty($variantsByFormat['jpeg'])) {
// Mittlere Größe als Fallback verwenden
$variants = $variantsByFormat['jpeg'];
return $variants[min(1, count($variants) - 1)];
}
// Alternativen, falls kein JPEG verfügbar
foreach (['webp', 'avif'] as $format) {
if (isset($variantsByFormat[$format]) && !empty($variantsByFormat[$format])) {
if (! empty($variantsByFormat[$format])) {
return $variantsByFormat[$format][0];
}
}

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace App\Domain\Media;
@@ -33,10 +34,10 @@ final readonly class ImageVariant
public string $filename,
#[Column(name: 'path')]
public string $path,
#[Column(name: 'id', primary: true, autoIncrement: true)]
public ?int $id = null,
) {}
) {
}
public function getUrl(): string
{

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace App\Domain\Media;
@@ -8,7 +9,7 @@ final readonly class ImageVariantConfig
public static function getAllVariants(): array
{
$variants = [];
foreach (ImageVariantType::cases() as $type) {
foreach ($type->getSizes() as $size) {
foreach (ImageFormat::cases() as $format) {
@@ -21,14 +22,14 @@ final readonly class ImageVariantConfig
}
}
}
return $variants;
}
public static function getVariantsForType(ImageVariantType $type): array
{
$variants = [];
foreach ($type->getSizes() as $size) {
foreach (ImageFormat::cases() as $format) {
$variants[] = [
@@ -39,7 +40,7 @@ final readonly class ImageVariantConfig
];
}
}
return $variants;
}
}

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace App\Domain\Media;
@@ -9,14 +10,16 @@ final class ImageVariantRepository
{
public function __construct(
private EntityManager $entityManager
){}
) {
}
public function save(ImageVariant $imageVariant): void
{
$this->entityManager->save($imageVariant);
}
public function findByFilename(string $filename): ?ImageVariant {
public function findByFilename(string $filename): ?ImageVariant
{
return $this->entityManager->findOneBy(ImageVariant::class, ['filename' => $filename]);
}
}

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Domain\Media;
enum ImageVariantType: string

View File

@@ -1,11 +1,11 @@
<?php
declare(strict_types=1);
namespace App\Domain\Media;
use Imagick;
use ImagickException;
use ImagickPixel;
/**
* ImageMagick-basierter Bildprozessor für optimale Bildqualität
@@ -102,6 +102,7 @@ final readonly class ImagickImageProcessor implements ImageProcessorInterface
private function generateVariantFilename(string $originalFilename, string $type, string $size, string $format): string
{
$pathInfo = pathinfo($originalFilename);
return $pathInfo['filename'] . "_{$type}_{$size}.{$format}";
}

View File

@@ -1,26 +1,41 @@
<?php
declare(strict_types=1);
namespace App\Domain\Media\Migrations;
use App\Framework\Database\ConnectionInterface;
use App\Framework\Database\Migration\Migration;
use App\Framework\Database\Migration\MigrationVersion;
use App\Framework\Database\Schema\Schema;
final readonly class AddSizeToImageVariantsTable implements Migration
{
public function up(ConnectionInterface $connection): void
{
$connection->execute("ALTER TABLE image_variants ADD COLUMN size VARCHAR(25) NOT NULL DEFAULT ''");
$schema = new Schema($connection);
/*$schema->table('image_variants', function ($table) {
$table->string('size', 25)->default('');
});*/
if (! $schema->hasColumn('image_variants', 'size')) {
$connection->execute("ALTER TABLE image_variants ADD COLUMN size VARCHAR(25) NOT NULL DEFAULT ''");
}
}
public function down(ConnectionInterface $connection): void
{
$connection->execute("ALTER TABLE image_variants DROP COLUMN size");
$schema = new Schema($connection);
if ($schema->hasColumn('image_variants', 'size')) {
$connection->execute("ALTER TABLE image_variants DROP COLUMN size");
}
}
public function getVersion(): string
public function getVersion(): MigrationVersion
{
return "005";
return MigrationVersion::fromTimestamp("2024_01_16_000005");
}
public function getDescription(): string

View File

@@ -1,13 +1,15 @@
<?php
declare(strict_types=1);
namespace App\Domain\Media\Migrations;
use App\Framework\Database\ConnectionInterface;
use App\Framework\Database\Migration\Migration;
use App\Framework\Database\Migration\MigrationVersion;
final readonly class CreateImageSlotsTable implements Migration
{
public function up(ConnectionInterface $connection): void
{
$sql = <<<SQL
@@ -31,9 +33,9 @@ SQL;
$connection->execute("DROP TABLE IF EXISTS image_slots");
}
public function getVersion(): string
public function getVersion(): MigrationVersion
{
return "004";
return MigrationVersion::fromTimestamp("2024_01_15_000004");
}
public function getDescription(): string

View File

@@ -1,14 +1,15 @@
<?php
declare(strict_types=1);
namespace App\Domain\Media\Migrations;
use App\Framework\Database\ConnectionInterface;
use App\Framework\Database\Migration\Migration;
use App\Framework\Database\Migration\MigrationVersion;
final class CreateImageVariantsTable implements Migration
{
public function up(ConnectionInterface $connection): void
{
$sql = <<<SQL
@@ -44,9 +45,9 @@ SQL;
$connection->execute("DROP TABLE IF EXISTS image_variants");
}
public function getVersion(): string
public function getVersion(): MigrationVersion
{
return "003";
return MigrationVersion::fromTimestamp("2024_01_15_000003");
}
public function getDescription(): string

View File

@@ -1,14 +1,15 @@
<?php
declare(strict_types=1);
namespace App\Domain\Media\Migrations;
use App\Framework\Database\ConnectionInterface;
use App\Framework\Database\Migration\Migration;
use App\Framework\Database\Migration\MigrationVersion;
final class CreateImagesTable implements Migration
{
public function up(ConnectionInterface $connection): void
{
$sql = <<<SQL
@@ -40,9 +41,9 @@ SQL;
$connection->execute("DROP TABLE IF EXISTS images");
}
public function getVersion(): string
public function getVersion(): MigrationVersion
{
return "002";
return MigrationVersion::fromTimestamp("2024_01_15_000002");
}
public function getDescription(): string

View File

@@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace App\Domain\Media\Migrations;
use App\Framework\Database\ConnectionInterface;
use App\Framework\Database\Migration\Migration;
use App\Framework\Database\Migration\MigrationVersion;
use App\Framework\Database\Schema\Blueprint;
use App\Framework\Database\Schema\Schema;
final class CreateImagesTableWithSchema implements Migration
{
public function up(ConnectionInterface $connection): void
{
$schema = new Schema($connection);
$schema->create('images', function (Blueprint $table) {
$table->ulid('ulid')->primary();
$table->string('filename', 255);
$table->string('original_filename', 255);
$table->string('mime_type', 100);
$table->bigInteger('file_size');
$table->unsignedInteger('width');
$table->unsignedInteger('height');
$table->string('hash', 255)->unique();
$table->string('path', 500);
$table->text('alt_text');
$table->timestamps();
// Indexes
$table->unique(['hash'], 'uk_images_hash');
$table->index(['mime_type']);
$table->index(['created_at']);
// Table options
$table->engine('InnoDB');
$table->charset('utf8mb4');
$table->collation('utf8mb4_unicode_ci');
});
$schema->execute();
}
public function down(ConnectionInterface $connection): void
{
$schema = new Schema($connection);
$schema->dropIfExists('images');
$schema->execute();
}
public function getVersion(): MigrationVersion
{
return MigrationVersion::fromTimestamp("2024_01_17_000003");
}
public function getDescription(): string
{
return "Create Images Table with Schema Builder";
}
}

View File

@@ -1,9 +1,12 @@
<?php
declare(strict_types=1);
namespace App\Domain\Media\Migrations;
use App\Framework\Database\ConnectionInterface;
use App\Framework\Database\Migration\Migration;
use App\Framework\Database\Migration\MigrationVersion;
final readonly class UpdateImageVariantsConstraint implements Migration
{
@@ -23,9 +26,9 @@ final readonly class UpdateImageVariantsConstraint implements Migration
$connection->execute("ALTER TABLE image_variants ADD UNIQUE KEY uk_image_variants_combination (image_id, variant_type, format)");
}
public function getVersion(): string
public function getVersion(): MigrationVersion
{
return "006";
return MigrationVersion::fromTimestamp("2024_01_18_000006");
}
public function getDescription(): string

View File

@@ -1,9 +1,9 @@
<?php
declare(strict_types=1);
namespace App\Domain\Media;
use App\Framework\Http\UploadedFile;
use function move_uploaded_file;
final readonly class SaveImageFile
@@ -11,9 +11,10 @@ final readonly class SaveImageFile
public function __invoke(Image $image, string $tempFileName): bool
{
$directory = $image->path;
if (!is_dir($directory)) {
if (! is_dir($directory)) {
mkdir($directory, 0755, true);
}
return move_uploaded_file($tempFileName, $image->path . $image->filename);
}
}