Accueil /  Blog / Symfony / SimpleCache (PSR16) dans Symfony

SimpleCache (PSR16) dans Symfony

Publié le mercredi 31 janvier 2018

Comment utiliser un cache PSR16 dans une application Symfony 4, avec autowiring.

Toutes les applications ont des besoins en cache, Symfony a le sien et il est possible de s'en resservir pour vous faciliter la vie.

Les PSRs

De cet état de fait ont émergé 2 standards : PSR6 & PSR16.

C'est quoi une PSR ? PSR est l’acronyme de PHP Standards Recommendations. Ce sont donc des standards qui sont proposés pour essayer de faire en sorte que l'écosystème PHP repose sur des pratiques communes, afin d'augmenter l'interopérabilité.

PSR6 est le premier standard de cache qui a été accepté. Outre le débat qui a résulté de son acceptation (il faut dire que les interfaces sont assez éloignées de ce qui était le plus répandu à l'époque : doctrine/cache), elle a eu du mal à percer, compte tenu du nombre de concepts à assimiler pour s'en servir.


C'est là que PSR16 est arrivée.

Son surnom, SimpleCache, est une promesse ! Faisons efficace, il n'y a qu'une seule interface, et elle se comprend en quelques secondes (je retire même la PHPDoc) :

<?php
interface CacheInterface
{
    public function get($key, $default = null);
    public function set($key, $value, $ttl = null);
    public function delete($key);
    public function clear();
    public function getMultiple($keys, $default = null);
    public function setMultiple($values, $ttl = null);
    public function deleteMultiple($keys);
    public function has($key);
}

C'est donc le candidat idéal pour mettre en place du cache dans notre application !

Mise en place

Vous pouvez écrire vous-même une implémentation de cette interface, ce n'est pas très complexe, mais ce n'est pas le but ici.

Nous souhaitons utiliser le système de cache de Symfony déjà en place.

Le framework repose sur PSR6 en interne (plus puissant, plus permissif), mais propose un adapteur d'interopérabilité entre les 2 normes.

Tout ce que nous avons à faire, c'est donc de déclarer un service qui fera le relai entre le service de cache de Symfony (déjà existant) et notre adapter simple cache :

services:
    simple_cache.app:
        class: Symfony\Component\Cache\Adapter\SimpleCacheAdapter
        arguments: ["@cache.app"]

Avec l'autowiring c'est même encore plus simple :

services:
    # default configuration for services in *this* file
    _defaults:
        autowire: true      # Automatically injects dependencies in your services.
        autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
        public: false       # Allows optimizing the container by removing unused services; this also means
                            # fetching services directly from the container via $container->get() won't work.
                            # The best practice is to be explicit about your dependencies anyway.

    Symfony\Component\Cache\Simple\Psr6Cache: ~
    Psr\SimpleCache\CacheInterface:
        alias: Symfony\Component\Cache\Simple\Psr6Cache

Tout le bloc _defaults a été repris du standard donc techniquement vous ne devriez pas avoir à l'écrire.

La première instruction sert juste à définir le service relatif à notre adapteur. La deuxième, quant à elle, met en place un alias sur l'interface de simple cache, permettant au service que nous avons déclaré au dessus, d'être injecté dans tous les services qui le demanderont.

Note : Si vous avez plusieurs adapteurs de cache dans votre application, l'autowiring risque de ne pas fonctionner, il faudra simplement spécifier quel adapteur de cache vous souhaitez injecter dans votre service.

Et donc ensuite, pour l'utiliser, rien de plus simple :

<?php

namespace App\GitHub\Api;

use Psr\SimpleCache\CacheInterface;

class Client
{
    private const GITHUB_API_URL = 'https://api.github.com/';
    private const GITHUB_API_TOKEN = 'aTokenToAccessGithubApi';
    private $cache;

    public function __construct(CacheInterface $cache)
    {
        $this->cache = $cache;
    }

    public function profile(string $username): array
    {
        return $this->wrapWithCache(
            sprintf('github-profile-%s', $username),
            3600,
            function () use ($username) {
                return $this->get(sprintf('/users/%s', $username));
            }
        );
    }

    private function get(string $path): array
    {
        $url = sprintf('%s/%s', rtrim(self::GITHUB_API_URL, '/'), ltrim($path, '/'));

        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt(
            $ch,
            CURLOPT_HTTPHEADER,
            [
                'User-Agent: Mozilla/5.0 (iPad; U; CPU OS 3_2_1 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Mobile/7B405',
                'Authorization: token ' . self::GITHUB_API_TOKEN,
                'Content-Type: application/json; charset=utf-8',
            ]
        );

        return json_decode(curl_exec($ch), true);
    }

    private function wrapWithCache(string $key, ?int $ttl, callable $callable): array
    {
        if (!$this->cache->has($key)) {
            $this->cache->set($key, $callable(), $ttl);
        }

        return $this->cache->get($key);
    }
}

Conclusion

Facile à mettre en place, facile à utiliser, plus aucune excuse alors pour accélérer votre application.

Et si vous avez envie d'approfondir le sujet, plongez dans la documentation du composant.

Suivez notre actualité en avant première. Pas plus d’une newsletter par mois.