<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Guillaume Sainthillier</title>
    <description>The latest articles on DEV Community by Guillaume Sainthillier (@guillaume_sainthillier).</description>
    <link>https://dev.to/guillaume_sainthillier</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F292728%2F92cb4c01-def7-47b2-b874-7161f31890fd.jpg</url>
      <title>DEV Community: Guillaume Sainthillier</title>
      <link>https://dev.to/guillaume_sainthillier</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/guillaume_sainthillier"/>
    <language>en</language>
    <item>
      <title>Gérer ses miniatures avec Symfony et Glide</title>
      <dc:creator>Guillaume Sainthillier</dc:creator>
      <pubDate>Sat, 13 Jun 2020 12:15:26 +0000</pubDate>
      <link>https://dev.to/silarhi/gerer-ses-miniatures-avec-symfony-et-glide-382i</link>
      <guid>https://dev.to/silarhi/gerer-ses-miniatures-avec-symfony-et-glide-382i</guid>
      <description>&lt;p&gt;Aujourd'hui j'ai envie de vous parler d'un pattern récurrent dans un projet Web : la gestion des images et surtout des miniatures associées.&lt;/p&gt;

&lt;p&gt;En 2020, le poids des images dans une page Web est toujours un facteur clé dans l'optimisation du poids des pages. Combien d'entre-nous continue encore de servir aux internautes mobiles des images de 1200px de long ?&lt;/p&gt;

&lt;p&gt;On va donc voir comment mettre en place dans un projet Symfony 5 la librairie PHP &lt;a href="https://glide.thephpleague.com/"&gt;Glide&lt;/a&gt; qui met à disposition une API pour gérer la manipulation d'image.&lt;/p&gt;

&lt;h2&gt;
  
  
  Démo
&lt;/h2&gt;

&lt;p&gt;Avant de partir dans les explications, je vais déjà tenter de vous attendrir avec le type de rendu final que l'on peut obtenir avec cette librairie :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rM47v_3Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.silarhi.fr/content/images/2020/06/Screenshot-2020-06-13-at-11.32.19-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rM47v_3Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.silarhi.fr/content/images/2020/06/Screenshot-2020-06-13-at-11.32.19-1.png" alt="Gérer ses miniatures avec Symfony et Glide"&gt;&lt;/a&gt;La magnifique démo de cet article : 👉 &lt;a href="https://labs.silarhi.fr/images"&gt;&lt;/a&gt;&lt;a href="https://labs.silarhi.fr/images"&gt;https://labs.silarhi.fr/images&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;Sans plus attendre on va commencer par installer tout doucement la librairie et créer un service Symfony pour gérer tout ça. Le package &lt;code&gt;league/glide-symfony&lt;/code&gt; possède un &lt;code&gt;adapter&lt;/code&gt; pour Symfony qui intègre la gestion des &lt;code&gt;Response&lt;/code&gt; mais ne fournit pas de service par défaut.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;composer require league/glide league/glide-symfony

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Configuration
&lt;/h2&gt;

&lt;p&gt;On va modifier un peu la configuration actuelle pour ajouter le service Symfony et préparer l'API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/services.yaml&lt;/span&gt;
&lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;app.public_dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%kernel.project_dir%/public'&lt;/span&gt;
    &lt;span class="s"&gt;app.image_cache_dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%kernel.project_dir%/var/storage/cache'&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;_defaults&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;#...&lt;/span&gt;
        &lt;span class="na"&gt;bind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="s"&gt;$secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%kernel.secret%'&lt;/span&gt;

    &lt;span class="s"&gt;League\Glide\Server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;factory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;League\Glide\ServerFactory'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;create&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;arguments&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%app.public_dir%'&lt;/span&gt;
            &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%app.image_cache_dir%'&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Mise en place
&lt;/h2&gt;

&lt;p&gt;L'idée est ensuite de créer un &lt;code&gt;Controller&lt;/code&gt; pour générer à la volée les miniatures (c'est notre "API"). On passera dans la requête le chemin relatif de l'image à utiliser ainsi que des pour indiquer la longueur / largeur désirée de la miniature, la couleur de fond, etc ...&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note sur la sécurité&lt;/strong&gt; : Pour empêcher des utilisateurs malveillants de demander à générer sur votre serveur des centaines de miniatures aux dimensions non voulues (Imaginez votre serveur répondre à 10 000 requêtes de miniatures au format 50000x50000), nous allons également ajouter un hash de signature qui sera unique pour les dimensions données et que le contrôleur devra valider pour générer la miniature.&lt;/p&gt;

&lt;h3&gt;
  
  
  Le controller
&lt;/h3&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;//src/Controller/AssetsController.php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Controller&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;League\Glide\Filesystem\FileNotFoundException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;League\Glide\Responses\SymfonyResponseFactory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;League\Glide\Server&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;League\Glide\Signatures\SignatureException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;League\Glide\Signatures\SignatureFactory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Symfony\Bundle\FrameworkBundle\Controller\AbstractController&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;Controller&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Symfony\Component\HttpFoundation\Request&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Symfony\Component\Routing\Annotation\Route&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AssetsController&lt;/span&gt; &lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/**
     * @Route("/assets/{path&amp;lt;(.+)&amp;gt;}", name="asset_url", methods={"GET"})
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Server&lt;/span&gt; &lt;span class="nv"&gt;$glide&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$parameters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;\count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$parameters&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;SignatureFactory&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;validateRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$parameters&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SignatureException&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;createNotFoundException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$glide&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;setResponseFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;SymfonyResponseFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$glide&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;getImageResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$parameters&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;\InvalidArgumentException&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;FileNotFoundException&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;createNotFoundException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  L'extension Twig
&lt;/h3&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// src/Twig/AssetExtension.php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Twig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;League\Glide\Signatures\SignatureFactory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Symfony\Component\Routing\Generator\UrlGeneratorInterface&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Twig\Extension\AbstractExtension&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Twig\TwigFunction&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AssetExtension&lt;/span&gt; &lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;AbstractExtension&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/** @var UrlGeneratorInterface */&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$router&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="cd"&gt;/** @var string */&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$secret&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;UrlGeneratorInterface&lt;/span&gt; &lt;span class="nv"&gt;$router&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$router&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$secret&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getFunctions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;TwigFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'app_asset'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'appAsset'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'is_safe'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'html'&lt;/span&gt;&lt;span class="p"&gt;]]),&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;appAsset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$parameters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nv"&gt;$referenceType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;UrlGeneratorInterface&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="na"&gt;ABSOLUTE_PATH&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$parameters&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'fm'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'pjpg'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'png'&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nb"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$parameters&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'fm'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'png'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$parameters&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SignatureFactory&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;generateSignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$parameters&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$parameters&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'path'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;ltrim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;router&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'asset_url'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$parameters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$referenceType&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Avec ces 2 nouveaux fichiers, vous avez un système tout prêt de génération de miniatures sécurisé pour votre belle application !&lt;/p&gt;

&lt;h2&gt;
  
  
  Utilisation
