chore: complete update
This commit is contained in:
265
.archive/Media/Services/ImageService.php
Normal file
265
.archive/Media/Services/ImageService.php
Normal file
@@ -0,0 +1,265 @@
|
||||
<?php
|
||||
|
||||
namespace Media\Services;
|
||||
|
||||
use App\Framework\Core\PathProvider;
|
||||
use App\Framework\Http\UploadedFile;
|
||||
use Media\Entities\Image;
|
||||
use Media\Entities\ImageVariant;
|
||||
use Media\Repositories\ImageRepository;
|
||||
use Media\Repositories\ImageVariantRepository;
|
||||
use function filesize;
|
||||
use function getimagesize;
|
||||
use function imageavif;
|
||||
use function imagecopyresampled;
|
||||
use function imagecreatefromstring;
|
||||
use function imagecreatetruecolor;
|
||||
use function imagedestroy;
|
||||
use function imagejpeg;
|
||||
use function imagesx;
|
||||
use function imagesy;
|
||||
use function imagewebp;
|
||||
|
||||
class ImageService
|
||||
{
|
||||
private const array VARIANTS = [
|
||||
'thumbnail' => ['width' => 150, 'height' => 150, 'crop' => true],
|
||||
'small' => ['width' => 400, 'height' => 400, 'crop' => false],
|
||||
'medium' => ['width' => 800, 'height' => 800, 'crop' => false],
|
||||
'large' => ['width' => 1200, 'height' => 1200, 'crop' => false],
|
||||
];
|
||||
|
||||
private const array FORMATS = ['jpg', 'webp', 'avif'];
|
||||
|
||||
public function __construct(
|
||||
private PathProvider $pathProvider,
|
||||
private ImageRepository $imageRepository,
|
||||
private ImageVariantRepository $variantRepository,
|
||||
) {}
|
||||
|
||||
public function uploadImage(UploadedFile $file): Image
|
||||
{
|
||||
// Validierung
|
||||
if (!$file->isValid()) {
|
||||
throw new \InvalidArgumentException('Ungültige Datei');
|
||||
}
|
||||
|
||||
if (!$this->isValidImageType($file->getMimeType())) {
|
||||
throw new \InvalidArgumentException('Ungültiger Bildtyp');
|
||||
}
|
||||
|
||||
// Basis-Image-Informationen erfassen
|
||||
$imageInfo = getimagesize($file->tmpName);
|
||||
$hash = \hash_file('sha256', $file->tmpName);
|
||||
|
||||
// Prüfen ob ein Bild mit gleichem Hash bereits existiert
|
||||
$existingImage = $this->imageRepository->findByHash($hash);
|
||||
if ($existingImage) {
|
||||
return $existingImage; // Duplikat gefunden, vorhandenes Bild zurückgeben
|
||||
}
|
||||
|
||||
// Image Entity erstellen
|
||||
$image = new Image(
|
||||
filename: $this->generateSecureFilename($file->name),
|
||||
originalFilename: $file->name,
|
||||
mimeType: $file->getMimeType(),
|
||||
fileSize: $file->size,
|
||||
width: $imageInfo[0],
|
||||
height: $imageInfo[1],
|
||||
hash: $hash,
|
||||
uploadPath: '',
|
||||
);
|
||||
|
||||
// In Datenbank speichern um ID zu erhalten
|
||||
$image = $this->imageRepository->save($image);
|
||||
|
||||
// Upload-Pfad setzen
|
||||
$image->uploadPath = $image->getUploadDirectory() . '/' . $image->getFilePathPattern();
|
||||
|
||||
$this->imageRepository->update($image);
|
||||
|
||||
// Ordnerstruktur erstellen
|
||||
$fullUploadPath = $this->pathProvider->resolvePath('storage' . $image->uploadPath);
|
||||
$this->createDirectoryStructure($fullUploadPath);
|
||||
|
||||
// Original-Datei speichern
|
||||
$originalPath = $fullUploadPath . '/original.' . $this->getFileExtension($file->getMimeType());
|
||||
$file->moveTo($originalPath);
|
||||
|
||||
// Varianten erstellen
|
||||
$this->createImageVariants($image, $originalPath);
|
||||
|
||||
// Image in Datenbank aktualisieren
|
||||
#$this->imageRepository->update($image);
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
private function createImageVariants(Image $image, string $originalPath): void
|
||||
{
|
||||
foreach (self::VARIANTS as $variantName => $config) {
|
||||
foreach (self::FORMATS as $format) {
|
||||
$variant = $this->createImageVariant(
|
||||
$image,
|
||||
$originalPath,
|
||||
$variantName,
|
||||
$format,
|
||||
$config
|
||||
);
|
||||
|
||||
// In Datenbank speichern
|
||||
$this->variantRepository->save($variant);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function createImageVariant(
|
||||
Image $image,
|
||||
string $originalPath,
|
||||
string $variantName,
|
||||
string $format,
|
||||
array $config
|
||||
): ImageVariant {
|
||||
$outputPath = $this->pathProvider->resolvePath('storage' . $image->uploadPath . '/' . $variantName . '.' . $format);
|
||||
|
||||
// Bild verarbeiten
|
||||
$resizedImage = $this->resizeImage($originalPath, $config['width'], $config['height'], $config['crop'] ?? false);
|
||||
$this->saveImageInFormat($resizedImage, $outputPath, $format);
|
||||
|
||||
// Neue Dateigröße und Dimensionen ermitteln
|
||||
$newImageInfo = getimagesize($outputPath);
|
||||
$fileSize = filesize($outputPath);
|
||||
|
||||
return new ImageVariant(
|
||||
imageId: $image->id,
|
||||
variant: $variantName,
|
||||
format: $format,
|
||||
width: $newImageInfo[0],
|
||||
height: $newImageInfo[1],
|
||||
fileSize: $fileSize,
|
||||
filename: $variantName . '.' . $format
|
||||
);
|
||||
}
|
||||
|
||||
private function resizeImage(string $sourcePath, int $maxWidth, int $maxHeight, bool $crop = false): \GdImage
|
||||
{
|
||||
$sourceImage = imagecreatefromstring(\file_get_contents($sourcePath));
|
||||
$sourceWidth = imagesx($sourceImage);
|
||||
$sourceHeight = imagesy($sourceImage);
|
||||
|
||||
if ($crop) {
|
||||
// Crop-Logik für quadratische Thumbnails
|
||||
$ratio = max($maxWidth / $sourceWidth, $maxHeight / $sourceHeight);
|
||||
$newWidth = (int)round($sourceWidth * $ratio);
|
||||
$newHeight = (int)round($sourceHeight * $ratio);
|
||||
|
||||
$tempImage = imagecreatetruecolor($newWidth, $newHeight);
|
||||
imagecopyresampled($tempImage, $sourceImage, 0, 0, 0, 0, $newWidth, $newHeight, $sourceWidth, $sourceHeight);
|
||||
|
||||
$finalImage = imagecreatetruecolor($maxWidth, $maxHeight);
|
||||
$cropX = (int)floor(($newWidth - $maxWidth) / 2);
|
||||
$cropY = (int)floor(($newHeight - $maxHeight) / 2);
|
||||
\imagecopy($finalImage, $tempImage, 0, 0, $cropX, $cropY, $maxWidth, $maxHeight);
|
||||
|
||||
imagedestroy($tempImage);
|
||||
} else {
|
||||
// Proportionales Skalieren
|
||||
$ratio = min($maxWidth / $sourceWidth, $maxHeight / $sourceHeight);
|
||||
$newWidth = (int)round($sourceWidth * $ratio);
|
||||
$newHeight = (int)round($sourceHeight * $ratio);
|
||||
|
||||
$finalImage = imagecreatetruecolor($newWidth, $newHeight);
|
||||
imagecopyresampled($finalImage, $sourceImage, 0, 0, 0, 0, $newWidth, $newHeight, $sourceWidth, $sourceHeight);
|
||||
}
|
||||
|
||||
imagedestroy($sourceImage);
|
||||
return $finalImage;
|
||||
}
|
||||
|
||||
private function saveImageInFormat(\GdImage $image, string $path, string $format): void
|
||||
{
|
||||
switch ($format) {
|
||||
case 'jpg':
|
||||
imagejpeg($image, $path, 85);
|
||||
break;
|
||||
case 'webp':
|
||||
imagewebp($image, $path, 85);
|
||||
break;
|
||||
case 'avif':
|
||||
if (\function_exists('imageavif')) {
|
||||
imageavif($image, $path, 85);
|
||||
} else {
|
||||
// Fallback auf WebP wenn AVIF nicht verfügbar
|
||||
imagewebp($image, $path, 85);
|
||||
}
|
||||
break;
|
||||
}
|
||||
imagedestroy($image);
|
||||
}
|
||||
|
||||
private function createDirectoryStructure(string $path): void
|
||||
{
|
||||
if (!is_dir($path)) {
|
||||
mkdir($path, 0755, true);
|
||||
}
|
||||
}
|
||||
|
||||
private function isValidImageType(string $mimeType): bool
|
||||
{
|
||||
return in_array($mimeType, [
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/gif',
|
||||
'image/webp'
|
||||
]);
|
||||
}
|
||||
|
||||
private function getFileExtension(string $mimeType): string
|
||||
{
|
||||
return match ($mimeType) {
|
||||
'image/jpeg' => 'jpg',
|
||||
'image/png' => 'png',
|
||||
'image/gif' => 'gif',
|
||||
'image/webp' => 'webp',
|
||||
default => 'jpg'
|
||||
};
|
||||
}
|
||||
|
||||
private function generateSecureFilename(string $originalName): string
|
||||
{
|
||||
$extension = pathinfo($originalName, PATHINFO_EXTENSION);
|
||||
return uniqid('img_', true) . '.' . strtolower($extension);
|
||||
}
|
||||
|
||||
public function getImageUrl(Image $image, string $variant = 'medium', string $format = 'jpg'): string
|
||||
{
|
||||
return '/media/' . $image->getUrlId() . '-' . $variant . '.' . $format;
|
||||
}
|
||||
|
||||
public function findById(int $id): ?Image
|
||||
{
|
||||
return $this->imageRepository->findById($id);
|
||||
}
|
||||
|
||||
public function getVariants(Image $image): array
|
||||
{
|
||||
return $this->variantRepository->findByImageId($image->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Löst eine URL-ID zu einem Image-Objekt auf
|
||||
*/
|
||||
public function resolveFromUrlId(string $urlId): ?Image
|
||||
{
|
||||
// URL-ID format: YYYYMMDDXXXXXXXX (Datum + 8-stellige ID)
|
||||
if (strlen($urlId) !== 16) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$idPart = substr($urlId, 8); // Die letzten 8 Zeichen
|
||||
$id = (int)ltrim($idPart, '0'); // Führende Nullen entfernen
|
||||
|
||||
return $this->imageRepository->findById($id);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user