<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>ArthurHoaro - Blog</title>
        <link>https://hoa.ro/fr/blog/</link>
        <description>Recent content in ArthurHoaro Blog</description>
        <generator>Hugo -- gohugo.io</generator>
        <lastBuildDate>Tue, 08 Dec 2020 11:16:47 +0000</lastBuildDate>
        <atom:link href="https://hoa.ro/fr/blog/index.xml" rel="self" type="application/rss+xml" />
        
        <item>
            <title>Guide : corriger les problèmes d&#39;heure dans Docker</title>
            <link>https://hoa.ro/fr/blog/2020-12-08-draft-docker-time-timezone/</link>
            <pubDate>Tue, 08 Dec 2020 11:16:47 +0000</pubDate>
            
            <guid>https://hoa.ro/fr/blog/2020-12-08-draft-docker-time-timezone/</guid>
            <description><![CDATA[<h3 id="régler-lheure-et-la-timezone-sur-linux">Régler l&rsquo;heure et la timezone sur Linux</h3>
<p>Avant d&rsquo;attaquer la partie Docker, il est nécessaire de rappeler comment régler le fuseau horaire et l&rsquo;heure de nos serveurs Linux, puisque finalement ce sont les mêmes méthodes que nous allons utiliser pour synchroniser nos containers Docker basés sur Linux.</p>
<p>Avant toute chose, voyons l&rsquo;état de l&rsquo;heure courante :</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ date
Tue Nov <span style="color:#ae81ff">17</span> 15:26:20 CET <span style="color:#ae81ff">2020</span>
</code></pre></div><p>Il y a deux choses importantes ici :</p>
<ul>
<li>Le fuseau horaire (<em>timezone</em>), ici CET pour <em>Central European Time</em>, l&rsquo;heure de Paris pour faire court.</li>
<li>L&rsquo;heure en elle-même, qui peut être ou non bien synchronisée. À comparer avec par exemple <a href="https://www.worldtimeserver.com/">celle-ci</a>.</li>
</ul>
<p>Pour <strong>changer le fuseau horaire sur Linux</strong>, c&rsquo;est très simple, il suffit de lancer la commande suivante et de suivre les instructions:</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">dpkg-reconfigure tzdata
</code></pre></div><p>Pour synchroniser l&rsquo;horloge avec la <em>bonne</em> heure, on utilise un service qui s&rsquo;appelle NTP pour <em>Network Time Protocol</em>. Assurons-nous que le service est bien installé et activé dans <code>systemd</code>.</p>
<p>Adaptez ces commandes à votre OS, ici pour Debian 10.</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">apt install ntp
systemctl enable ntp
systemctl start ntp
</code></pre></div><blockquote>
<p>Il n&rsquo;est pas nécessaire d&rsquo;installer <code>ntp</code> dans les containers Docker, parce que leur horloge est basée sur celle du host.</p>
</blockquote>
<p>Bien, maintenant que nous avons couvert les bases, assurons nous que nos containers Docker aient une horloge synchronisé, soient sur le bon fuseau horaire, et arrêtent de décaler chaque élément.</p>
<h3 id="utiliser-la-configuration-de-la-machine-host">Utiliser la configuration de la machine host</h3>
<p>C&rsquo;est la solution la plus simple pour caler l&rsquo;heure de vos containers : s&rsquo;appuyer sur l&rsquo;heure et le fuseau horaire de la machine host, celle qui héberge le service Docker.</p>
<p>En revanche cette solution ne fonctionne que si vous avez la maîtrise du host, et elle n&rsquo;est pas applicable si vous envoyez vos containers dans un quelconque solution <em>cloud</em>.</p>
<p>Pour cela, on va monter en lecture seule deux volumes sur les fichiers de configuration de l&rsquo;horloge du host dans nos containers.</p>
<p>Dans un <code>docker run</code>, on ajoute deux volumes :</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">docker run -d <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>   -v /etc/timezone:/etc/timezone:ro <span style="color:#ae81ff">\ </span> 
   -v /etc/localtime:/etc/localtime:ro <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>   <span style="color:#f92672">[</span>...<span style="color:#f92672">]</span>
</code></pre></div><p>Ou avec un <code>docker-compose.yml</code> :</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yml" data-lang="yml"><span style="color:#66d9ef">services</span>:
  <span style="color:#66d9ef">service_name</span>:
    <span style="color:#66d9ef">volumes</span>:
      - <span style="color:#e6db74">&#34;/etc/timezone:/etc/timezone:ro&#34;</span>
      - <span style="color:#e6db74">&#34;/etc/localtime:/etc/localtime:ro&#34;</span>
</code></pre></div><h3 id="régler-le-fuseau-horaire-des-containers">Régler le fuseau horaire des containers</h3>
<p>Si la méthode précédente ne peut pas s&rsquo;appliquer, parce que vous n&rsquo;avez pas la main sur le host, ou s&rsquo;il est nécessaire d&rsquo;avoir des fuseaux horaires différents sur différents containers, alors il faut pouvoir régler la timezone directement dans l&rsquo;image Docker.</p>
<p>Avant tout, pour Alpine Linux, le paquet <code>tzdata</code> n&rsquo;est pas inclus par défaut, il est donc nécessaire de l&rsquo;ajouter manuellement dans le Dockerfile :</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-Dockerfile" data-lang="Dockerfile"><span style="color:#66d9ef">RUN</span> apk add --no-cache tzdata<span style="color:#960050;background-color:#1e0010">
</span></code></pre></div><p>Pour changer le fuseau horaire de nos containers Linux, plutôt que d&rsquo;utiliser <code>dpkg-reconfigure</code> qui est interactif, on va utiliser une petite subtilité et faire la manipulation manuellement dans notre Dockerfile.</p>
<p>Ce que fait cette commande, c&rsquo;est d&rsquo;une part de mettre à jour le fichier <code>/etc/timezone</code> avec le bon fuseau horaire, mais aussi de créer une copie du binaire associé à la timezone dans <code>/etc/localtime</code>. Faisons donc ça :</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-Dockerfile" data-lang="Dockerfile"><span style="color:#66d9ef">FROM</span><span style="color:#e6db74"> alpine  </span><span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">ENV</span> TZ<span style="color:#f92672">=</span>Europe/Paris <span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> apk add --no-cache tzdata<span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> ln -snf /usr/share/zoneinfo/$TZ /etc/localtime <span style="color:#f92672">&amp;&amp;</span> echo $TZ &gt; /etc/timezone<span style="color:#960050;background-color:#1e0010">
</span></code></pre></div><p>Cette méthode fonctionne partout, et permet de définir directement la variable d&rsquo;environnement <code>TZ</code> détaillée dans la section suivante.</p>
<p>Vous pouvez tester ça très facilement avec la commande :</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">docker build -t tzalpine . <span style="color:#f92672">&amp;&amp;</span> docker run -it --rm tzalpine
$ date
<span style="color:#75715e"># la date doit avoir le bon fuseau horaire ici</span>
</code></pre></div><p>L&rsquo;OS est maintenant à jour à l&rsquo;intérieur des containers, quels que soit la configuration et l&rsquo;environnement. Il ne reste plus qu&rsquo;à s&rsquo;assurer que les différents logiciels prennent bien en compte le fuseau horaire.</p>
<h3 id="la-variable-denvironnement-tz">La variable d&rsquo;environnement <code>TZ</code></h3>
<p>Une bonne partie des langages de programmation et/ou frameworks modernes s&rsquo;appuient sur la même variable d&rsquo;environnement : <code>TZ</code>. C&rsquo;est le cas par exemple pour régler l&rsquo;heure en Python et sur NodeJS.</p>
<p>En la définissant, et avec celle du système d&rsquo;exploitation, vous devriez couvrir une bonne majorité des cas d&rsquo;utilisation.</p>
<p>Attention toutefois, cette variable peut être généralement être surchargée, si c&rsquo;est le choix des développeurs. Dans ces cas là, il y a généralement une option disponible dans la configuration de l&rsquo;outil que vous utilisez, ou parfois dans son UI.</p>
<p>Il suffit de la définir au lancement de votre container :</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">docker run -d <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>   -e TZ<span style="color:#f92672">=</span>Europe/Paris <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>   <span style="color:#f92672">[</span>...<span style="color:#f92672">]</span>
</code></pre></div><p>Ou dans le <code>docker-compose.yml</code>:</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yml" data-lang="yml"><span style="color:#66d9ef">services</span>:
  <span style="color:#66d9ef">service_name</span>:
    <span style="color:#66d9ef">environment</span>:
      - TZ=Europe/Paris
</code></pre></div><h3 id="configuration-php">Configuration PHP</h3>
<p>Évidemment, PHP utilise un fonctionnement différent et ne prend pas en compte la variable d&rsquo;environnement <code>TZ</code>.</p>
<p>Pour <strong>régler l&rsquo;heure en PHP</strong>, il faut définir la valeur du paramètre <code>date.timezone</code> dans le fichier <code>php.ini</code>.</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-ini" data-lang="ini"><span style="color:#66d9ef">[Date]</span>
<span style="color:#75715e">; Attention à bien enlever le point-virgule, qui signifie commentaire.</span>
<span style="color:#a6e22e">date.timezone</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">Europe/Paris</span>
</code></pre></div><h3 id="conclusion">Conclusion</h3>
<p>Voilà, en principe on a fait le tour du sujet. J&rsquo;ai mis à jour la plupart des mes containers pour qu&rsquo;ils prennent en compte ces modifications, et tout semble rouler à la perfection.</p>
<p>Si jamais vous rencontrez des cas où les manipulations décrites dans cet article ne sont pas suffisantes, n&rsquo;hésitez pas à me le faire remonter - je mettrais à jour ce guide.</p>
<hr>
<p>Ressources :</p>
<ul>
<li><a href="https://serverfault.com/questions/683605/docker-container-time-timezone-will-not-reflect-changes">https://serverfault.com/questions/683605/docker-container-time-timezone-will-not-reflect-changes</a></li>
<li><a href="https://stackoverflow.com/questions/24551592/how-to-make-sure-dockers-time-syncs-with-that-of-the-host/">https://stackoverflow.com/questions/24551592/how-to-make-sure-dockers-time-syncs-with-that-of-the-host/</a></li>
<li><a href="https://docs.docker.com/engine/reference/run/">https://docs.docker.com/engine/reference/run/</a></li>
<li><a href="https://docs.docker.com/engine/reference/builder/">https://docs.docker.com/engine/reference/builder/</a></li>
<li><a href="https://github.com/nodejs/help/issues/36">https://github.com/nodejs/help/issues/36</a></li>
<li><a href="https://help.pythonanywhere.com/pages/SettingTheTimezone/">https://help.pythonanywhere.com/pages/SettingTheTimezone/</a></li>
<li><a href="https://www.php.net/manual/en/datetime.configuration.php#ini.date.timezone">https://www.php.net/manual/en/datetime.configuration.php#ini.date.timezone</a></li>
</ul>
]]></description>
        </item>
        
        <item>
            <title>PHP 8.0 est sorti ! Quoi de neuf, Docteur ?</title>
            <link>https://hoa.ro/fr/blog/2020-11-26-php-8-0-is-out/</link>
            <pubDate>Thu, 26 Nov 2020 15:31:11 +0000</pubDate>
            
            <guid>https://hoa.ro/fr/blog/2020-11-26-php-8-0-is-out/</guid>
            <description><![CDATA[<p>La version finale de PHP 8.0 vient d&rsquo;être publiée. C&rsquo;est une excellente nouvelle ! Au-delà des améliorations techniques qui ont pu être apportées, il y a surtout de nouvelles fonctionnalités dont certaines sont très attendues parmi les développeurs PHP.</p>
<p>Si vous n&rsquo;avez pas vraiment suivi tout ça, je vous propose de faire un tour des nouvelles <em>features</em>, dans le désordre.</p>
<h3 id="les-arguments-nommés">Les arguments nommés</h3>
<p>Il est possible de passer les arguments d&rsquo;une fonction ou d&rsquo;une méthode en utilisant leur nom de variable, et de fait d&rsquo;ignorer l&rsquo;ordre de déclaration des paramètres. On retrouve un comportement similaire avec les arguments nommés de Python.</p>
<p>C&rsquo;est très pratique, car cela permet également d&rsquo;ignorer les paramètres facultatifs qui ne nous intéressent pas.</p>
<p>Par exemple pour faire une recherche où seule la casse nous intéresse :</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-php" data-lang="php">function search(string $terms, $sortBy = &#39;dates&#39;, $caseSensitive = false) {}

// PHP 7.x
$results = search(&#39;my query&#39;, &#39;dates&#39;, true);

// PHP 8.0
$results = search(caseSensitive: true, terms: &#39;my query&#39;);
</code></pre></div><p>On voit clairement l&rsquo;avantage dans cet exemple, le paramètre <code>$sortBy</code> n&rsquo;a pas besoin d&rsquo;être explicitement fourni, et donc on s&rsquo;appuie toujours sur la valeur par défaut définie par la fonction elle-même. Le couplage est donc légèrement réduit.</p>
<p>Le contre-argument qui a été principalement présenté lors du vote de cette feature en revanche, c&rsquo;est que l&rsquo;on rajoute un lien fort sur le nom des paramètres, qui n&rsquo;existait pas jusqu&rsquo;alors. Il sera désormais plus difficile de renommer le nom d&rsquo;une variable, en particulier dans les libraries.</p>
<h3 id="les-attributs">Les attributs</h3>
<p>LA feature attendue dans PHP 8.0 est clairement les attributs ; enfin, par moi en tout cas. C&rsquo;est aussi ce qu&rsquo;on appelle les annotations, par exemple en Java. Actuellement, la plupart des frameworks et libraries en PHP ont contourné le problème en utilisant des annotations via la PHPDoc, mais maintenant la fonctionnalité est officiellement supportée.</p>
<p>La syntaxe choisie est celle de Rust, c&rsquo;est-à-dire : <code>#[Attribute]</code>.</p>
<p>Exemple avec le routeur Symfony :</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-php" data-lang="php">// PHP 7.x
class SomeController
{
    /** @Route(&#34;/path&#34;, name=&#34;action&#34;) */
    public function someAction() {}
}

// PHP 8.0
class SomeController
{
    #[Route(&#39;/path&#39;, name: &#39;action&#39;)]
    public function someAction() {}
}
</code></pre></div><p>Pour l’anecdote, les débats ont été assez virulents sur la meilleure syntaxe à choisir. En réalité celle retenue n&rsquo;est même pas celle qui avait été votée en premier lieu. C&rsquo;était à l&rsquo;origine <code>&lt;&lt;Attribute()&gt;&gt;</code> qui avait été choisie, mais il s&rsquo;est avéré que cette syntaxe posait des problèmes au niveau de l&rsquo;implémentation au sein du langage.</p>
<p>De la même façon, il n&rsquo;était pas possible de choisir <code>@Attribute()</code>, l&rsquo;arobase étant déjà un caractère réservé en PHP (pour rendre les erreurs silencieuses, ne l&rsquo;utilisez pas). L&rsquo;autre option était <code>@@Attribute()</code>.</p>
<h3 id="les-types-dunion">Les types d&rsquo;union</h3>
<p>Les types d&rsquo;union (union types) permettent de spécifier plusieurs types sur un même paramètre, retour de fonction/méthode, ou attribut de classe.</p>
<p>Cette fonctionnalité permet de peaufiner encore un petit peu la déclaration des types en PHP introduits en version 7.0, et qui ont déjà subi quelques améliorations depuis leur sortie (nullable, attributs, etc.).</p>
<p>Exemple :</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-php" data-lang="php">// PHP 7.x
/**
 * @param int|float|null $threeshold
 * @return ArrayAccessImplementation|array
 **/
public function getListByPrice($threeshold) {}

// PHP 8.0
public function getListByPrice(int|float|null $threeshold): ArrayAccessImplementation|array {}
</code></pre></div><p>C&rsquo;est évidemment à utiliser avec parcimonie, puisque l&rsquo;avantage d&rsquo;un typage strict des méthodes évite justement que l&rsquo;on se retrouve avec n&rsquo;importe quel type en entrée/sortie de celles-ci.</p>
<blockquote>
<p>Note : inutile d&rsquo;en faire une section complète ici, mais <strong>le type <code>mixed</code> a également été ajouté au langage</strong>, ce qui permet de ne <em>plus jamais</em> avoir de typage non défini, et simplifie grandement la vie des linters.</p>
</blockquote>
<h3 id="déclaration-des-attributs-dans-le-constructeur">Déclaration des attributs dans le constructeur</h3>
<p>Faute d&rsquo;une meilleure traduction de cette nouvelle simplification du langage appelée « constructor property promotion », le titre décrit exactement ce changement. Les attributs de classe peuvent être directement et <em>uniquement</em> déclarés dans les paramètres du constructeur.</p>
<p>Voyons ça :</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-php" data-lang="php">// PHP 7.x
class BlogPost
{
  protected string $title;
  protected TagList $tags;
  protected DateTimeInterface $createdAt
  public function __construct(string $title, TagList $tags, DateTimeInterface $createdAt) {
    $this-&gt;title = $title;
    $this-&gt;tags = $tags;
    $createdAt = $createdAt;
  }
}

// PHP 8.0
class BlogPost
{
  public function __construct(
    protected string $title,
    protected TagList $tags,
    protected DateTimeInterface $createdAt,
  ) {}
}
</code></pre></div><p>En réalité avec un IDE avancé comme PHPStorm, le volume de code écrit est plus ou moins le même. Mais il faut avouer que c&rsquo;est plus lisible.</p>
<h3 id="virgule-de-fin-dans-les-paramètres">Virgule de fin dans les paramètres</h3>
<p>Vous aurez peut-être remarqué une petite subtilité dans la déclaration du constructeur de la section précédente : le dernier paramètre est suivi d&rsquo;une virgule&hellip; et ce n&rsquo;est pas une typo !</p>
<p>En effet, les listes de paramètres acceptent désormais une virgule de fin, comme les arrays, à la déclaration et à l&rsquo;appel. Aucun doute que j&rsquo;ajouterai cette règle à ma configuration PHPCS.</p>
<p>Pour l&rsquo;exemple, voyez au-dessus.</p>
<h3 id="nouvelles-fonctions-str_">Nouvelles fonctions <code>str_*</code></h3>
<p>Déjà il y a 10 ans, l&rsquo;une des premières versions de Shaarli contenait les fonctions <code>startsWith()</code> et <code>endsWith()</code> pour faire des vérifications sur les chaînes de caractères. Ces méthodes et leurs dérivées à base de <code>strpos</code> je les ai vues dans quasiment les projets sur lesquels j&rsquo;ai pu travailler.</p>
<p>Une décennie plus tard, 3 nouvelles fonctions sont désormais disponibles dans la bibliothèque standard, et leur nom parle d&rsquo;elles-mêmes : <code>str_contains</code>, <code>str_starts_with()</code> et <code>str_ends_with()</code>.</p>
<p>Utilisation :</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-php" data-lang="php">// All return true.
str_contains(&#39;All work and no play makes Jack a dull boy&#39;, &#39;Jack&#39;);
str_starts_with(&#39;All work and no play makes Jack a dull boy&#39;, &#39;All work&#39;);
str_ends_with(&#39;All work and no play makes Jack a dull boy&#39;, &#39;a dull boy&#39;);
</code></pre></div><h3 id="nouvelle-interface-stringable">Nouvelle interface <code>Stringable</code></h3>
<p><code>Stringable</code> est une nouvelle interface de la bibliothèque standard. Pour l&rsquo;implémenter il suffit d&rsquo;implémenter une méthode <code>__toString()</code> qui sera appelée par défaut lors d&rsquo;une conversion de l&rsquo;objet.</p>
<p>En réalité, <em>tout ça existe déjà</em>, et il n&rsquo;est <strong>pas</strong> nécessaire de spécifier explicitement <code>implements Stringable</code>, cela permet simplement de mieux gérer le typage.</p>
<p>En gros, cette nouvelle interface vous permet de faire ça :</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-php" data-lang="php">class Greeting
{
  public function __toString(): string {
    return &#39;hi!&#39;;
  }
}

class NotGreeting
{
  public function __toString(): string {
    return &#39;come back later&#39;;
  }
}

class Greeter
{
  public function greet(Stringable $greeting): string {
    return &#39;Greeting: &#39; . $greeting;
  }
}

$greeter = new Greeter();
echo $greeter-&gt;greet(new NotGreeting()); // &#39;Greeting: come back later&#39;
echo $greeter-&gt;greet(new Greeting()); // &#39;Greeting: hi!&#39;
</code></pre></div><p>Pour faire cela en PHP 7.x, il n&rsquo;aurait pas été possible de spécifier le type de la méthode <code>greet()</code> sans réunir <code>NotGreeting</code> et <code>Greeting</code> sous une même interface.</p>
<h3 id="lopérateur-nullsafe">L&rsquo;opérateur nullsafe</h3>
<p>Une de mes <em>features</em> préférées de PHP 7.0 était <a href="https://www.php.net/manual/fr/migration70.new-features.php#migration70.new-features.null-coalesce-op">l&rsquo;opérateur null coalescent</a>. Il n&rsquo;y a rien de plus contre-productif que d&rsquo;écrire 8 lignes de <code>if</code> pour vérifier que <code>isset</code>, <code>array_key_exists</code>, <code>!==</code> null ou autre <code>instanceof</code>.</p>
<p>PHP 8.0 me ravit donc en ajoutant l&rsquo;opérateur nullsafe, qui permet de faire des appels de méthode sur un objet null.</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-php" data-lang="php">// PHP 7.x
if (!$blogPost instanceof BlogPost || $blogPost-&gt;getCreatedAt() === null) {
  return null;
}
return $blogPost-&gt;getCreatedAt()-&gt;format(&#39;Y-m-d&#39;);

// PHP 8.0
return $blogPost?-&gt;getCreatedAt()?-&gt;format(&#39;Y-m-d&#39;);
</code></pre></div><p>Et si <code>$blogPost</code> ou <code>$createdAt</code> est null, on retourne tout simplement null, sans générer d&rsquo;erreur fatale.</p>
<h3 id="lever-des-exceptions-dans-les-expressions">Lever des exceptions dans les expressions</h3>
<p>Aller, tant qu&rsquo;on est dans les petites améliorations qui améliorent la productivité et la lisibilité du code, il est désormais possible de lever des exceptions dans n&rsquo;importe quelle expression. Je n&rsquo;ai jamais bien compris pourquoi ce n&rsquo;était pas possible - probablement des questions d&rsquo;implémentation au niveau du code source de PHP - mais c&rsquo;est réglé.</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-php" data-lang="php">// PHP 7.x
$item = $collection-&gt;getItem($id);
if ($item === null) {
  throw new ItemNotFoundException();
}
return $item;

// PHP 8.0
return $collection-&gt;getItem($id) ?? throw new ItemNotFoundException();
</code></pre></div><h3 id="match-le-cousin-du-switch"><code>match</code>, le cousin du <code>switch</code></h3>
<p>J&rsquo;utilise relativement rarement les <code>switch {}</code>, peut-être inconsciemment parce que c&rsquo;est une expression assez verbeuse&hellip; <code>switch</code>, <code>case:</code>, <code>break</code>, et on répète. PHP 8.0 ajoute désormais l&rsquo;expression <code>match</code> dont la syntaxe est similaire à celle existante en Java.</p>
<p>Concrètement, ça répond au même besoin que le <code>switch</code>, mais en plus lisible et compact avec une syntaxe <em>array-like</em>.</p>
<p>Voyons plutôt :</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-php" data-lang="php">// PHP 7.x
switch ($locale) {
  case &#39;en_GB&#39;:
  case &#39;en_US&#39;:
    $language = Languages::ENGLISH;
    break;
  case &#39;de_DE&#39;:
    $language = Languages::GERMAN;
    break;
  default:
    $language = Languages::FRENCH;
}

// PHP 8.0
$language = match ($locale) {
  &#39;en_GB&#39;, &#39;en_US&#39; =&gt; Languages::ENGLISH,
  &#39;de_DE&#39; =&gt; Languages::GERMAN,
  default =&gt; Languages::FRENCH,
};
</code></pre></div><h3 id="jit-just-in-time-compiler">JIT (Just In Time) compiler</h3>
<p>JIT compiler est une fonctionnalité de PHP 8.0 qui permet de compiler du code ré-utilisable directement pendant l&rsquo;exécution, de la même façon qu&rsquo;un cache pourrait opérer. Cela peut mener à de grandes amélioration de performances dans certains contextes.</p>
<p>C&rsquo;est prometteur, mais c&rsquo;est aussi une fonctionnalité assez complexe que je préfèrerait développer dans un article dédié. On en reparle ici bientôt !</p>
<h3 id="type-de-retour-static">Type de retour <code>static</code></h3>
<p>Je ne vais pas refaire le discours <code>self</code> vs <code>static</code> ici, mais sachez qu&rsquo;il est désormais possible de définir le type de retour à <code>static</code>, en plus de <code>self</code> qui était déjà disponible.</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-php" data-lang="php">class Entity {
  public function setProperty(int $property): static {
    $this-&gt;property = $property;

    return $this;
  }
}
</code></pre></div><h3 id="utilisation-de-class-sur-les-objets">Utilisation de <code>::class</code> sur les objets</h3>
<p>Il est maintenant possible d&rsquo;appeler <code>::class</code> directement sur les objets et pas uniquement sur les classes. Encore une petite amélioration qui simplifie la vie des développeurs.</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-php" data-lang="php">// PHP 7.x
$className = get_class($myObject);
$className = MyClass::class;

// PHP 8.0
$className = $myObject::class;
$className = MyClass::class;
</code></pre></div><h3 id="datetime-createfrominterface">DateTime <code>createFromInterface()</code></h3>
<p>Vous avez déjà essayé de manipuler des <code>DateTimeInterface</code>, de créer des <code>DateTimeImmutable</code> et d&rsquo;effectuer des opérations sur tout ça ? Non ? Eh bien vous avez de la chance, puisque l&rsquo;implémentation en PHP 7.x est trouée, et qu&rsquo;il y a de quoi y perdre quelques cheveux.</p>
<p>PHP 8.0 vient régler ça en ajoutant une méthode <code>createFromInterface()</code> aux classes <code>DateTime</code> et <code>DateTimeImmutable</code>, ce qui permet de convertir les objets de l&rsquo;un à l&rsquo;autre facilement.</p>
<p>Exemple, qui devrait vous faire saisir l&rsquo;idée :</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-php" data-lang="php">public function addMonth(DateTimeInterface $dateTime): DateTimeImmutable {
  $dateTime = DateTime::createFromInterface($dateTime);
  $dateTime-&gt;add(new DateInterval(&#39;P1M&#39;);

  return DateTimeImmutable::createFromInterface($dateTime);
}
</code></pre></div><h3 id="conclusion">Conclusion</h3>
<p>Ça fait déjà pas mal de choses à se mettre sous la dent. Cet article n&rsquo;a pas non plus vocation à être une <em>release note</em> complète.</p>
<p>Concernant les <em>breaking changes</em>, ils sont assez peu nombreux. Ça a toujours été une volonté forte de la part des développeurs de PHP - la contrepartie étant que cela peut freiner les évolutions.</p>
<p>Concrètement, la bascule en PHP 8.0 se fera facilement, si vous n&rsquo;avez pas utilisé de syntaxes alambiquées. Mais <strong>attention</strong>, un certain nombre d&rsquo;avertissements ont été convertis en erreur <em>réelle</em>. Il faudra donc peut être revoir votre gestion des erreurs.</p>
<p>Bon, on ne va pas se mentir, les montées de version sont souvent plus douloureuses au niveau de la gestion des dépendances que du langage en lui-même. Vous devriez évidemment attendre quelques semaines pour que les choses se stabilisent.</p>
<p>Encore quelques nouveautés pour conclure :</p>
<ul>
<li>Les méthodes privées (<code>private</code>) ne peuvent plus être déclarées comme <code>final</code>, ce qui était de toute façon un non sens.</li>
<li>Il est désormais possible de catch une exception sans l&rsquo;utiliser - et donc sans déclarer son nom de variable.</li>
</ul>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-php" data-lang="php">try {
} catch (SomeException) {
  // Do something not related to the exception variable
}
</code></pre></div><ul>
<li>La signature des méthodes abstraites dans les <code>Trait</code> doit désormais être <em>strictement</em> respectée dans les classes qui l&rsquo;utilisent.</li>
<li>Une implémentation de <a href="https://en.wikipedia.org/wiki/Weak_reference">WeakMap</a> a été ajoutée dans PHP 8.0. J&rsquo;y reviendrais possiblement dans un article dédié.</li>
</ul>
]]></description>
        </item>
        
        <item>
            <title>Construire un blog statique et un workflow de publication avec Hugo, Github, Docker et NetlifyCMS</title>
            <link>https://hoa.ro/fr/blog/2020-11-12-draft-run-your-blog-using-hugo-and-netlifycms/</link>
            <pubDate>Thu, 12 Nov 2020 11:33:00 +0000</pubDate>
            
            <guid>https://hoa.ro/fr/blog/2020-11-12-draft-run-your-blog-using-hugo-and-netlifycms/</guid>
            <description><![CDATA[<blockquote>
<p><em>Disclaimer</em> : je propose ici <em>une</em> solution technique, et un workflow de publication, que j&rsquo;ai adopté. La force des sites statiques étant leur grande souplesse, chaque partie détaillée dans cet article est interchangeable avec une autre solution de votre choix.
Aussi, j&rsquo;aurais pu utiliser Netlify qui est adapté aux sites statiques avec leur CDN, mais je ne souhaitais pas changer mon DNS racine.</p>
</blockquote>
<h2 id="overview">Overview</h2>
<p>Les chapitres suivants sont résumés dans ce diagramme :</p>

<a href="/img/20201109-hugo-blog.png" class="lightbox">
  <img src="/img/20201109-hugo-blog.png"  alt="Overall architecture diagram" 
    class="left"  />
</a>


<h2 id="le-moteur-de-blog--hugo">Le moteur de blog : Hugo</h2>
<p>Au coeur de notre workflow, il y a l&rsquo;outil qui va nous permettre de construire notre site final, le blog. Pour produire un site dont le but est d&rsquo;exposer du contenu accessible en lecture, les <em>générateurs de sites statiques</em> sont l&rsquo;outil idéal, grâce à la souplesse qu&rsquo;ils apportent.</p>
<p>À la différence d&rsquo;un CMS (par exemple WordPress ou Drupal) qui traite dynamiquement chaque requête client via du code serveur et/ou une base de données, les générateurs de sites statiques produisent l&rsquo;intégralité des fichiers HTML qui pourront être accessibles.</p>
<p>Par exemple, cet article que vous êtes en train de lire est un simple fichier HTML, qui a été généré à partir d&rsquo;un fichier de contenu écrit en Markdown. De plus, le tag <a href="https://hoa.ro/tags/hugo/"><code>hugo</code></a> est utilisé. Il existe donc un autre fichier HTML, qui contient la liste des articles associés à ce tag. L&rsquo;important à retenir c&rsquo;est qu&rsquo;il n&rsquo;y a pas de traitement serveur, ou de langage de programmation derrière, on sert simplement du contenu statique.</p>
<p>Les générateurs de sites statiques ne sont pas nouveaux, mais j&rsquo;aime donner du contexte à ce que je publie et ne pas présumer du niveau du lecteur.</p>
<p>Dans ce qui nous intéresse ici, j&rsquo;ai donc choisi le framework <a href="https://gohugo.io/">Hugo</a>, écrit en Go, qui est probablement l&rsquo;un des plus populaires au monde. Il en existe beaucoup d&rsquo;autres, comme <a href="https://jekyllrb.com/">Jekyll</a> (Ruby), <a href="https://www.gatsbyjs.com/">Gatsby</a> (JS), <a href="https://www.getzola.org/">Zola</a> (Rust), <a href="https://github.com/getpelican/pelican">Pelican</a> (Python), <a href="https://sculpin.io/">Sculpin</a> (PHP), etc.</p>
<p>Finalement, peu importe la solution retenue, l&rsquo;important c&rsquo;est de trouver l&rsquo;outil qui couvre les fonctionnalités que vous souhaitez intégrer à votre blog. Ici par exemple, la contrainte était principalement de gérer nativement le contenu multi-langues.</p>
<p>Je ne vais pas m&rsquo;étendre sur l&rsquo;installation et la configuration d&rsquo;Hugo, pour cela <a href="https://gohugo.io/documentation/">la documentation</a> est très complète et facile d&rsquo;accès. L&rsquo;essentiel est de pouvoir générer votre blog statique en une ligne de commande, ici simplement <code>hugo</code>.</p>
<h2 id="la-rédaction-en-local--zettlr--git">La rédaction en local : Zettlr + git</h2>
<p>Les générateurs de sites statiques offrent la plus grande flexibilité, puisque la seule chose nécessaire pour publier du contenu est d&rsquo;écrire un fichier en Markdown.</p>
<blockquote>
<p>Note : d&rsquo;autres formats que Markdown sont supportés, mais c&rsquo;est à mon avis le plus simple d&rsquo;utilisation, tout en étant très complet.</p>
</blockquote>
<p>Tous mes articles sont enregistrés dans le dossier <code>/content/blog/</code> et correspondent au format suivant : <code>YYYY-MM-DD-nom-d-article.lang.md</code>. C&rsquo;est simple, lisible et efficace.</p>
<p>Pour l&rsquo;éditeur, évidemment tout est possible. Il est possible de tout rédiger sous ViM ou VSCode. Pour ma part, j&rsquo;ai préféré me tourner vers des éditeurs spécialisés dans le Markdown. J&rsquo;ai le sentiment que ma productivité est meilleure lorsque j&rsquo;utilise un outil qui me procure une expérience utilisateur agréable et immersive.</p>
<p>J&rsquo;ai commencé à écrire mes articles en local avec <a href="https://typora.io/">Typora</a> avant de découvrir <a href="https://www.zettlr.com/">Zettlr</a> qui est un bonheur à utiliser. Les deux outils sont puissants, mais ce dernier gère mieux la correction orthographique, la sauvegarde automatique, les métadonnées en en-tête, et le couple syntaxe markdown/formatage visuel.</p>
<p>Et bien sûr, tout le blog est placé dans un repository git. Plus précisément, dans un repo privé Github, avec le dossier <code>/public</code> ignoré. Vous allez comprendre pourquoi dans la suite de cet article lorsque l&rsquo;on va attaquer l&rsquo;automatisation du déploiement.</p>
<p>Il est privé parce que je fonctionne beaucoup avec des prises de note et des brouillons avant d&rsquo;arriver à un article publié, et je ne souhaite pas que ce processus de création soit public. Pascal Martin décrit dans <a href="https://blog.pascal-martin.fr/post/mon-processus-ecriture/">ce billet</a> un fonctionnement dans lequel je me retrouve, même si mon cycle d&rsquo;écriture est un peu plus court.</p>
<h2 id="dockerize-me">Dockerize me</h2>
<p>Il est temps de mettre en ligne notre blog flambant neuf. Encore une fois, chacun a sa recette. Tout ce que l&rsquo;on souhaite, c&rsquo;est mettre nos fichiers HTML statiques derrière un serveur web.</p>
<p>Dans mon architecture, que je décrirai plus en détail à l&rsquo;avenir, j&rsquo;ai un ensemble de containers Docker qui tournent sur un serveur dédié, tous reliés à un <a href="https://github.com/nginx-proxy/nginx-proxy">reverse proxy nginx</a> qui automatise la <a href="https://github.com/nginx-proxy/docker-letsencrypt-nginx-proxy-companion">génération de certificats SSL Let&rsquo;s Encrypt</a>.</p>
<p>De fait, pour monter un nouveau service, tout ce que j&rsquo;ai à faire c&rsquo;est de créer un container Docker relié au réseau, généralement avec Docker Compose pour se simplifier la vie, et tout roule automatiquement.</p>
<p>Je ne crois pas qu&rsquo;Hugo fournisse d&rsquo;image officielle, mais une rapide requête dans mon moteur de recherche préféré m&rsquo;a rapidement redirigé vers l&rsquo;image de <a href="https://github.com/jojomi/docker-hugo">jojomi</a>, qui en plus fourni une <a href="https://github.com/jojomi/docker-nginx-static">image annexe avec un nginx</a> préconfiguré pour servir les fichiers statiques.</p>
<p>C&rsquo;est la beauté de Docker, à partir de là, je n&rsquo;ai plus rien à faire à part écrire un <code>docker-compose.yml</code> avec les variables qui vont bien :</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yml" data-lang="yml"><span style="color:#66d9ef">version</span>: <span style="color:#e6db74">&#39;2&#39;</span>

<span style="color:#66d9ef">services</span>:
  <span style="color:#66d9ef">hugo</span>:
    <span style="color:#66d9ef">image</span>: jojomi/hugo:<span style="color:#ae81ff">0.74.3</span>
    <span style="color:#66d9ef">volumes</span>:
      - ./:/src
      - /data/blog/output/:/output
    <span style="color:#66d9ef">environment</span>:
      - HUGO_REFRESH_TIME=<span style="color:#ae81ff">3600</span>
      - HUGO_THEME=hello-friend-ng
      - HUGO_BASEURL=https://hoa.ro/
    <span style="color:#66d9ef">restart</span>: always
    <span style="color:#66d9ef">network_mode</span>: bridge

  <span style="color:#66d9ef">web</span>:
    <span style="color:#66d9ef">image</span>: jojomi/nginx-static
    <span style="color:#66d9ef">volumes</span>:
      - /data/blog/output:/var/www
    <span style="color:#66d9ef">environment</span>:
      - VIRTUAL_HOST=hoa.ro
      - VIRTUAL_PORT=<span style="color:#ae81ff">80</span>
      - LETSENCRYPT_HOST=hoa.ro
      - LETSENCRYPT_EMAIL=arthur@hoa.ro
    <span style="color:#66d9ef">ports</span>:
      - <span style="color:#ae81ff">80</span>
    <span style="color:#66d9ef">network_mode</span>: bridge
    <span style="color:#66d9ef">restart</span>: always
</code></pre></div><p>On lance ensuite l&rsquo;éternel</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">docker-compose up -d
</code></pre></div><p>Et vous y êtes. Ni plus, ni moins.</p>
<p>Maintenant, comment mettre à jour notre contenu ? Dans un premier temps, réfléchissons à le faire manuellement. C&rsquo;est la première étape de l&rsquo;automatisation.</p>
<p>Notre blog est relié à notre repository Github, donc il suffit de <code>pull</code> les dernières modifications, et de régénérer le contenu statique ; rappelez-vous que le dossier <code>/public</code> n&rsquo;est pas synchronisé sur Git.</p>
<p>Une bonne habitude à prendre, c&rsquo;est de toujours mettre dans un script ce type de commandes parce que, croyez-moi, on oublie rapidement. <code>update.sh</code>:</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash"><span style="color:#75715e">#!/bin/bash
</span><span style="color:#75715e"></span>
git pull origin master
docker-compose exec -T hugo hugo -d /output
</code></pre></div><p>Même pas besoin de redémarrer le container !</p>
<p>Cette commande va exécuter <code>hugo -d /output</code> dans notre container Hugo et le tout ne prend que quelques secondes. Le dossier de sortie est modifié, car c&rsquo;est ce qui a été spécifié dans notre Docker Compose.</p>
<p>Bien. On a un blog et un script de mise à jour. C&rsquo;est rapide, il n&rsquo;y a pas d&rsquo;interruption de service. Mais on ne va pas s&rsquo;amuser à se connecter en SSH chaque fois que l&rsquo;on veut faire une mise à jour !</p>
<h2 id="continuous-deployment--githubs-webhooks">Continuous Deployment : Github&rsquo;s webhooks</h2>
<p>Des solutions de <em>CD</em> (Continuous Deployment), il y en a autant que de marques de lessive. Je ne vais pas les lister ni les analyser ici. En revanche, je sais exactement ce que je veux :</p>
<blockquote>
<p>Quand un commit est effectué sur la branche <code>master</code> de mon repository sur Github, je veux exécuter le script <code>update.sh</code>  de mon serveur dédié.</p>
</blockquote>
<p>En bon développeur, au-delà de rechercher une solution adaptée, je cherche également la plus simple à mettre en place. Et un moyen simple de déclencher des évènements suite à un commit, ce sont les <em>webhooks</em> proposés par Github.</p>
<p>Un <em>webhook</em> c&rsquo;est une requête HTTP POST qui est envoyée sur une URL de notre choix, chaque fois qu&rsquo;un évènement se produit sur le repo. Ici, on ne s&rsquo;intéresse qu&rsquo;aux évènements de type <em>push</em>. Parfait, c&rsquo;est tout ce dont on a besoin. Il n&rsquo;y a plus qu&rsquo;à traiter l&rsquo;évènement sur notre serveur.</p>
<p>Encore une fois, je préfère aller au plus simple, et ça tombe bien puisque c&rsquo;est ce que propose <a href="https://github.com/ncarlier/webhookd"><code>webhookd</code></a>, un outil développé en Go :</p>
<blockquote>
<p>A very simple webhook server launching shell scripts.</p>
</blockquote>
<p>L&rsquo;installation est simple, il suffit de suivre le README. Par contre, ce qui n&rsquo;est pas vraiment expliqué, c&rsquo;est comment utiliser ce logiciel comme un service <code>systemd</code>, afin de pérenniser notre installation.</p>
<p>Nous allons donc créer un service <em>webhookd</em>. Évidemment, je vous laisse adapter le tout à votre environnement. Ici, j&rsquo;utilise un Debian et c&rsquo;est <code>root</code> qui fait le café.</p>
<p><code>/etc/systemd/system/webhookd.service</code>:</p>
<pre><code>[Unit]
Description=WEBHOOKD

[Service]
ExecStart=/root/work/bin/webhookd
User=root
Group=root
EnvironmentFile=-/root/work/src/github.com/ncarlier/webhookd/etc/default/webhookd.env
EnvironmentFile=-/root/webhookd.env
Restart=always
Type=simple
RestartSec=30s

[Install]
WantedBy=multi-user.target
</code></pre><p><code>/root/webhookd.env</code>:</p>
<pre><code>WHD_SCRIPTS=/root/scripts  
WHD_LISTEN_ADDR=&quot;:&lt;port&gt;&quot;
</code></pre><p>J&rsquo;ai configuré l&rsquo;installation pour glisser tous mes scripts dans <code>/root/scripts</code>, et j&rsquo;ai configuré un port personnalisé sur lequel <em>webhookd</em> écoute.</p>
<p>J&rsquo;ai ensuite créé un script intermédiaire, qui ajoute une vérification de token de sécurité, et qui lance notre fameux <code>update.sh</code>. <em>webhookd</em> gérant nativement les paramètres passés dans l&rsquo;URL, le script n&rsquo;est pas compliqué. <code>/root/scripts/blog.sh</code>:</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash"><span style="color:#75715e">#!/bin/bash  
</span><span style="color:#75715e"></span>  
<span style="color:#66d9ef">if</span> <span style="color:#f92672">[[</span> $token !<span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;&lt;token&gt;&#39;</span> <span style="color:#f92672">]]</span>; <span style="color:#66d9ef">then</span>  
       echo <span style="color:#e6db74">&#34;Access denied&#34;</span>  
       exit 1;  
<span style="color:#66d9ef">fi</span>  
  
cd /path/to/blog <span style="color:#f92672">&amp;&amp;</span> ./update.sh
</code></pre></div><p>Et voilà, il ne reste qu&rsquo;à créer un <em>hook</em> sur Github, et le tour est joué. Chaque push sur notre repo redéploie le blog statique avec les derniers changements.</p>

<a href="/img/20201109-github-webhook.png" class="lightbox">
  <img src="/img/20201109-github-webhook.png"  alt="Github&#39;s screenshot" 
    class="left"  />
</a>


<h2 id="netlifycms">NetlifyCMS</h2>
<p>La mécanique est maintenant bien huilée, et je suis très satisfait de ce fonctionnement. Mes modifications apparaissent en production après un <code>git push</code> en quelques secondes.</p>
<p>Mais il reste un petit détail qui me chiffonne. Je l&rsquo;ai dit plus haut, je fonctionne beaucoup à la prise de note ponctuelle, et aux brouillons sommaires avant de me lancer réellement dans la rédaction des articles. Et pour cela, devoir lancer un éditeur sur mon OS, une console, etc, ça peut être un peu lourd. C&rsquo;est aussi pour cela que j&rsquo;apprécie et utilise si facilement <a href="https://github.com/shaarli/Shaarli">Shaarli</a>.</p>
<p>En réponse à ce besoin, j&rsquo;ai découvert <a href="https://www.netlifycms.org/">NetlifyCMS</a>. Il s&rsquo;agit d&rsquo;un outil open-source qui agit comme une UI de CMS, mais qui génère du contenu statique (des fichiers Markdown) et push le tout sur Git en un clic. Il fonctionne avec n&rsquo;importe quel générateur de site statique, puisqu&rsquo;il s&rsquo;appuie uniquement sur un fichier de configuration TOML, et un import de fichier JS.</p>
<p>En clair, on met un fichier de configuration, un HTML avec une balise <code>&lt;script&gt;</code> dans notre repository, et on a une interface de rédaction d&rsquo;article accessible dans le navigateur, qui publie automatiquement les modifications en production. Et la cerise sur le gâteau, il gère la rédaction multi-langues.</p>

<a href="/img/20201109-netlifycms-screenshot-2.png" class="lightbox">
  <img src="/img/20201109-netlifycms-screenshot-2.png"  alt="NetlifyCMS screenshot" 
    class="left"  />
</a>


<p>La documentation est assez bien fichue, donc je ne vais pas trop rentrer dans le détail de ma configuration. Voilà ce que ça donne avec Hugo, et un thème qui gère le multi-langues, <code>static/admin/config.yml</code>:</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-toml" data-lang="toml"><span style="color:#a6e22e">backend</span><span style="color:#960050;background-color:#1e0010">:</span>
  <span style="color:#a6e22e">name</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#a6e22e">github</span>
  <span style="color:#a6e22e">repo</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#a6e22e">arthurhoaro</span><span style="color:#960050;background-color:#1e0010">/&lt;</span><span style="color:#a6e22e">repo</span><span style="color:#960050;background-color:#1e0010">-</span><span style="color:#a6e22e">name</span><span style="color:#960050;background-color:#1e0010">&gt;</span>
  <span style="color:#a6e22e">branch</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#a6e22e">master</span>
<span style="color:#a6e22e">media_folder</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;static/img&#34;</span>
<span style="color:#a6e22e">public_folder</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;/img&#34;</span>
<span style="color:#a6e22e">i18n</span><span style="color:#960050;background-color:#1e0010">:</span>
  <span style="color:#a6e22e">structure</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#a6e22e">multiple_files</span>
  <span style="color:#a6e22e">locales</span><span style="color:#960050;background-color:#1e0010">:</span> [<span style="color:#a6e22e">en</span>, <span style="color:#a6e22e">fr</span>]
  <span style="color:#a6e22e">default_locale</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#a6e22e">en</span>
<span style="color:#a6e22e">collections</span><span style="color:#960050;background-color:#1e0010">:</span>
  <span style="color:#960050;background-color:#1e0010">-</span> <span style="color:#a6e22e">name</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;blog&#34;</span>
    <span style="color:#a6e22e">label</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;Blog&#34;</span>
    <span style="color:#a6e22e">folder</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;content/blog&#34;</span>
    <span style="color:#a6e22e">create</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#66d9ef">true</span>
    <span style="color:#a6e22e">i18n</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#66d9ef">true</span>
    <span style="color:#a6e22e">slug</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;{{year}}-{{month}}-{{day}}-{{slug}}&#34;</span>
    <span style="color:#a6e22e">fields</span><span style="color:#960050;background-color:#1e0010">:</span>
      <span style="color:#960050;background-color:#1e0010">-</span> <span style="color:#960050;background-color:#1e0010">{</span><span style="color:#a6e22e">label</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;Title&#34;</span>, <span style="color:#a6e22e">name</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;title&#34;</span>, <span style="color:#a6e22e">widget</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;string&#34;</span>, <span style="color:#a6e22e">i18n</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#66d9ef">true</span><span style="color:#960050;background-color:#1e0010">}</span>
      <span style="color:#960050;background-color:#1e0010">-</span> <span style="color:#960050;background-color:#1e0010">{</span><span style="color:#a6e22e">label</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;Date&#34;</span>, <span style="color:#a6e22e">name</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;date&#34;</span>, <span style="color:#a6e22e">widget</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;datetime&#34;</span><span style="color:#960050;background-color:#1e0010">}</span>
      <span style="color:#960050;background-color:#1e0010">-</span> <span style="color:#960050;background-color:#1e0010">{</span><span style="color:#a6e22e">label</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;Author&#34;</span>, <span style="color:#a6e22e">name</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;author&#34;</span>, <span style="color:#a6e22e">widget</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;hidden&#34;</span>, <span style="color:#a6e22e">default</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;ArthurHoaro&#34;</span><span style="color:#960050;background-color:#1e0010">}</span>
      <span style="color:#960050;background-color:#1e0010">-</span> <span style="color:#960050;background-color:#1e0010">{</span><span style="color:#a6e22e">label</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;Cover&#34;</span>, <span style="color:#a6e22e">name</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;cover&#34;</span>, <span style="color:#a6e22e">widget</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;hidden&#34;</span>, <span style="color:#a6e22e">default</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;&#34;</span><span style="color:#960050;background-color:#1e0010">}</span>
      <span style="color:#960050;background-color:#1e0010">-</span> <span style="color:#960050;background-color:#1e0010">{</span><span style="color:#a6e22e">label</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;Tags&#34;</span>, <span style="color:#a6e22e">name</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;tags&#34;</span>, <span style="color:#a6e22e">widget</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;list&#34;</span>, <span style="color:#a6e22e">i18n</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#66d9ef">true</span><span style="color:#960050;background-color:#1e0010">}</span>
      <span style="color:#960050;background-color:#1e0010">-</span> <span style="color:#960050;background-color:#1e0010">{</span><span style="color:#a6e22e">label</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;keywords&#34;</span>, <span style="color:#a6e22e">name</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;keywords&#34;</span>, <span style="color:#a6e22e">widget</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;list&#34;</span>, <span style="color:#a6e22e">i18n</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#66d9ef">true</span><span style="color:#960050;background-color:#1e0010">}</span>
      <span style="color:#960050;background-color:#1e0010">-</span> <span style="color:#960050;background-color:#1e0010">{</span><span style="color:#a6e22e">label</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;Description&#34;</span>, <span style="color:#a6e22e">name</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;description&#34;</span>, <span style="color:#e6db74">&#34;string&#34;</span>, <span style="color:#a6e22e">i18n</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#66d9ef">true</span><span style="color:#960050;background-color:#1e0010">}</span>
      <span style="color:#960050;background-color:#1e0010">-</span> <span style="color:#960050;background-color:#1e0010">{</span><span style="color:#a6e22e">label</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;showFullContent&#34;</span>, <span style="color:#a6e22e">name</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;showFullContent&#34;</span>, <span style="color:#a6e22e">widget</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;hidden&#34;</span>, <span style="color:#a6e22e">default</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#66d9ef">true</span><span style="color:#960050;background-color:#1e0010">}</span>
      <span style="color:#960050;background-color:#1e0010">-</span> <span style="color:#960050;background-color:#1e0010">{</span><span style="color:#a6e22e">label</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;draft&#34;</span>, <span style="color:#a6e22e">name</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;draft&#34;</span>, <span style="color:#a6e22e">widget</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;boolean&#34;</span>, <span style="color:#a6e22e">default</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#66d9ef">true</span><span style="color:#960050;background-color:#1e0010">}</span>
      <span style="color:#960050;background-color:#1e0010">-</span> <span style="color:#960050;background-color:#1e0010">{</span><span style="color:#a6e22e">label</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;Body&#34;</span>, <span style="color:#a6e22e">name</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;body&#34;</span>, <span style="color:#a6e22e">widget</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;markdown&#34;</span>, <span style="color:#a6e22e">i18n</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#66d9ef">true</span><span style="color:#960050;background-color:#1e0010">}</span>
</code></pre></div><h2 id="prévisualisation-des-brouillons">Prévisualisation des brouillons</h2>
<p>Il reste un petit détail à peaufiner. Pour la phase de relecture, je préfère le faire sur le rendu final plutôt que dans l&rsquo;éditeur Markdown.</p>
<p>Évidemment, il est possible de faire tourner le serveur en local avec <code>hugo server -D -F</code> : <code>-D</code> pour compiler les brouillons, <code>-F</code> pour compiler les fichiers datés dans le futur. Sauf que ça n&rsquo;est pas du tout pratique avec la publication via NetlifyCMS.</p>
<p>Encore une fois, j&rsquo;ai opté pour une solution simple : monter une instance développement sur laquelle les brouillons sont accessibles. L&rsquo;instance de test est protégée par une authentification basic HTTP.</p>
<p>Je vous invite à rependre les autres sections, mais pour résumer rapidement : nouvelle image Docker, nouveau script <code>update.sh</code> en compilant les brouillons, nouveau webhook Github, nouveau script webhookd et on est bon. Une fois qu&rsquo;on a tout fait en production, cette étape est réglée en 5 minutes.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Cet article est un peu plus long que ce que j&rsquo;avais anticipé. Je pense que j&rsquo;ai mis plus de temps à l&rsquo;écrire qu&rsquo;à le mettre en place le workflow à l&rsquo;origine.</p>
<p>Toujours est-il que vous avez toutes les clés en main pour mettre en place un blog statique, avec l&rsquo;outillage de rédaction et le déploiement automatique.</p>
<p>J&rsquo;ai décrit ici <em>mon</em> workflow, et j&rsquo;ai tenté de faire en sorte que chaque section soit modulable, pour qu&rsquo;elles soient adaptables à <em>vos</em> besoins et usages.</p>
<p>À vous de jouer ! Et n&rsquo;hésitez pas à me partager vos améliorations.</p>
]]></description>
        </item>
        
        <item>
            <title>COVID-19 : Générateur d&#39;attestation simplifié</title>
            <link>https://hoa.ro/fr/blog/2020-11-01-covid-19-generateur-attestation-simplifie/</link>
            <pubDate>Sun, 01 Nov 2020 13:36:31 +0000</pubDate>
            
            <guid>https://hoa.ro/fr/blog/2020-11-01-covid-19-generateur-attestation-simplifie/</guid>
            <description><![CDATA[<blockquote>
<p>UPDATE 2020-11-29: le générateur a été mis à jour avec la nouvelle version du PDF (déplacement à 20km)</p>
</blockquote>
<p>Un second confinement est en place depuis le 30 novembre en France métropolitaine afin de lutter contre l&rsquo;épidémie de COVID-19. À ce titre, tout déplacement doit être justifié au moyen d&rsquo;une <strong>attestation de déplacement dérogatoire</strong>. Si vous êtes français, je ne vous apprends rien, mais je préfère toujours donner le contexte.</p>
<p>Bref, pour obtenir cette attestation, vous pouvez soit l&rsquo;imprimer, soit la recopier sur papier libre, soit - et c&rsquo;est ce qui nous intéresse - utiliser le générateur de PDF mis à disposition par l&rsquo;incubateur du ministère de l&rsquo;Intérieur. Il est <a href="https://media.interieur.gouv.fr/deplacement-covid-19/">disponible ici</a>. Sauf qu&rsquo;à l&rsquo;heure où j&rsquo;écris ces lignes, il a plusieurs défauts qui rendent son utilisation vraiment fastidieuse :</p>
<ul>
<li>il faut remplir ses informations personnelles à <em>chaque</em> génération et l&rsquo;autocomplétion du navigateur est désactivée</li>
<li>la description des motifs alambiquée, il m&rsquo;a fallu quelques minutes pour comprendre quelle case cocher pour aller faire des courses</li>
<li>l&rsquo;heure n&rsquo;est pas préremplie</li>
</ul>
<p>Cela dit, il est appréciable que le gouvernement ait mis à disposition le code source, puisque j&rsquo;ai pu régler tous ces problèmes, Docker-iser tout ça et mettre en ligne une version modifiée en à peine plus d&rsquo;une heure. <a href="https://github.com/ArthurHoaro/attestation-deplacement-derogatoire-q4-2020">Code source</a></p>
<p><strong>C&rsquo;est accessible ici : <a href="https://attestation.hoa.ro">https://attestation.hoa.ro</a></strong>
Vous pouvez l&rsquo;utiliser si vous le souhaitez.</p>
<p><em>Aucune information n&rsquo;est stockée sur le serveur</em>. Les valeurs des champs renseignés sont stockées dans le local storage du navigateur, et pour le reste je n&rsquo;ai pas modifié le fonctionnement : toute la tambouille est faite côté client.</p>
<p>Détail des modifications :</p>
<ul>
<li>les champs renseignés sont stockés dans le local storage, donc il suffit de les renseigner une seule fois</li>
<li>ajout d&rsquo;un <a href="https://hub.docker.com/r/arthurhoaro/attestation-covid19">build Docker</a> afin de le faire tourner facilement sur mon serveur</li>
<li>pour le moment, le serveur tourne en mode dev, parce qu&rsquo;il y a des choses que je ne veux pas générer en HTML statique (l&rsquo;heure actuelle par exemple)</li>
<li>ajout d&rsquo;un deuxième bouton de validation en haut de page</li>
<li>ultra-simplification des motifs de déplacement, si vous avez besoin du détail, allez sur le <a href="https://www.gouvernement.fr/info-coronavirus">site du gouvernement</a></li>
</ul>
]]></description>
        </item>
        
        <item>
            <title>Ré-ouverture du blog en 2020</title>
            <link>https://hoa.ro/fr/blog/2020-10-06-blog-opening/</link>
            <pubDate>Tue, 06 Oct 2020 08:17:01 +0200</pubDate>
            
            <guid>https://hoa.ro/fr/blog/2020-10-06-blog-opening/</guid>
            <description><![CDATA[<p>Il y a trois ans, le disque de mon serveur dédié du moment à rendu l&rsquo;âme. J&rsquo;avais des sauvegardes de tout, mais je n&rsquo;ai pas restauré mon blog de l&rsquo;époque, par manque d&rsquo;envie principalement, après plus de 10 ans à publier des billets plus ou moins régulièrement sur différents sujets.</p>
<p>Aujourd&rsquo;hui, je ressens de nouveau l&rsquo;envie d&rsquo;écrire sur les sujets qui me passionnent, en particulier le développement web. Nous y voilà donc, et il va me falloir un peu de pratique pour reprendre cette activité qui me paraissait alors plus naturelle.</p>
<p>Pour ce blog, j&rsquo;ai pris le parti d&rsquo;utiliser <a href="https://gohugo.io/">Hugo</a> qui est un <em>excellent</em> outil de site statique, écrit en Go, dans lequel je peux rédiger en Markdown.</p>
<p>J&rsquo;ai également décidé d&rsquo;écrire mes billets en français <strong>et</strong> en anglais. Il me parait important d&rsquo;écrire en français parce que c&rsquo;est ma langue maternelle, et que je souhaite sincérement que la communauté française de développeurs et autres curieux perdure. Mais aussi en anglais puisqu&rsquo;il s&rsquo;agit d&rsquo;un langage international, en particulier dans ce domaine, touchant le plus grand nombre. De plus, d&rsquo;un point de vue personnel, j&rsquo;envisage dans un futur proche de m&rsquo;installer dans un pays anglophone.</p>
<p>Je ne vais pas m&rsquo;étendre davantage dans ce billet d&rsquo;introduction. Si vous voulez en savoir plus sur moi, je vous invite à visiter à visiter la page « <a href="/fr/about">À propos</a> », ou mes différents contacts disponibles sur la page d&rsquo;accueil.</p>
<p>J&rsquo;espère que ce que je publierai vous intéressera, et d&rsquo;ici là n&rsquo;hésiter pas à ajouter le <a href="/fr/blog/index.xml">flux RSS</a> à vos aggrégateurs, ou celui de <a href="https://links.hoa.ro">mon Shaarli</a> sur lequel je relaierai les billets.</p>
]]></description>
        </item>
        
    </channel>
</rss>