&lt;/h2&gt;

&lt;p&gt;Il ne vous reste plus qu'à utiliser votre fonction twig dans vos templates. La version minimale pourrait ressembler à ceci :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight twig"&gt;&lt;code&gt;&lt;span class="c"&gt;{# templates/images.html.twig #}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"img-fluid"&lt;/span&gt;
     &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;app_asset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'images/mon-image.jpg'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;200&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
     &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"400"&lt;/span&gt;
     &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"200"&lt;/span&gt;
     &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Ma super image de 400x200"&lt;/span&gt;
     &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"Ma super image de 400x200"&lt;/span&gt;
&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Ce qui produit l'URL suivante :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/assets/images/mon-image.jpg?w=400&amp;amp;h=200&amp;amp;fm=pjpg&amp;amp;s=d6ba0d9ef7622516b83fa3402527affe

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Ce qui revient à demander au serveur :&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Génère-moi la miniature du fichier &lt;code&gt;/images/mon-image.jpg&lt;/code&gt; (si elle n'est pas déjà générée) d'une longueur de 400px (paramètre &lt;code&gt;w&lt;/code&gt;), d'une hauteur de 200px (paramètre &lt;code&gt;h&lt;/code&gt;), au format JPG Progressive (paramètre &lt;code&gt;fm&lt;/code&gt;). Pour te prouver que je suis de bonne foi, je te donne le hash de vérification de ces 4 paramètres (paramètre &lt;code&gt;s&lt;/code&gt;). Merci bisous.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Gestion des écrans retina
&lt;/h3&gt;

&lt;p&gt;La gestion des écrans retina devient beaucoup plus simple vu qu'il n'y a plus qu'à passer les bons paramètres au contrôleur.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight twig"&gt;&lt;code&gt;&lt;span class="c"&gt;{# templates/images.html.twig #}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"img-fluid"&lt;/span&gt;
     &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;app_asset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'images/mon-image.jpg'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;200&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
     &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;app_asset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'images/mon-image.jpg'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'dpr'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; &lt;span class="na"&gt;2x&lt;/span&gt;
     &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"400"&lt;/span&gt;
     &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"200"&lt;/span&gt;
     &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Ma super image de 400x200"&lt;/span&gt;
     &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"Ma super image de 400x200"&lt;/span&gt;
&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;J'ai juste ajouté avec le paramètre &lt;code&gt;dpr&lt;/code&gt; (&lt;em&gt;Device pixel ratio&lt;/em&gt;) pour générer une image 2 fois plus dense (en l'occurence 800x400).&lt;/p&gt;

&lt;h3&gt;
  
  
  Aller plus loin
&lt;/h3&gt;

