Si vous rencontrez des problèmes de générations d’images sur votre Magento, cet article est fait pour vous !
Pour commencer, il y a un certain nombre de choses à savoir sur le mécanisme de génération des images produits.
Le répertoire de stockage
Les images produits sont stockées dans pub/media/catalog/X/Y/XY.jpg
Les deux sous-dossiers correspondent aux deux premières lettres du nom de l’image.
L’objectif est d’éviter d’avoir des problèmes de “list directory” qui prendraient trop de temps, notamment sur des sites avec plusieurs milliers d’images.
Exemple :
Les images redimensionnées sont physiquement stockées dans un sous-répertoire cache
Exemple :
Un dossier est créé avec un nom qui semble aléatoire : 0d72763c48bdde2aa070b8e114ee4163
À quoi correspondent les noms de dossiers dans le répertoire catalog/product/cache ?
Il s’agit en fait d’un ID généré, basé sur le format d’image que l’on demande à générer.
Ça provient donc du fichier etc/view.xml de son thème (ou de celui du core) :
XML :
<view xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Config/etc/view.xsd">
<media>
<images module="Magento_Catalog">
<image id="bundled_product_customization_page" type="thumbnail">
<width>140</width>
<height>140</height>
</image>
HTML :
<div class="content">
<div class="bundle-info">
<?= $block->getImage($_product, 'bundled_product_customization_page')->toHtml() ?>
Si vous changez la dimension, ou l’image source utilisée (thumbnail dans ce cas), l’ID va changer et un nouveau répertoire sera créé par Magento lors de la génération de l’image (les anciens répertoires ne sont jamais supprimés, alors attention à l’espace disque sur les gros catalogues !).
Quand et où sont générées les images produits redimensionnées sur Magento ?
Directement lors de l’appel à l’image dans son navigateur en règle générale.
Il existe bien une commande CLI catalog:images:resize mais pour des économies d’espace disque il n’est PAS RECOMMANDÉ de l’utiliser ! En effet, cette commande va simplement boucler sur toutes les dimensions existantes dans votre fichier etc/view.xml et prégénérer tous les formats pour toutes les images. Un réel gâchis donc ! Seul peu de format sont réellement utilisés sur le site. Certains formats sont d’ailleurs dédiés à des types de produits (bundle ou autre) donc on se rend bien compte que ce n’est pas utile de les générer tous.
Mais alors que se passe-t-il lorsque j’appelle mon URL dans mon navigateur ?
NGINX
En premier lieu, vos règles NGINX (ou apache) devraient d’abord vérifier si le fichier demandé existe. Si c’est le cas, il le retourne directement.
Si ce n’est pas le cas, il va transférer la requête à pub/get.php plutôt que de faire une 404.
location /media/ {
try_files $uri $uri/ /get.php$is_args$args;
location ~ ^/media/theme_customization/.*\.xml {
deny all;
}
location ~* \.(ico|jpg|jpeg|png|gif|svg|svgz|webp|avif|avifs|js|css|eot|ttf|otf|woff|woff2)$ {
add_header Cache-Control "public";
add_header X-Frame-Options "SAMEORIGIN";
expires +1y;
try_files $uri $uri/ /get.php$is_args$args;
}
pub/get.php
Ce fichier est le point d’entrée pour toute génération d’image redimensionnée qui n’existe pas encore.
La première action est de lire ou générer un fichier /var/resource_config.json
Ce fichier est généré aussi à la volée par Magento, lorsque vous appelez le front pour la première fois. Ce fichier contient la liste des fichiers qu’il est possible de générer.
Si vous rencontrez des pages “404 Page not found” provenant de Magento, vous pouvez vérifier si vous ne tombez pas dans l’un de ces points de sorties :
if (file_exists($configCacheFile) && is_readable($configCacheFile)) {
$config = json_decode(file_get_contents($configCacheFile), true);
//checking update time
if (filemtime($configCacheFile) + $config['update_time'] > time()) {
$mediaDirectory = $config['media_directory'];
$allowedResources = $config['allowed_resources'];
// Serve file if it's materialized
if ($mediaDirectory) {
$fileAbsolutePath = __DIR__ . '/' . $relativePath;
$fileRelativePath = str_replace(rtrim($mediaDirectory, '/') . '/', '', $fileAbsolutePath);
if (!$isAllowed($fileRelativePath, $allowedResources)) {
require_once 'errors/404.php';
exit;
}
if (is_readable($fileAbsolutePath)) {
if (is_dir($fileAbsolutePath)) {
require_once 'errors/404.php';
exit;
}
Plus loin dans l’exécution, vous aller atterrir dans vendor/magento/module-media-storage/App/Media.php soit la classe PHP : Magento\MediaStorage\App\Media
Tout est ensuite géré par la méthode launch :
public function launch(): ResponseInterface
{
$this->appState->setAreaCode(Area::AREA_GLOBAL);
if ($this->checkMediaDirectoryChanged()) {
// Path to media directory changed or absent - update the config
/** @var Config $config */
$config = $this->configFactory->create(['cacheFile' => $this->configCacheFile]);
$config->save();
$this->mediaDirectoryPath = $config->getMediaDirectory();
$allowedResources = $config->getAllowedResources();
$isAllowed = $this->isAllowed;
$fileAbsolutePath = $this->directoryPub->getAbsolutePath($this->relativeFileName);
$fileRelativePath = str_replace(rtrim($this->mediaDirectoryPath, '/') . '/', '', $fileAbsolutePath);
if (!$isAllowed($fileRelativePath, $allowedResources)) {
throw new LogicException('The path is not allowed: ' . $this->relativeFileName);
}
}
try {
$this->createLocalCopy();
if ($this->directoryPub->isReadable($this->relativeFileName)) {
$this->response->setFilePath($this->directoryPub->getAbsolutePath($this->relativeFileName));
} else {
$this->setPlaceholderImage();
}
} catch (Exception $e) {
$this->setPlaceholderImage();
}
return $this->response;
}
- Magento commence par vérifier l’existence du fichier de configuration /var/resource_config.json ou d'éventuels changements à y opérer.
- Puis dans le try / catch, on crée l’image : createLocalCopy
- On vérifie si elle est bien créée avec l’utilisation de isReadable
- Si c’est le cas on retourne son contenu : $this->response->setFilePath(…
- Autrement : on retourne le placeholder du thème en cours
On remarque que quelle que soit l’Exception catchée on se contente de retourner le placeholder sans avertir qui que ce soit d’une quelconque erreur, pas de log, rien en front.
En situation de debug (voir plus bas) il vous est possible de dump l’exception pour mieux comprendre ce qui bloque.
Comment debugger un problème de génération alors ?
Maintenant que l’on connait ce cheminement, ça semble plutôt simple et plusieurs options se portent à nous selon le contexte :
- Il y a une 404 nginx : vous n'avez peut-être pas le vhost correctement défini qui renvoie vers get.php. Allez donc mettre un var_dump / die dans votre fichier get.php pour savoir si vous l’atteignez
- J’ai une 404, mais qui vient de Magento (pub/errors/404.php) : vous avez sûrement un problème de droits sur un répertoire var/ mettez vos breakpoints dans get.php pour vérifier ça.
- Mon image source existe, mais ça me renvoie un placeHolder systématique : vous pouvez afficher l’exception dans la méthode launch, plutôt que de retourner le placeHolder en toutes circonstances dans le catch.
À ne pas oublier dans vos démarches :
- Si votre fichier existe dans pub/media/catalog/product/cache/ID/X/Y/XY.jpg : il faut le supprimer du disque puis relancer le call HTTP pour forcer sa génération
- Ne pas oublier votre cache navigateur ou varnish car il stock les retours de type 200 (donc les placeholder). Ce n’est pas le cas pour les 404 par contre.