Serve images without PHP, add the 720 and 1280 steps, trim two hot paths
- Image widths are 480, 720, 960, 1280, 1600 and 2400. A phone needed 721 pixels and got 960. Existing images receive the new steps from a task; stored pages pick them up at render time, so no document is rewritten. Mobile backgrounds use 720. - A variant served once is mirrored to public/media, and a rewrite rule lets the web server deliver it from there. var/media stays the source. - The hero preload follows the image each device really shows. - The post navigation reads the order and then two posts instead of up to 500 documents. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
1 parent
dca2665138
commit
7d0369bada
32 files changed
+550
-57
No files matched your search
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace App\Content;
|
||||
|
||||
/**
|
||||
* Legt Bildvarianten dorthin, wo der Webserver sie ohne PHP ausliefern kann.
|
||||
*
|
||||
* Jedes Bild einer Seite war eine Anfrage an die Anwendung: Kernel starten, Datenbank fragen,
|
||||
* Datei durchreichen. Gemessen auf kant-it.de (10.11.2026) rund 250 ms bis zum ersten Byte, eine
|
||||
* statische Datei daneben rund 100 ms - und jede dieser Anfragen belegt einen der wenigen
|
||||
* PHP-Prozesse, die ein geteiltes Hosting gleichzeitig hergibt.
|
||||
*
|
||||
* Der Medienordner unter var/ bleibt die Quelle; Sicherung, Website-Paket und Wiederherstellung
|
||||
* kennen nur ihn. Dieser Ordner ist ein Abbild, das sich von selbst füllt: Beim ersten Abruf über
|
||||
* PHP landet die Variante hier, alle weiteren beantwortet der Webserver (Regel in
|
||||
* public/.htaccess). Fehlt das Abbild oder lässt es sich nicht schreiben, bleibt alles beim Alten.
|
||||
*
|
||||
* Die Adressen der Bilder ändern sich nicht. Was hier liegt, war auch vorher für jeden abrufbar,
|
||||
* der die Adresse kennt - die Auslieferung über PHP prüft keine Anmeldung.
|
||||
*/
|
||||
final class MediaMirror
|
||||
{
|
||||
/** @param string $directory das Abbild, üblich public/media; leer schaltet es ab */
|
||||
public function __construct(private string $directory, private string $mediaDirectory)
|
||||
{
|
||||
}
|
||||
|
||||
public function enabled(): bool
|
||||
{
|
||||
return '' !== $this->directory;
|
||||
}
|
||||
|
||||
/** Legt eine Variante ab. Scheitert still: das Abbild ist eine Abkürzung, keine Voraussetzung. */
|
||||
public function publish(string $id, int $width, string $source): void
|
||||
{
|
||||
if (!$this->enabled() || 1 !== preg_match('/^[a-f0-9]{32}$/D', $id) || $width < 1 || $width > 9999) {
|
||||
return;
|
||||
}
|
||||
$folder = $this->directory.'/'.$id;
|
||||
$target = $folder.'/'.$width.'.webp';
|
||||
if (is_file($target)) {
|
||||
return;
|
||||
}
|
||||
if (!is_dir($folder) && !@mkdir($folder, 0o775, true) && !is_dir($folder)) {
|
||||
return;
|
||||
}
|
||||
// Ein harter Verweis kostet keinen zweiten Platz auf der Platte. Wo das Dateisystem ihn
|
||||
// nicht kennt oder der Medienordner woanders liegt, wird kopiert - über eine Zwischendatei,
|
||||
// damit der Webserver nie ein halbes Bild ausliefert.
|
||||
if (@link($source, $target)) {
|
||||
return;
|
||||
}
|
||||
$teil = $target.'.'.bin2hex(random_bytes(4)).'.tmp';
|
||||
if (@copy($source, $teil) && filesize($source) === filesize($teil) && @rename($teil, $target)) {
|
||||
return;
|
||||
}
|
||||
@unlink($teil);
|
||||
}
|
||||
|
||||
/**
|
||||
* Entfernt aus dem Abbild, was es im Medienordner nicht mehr gibt - nach dem Import eines
|
||||
* Website-Pakets etwa. Ohne das lieferte der Webserver Bilder weiter aus, die die Anwendung
|
||||
* selbst nicht mehr kennt.
|
||||
*
|
||||
* @return int entfernte Dateien
|
||||
*/
|
||||
public function prune(): int
|
||||
{
|
||||
if (!$this->enabled() || !is_dir($this->directory)) {
|
||||
return 0;
|
||||
}
|
||||
$removed = 0;
|
||||
foreach (scandir($this->directory) ?: [] as $id) {
|
||||
if (1 !== preg_match('/^[a-f0-9]{32}$/D', $id) || !is_dir($this->directory.'/'.$id)) {
|
||||
continue;
|
||||
}
|
||||
foreach (scandir($this->directory.'/'.$id) ?: [] as $name) {
|
||||
if ('.' === $name || '..' === $name) {
|
||||
continue;
|
||||
}
|
||||
$width = 1 === preg_match('/^(\d{1,4})\.webp$/D', $name, $treffer) ? $treffer[1] : null;
|
||||
if (null === $width || !is_file($this->mediaDirectory.'/'.$id.'-'.$width.'.webp')) {
|
||||
$removed += (int) @unlink($this->directory.'/'.$id.'/'.$name);
|
||||
}
|
||||
}
|
||||
// Gelingt nur, wenn der Ordner leer ist - genau dann soll er weg.
|
||||
@rmdir($this->directory.'/'.$id);
|
||||
}
|
||||
|
||||
return $removed;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user