&lt;p&gt;Le bon développeur ou la bonne développeuse que vous êtes a sûrement détecté un pattern à factoriser pour améliorer la maintenabilité de tout ça. Je vous joins les 2 macros que j'utilise dans quasiment tous mes projets Symfony :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight twig"&gt;&lt;code&gt;&lt;span class="c"&gt;{# templates/macros.html.twig #}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;macro&lt;/span&gt; &lt;span class="nv"&gt;image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;app_asset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;params&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;{}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
         &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;app_asset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;params&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;{}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'dpr'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="s"&gt; 2x"&lt;/span&gt;
         &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;width&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
         &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;height&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
         &lt;span class="na"&gt;loading=&lt;/span&gt;&lt;span class="s"&gt;"lazy"&lt;/span&gt;
        &lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;attrs&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;([])&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;k&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;'class'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%}{{&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="err"&gt;="&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;e&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt; &lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endfor&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
    &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endmacro&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;macro&lt;/span&gt; &lt;span class="nv"&gt;fixedHeightImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"img-fluid &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;attrs.class&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
         &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;app_asset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
         &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;app_asset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'dpr'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="s"&gt; 2x"&lt;/span&gt;
         &lt;span class="na"&gt;loading=&lt;/span&gt;&lt;span class="s"&gt;"lazy"&lt;/span&gt;
        &lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;attrs&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;([])&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;k&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;'class'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%}{{&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="err"&gt;="&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;e&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt; &lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endfor&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
    &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endmacro&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;


&lt;span class="c"&gt;{# templates/images.html #}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s1"&gt;'macros.html.twig'&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;macros&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;macros.image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'images/mon-image.jpg'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nv"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;'class'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'img-fluid'&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Voilà ! Vous êtes maintenant un pro des miniatures ! N'hésitez pas à regarder le code source de la démo qui intègre Webpack en plus de tout ça.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pour aller plus loin
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://labs.silarhi.fr/images"&gt;La démo du tuto&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/silarhi/symfony-docker-ci"&gt;Le code source utilisé sur GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://glide.thephpleague.com/"&gt;La documentation de Glide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>symfony</category>
      <category>php</category>
      <category>code</category>
    </item>
    <item>
      <title>Adieu jQuery ? Bootstrap 5 annonce la tendance, retour d'expérience</title>
      <dc:creator>Guillaume Sainthillier</dc:creator>
      <pubDate>Thu, 16 May 2019 06:49:15 +0000</pubDate>
      <link>https://dev.to/silarhi/adieu-jquery-bootstrap-5-annonce-la-tendance-retour-d-experience-5feb</link>
      <guid>https://dev.to/silarhi/adieu-jquery-bootstrap-5-annonce-la-tendance-retour-d-experience-5feb</guid>
      <description>&lt;p&gt;jQuery va-t-il mourir ? Le bug de l'an 2000 va-t-il se reproduire ? Toutes les réponses dans cet article.&lt;/p&gt;

&lt;p&gt;C'est officiel, &lt;a href="https://getbootstrap.com/"&gt;Bootstrap&lt;/a&gt; va se séparer de sa dépendance la plus forte : &lt;a href="https://jquery.com/"&gt;jQuery&lt;/a&gt;. C'est ce qu'annonce &lt;a href="https://github.com/twbs/bootstrap/pull/23586"&gt;cette PR&lt;/a&gt; sur le Github du plus célèbre Framework CSS. A la place, pas de nouveau framework Javascript, pas de super lib next gen : du Vanilla JS, tout simplement. Cette PR montre pour moi la tendance Javascript de 2019 : jQuery a-t-il encore une raison d'exister ?&lt;/p&gt;

&lt;p&gt;Si l'on remonte de quelques années aux temps où IE 6 était roi, jQuery avait du sens lorsque les navigateurs avaient d'énormes différences dans l'implémentation des specs. Aujourd'hui, bien qu'encore présentes, ces différences tendent à se réduire de plus en plus :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;querySelector&lt;/code&gt; est devenu stable et consistent entre les navigateurs.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;XMLHTTPRequest&lt;/code&gt; peut être remplacé par &lt;code&gt;fetch&lt;/code&gt; qui est devenu standard.&lt;/li&gt;
&lt;li&gt;Les animations peuvent être gérées en CSS3 et sont d'ailleurs plus performantes car elles utilisent le GPU du client.&lt;/li&gt;
&lt;li&gt;Microsoft a &lt;a href="https://blogs.windows.com/windowsexperience/2018/12/06/microsoft-edge-making-the-web-better-through-more-open-source-collaboration/#lYvJtFqtZ1pB83I7.97"&gt;annoncé&lt;/a&gt; l'arrêt de l'utilisation de son moteur de rendu en faveur de Chromium pour son navigateur Edge.&lt;/li&gt;
&lt;li&gt;etc etc&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pour enfoncer le clou, GitHub &lt;a href="https://github.blog/2018-09-06-removing-jquery-from-github-frontend/"&gt;annonce lui aussi&lt;/a&gt; qu'il a supprimé jQuery de son interface frontend. Alors, faut-il bannir absolument jQuery de ses nouveaux projets ?&lt;/p&gt;

&lt;h2&gt;
  
  
  Mon retour d'expérience
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Un zeste de contexte
&lt;/h3&gt;

&lt;p&gt;J'ai récemment développé une application Symfony pour un client. Fort de cette tendance, j'ai donc décidé d'utiliser du pur Javascript dans le projet et voir si j'arrivais à m'en sortir.&lt;/p&gt;

&lt;p&gt;Pour ça, je me suis fortement inspiré de &lt;a href="https://github.com/EnMarche/en-marche.fr/tree/master/front"&gt;la structure frontend du site en-marche.fr&lt;/a&gt; qui est Open Source (pro tip : vous n'avez pas besoin d'adhérer aux idées du partie politique pour pouvoir tirer le meilleur de l'architecture du site 🤫). Je trouve d'ailleurs que &lt;a href="https://github.com/tgalopin"&gt;Titouan Galopin&lt;/a&gt; a fait de l'excellent travail lorsqu'il était sur ce projet.&lt;/p&gt;

&lt;h3&gt;
  
  
  La stack technique
&lt;/h3&gt;

&lt;p&gt;Pour ce projet, j'ai utilisé Bootstrap, &lt;a href="https://github.com/axios/axios"&gt;axios&lt;/a&gt; pour les requêtes HTTP, &lt;a href="https://sweetalert2.github.io/"&gt;SweetAlert2&lt;/a&gt; pour les popups, &lt;a href="https://select2.org/"&gt;Select2&lt;/a&gt; pour des jolis select et &lt;a href="https://github.com/MoOx/pjax"&gt;pjax&lt;/a&gt; pour accélérer les changements de page. Le code front métier étant écrit en &lt;a href="http://es6-features.org/#Constants"&gt;ES6&lt;/a&gt; et transpilé avec Webpack et Babel.&lt;/p&gt;

&lt;p&gt;À cause de certaines de ces libs, j'ai donc quand même été contraint d'inclure jQuery dans mon projet, mais j'ai décidé de restreindre son utilisation uniquement pour manipuler les plugins. Histoire de jouer le jeu pour de vrai.&lt;/p&gt;

&lt;h3&gt;
  
  
  Résultat
&lt;/h3&gt;

&lt;p&gt;Globalement, l'absence de jQuery ne s'est pas tant fait ressentir. C'est d'ailleurs &lt;strong&gt;gratifiant&lt;/strong&gt; de savoir que l'on peut développer rapidement des apps sans framework JS et leurs contraintes associées.&lt;/p&gt;

&lt;p&gt;En utilisant jQuery, je trouve aussi qu'on se place dans une zone de confort qui nous maintient à l’écart de l’univers JS. Et ça bouge vite, très vite. De nouveaux standards apparaissent quasiment chaque année, et apportent des évolutions intéressantes et de nouvelles façons de penser et d’écrire le Javascript. Il se passe des choses et il ne faut pas rater le coche, il n'y a qu’à voir du &lt;a href="https://github.com/EnMarche/en-marche.fr/blob/master/front/components/DataGrid.js#L293-L314"&gt;code écrit avec React&lt;/a&gt; pour se rendre compte du fossé qu'il y a avec jQuery, alors que les 2 s'appuient sur le même langage.&lt;/p&gt;

&lt;p&gt;Mais tout n'est pas rose, il y a quand même eu quelques points de frustration :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Il y a toujours beaucoup de libs qui ne marchent qu'avec jQuery (Bootstrap 4, select2, etc etc). C'est pourquoi même si je n'en voulais pas, jQuery s'est invité de force dans mon fichier &lt;code&gt;package.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://medium.com/wix-engineering/why-i-wont-be-using-fetch-api-in-my-apps-6900e6c6fe78"&gt;fetch n'est pas si cool que ça&lt;/a&gt;. J'ai fini par utiliser axios après avoir perdu pas mal de temps à tenter de cerner ses subtilités.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Et vous ? Utilisez-vous encore jQuery en 2020 dans vos nouveaux projets ? Pensez-vous que jQuery va mourir ?&lt;/p&gt;

&lt;p&gt;PS : Le bug de l'an 2000 risque bien de se reproduire si les systèmes ne se mettent pas à jour avant le &lt;a href="https://en.wikipedia.org/wiki/Year_2038_problem"&gt;19 Janvier 2038 à 03h14 et 08s GMT&lt;/a&gt;, date à laquelle toutes les architectures d'ordinateurs qui sont encore en 32 bits cesseront de gérer correctement les dates. Mais ça reste entre nous 🤫&lt;/p&gt;

</description>
      <category>tech</category>
      <category>javascript</category>
      <category>jquery</category>
    </item>
    <item>
      <title>Divisez le temps de réponse de votre site Web par 100 avec Varnish et les fragments ESI</title>
      <dc:creator>Guillaume Sainthillier</dc:creator>
      <pubDate>Sat, 11 May 2019 13:21:29 +0000</pubDate>
      <link>https://dev.to/silarhi/divisez-le-temps-de-reponse-de-votre-site-web-par-100-avec-varnish-et-les-fragments-esi-46kd</link>
      <guid>https://dev.to/silarhi/divisez-le-temps-de-reponse-de-votre-site-web-par-100-avec-varnish-et-les-fragments-esi-46kd</guid>
      <description>&lt;p&gt;On évoque souvent la mise en place du cache applicatif comme la première solution pour optimiser les temps de réponse des sites. Découvrez ici seulement la vraie solution pour réduire les temps de réponse de votre site Web que seul 1% des gens connait.&lt;/p&gt;

&lt;p&gt;Si vous lisez encore ces lignes, c'est que j'ai bien fais mon travail de teasing. Sans plus attendre, voici la solution ultime pour réduire les temps de réponse de votre site : &lt;strong&gt;Ne pas appeler votre site&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/ODU1I5zAgOwX6/source.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/ODU1I5zAgOwX6/source.gif" alt="Mais quelle drôle d'idée."&gt;&lt;/a&gt;Mais quelle drôle d'idée.&lt;/p&gt;

&lt;p&gt;Démarrer un backend et retourner une réponse au client, même avec un super framework récent, ça reste long et coûteux. Bootstrapping du framework, connexion à une base de données, à un serveur Redis, vérification des droits utilisateurs, moteur de templating, etc etc. Sans trop forcer, vous pouvez vite atteindre les 200 ms pour retourner du contenu.&lt;/p&gt;

&lt;p&gt;🤓« &lt;em&gt;Alors comment faire pour réduire mes temps de réponse ? Est-ce que je dois investir, booster mon serveur de prod et doubler le montant ma facture ?&lt;/em&gt; »&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pas du tout !&lt;/strong&gt; L'idée est d'utiliser à fond le cache HTTP pour stocker le rendu HTML généré par le backend. Pour ça, on va jouer avec &lt;a href="https://varnish-cache.org/" rel="noopener noreferrer"&gt;Varnish&lt;/a&gt;, un reverse proxy ultra puissant écrit en C. On parle ici de temps de réponse de l'ordre de &lt;strong&gt;quelques millisecondes&lt;/strong&gt; si la page existe dans le cache.&lt;/p&gt;

&lt;p&gt;Le principe de Varnish est plutôt simple : Le reverse proxy est en première ligne des requêtes utilisateurs. S'il possède déjà la réponse à cette requête, il la délivre tout simplement. Sinon, il forward la request au backend et stocke le résultat. C'est aussi simple que ça. On va voir comment mettre en place concrètement ce flux en place.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.silarhi.fr%2Fcontent%2Fimages%2F2019%2F05%2Fintroduction-to-varnish-vcl-6-1024.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.silarhi.fr%2Fcontent%2Fimages%2F2019%2F05%2Fintroduction-to-varnish-vcl-6-1024.jpg" alt="Divisez le temps de réponse de votre site Web par 100 avec Varnish et les fragments ESI"&gt;&lt;/a&gt;Le principe simple de Varnish&lt;/p&gt;

&lt;h2&gt;
  
  
  C'est parti
&lt;/h2&gt;

&lt;p&gt;L'idée est donc de mettre Varnish en frontal des requêtes utilisateur. C'est lui qui va maintenant écouter sur le port 80 et envoyer les requêtes vers votre serveur Web qui était en frontal jusqu'ici.&lt;/p&gt;

&lt;p&gt;La première étape est de modifier les ports d'écoute de votre backend. Par exemple pour Apache on va modifier la configuration :&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Listen 80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;par&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Listen 8000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gardez en tête le nouveau port d'écoute de votre serveur Web. Ensuite, on va installer Varnish. Rendez vous sur packagecloud et suivez les instructions selon votre distribution : &lt;a href="https://packagecloud.io/varnishcache" rel="noopener noreferrer"&gt;https://packagecloud.io/varnishcache&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Un premier exemple : du contenu statique
&lt;/h2&gt;

&lt;p&gt;Varnish utilise son propre langage de configuration (VCL) qu'il compile ensuite en C. C'est un poil bas niveau mais une fois bien configuré, vous allez mettre un moteur de Ferrari dans votre 2 CV.&lt;br&gt;&lt;br&gt;
On va commencer par un VCL minimaliste :&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vcl 4.0;

backend default {
    .host = "127.0.0.1"; 
    .port = "8000"; # Le port d'écoute de votre backend (Apache, Nginx, ...)
}

# Appelé à la fin de chaque réponse (en cache ou non)
sub vcl_deliver {
    if (obj.hits &amp;gt; 0) {
        set resp.http.X-Cache = "HIT";
    } else {
        set resp.http.X-Cache = "MISS";
    }

    set resp.http.X-Cache-Hits = obj.hits;

    return (deliver);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prenez le temps de vous familiariser avec la syntaxe et la configuration. Ici, on a indiqué à Varnish qu'il existe un backend (default) accessible à l'adresse 127.0.0.1 sur le port 8000 (souvenez-vous, c'est le nouveau port d'écoute de votre serveur Web). On a aussi configuré une entête de réponse que l'on a nommé &lt;em&gt;X-Cache&lt;/em&gt; pour savoir si la réponse a été récupérée du cache ou non. Vérifions le résultat en appelant la page d'accueil de notre site pour la première fois :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.silarhi.fr%2Fcontent%2Fimages%2F2019%2F07%2Fimage-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.silarhi.fr%2Fcontent%2Fimages%2F2019%2F07%2Fimage-1.png" alt="Divisez le temps de réponse de votre site Web par 100 avec Varnish et les fragments ESI"&gt;&lt;/a&gt;Première requête : Varnish n'a rien en cache&lt;/p&gt;

&lt;p&gt;Étant donné que c'est la première fois que l'on accède à la ressource, Varnish ne possède pas la réponse dans son cache, ce qui est logique. Il appelle donc le backend normalement et stocke le résultat pour une durée de 120 secondes (c'est la configuration par défaut de Varnish, vous pouvez bien sur le changer selon vos besoins). Si on actualise la page, on constate que Varnish réagit différemment :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.silarhi.fr%2Fcontent%2Fimages%2F2019%2F07%2Fimage-2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.silarhi.fr%2Fcontent%2Fimages%2F2019%2F07%2Fimage-2.png" alt="Divisez le temps de réponse de votre site Web par 100 avec Varnish et les fragments ESI"&gt;&lt;/a&gt;Deuxième requête : Varnish retourne directement la réponse&lt;/p&gt;

&lt;p&gt;Parfait ! Varnish a directement retourné le résultat qu'il avait stocké lors de la première requête. Le backend n'a purement pas été sollicité, vous n'aurez aucune trace d'accès dans les logs de votre serveur Web. &lt;strong&gt;Ça commence à ressembler à quelque chose.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;🤓 « &lt;em&gt;Mais si mon contenu varie en fonction de l'utilisateur ... Je ne veux pas retourner le même contenu pour tout le monde. Comment faire ?&lt;/em&gt; »&lt;/p&gt;

&lt;p&gt;C'est là qu'interviennent les &lt;strong&gt;fragments ESI&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mettre en cache des fragments de page avec ESI
&lt;/h2&gt;

&lt;p&gt;Il est possible de mettre en cache &lt;strong&gt;une partie&lt;/strong&gt; de la page grâce aux blocs de cache ESI (Edge Side Includes). Cette specification a été écrite par Akamai il y a une dizaine d'années dans le but de mettre en place des stratégies de cache différentes par "bloc". Par exemple : ma page possède un menu type &lt;em&gt;navbar&lt;/em&gt; propre à l'utilisateur. Le reste de ma page est identique pour tout le monde. Je peux donc mettre en place un bloc qui va contenir le menu et mettre en cache tout le reste.&lt;/p&gt;

&lt;p&gt;Assez de théorie, voyons comment ça se traduit dans une app :&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;h1&amp;gt;Ma super app&amp;lt;/h1&amp;gt;

        &amp;lt;!-- Le bloc du ESI est défini là --&amp;gt;
        &amp;lt;esi:include src="/mon-menu-prive.php"/&amp;gt;
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;C'est aussi simple que ça ! On peut le voir un peu comme un appel Ajax qui serait fait pour chaque bloc, sauf que c'est le reverse proxy (Varnish) qui fait la requête. La différence est que la sous-requête est effectuée par le serveur et non par le client.&lt;/p&gt;

&lt;h3&gt;
  
  
  Un cas d'étude concret
&lt;/h3&gt;

&lt;p&gt;On va mettre en plage une page concrète qui sera composée de plusieurs blocs. Certains blocs vont varier en fonction de l'utilisateur (ex: un menu), d'autres seront publics. La durée de mise en cache des blocs va aussi être variable.&lt;/p&gt;

&lt;p&gt;Le cas d'étude : &lt;a href="https://labs.silarhi.fr/esi" rel="noopener noreferrer"&gt;démo disponible&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.silarhi.fr%2Fcontent%2Fimages%2F2019%2F07%2FScreenshot-2019-07-19-at-13.06.00.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.silarhi.fr%2Fcontent%2Fimages%2F2019%2F07%2FScreenshot-2019-07-19-at-13.06.00.png" alt="Divisez le temps de réponse de votre site Web par 100 avec Varnish et les fragments ESI"&gt;&lt;/a&gt;Admirez ces belles bordures&lt;/p&gt;

&lt;p&gt;Entrons dans le vif du sujet, voici le code :&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php // index.php
    header('Surrogate-Control: abc=ESI/1.0');
    header("X-Reverse-Proxy-TTL: 10");
?&amp;gt;
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset="utf-8"&amp;gt;
    &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"&amp;gt;
    &amp;lt;link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"&amp;gt;
    &amp;lt;title&amp;gt;Ma super page&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body class="public cached p-2"&amp;gt;
    &amp;lt;esi:include src="/menu.php"/&amp;gt;
    &amp;lt;div class="container"&amp;gt;
        &amp;lt;esi:include src="/time.php"/&amp;gt;
        &amp;lt;div class="row"&amp;gt;
            &amp;lt;div class="col-md-8 col-lg-9"&amp;gt;
                &amp;lt;h1&amp;gt;Page de contenu public&amp;lt;/h1&amp;gt;
                &amp;lt;p class="lead"&amp;gt;Mise en cache le &amp;lt;span class="badge badge-secondary"&amp;gt;&amp;lt;?= date('d/m/Y H:i:s') ?&amp;gt;&amp;lt;/span&amp;gt; (10s)&amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class="col-md-4 col-lg-3"&amp;gt;
                &amp;lt;esi:include src="/user.php"/&amp;gt;
                &amp;lt;esi:include src="/sidebar.php"/&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ne vous focalisez pas encore trop sur le détail de ce code, l'idée est juste d'imaginer la mise en place de vos propres blocs dans votre application.&lt;/p&gt;

&lt;p&gt;Outre ce design de qualité, vous avez sûrement identifié les 4 blocs. Le menu du haut (menu.php), l'heure du serveur avec la légende (time.php), l'heure de la dernière connexion (user.php) et les widgets (sidebar.php). 4 blocs pour 4 politiques de mise en cache différentes. C'est le mélange de la configuration dans Varnish et l'envoi d'entêtes HTTP de réponses par le backend qui vont permettre de mettre en place nos 4 politiques.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuration dans Varnish
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# /etc/varnish/default.vcl
vcl 4.0;

import std;
import directors;

backend default {
    .host = "127.0.0.1";
    .port = "8000"; # Le port d'écoute de votre serveur Apache / Nginx
}

# Appelé au début de chaque requête
sub vcl_recv {
    # Pas de mise en cache pour les méthodes type POST / DELETE
    if (req.method != "GET" &amp;amp;&amp;amp; req.method != "HEAD") {
        return (pass);
    }

    # On supprime les cookies sur les pages publiques (cf vcl_hash)
    if(! req.url ~ "^/(login|logout|menu|user)\.php") {
        unset req.http.Cookie;
    }

    # On supprime tous les autres cookies que PHPSESSID pour les pages privées
    if (req.http.Cookie) {
        set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";");
        set req.http.Cookie = regsuball(req.http.Cookie, ";(PHPSESSID)=", "; \1=");
        set req.http.Cookie = regsuball(req.http.Cookie, ";[^][^;]*", "");
        set req.http.Cookie = regsuball(req.http.Cookie, "^[;]+|[;]+$", "");
        set req.http.Cookie = regsuball(req.http.Cookie, "^;\s*", "");

        if (req.http.Cookie ~ "^\s*$") {
            unset req.http.Cookie;
        }
    }

    return (hash);
}

# Appelé pour calculer un hash de la requête
sub vcl_hash {
    hash_data(req.url);

    if (req.http.host) {
        hash_data(req.http.host);
    } else {
        hash_data(server.ip);
    }

    if (req.http.Cookie) {
        hash_data(req.http.Cookie);
    }
}

# Appelé si le hash a été trouvé (= page en cache)
sub vcl_hit {
    if (obj.ttl &amp;gt;= 0s) {
        return (deliver);
    }

    return (miss);
}

# Appelé au retour de la réponse par le backend
sub vcl_backend_response {
    # L'entête est envoyée par index.php
    if (beresp.http.Surrogate-Control ~ "ESI/1.0") {
        unset beresp.http.Surrogate-Control;
        set beresp.do_esi = true;
    }

    # Notre fameuse entête custom
    if (beresp.http.X-Reverse-Proxy-TTL) {
        set beresp.ttl = std.duration(beresp.http.X-Reverse-Proxy-TTL + "s", 0s);
        unset beresp.http.X-Reverse-Proxy-TTL;
    }

    return (deliver);
}

# Appelé à la fin de chaque réponse (en cache ou non)
sub vcl_deliver {
    if (obj.hits &amp;gt; 0) {
        set resp.http.X-Cache = "HIT";
    } else {
        set resp.http.X-Cache = "MISS";
    }

    set resp.http.X-Cache-Hits = obj.hits;

    return (deliver);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Il y a plusieurs concepts clés à comprendre dans cette configuration. L'idée de fond est de générer un &lt;strong&gt;hash unique&lt;/strong&gt; pour chaque requête / sous - requête. Ce hash doit dépendre du contexte utilisateur pour les blocs privés, en revanche il n'est pas nécéssaire pour les blocs publics. En PHP, l'utilisateur est lié à la session du serveur via le cookie PHPSESSID. C'est cette valeur de cookie que l'on doit conserver pour générer ce fameux hash unique dans les pages qui nous intéresse. Pour les autres pages, on peut se permettre de supprimer ce cookie pour maximiser les chances de délivrer un contenu mis en cache par un autre utilisateur.&lt;/p&gt;

&lt;p&gt;On supprime également les cookies non utiles au backend (du type Google Analytics etc) qui vont faire baisser le taux de HIT pour rien. Adaptez bien sûr la fonction vcl_recv en fonction de vos besoins.&lt;/p&gt;

&lt;p&gt;L'illustration ci-dessous résume bien les différentes étapes pendant lesquelles nous devons agir :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.silarhi.fr%2Fcontent%2Fimages%2F2019%2F05%2Fsimplified_fsm.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.silarhi.fr%2Fcontent%2Fimages%2F2019%2F05%2Fsimplified_fsm.svg" alt="Divisez le temps de réponse de votre site Web par 100 avec Varnish et les fragments ESI"&gt;&lt;/a&gt;Crédits : &lt;a href="https://book.varnish-software.com/4.0/chapters/VCL_Basics.html" rel="noopener noreferrer"&gt;Varnish Basics&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;sidebar.php&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php //sidebar.php
    header("X-Reverse-Proxy-TTL: 20");
?&amp;gt;
&amp;lt;div class="public cached p-2"&amp;gt;
    &amp;lt;h1&amp;gt;Widgets publics&amp;lt;/h1&amp;gt;
    &amp;lt;p class="lead"&amp;gt;
        Mise en cache le &amp;lt;span class="badge badge-secondary"&amp;gt;&amp;lt;?= date('d/m/Y H:i:s') ?&amp;gt;&amp;lt;/span&amp;gt; (20s)
    &amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&amp;lt;!--kg-card-end: markdown--&amp;gt;&amp;lt;!--kg-card-begin: markdown--&amp;gt;&lt;/p&gt;

&lt;p&gt;Politique de mise en cache du bloc : Contenu publique mis en cache pendant 20 secondes.&lt;br&gt;&lt;br&gt;
C'est l'entête &lt;code&gt;X-Reverse-Proxy-TTL&lt;/code&gt; qui va donner la durée de mise en cache par Varnish. Cette entête &lt;strong&gt;n'est pas standard&lt;/strong&gt;. On aurait pu jouer sur l'entête standard &lt;code&gt;Cache-Control&lt;/code&gt;, mais elle possède un inconvénient majeur : &lt;strong&gt;On ne contrôle pas la propagation du cache&lt;/strong&gt; avec la directive &lt;code&gt;Cache-Control: public, smax-age=20&lt;/code&gt;. Eh oui, il peut y avoir d'autres serveurs mandataires entre le client et votre serveur qui peuvent eux-aussi mettre en cache le contenu HTML avec cette directive. Et si vous mettez des grandes valeurs de TTL dans votre politique de Cache-Control, adieu la maîtrise de vos mises à jour. Mieux vaut rester maître de la diffusion de vos contenus. En revanche, pour vos contenus type Images/CSS/JS, faites vous plaisir et laissez le cache-control public avec un grand TTL si vous maîtrisez le cache busting.&lt;/p&gt;

&lt;h3&gt;
  
  
  menu.php
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php //menu.php
    session_start();
    header("X-Reverse-Proxy-TTL: 60");
?&amp;gt;
&amp;lt;nav class="navbar navbar-expand-lg navbar-light bg-light private cached"&amp;gt;
    &amp;lt;div class="container"&amp;gt;
        &amp;lt;div class="collapse navbar-collapse" id="navbarSupportedContent"&amp;gt;
            &amp;lt;ul class="navbar-nav mr-auto"&amp;gt;
                &amp;lt;?php if (isset($_SESSION['logged'])): ?&amp;gt;
                    &amp;lt;li class="navbar-text"&amp;gt;
                        &amp;lt;span class="badge badge-success"&amp;gt;Connecté&amp;lt;/span&amp;gt;
                    &amp;lt;/li&amp;gt;
                    &amp;lt;li class="nav-item"&amp;gt;
                        &amp;lt;a class="nav-link" href="logout.php"&amp;gt;Déconnexion&amp;lt;/a&amp;gt;
                    &amp;lt;/li&amp;gt;
                &amp;lt;?php else: ?&amp;gt;
                    &amp;lt;li class="navbar-text"&amp;gt;
                        &amp;lt;span class="badge badge-danger"&amp;gt;Anonyme&amp;lt;/span&amp;gt;
                    &amp;lt;/li&amp;gt;
                    &amp;lt;li class="nav-item"&amp;gt;
                        &amp;lt;a class="nav-link" href="login.php"&amp;gt;Connexion&amp;lt;/a&amp;gt;
                    &amp;lt;/li&amp;gt;
                &amp;lt;?php endif; ?&amp;gt;
            &amp;lt;/ul&amp;gt;
            &amp;lt;span class="navbar-text"&amp;gt;
                Mise en cache le &amp;lt;span class="badge badge-secondary"&amp;gt;&amp;lt;?= date('d/m/Y H:i:s') ?&amp;gt;&amp;lt;/span&amp;gt; (60s)
            &amp;lt;/span&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/nav&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Politique de mise en cache du bloc : Contenu privé mis en cache pendant 60 secondes.&lt;br&gt;&lt;br&gt;
La mise en cache du contenu en fonction de l'utilisateur sera gérée dans Varnish directement. Le truc étant d'inclure le PHPSESSID de l'utilisateur dans le calcul du "hash" d'une requête pour ce bloc-ci, ce que nous avons fait dans la méthode vcl_recv.&lt;/p&gt;

&lt;h3&gt;
  
  
  time.php
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php //time.php ?&amp;gt;
&amp;lt;div class="text-center p-2 public"&amp;gt;
    &amp;lt;p class="lead text-center"&amp;gt;Heure serveur : &amp;lt;span class="badge badge-secondary"&amp;gt;&amp;lt;?= date('d/m/Y H:i:s') ?&amp;gt;&amp;lt;/span&amp;gt; (pas de cache)&amp;lt;/p&amp;gt;
    &amp;lt;div class="row"&amp;gt;
        &amp;lt;div class="col-3"&amp;gt;&amp;lt;span class="public p-1"&amp;gt;Public&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;
        &amp;lt;div class="col-3"&amp;gt;&amp;lt;span class="public cached p-1"&amp;gt;Public + cache&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;
        &amp;lt;div class="col-3"&amp;gt;&amp;lt;span class="private p-1"&amp;gt;Privé&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;
        &amp;lt;div class="col-3"&amp;gt;&amp;lt;span class="private cached p-1"&amp;gt;Privé + cache&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Politique de mise en cache du bloc : Contenu public sans mise en cache particulière.&lt;br&gt;&lt;br&gt;
Le backend est appelé systématiquement pour ce bloc (j'ai changé la configuration de base de Varnish pour mettre le TTL par défaut à 0s).&lt;/p&gt;

&lt;h3&gt;
  
  
  user.php
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php //user.php
    session_start();
?&amp;gt;
&amp;lt;br /&amp;gt;
&amp;lt;div class="private p-2"&amp;gt;
    &amp;lt;?php if($_SESSION['logged'] ?? false): ?&amp;gt;
        Dernière connexion il y a &amp;lt;?= time() - $_SESSION['last_login'] ?&amp;gt;s
    &amp;lt;?php else: ?&amp;gt;
        Pas de dernière connexion
    &amp;lt;?php endif; ?&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Politique de mise en cache du bloc : Contenu privé sans mise en cache particulière.&lt;br&gt;&lt;br&gt;
Le backend est appelé systématiquement pour ce bloc.&lt;/p&gt;

&lt;h3&gt;
  
  
  login.php
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php // login.php

session_start();
$_SESSION['logged'] = true;
$_SESSION['last_login'] = time();

header('Location: /', true, 301);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rien de particulier ici, on est sur un système de login maison très classique.&lt;/p&gt;

&lt;h3&gt;
  
  
  logout.php
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php // logout.php

if ( isset( $_COOKIE[session_name()] ) ) {
    setcookie( session_name(), "deleted", time()-3600, "/");
}

session_start();
session_destroy();

header('Location: /', true, 301);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Il y a une particularité importante ici : &lt;strong&gt;On détruit entièrement le cookie&lt;/strong&gt; de la session PHP pour que Varnish ne nous retourne pas l'ancien contenu utilisateur mis en cache qui n'a plus de raison d'exister.&lt;/p&gt;

&lt;h2&gt;
  
  
  Le mot de la fin
&lt;/h2&gt;

&lt;p&gt;On a vu comment mettre en place plusieurs stratégies de cache différentes en fonction des types de blocs de vos pages. N'hésitez pas à vous approprier le concept de la mise en cache HTTP avec les fragments ESI et à l'utiliser massivement pour réduire drastiquement le temps de réponse de vos belles pages Web.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pour aller plus loin
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://labs.silarhi.fr/esi" rel="noopener noreferrer"&gt;La démo de l'exemple utilisé&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/silarhi/labs.silarhi.fr/tree/master/esi" rel="noopener noreferrer"&gt;Le code source de la démo pour bien comprendre (utilise Docker)&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://varnish-cache.org/docs/4.1/users-guide/index.html" rel="noopener noreferrer"&gt;La documentation officielle de Varnish&lt;/a&gt;&lt;/p&gt;

</description>
      <category>code</category>
      <category>varnish</category>
      <category>php</category>
      <category>docker</category>
    </item>
    <item>
      <title>Une image Docker PHP + Apache sur mesure pour la production</title>
      <dc:creator>Guillaume Sainthillier</dc:creator>
      <pubDate>Mon, 25 Feb 2019 14:53:00 +0000</pubDate>
      <link>https://dev.to/silarhi/une-image-docker-php-apache-sur-mesure-pour-la-production-15e2</link>
      <guid>https://dev.to/silarhi/une-image-docker-php-apache-sur-mesure-pour-la-production-15e2</guid>
      <description>&lt;p&gt;Créez l'image Docker PHP + Apache qui vous ressemble. Elle sera donc parfaite puisque vous êtes un lecteur de ce blog et que vous êtes abonnés aux nouveaux articles 💖.&lt;/p&gt;

&lt;h2&gt;
  
  
  C'est quoi une image Docker parfaite ?
&lt;/h2&gt;

&lt;p&gt;C'est avant tout une image établie selon &lt;strong&gt;votre besoin et votre expérience&lt;/strong&gt;. Vous avez identifié des &lt;strong&gt;patterns communs&lt;/strong&gt; de configuration qui se répètent au fil de vos développements. Et comme vous êtes quelqu'un de bien, vous voulez donc les regrouper dans une couche commune à toutes vos apps pour en faciliter la maintenance.&lt;/p&gt;

&lt;p&gt;À partir de ma propre expérience et du type d'applications que je développe la plupart du temps, une image PHP Apache parfaite selon moi c'est :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://getcomposer.org/"&gt;Composer&lt;/a&gt; installé &lt;strong&gt;par défaut&lt;/strong&gt; (qui n'utilise pas de gestionnaire de dépendances de nos jours ?).&lt;/li&gt;
&lt;li&gt;PHP et Apache configurés pour la &lt;strong&gt;production&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Des modules PHP récurrents activés par défaut.&lt;/li&gt;
&lt;li&gt;De jolies page d'erreurs type 404 un peu plus silencieuses que celles par défaut d'Apache.&lt;/li&gt;
&lt;li&gt;Idéalement des variantes déjà prêtes pour des applications qui tournent avec Symfony.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Créer son image parfaite
&lt;/h2&gt;

&lt;p&gt;En premier lieu on va partir de &lt;a href="https://hub.docker.com/_/php/"&gt;l'image officielle&lt;/a&gt; de php, parce qu'on veut gagner du temps et ne pas réinventer la roue. Vous savez quoi ? L'image est même fournie avec un serveur Apache intégré. &lt;strong&gt;TOP.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;🤓 « &lt;em&gt;Pourquoi utiliser Apache plutôt que nginx ?&lt;/em&gt; »&lt;/p&gt;

&lt;p&gt;Parce que cet article n'est pas neutre et qu'il faut trancher. Personnellement, je trouve la différence de performances négligeables entre les deux et Apache convient parfaitement pour la plupart des projets. C'est également plus serein pour les mises à jour de sécurité qui seront directement effectuées par la team PHP Docker. Il m'arrive par contre pour &lt;a href="https://github.com/guillaume-sainthillier/ByNight"&gt;des projets un peu plus importants&lt;/a&gt; d'utiliser nginx. Encore une fois, cela reste vraiment &lt;strong&gt;une question de besoin, d'expérience et de goûts&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;On va donc commencer notre Dockerfile avec la ligne suivante :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Dockerfile
FROM php:7.4-apache
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;On build et on run tout ça pour vérifier que tout va bien :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker build -t super/hero .
docker run -it -p 8080:80 super/hero
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;En vous rendant sur &lt;a href="http://127.0.0.1:8080"&gt;http://127.0.0.1:8080&lt;/a&gt; vous devez tomber sur une belle page 403. Ça veut bien dire que tout fonctionne, il n'y a pas encore vos sources donc Apache vous répond gentiement !&lt;/p&gt;

&lt;h3&gt;
  
  
  Composer par défaut
&lt;/h3&gt;

&lt;p&gt;Rien de plus simple, on installe la dernière version de composer et on rend la commande accessible partout dans le conteneur.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Dockerfile
ENV COMPOSER_ALLOW_SUPERUSER=1
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
RUN composer --version
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Configuration d'Apache
&lt;/h3&gt;

&lt;p&gt;Par défaut, l'image officielle est configurée pour avoir la racine du serveur Web dans /var/www/html. &lt;strong&gt;Trop long !&lt;/strong&gt; On va ignorer tout ça et accueillir notre futur code source dans un dossier &lt;strong&gt;/app&lt;/strong&gt; directement à la racine du conteneur.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# conf/vhost.conf
&amp;lt;VirtualHost *:80&amp;gt;
    ServerAdmin contact@monsupersite.fr

    DocumentRoot /app

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
&amp;lt;/VirtualHost&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# conf/apache.conf
&amp;lt;Directory /app/&amp;gt;
    Options -Indexes +FollowSymLinks
    AllowOverride None
    Require all granted
&amp;lt;/Directory&amp;gt;

ServerTokens Prod
ServerSignature Off
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Dockerfile
# ...
ADD conf/vhost.conf /etc/apache2/sites-available/000-default.conf
ADD conf/apache.conf /etc/apache2/conf-available/z-app.conf
RUN a2enconf z-app
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;On a changé ici la configuration du vhost de base d'Apache (000-default.conf), qui délivre maintenant le contenu du dossier /app. On configure aussi le dossier /app pour ne pas autoriser Apache de lister les fichiers d'un répertoire avec &lt;code&gt;Options -Indexes&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Pour ma part, je n'aime pas configurer mes sites avec un .htaccess (pour des raisons de performance). C'est pourquoi j'ai placé la directive &lt;code&gt;AllowOverride None&lt;/code&gt;. Comme l'image est destinée pour le staging / production, j'ai aussi supprimé les infos sur l'infrastructure utilisée avec les directives &lt;code&gt;ServerTokens Prod&lt;/code&gt; et &lt;code&gt;ServerSignature Off&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;J'ai volontairement nommé la configuration &lt;code&gt;z-app&lt;/code&gt; pour être sûr qu'elle soit chargée en dernier et qu'aucun autre module ne vienne surcharger les instructions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuration de PHP
&lt;/h3&gt;

&lt;p&gt;On va configurer PHP selon les besoins récurrents de nos futures images. Ne mettez pas de valeurs trop importantes ici, si un de vos conteneurs en particulier a besoin d'une configuration plus véloce, vous pourrez override cette configuration de base dans le Dockerfile propre à cette app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;; conf/php.ini
date.timezone = Europe/Paris

opcache.enable = 1
opcache.enable_cli = 1
opcache.memory_consumption = 128
opcache.revalidate_freq = 0
apc.enable_cli = On

upload_max_filesize = 16M
post_max_size = 16M

realpath_cache_size = 4096k
realpath_cache_ttl = 7200

display_errors = Off
display_startup_errors = Off
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Dockerfile
# ...
RUN apt-get update &amp;amp;&amp;amp; \
    apt-get install -y --no-install-recommends \
    libicu-dev
RUN docker-php-ext-install -j$(nproc) opcache pdo_mysql
ADD conf/php.ini /usr/local/etc/php/conf.d/app.ini
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Adaptez les modules par défaut qui vous semblent essentiels. Pour moi, la quasi totalité de mes projets utilisent PDO et MySQL. OPCache est bien sûr par défaut également.&lt;/p&gt;

&lt;h3&gt;
  
  
  Des jolies pages d'erreur par défaut
&lt;/h3&gt;

&lt;p&gt;Qui ne s'est jamais retrouvé devant la page 404 par défaut d'Apache ? C'est l'assurance de perdre la crédibilité du &lt;strong&gt;&lt;a href="https://www.mathieupassenaud.fr/un-developpeur-nest-pas-un-joueur/"&gt;ninja DevOp big data pro de baby-foot&lt;/a&gt;&lt;/strong&gt; et autres buzzwords que vous êtes auprès de votre client. On va donc modifier la conf existante pour ajouter la gestion des pages d'erreurs :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# conf/apache.conf
# ...
&amp;lt;Directory /errors/&amp;gt;
    Options -Indexes
    AllowOverride None
    Require all granted
&amp;lt;/Directory&amp;gt;

Alias /_errors/ /errors/
ErrorDocument 404 /_errors/404.html
ErrorDocument 403 /_errors/403.html
ErrorDocument 500 /_errors/500.html
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Dockerfile
# ...
ADD errors /errors
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Pour ne pas causer de conflits avec les futures apps,  j'ai placé le contenu des pages d'erreur dans un dossier à part (/errors à la racine du conteneur) et j'isole les requêtes HTTP spécifiques à ces pages via le chemin /_errors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ça donne quoi au final ?
&lt;/h2&gt;

&lt;p&gt;Le Dockerfile a été optimisé pour réduire la taille des couches.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Dockerfile
FROM php:7.2-apache

ENV COMPOSER_ALLOW_SUPERUSER=1

EXPOSE 80
WORKDIR /app

RUN apt-get update -qy &amp;amp;&amp;amp; \
    apt-get install -y \
    git \
    libicu-dev \
    unzip \
    zip &amp;amp;&amp;amp; \
    curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer &amp;amp;&amp;amp; \
    apt-get clean &amp;amp;&amp;amp; rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

# PHP Extensions
RUN docker-php-ext-install -j$(nproc) opcache pdo_mysql
ADD conf/php.ini /usr/local/etc/php/conf.d/app.ini

# Apache
ADD errors /errors
RUN a2enmod rewrite remoteip
ADD conf/vhost.conf /etc/apache2/sites-available/000-default.conf
ADD conf/apache.conf /etc/apache2/conf-available/z-app.conf
RUN a2enconf z-app
ADD index.php /app/index.php
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# conf/apache.conf
&amp;lt;Directory /app/&amp;gt;
    Options -Indexes +FollowSymLinks
    AllowOverride None
    Require all granted

    SetEnvIf X_FORWARDED_PROTO https HTTPS=on
&amp;lt;/Directory&amp;gt;

ServerTokens Prod
ServerSignature Off

&amp;lt;Directory /errors/&amp;gt;
    Options -Indexes
    AllowOverride None
    Require all granted
&amp;lt;/Directory&amp;gt;

Alias /_errors/ /errors/
ErrorDocument 404 /_errors/404.html
ErrorDocument 403 /_errors/403.html
ErrorDocument 500 /_errors/500.html
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# conf/vhost.conf
&amp;lt;VirtualHost *:80&amp;gt;
    ServerAdmin guillaume@silarhi.fr

    DocumentRoot /app

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
&amp;lt;/VirtualHost&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;; conf/php.ini
date.timezone = Europe/Paris

opcache.enable = 1
opcache.enable_cli = 1
opcache.memory_consumption = 128
opcache.revalidate_freq = 0
apc.enable_cli = On

upload_max_filesize = 16M
post_max_size = 16M

realpath_cache_size=4096k
realpath_cache_ttl=7200

display_errors = Off
display_startup_errors = Off
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Curieux ? J'ai mis en place rien que pour vous :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://labs.silarhi.fr/php"&gt;La démo de l'image Docker avec un super phpinfo en guise de bonjour&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://labs.silarhi.fr"&gt;Une démo plus concrète basée sur Symfony&lt;/a&gt; (&lt;a href="https://blog.silarhi.fr/deploiement-continu-symfony-docker-circleci/"&gt;et son article de blog associé&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://labs.silarhi.fr/php/liencass%C3%A9"&gt;Un exemple de page 404&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/silarhi/labs.silarhi.fr/tree/master/php"&gt;Le code source qui gère tout ça avec Traefik&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pour aller plus loin
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/silarhi/docker-php"&gt;L'intégralité du code source utilisé dans l'article sur GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.silarhi.fr/docker-compose-traefik-https/"&gt;Mettre en place vos images Docker sur votre serveur de production avec Traefik&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hub.docker.com/r/silarhi/php-apache"&gt;L'image dans le Hub Docker&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>code</category>
      <category>docker</category>
      <category>php</category>
    </item>
  </channel>
</rss>
