groupVariantsByFormat($image, $variantType);
// Source-Tags für jedes Format generieren
foreach (['avif', 'webp', 'jpeg'] as $format) {
if (isset($variantsByFormat[$format])) {
$sources[] = $this->generateSourceElement($variantsByFormat[$format], ImageFormat::from($format));
}
}
// Fallback img-Tag
$fallbackImage = $this->getFallbackImage($variantsByFormat, $image);
$attributes['src'] = $fallbackImage->getUrl();
$attributes['width'] = $fallbackImage->width;
$attributes['height'] = $fallbackImage->height;
if (! isset($attributes['alt'])) {
$attributes['alt'] = '';
}
$sources[] = $this->generateImgTag($attributes);
return '' . implode('', $sources) . '';
}
/**
* Generiert einen einfachen source-Tag mit srcset
*
* @param array $variants Bildvarianten für ein bestimmtes Format
* @param ImageFormat $format Das Bildformat
* @return string HTML source-Element
*/
private function generateSourceElement(array $variants, ImageFormat $format): string
{
$srcset = [];
$sizes = [];
// Nach Größe sortieren (klein nach groß)
usort($variants, function (ImageVariant $a, ImageVariant $b) {
return $a->width <=> $b->width;
});
foreach ($variants as $variant) {
// URL und Breite für srcset
$srcset[] = $variant->getUrl() . ' ' . $variant->width . 'w';
// Sizes basierend auf Breakpoints
$size = ImageSize::from($variant->size);
$breakpoint = $size->getBreakpoint();
if ($breakpoint !== null) {
$sizes[] = "(max-width: {$breakpoint}px) {$variant->width}px";
}
}
// Größte Variante als Standardgröße hinzufügen
$sizes[] = $variants[count($variants) - 1]->width . 'px';
return sprintf(
'',
$format->getMimeType(),
implode(', ', $srcset),
implode(', ', $sizes)
);
}
/**
* Erzeugt einen img-Tag mit den angegebenen Attributen
*
* @param array $attributes HTML-Attribute für das img-Tag
* @return string HTML img-Element
*/
private function generateImgTag(array $attributes): string
{
$htmlAttributes = [];
foreach ($attributes as $name => $value) {
$htmlAttributes[] = sprintf('%s="%s"', $name, htmlspecialchars((string)$value));
}
return '
';
}
/**
* Gruppiert Bildvarianten nach Format
*
* @param Image $image Das Originalbild
* @param string $variantType Der Variantentyp
* @return array Varianten gruppiert nach Format
*/
private function groupVariantsByFormat(Image $image, string $variantType): array
{
$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;
}
}
return $variantsByFormat;
}
/**
* Wählt das beste Fallback-Bild aus
*
* @param array $variantsByFormat Varianten gruppiert nach Format
* @param Image $image Das Originalbild
* @return ImageVariant Die beste Fallback-Variante
*/
private function getFallbackImage(array $variantsByFormat, Image $image): ImageVariant
{
// Bevorzugen JPEG als Fallback
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 (! empty($variantsByFormat[$format])) {
return $variantsByFormat[$format][0];
}
}
// Wenn keine Varianten gefunden wurden, das Originalbild verwenden
return new ImageVariant(
imageId: (string) $image->ulid,
variantType: 'original',
size: 'original',
format: pathinfo($image->filename, PATHINFO_EXTENSION),
mimeType: $image->mimeType,
fileSize: $image->fileSize,
width: $image->width,
height: $image->height,
filename: $image->filename,
path: $image->path->toString(),
);
}
}