Blog ENI : Toute la veille numérique !
Accès illimité 24h/24 à tous nos livres & vidéos ! 
Découvrez la Bibliothèque Numérique ENI. Cliquez ici
Accès illimité 24h/24 à tous nos livres & vidéos ! 
Découvrez la Bibliothèque Numérique ENI. Cliquez ici
  1. Livres et vidéos
  2. Symfony 4 (LTS)
  3. Amélioration des performances
Extrait - Symfony 4 (LTS) Développez des sites web PHP structurés et performants
Extraits du livre
Symfony 4 (LTS) Développez des sites web PHP structurés et performants
1 avis
Revenir à la page d'achat du livre

Amélioration des performances

La mise en cache de pages

1. Autour du protocole HTTP

Symfony s’appuie sur les spécifications HTTP pour la gestion du cache des pages. Le protocole HTTP définit en effet un certain nombre d’en-têtes relatifs à la mise en cache.

Voici un rapide avant-goût de ses fonctionnalités :

<?php header('Cache-Control: max-age=10') ?> 
 
<!DOCTYPE HTML> 
<html> 
  <head> 
    <meta charset="utf-8" /> 
  </head> 
  <body> 
      <?php echo date('y-m-d H:i:s') ?><br /> 
      <a href="">Rafraîchir</a> 
  </body> 
</html> 

Si vous ouvrez cette page avec un navigateur web, puis cliquez sur le bouton Rafraîchir, vous vous rendrez compte que la date affichée se met à jour seulement au bout de 10 secondes.

Lors des autres chargements, le navigateur utilise la page présente dans son cache, car, lorsqu’il récupère la réponse la première fois, l’en-tête Cache-Control indique que la page peut être gardée en cache pendant 10 secondes, et qu’au-delà de ce délai, une nouvelle version devra être demandée au serveur web.

Grâce à cette technique, la page peut être renvoyée très rapidement à l’utilisateur, aucune requête HTTP n’est effectuée, et la charge du serveur web s’en trouve allégée.

2. Un serveur proxy inverse (ou « reverse proxy »)

Cette technique de mise en cache « côté client » montre malheureusement très rapidement ses limites.

Les pages étant mises en cache au niveau du navigateur, un nouvel utilisateur, lors de sa première ouverture de la page, doit obligatoirement envoyer une requête au serveur web, de manière à récupérer la page à mettre en cache. Votre application doit donc sans cesse régénérer la même page, pour chaque nouvel utilisateur : le cache n’est pas partagé.

images/11EI01.png

Le serveur web doit générer la page pour chaque client, et si ces derniers effectuent une nouvelle requête...

L’autochargement des classes

Nous savons que Symfony s’appuie sur Composer pour l’autochargement des classes (cf. chapitre Architecture du framework - Le chargement automatique de classes). Charger une classe n’est pas une action lourde atomiquement, mais cette action est répétée des milliers de fois au cours d’une requête sur un projet Symfony. Ainsi, mettre en cache le fichier associé à chacune des classes utilisées par votre projet est une optimisation intéressante.

1. Générer un classmap

Avec l’option -o de la commande dump-autoload, Composer regénère un chargeur de classes plus performant : un tableau contenant chaque classe du projet, associée à son emplacement physique, est créé.

Ce dernier est utilisé par le chargeur de classes pour trouver très rapidement le fichier à inclure pour charger une classe donnée.

Voici la commande en question :

composer dump-autoload -o 

Si votre projet comporte beaucoup de classes et/ou beaucoup de dépendances, le tableau à charger pour chaque exécution de script pourrait s’avérer lourd. Si vous êtes très pointilleux sur les ressources utilisées par votre site, vous serez peut-être intéressé par la technique suivante.

Le cache avec Doctrine

1. Les différents types de cache

Doctrine dispose de trois types de cache.

Le cache des métadonnées

Les métadonnées sont toutes les informations de mapping de vos entités. Ces données ne sont pas amenées à changer une fois l’application déployée en production. Elles pourront donc être mises en cache pour une durée indéterminée.

Le cache des requêtes

Il est possible de mettre en cache le processus de transformation des requêtes DQL en requêtes SQL. Tant qu’une requête DQL n’est pas modifiée, son équivalent en SQL sera toujours le même. Il est donc recommandé d’utiliser le cache des requêtes en production.

Le cache des résultats

Le cache des résultats permet de sauvegarder le résultat d’une requête, de manière à éviter d’effectuer de trop nombreuses requêtes vers la base de données. 

Il est configuré grâce à la méthode useResultCache() de l’objet Query de Doctrine :

$query = $manager->createQuery(  
   'SELECT u FROM App:Utilisateur u'  
);   
  
$query->useResultCache(true, 60); 

Le premier argument indique que le cache de résultats doit être activé et le second définit...

Le cache d’annotations

Si vous utilisez les annotations pour des tâches comme la définition de routes ou de règles de validation, vous serez sûrement intéressé par cette optimisation.

