Le Full Page Cache (FPC) est un élément important à prendre en compte lors du développement d’un site quel qu’il soit afin d’en améliorer nettement les performances. L’arrivée de Magento 2 apporte une gestion native du FPC dans la version Community Edition (CE) lorsque Magento 1 ne le proposait que dans la version Enterprise Edition (EE). Il est possible d’utiliser le Full Page Cache avec un des mécanismes suivants :

  • le système de fichier,
  • la base de données,
  • Redis,
  • Varnish.

La recommandation de Magento est d’utiliser Varnish pour l’environnement de production, la configuration est en effet très simpliste car accessible depuis le back-office de Magento dans Stores > Configuration > System.

Il y a cependant quelques bonnes pratiques à respecter lors du développement de nouveaux modules pour bien exploiter le FPC de Magento 2.

Caché ou pas caché ?

Par défaut, sans instructions particulières, toutes les pages de Magento 2 sont stockées dans le FPC (peu importe le mécanisme utilisé). Lorsque la Home Page du site est demandée, Magento va dans un premier temps interroger le système FPC et retourner le code HTML brut de la page s’il existe. Ce mécanisme a un fort impact sur les performances car la page va être retournée à l’utilisateur quasiment instantanément. Si la page n’existe pas encore dans le FPC, Magento va la générer et la stocker pour le prochain appel.

Bien évidemment, certaines pages ne doivent pas provenir du cache car elles contiennent des données dynamiques propres à chaque utilisateur. L’espace client (customer/account), par exemple, ne provient pas du FPC car il contient tout un tas d’information liées à l’utilisateur connecté (commandes, adresses, bons de réductions, etc…).

Pour informer Magento qu’une page ne doit pas être stockée en cache, il faut définir l’attribut cacheable d’un block à false au niveau du layout XML de la page. Il suffit d’un seul block sur toute la page déclaré comme non cacheable pour que le FPC ne soit pas utilisé (à utiliser avec prudence donc). L’exemple suivant met en évidence la définition de cet attribut sur le block qui contient le formulaire d’édition de compte dans l’espace client :

<!-- Attribut cacheable sur le layout handle customer_account_edit -->
<?xml version="1.0"?>
<page>
    <update handle="customer_account"/>
    <body>
        <!-- ... -->
        <referenceContainer name="content">
            <block class="Magento\Customer\Block\Form\Edit" name="customer_edit" template="form/edit.phtml" cacheable="false">
                <container name="form.additional.info" as="form_additional_info"/>
            </block>
        </referenceContainer>
    </body>
</page>
 

Contenu public et privé

Magento distingue deux types de contenu dans une page : le contenu public et le contenu privé.

Le contenu public est un contenu qui ne change pas en fonction de l’utilisateur qui visite le site, par exemple la home page ou encore la liste des produits d’une catégorie est un contenu qualifié de public et est stocké dans un des systèmes de cache listés en début d’article. En revanche, tout ce qui est fonction de l’utilisateur connecté, comme par exemple le nom de l’utilisateur affiché dans la partie haut du site ou encore le contenu de son panier, est un contenu privé et est stocké au niveau du client directement dans le cache du navigateur dans le Local Storage, mis à jour en AJAX. Sur cette capture d’écran on peut par exemple constater que le panier ou encore la wishlist du client sont stockés dans le local storage du navigateur :

Magento 2 cache local storage

Ajouter un block de contenu privé

Voyons à présent en détails comment ajouter un block et le spécifier en tant que contenu privé, stocké dans le local storage. Pour l’exemple, l’idée est d’afficher le montant TTC de la dernière commande de l’utilisateur connecté au même niveau que son nom dans le bandeau du header.

Créer une source de section

Un block de contenu privé est appelé section dans Magento 2. La source de section est une classe qui a pour rôle de fournir les données de la section. La configuration XML suivante permet d’ajouter une nouvelle section appelée last-ordered-amount au pool de section :

<!-- Déclaration d'une nouvelle section dans le fichier etc/di.xml de notre module custom -->
<type name="Magento\Customer\CustomerData\SectionPoolInterface">
        <arguments>
            <argument name="sectionSourceMap" xsi:type="array">
                <item name="last-ordered-amount" xsi:type="string">Sky\LastOrderedAmount\CustomerData\Amount</item>
            </argument>
        </arguments>
    </type>

La convention Magento est de définir la classe de données dans le répertoire CustomerData du module. Voyons à quoi ressemble cette classe :

<?php
// Classe de source de section
namespace Sky\LastOrderedAmount\CustomerData;

use Magento\Customer\CustomerData\SectionSourceInterface;

class Amount implements SectionSourceInterface 
{
    /**
     * @return array
     */
    public function getSectionData()
    {
        return ['amount' => $this->getLastOrderAmount()];
    }
    
    protected function getLastOrderAmount()
    {
        // ...
    }
}

