*/ public const WIDTHS = [480, 720, 960, 1440]; public static function generate(string $path, ?string $disk = null): void { self::forgetMetadata($path, $disk); $filesystem = self::filesystem($disk); try { if (! $filesystem->exists($path)) { return; } $manager = new ImageManager(new Driver); $contents = $filesystem->get($path); if ($contents === null) { return; } $extension = strtolower(pathinfo($path, PATHINFO_EXTENSION)); foreach (self::WIDTHS as $width) { $variantPath = self::variantPath($path, $width); $variant = $manager->read($contents); if ($variant->width() > $width) { $variant->scale(width: $width); } $encoded = match ($extension) { 'png' => $variant->toPng(), 'webp' => $variant->toWebp(quality: 82), default => $variant->toJpeg(quality: 82), }; $filesystem->put($variantPath, (string) $encoded); // A webp sibling for every variant. The MAN-109 audit measured the // hero as the LCP element on every mobile page, at 143 KiB for a // 960 px jpeg — webp carries the same picture for roughly a third of // that. `x-media.image` offers these through a so a browser // that cannot decode webp still gets the original format. if ($extension === 'webp') { continue; } $webp = $manager->read($contents); if ($webp->width() > $width) { $webp->scale(width: $width); } $filesystem->put( self::webpVariantPath($path, $width), (string) $webp->toWebp(quality: 80) ); } } finally { self::forgetMetadata($path, $disk); } self::metadata($path, $disk); } public static function deleteVariants(string $path, ?string $disk = null): void { self::forgetMetadata($path, $disk); $filesystem = self::filesystem($disk); try { foreach (self::WIDTHS as $width) { foreach ([self::variantPath($path, $width), self::webpVariantPath($path, $width)] as $variantPath) { if ($filesystem->exists($variantPath)) { $filesystem->delete($variantPath); } } } } finally { self::forgetMetadata($path, $disk); } } public static function delete(string $path, ?string $disk = null): void { self::forgetMetadata($path, $disk); $filesystem = self::filesystem($disk); try { self::deleteVariants($path, $disk); if ($filesystem->exists($path)) { $filesystem->delete($path); } } finally { self::forgetMetadata($path, $disk); } } public static function replace(string $previousPath, string $newPath, ?string $disk = null): void { self::forgetMetadata($previousPath, $disk); self::forgetMetadata($newPath, $disk); try { if ($previousPath !== '' && $previousPath !== $newPath) { self::delete($previousPath, $disk); } self::generate($newPath, $disk); } finally { self::forgetMetadata($previousPath, $disk); self::forgetMetadata($newPath, $disk); } self::metadata($newPath, $disk); } public static function variantPath(string $path, int $width): string { $directory = trim(dirname($path), '.'); $filename = pathinfo($path, PATHINFO_FILENAME); $extension = pathinfo($path, PATHINFO_EXTENSION); $variantName = $filename.'-'.$width.($extension !== '' ? '.'.$extension : ''); return $directory === '' ? $variantName : $directory.'/'.$variantName; } /** * The webp sibling of a variant, named by appending rather than replacing the * extension. Uploads are stored under a UUID so a collision is already * unlikely, but `photo-480.jpg.webp` cannot collide with the webp variant of * a `photo.png` the way `photo-480.webp` would. */ public static function webpVariantPath(string $path, int $width): string { return self::variantPath($path, $width).'.webp'; } /** * @return list */ public static function availableVariants(string $path, ?string $disk = null): array { return self::metadata($path, $disk)['variants'] ?? []; } /** * The webp variants that exist for a path. Empty when the media predates * `media:generate-variants` running with webp support, which is why * `x-media.image` treats the as optional rather than assuming it. * * @return list */ public static function availableWebpVariants(string $path, ?string $disk = null): array { return self::metadata($path, $disk)['webp_variants'] ?? []; } /** * @return array{width: int, height: int}|null */ public static function dimensions(string $path, ?string $disk = null): ?array { $metadata = self::metadata($path, $disk); return $metadata === null ? null : [ 'width' => $metadata['width'], 'height' => $metadata['height'], ]; } /** * @return array{ * width: int, * height: int, * variants: list, * webp_variants: list * }|null */ public static function metadata(string $path, ?string $disk = null): ?array { $key = self::metadataCacheKey($path, $disk); $cached = self::cachedMetadata($key); if (is_array($cached)) { return $cached; } try { $filesystem = self::filesystem($disk); if (! $filesystem->exists($path)) { return null; } $contents = $filesystem->get($path); if ($contents === null) { return null; } $image = (new ImageManager(new Driver))->read($contents); $metadata = [ 'width' => $image->width(), 'height' => $image->height(), 'variants' => self::existingVariants($filesystem, $path, false), 'webp_variants' => self::existingVariants($filesystem, $path, true), ]; self::storeMetadata($key, $metadata); return $metadata; } catch (Throwable) { return null; } } public static function forgetMetadata(string $path, ?string $disk = null): void { try { Cache::forget(self::metadataCacheKey($path, $disk)); } catch (Throwable) { // Cache availability must not block public media rendering or cleanup. } } private static function filesystem(?string $disk): Filesystem { return Storage::disk(self::diskName($disk)); } /** * @return list */ private static function existingVariants(Filesystem $filesystem, string $path, bool $webp): array { $variants = []; foreach (self::WIDTHS as $width) { $variantPath = $webp ? self::webpVariantPath($path, $width) : self::variantPath($path, $width); if ($filesystem->exists($variantPath)) { $variants[] = ['path' => $variantPath, 'width' => $width]; } } return $variants; } private static function metadataCacheKey(string $path, ?string $disk): string { return 'responsive-image:metadata:'.sha1(self::diskName($disk).'|'.$path); } /** * @return array|null */ private static function cachedMetadata(string $key): ?array { try { $cached = Cache::get($key); return is_array($cached) ? $cached : null; } catch (Throwable) { return null; } } /** * @param array $metadata */ private static function storeMetadata(string $key, array $metadata): void { try { Cache::forever($key, $metadata); } catch (Throwable) { // The uncached result remains safe to use for this request. } } private static function diskName(?string $disk): string { return $disk ?? PublicImageUploadRules::disk(); } }