Comme nous l’avons déjà expliqué (cf. chapitre Routage et contrôleur - Définition des routes), le support des annotations n’est pas natif au langage PHP, il est fourni par Doctrine et se base sur l’API d’introspection de PHP. Cette dernière n’est pas destinée à être utilisée en production, mais plutôt destinée à des outils d’analyse de code ou des tests.

C’est pour cette raison que, par défaut, Doctrine met en cache les annotations dans des fichiers. Ce système de cache peut toutefois être optimisé.

# config/framework.yaml  
  
framework:  
   annotations:  
       cache: mon_cache_doctrine  
  
# config/services.yaml 
  
services:  
   memcached:  
       class: Memcached  
       arguments: ['ma_connexion']  
       calls:  
           - [addServer, ["localhost", 11211]]  
   mon_cache_doctrine:  ...

Les sessions

Par défaut, les sessions sont enregistrées dans des fichiers. Ce comportement est facilement modifiable et voici comment sauvegarder les sessions en mémoire partagée grâce à Memcache :

# config/framework.yaml  
  
framework:   
   # ....   
   session:   
     handler_id: ma_session_memcache  
  
# config/services.yaml 
  
services:  
   memcached:  
       class: Memcached  
       arguments: ['ma_connexion']  
       calls:  
           - [addServer, ["localhost", 11211]]  
   ma_session_memcache:   
       class:  
Symfony\Component\HttpFoundation\Session\Storage\Handler\Memcached 
SessionHandler   
       arguments: [@memcached] 

Il existe d’autres systèmes de sauvegarde (appelés « handlers »). Pour un listing complet, reportez-vous à l’adresse URL suivante : https://symfony.com/doc/4.4/components/http_foundation/session_configuration.html

Autres optimisations

Les optimisations présentées jusqu’à présent sont pour la plupart spécifiques à Symfony. Néanmoins, un projet sous ce framework n’échappe guère aux optimisations plus génériques.

La suite de ce chapitre ne concernant pas spécifiquement Symfony, notre approche sera orientée sur les concepts et la théorie, dans l’objectif de rester concis. Nous vous suggérerons tout de même des outils adaptés tout au long de nos explications.

1. Choix de sa SAPI PHP

a. Qu’est-ce qu’une SAPI ?

Une SAPI (Server Application Programming Interface) définit le mode de communication entre le système et un programme PHP.

PHP dispose de plusieurs SAPI car il peut être utilisé dans une multitude d’environnements, chacun ayant ses spécificités. Ainsi, les programmes en ligne de commande utilisent la SAPI CLI tandis que les programmes à destination du Web utilisent la SAPI CGI ou FastCGI, par exemple.

Selon la SAPI utilisée, PHP n’est pas invoqué de la même manière et comporte des différences à l’exécution. En ligne de commande, vous pouvez par exemple passer des arguments à vos scripts et les récupérer dans la variable $argv. En environnement web, les variables superglobales $_GET et $_POST contiennent des informations sur la requête HTTP. Ici, nous allons nous intéresser à l’environnement web et présenter les différentes manières d’intégrer PHP à un serveur web.

b. Module du serveur

Cette technique d’intégration a longtemps été la plus populaire. Elle consiste à « charger » PHP directement dans les processus du serveur web. C’est ce que fait par exemple le mod_php d’Apache.

Si ce type d’intégration offre de bonnes performances, il présente tout de même un inconvénient majeur : la gestion des permissions. Vos scripts PHP étant exécutés au sein d’Apache, ils auront les mêmes permissions que l’utilisateur d’Apache. Cette contrainte a pour conséquence de rendre la gestion des droits difficile, notamment dans un environnement mutualisé.

Pour contourner...

Test des performances d’un site web

Lors de la phase de développement, il est parfois délicat de juger de l’efficacité des optimisations qui sont mises en place. Il n’est pas impossible que certaines d’entre elles, une fois déployées en production, n’apportent aucun gain en performance, voire fassent augmenter les temps de réponse de l’application.

1. Côté serveur

a. Apache Bench

Apache Bench est un outil qui mesure la capacité de votre serveur web à monter en charge. Il permet de solliciter votre serveur web avec un grand nombre de requêtes.

Si vous utilisez le serveur web Apache, Apache Bench devrait déjà être installé sur votre machine.

Si vous n’utilisez pas Apache et que vous êtes sur Ubuntu/Debian, vous pouvez le récupérer en installant le paquet apache2-utils. Si vous êtes sur un autre système d’exploitation, vous devrez installer Apache.

Utilisation

La commande suivante sollicite votre site web pendant 30 secondes (option -t) avec un niveau de concurrence de 100 (option -c) :

ab -t 30 -c 100 http://www.mon-projet.demo/ 

Le niveau de concurrence indique le nombre de requêtes à soumettre en même temps. Ici, le serveur traite 100 requêtes simultanément : pour chaque réponse renvoyée par le serveur, Apache Bench soumet une nouvelle requête...