Cette classe doit implémenter l’interface SectionSourceInterface de Magento et définir la méthode getSectionData. Dans notre cas, la méthode retourne simplement un tableau contenant le montant de la dernière commande du client.

Création du template

Nous avons maintenant notre source de données qui est définie, nous allons maintenant créer le template qui va afficher ces données. Ce template est un template classique de Magento, c’est à dire qu’il s’agit d’un block défini dans un layout auquel on a assigné un template .phtml.

<!-- view/frontend/layout/default.xml-->
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceBlock name="header.links">
            <block class="Magento\Framework\View\Element\Template"
                   name="header.customer.lastorderedamount"
                   template="Sky_LastOrderedAmount::account/customer/amount.phtml"
                   before="-" />
        </referenceBlock>
    </body>
</page>

J’ai choisi d’ajouter le block dans le block header.links existant, je vous laisse vous occuper du css pour rendre le tout tout beau. La partie la plus intéressante se trouve en fait dans le template amount.phtml :

<!-- Template amount.phtml -->
<li data-bind="scope: 'lastOrderedAmount'" id="last-ordered-amount">
    Last ordered amount : <span data-bind="text: lastOrderedAmount().amount"></span>
</li>
<script type="text/x-magento-init">
{
    "#last-ordered-amount": {
        "Magento_Ui/js/core/app": {
            "components": {
                "lastOrderedAmount": {
                    "component": "Sky_LastOrderedAmount/js/view/lastorderedamount"
                }
            }
        }
    }
}
</script>

En comparaison à la première version de Magento, il y a un certain nombre de concepts nouveaux dans ce template. Tout d’abord, vous pouvez remarquer qu’il n’y a pas de code PHP pour afficher notre montant comme on peut l’attendre avec l’extension phtml. En effet, comme je vous le disais, le contenu privé est stocké au sein du local storage du navigateur de l’utilisateur, ce qui veut dire que la récupération et l’affichage des données sont délégués à Javascript.

Création de l’UI Component

Ceci est réalisé grâce à l’intégration de la librairie Knockout au sein de Magento. Le data binding est donc réalisé grâce à notre composant lastOrderedAmount que nous avons déclaré dans l’attribut data-bind de la balise “li”. Le script de type text/x-magento-init permet de définir le chemin du fichier javascript qui contient la logique de notre composant. Enfin, le montant est finalement récupéré avec l’appel de lastOrderedAmount().amount grâce au data binding de type text de Knockout. Voyons maintenant le fichier javascript contenant notre composant :

// Fichier javascript contenant le composant lastOrderedAmount
define([
    'uiComponent',
    'Magento_Customer/js/customer-data'
], function (Component, customerData) {
    'use strict';

    return Component.extend({
        initialize: function () {
            this._super();
            this.lastOrderedAmount = customerData.get('last-ordered-amount');
        }
    });
});

Etant donné que notre composant n’a pas besoin d’une logique particulière, nous faisons rien d’autre que de déclarer une nouvelle propriété lastOrderedAmount au composant et de l’initialiser grâce à l’appel du composant customerData qui va se charger de récupérer la valeur directement dans le localStorage.

Invalidation du cache de section

Nous en avons maintenant terminé avec la mise en place d’un block de contenu de type privé qui va dans notre cas afficher le montant de la dernière commande du client. Il reste néanmoins une dernière étape : l’invalidation du cache de section. En effet, si nous ne faisons rien, la même valeur sera toujours affichée car la valeur stockée dans le local storage ne se rafraîchira pas toute seule au passage d’une nouvelle commande. Nous devons informer Magento que pour telle ou telle action notre section de contenu privé doit être rafraîchie. La logique de Magento est de spécifier les requêtes de types POST ou PUT sur lesquelles l’invalidation du cache va se faire, donc dans notre cas, le passage d’une commande. Je vous redirige vers la documentation de Magento pour cette partie.

Conclusion

Le full page cache, géré nativement par Magento 2, apporte à lui seul un gain de performance non négligeable pour un site e-commerce. Pour un développeur, comme nous avons pu le voir, c’est un aspect à prendre très au sérieux lors du développement de nouvelles fonctionnalités. Il faut bien différencier un contenu public d’un contenu privé car ces deux contenus ne seront pas gérés de la même manière. Le principe de combiner des classes de blocks PHP avec des templates contenant des directives pour Knockout chargées d’interroger le local storage du navigateur, le tout mis à jour par des requêtes AJAX, peut paraître au premier abord assez lourd en terme d’architecture (oui, ça l’est). Néanmoins, c’est un système qui offre malgré lui une certaine souplesse et flexibilité pour manier des contenus privés dans des pages provenant brut du mécanisme de FPC.

Si vous voulez en savoir davantage sur le fonctionnement sous le capot, je vous invite à parcourir les fichiers javascript de Magento page-cache.js et customer-data.js.

Ressources :