Cours Laravel 5.5 – les fichiers et le cache

image_pdfimage_print

On a vu comment utiliser une base de données pour gérer des informations mais ce n’est pas la seule façon de mémoriser et retrouver des informations et il y a bien d’autres éléments à gérer, en particulier des fichiers. dans ce chapitre on va voir comment Laravel permet avec élégance de gérer des fichiers et également le système de cache qui peut booster vos applications.

Le système de gestion des fichiers

Laravel fournit un système de gestion des fichiers suffisamment abstrait pour s’adapter à toutes les situations, que ce soit en local ou sur un nuage (cloud). Sous le capot c’est le package flysystem qui est utilisé. Les avantages de ce package peuvent se résumer ainsi :

  • on dispose d’une API unique pour tous les systèmes de gestion (local ou nuage)
  • on peut utiliser un cache
  • permet de gérer de gros fichiers (stream)
  • facile à tester

Par défaut on dispose de nombreux drivers, en particulier :

  • local
  • Azure
  • AWS S3
  • Dropbox
  • FTP
  • Rackspace…

La configuration

La configuration se trouve dans le fichier config/filesystem.php avec ce code par défaut :

'disks' => [

    'local' => [
        'driver' => 'local',
        'root' => storage_path('app'),
    ],

    'public' => [
        'driver' => 'local',
        'root' => storage_path('app/public'),
        'url' => env('APP_URL').'/storage',
        'visibility' => 'public',
    ],

    's3' => [
        'driver' => 's3',
        'key' => env('AWS_KEY'),
        'secret' => env('AWS_SECRET'),
        'region' => env('AWS_REGION'),
        'bucket' => env('AWS_BUCKET'),
    ],

],

On raisonne en “disques” (disks), chacun utilisant un driver et un emplacement :

  • local : interagit avec les fichiers locaux et présume qu’ils se trouvent dans le dossier storage/app
  • public : interagit avec les fichiers locaux qui doivent être accessibles par les clients. Le dossier par défaut est storage/app/public. Mais ce dossier n’est pas accessible par défaut alors il faut ajouter un lien symbolique (symlink) pour pointer quelque part dans le dossier public. On dispose d’un commande d’artisan pour le faire :
php artisan storage:link

La motivation principale est d’avoir les fichiers accessibles publiquement dans un dossier géré par les outils de déploiement comme Envoyer. A vous de voir si vous voulez suivre cette procédure ou tout simplement diriger tout sur le dossier public.

  • s3 : c’est un exemple de configuration d’un nuage avec renseignement des paramètres. Pour que ça fonctionne il faut installer le package correspondant, par exemple pour s3 c’est league/flysystem-aws-s3-v3 ~1.0.

Le disque par défaut est précisé dans le même fichier de configuration :

'default' => env('FILESYSTEM_DRIVER', 'local'),

La façade

Une fois que vous avez défini vos disques dans la configuration vous pouvez utiliser la façade Storage pour gérer les fichiers. Il suffit de préciser le disque concerné par la manipulation (s’il n’est pas précisé ça sera le disque par défaut défini dans la configuration). Par exemple avec :

Storage::disk('s3')->get(image.png);

On va aller chercher dans le nuage s3 l’image image.png.

On dispose de nombreuses méthodes pour manipuler les fichiers, voici les principales :

  • get : récupération d’un fichier comme on l’a vu ci-dessus
  • put : sauvegarde d’un contenu dans un fichier put(‘fichier.txt’, $contenu)
  • putFile : sauvegarde le contenu d’un fichier dans un emplacement putFile(‘dossier’, $fichier)
  • exists : détermine si un fichier existe exists(‘dossier’, $fichier)
  • delete : supprime un fichier delete(‘fichier.txt’)
  • deleteDirectory : supprime un dossier deleteDirectory(‘dossier’)
  • copy : copie un fichier copy(‘fichier.txt’, ‘nouveaufichier.txt’)
  • move : déplace un fichier move(‘fichier.txt’, ‘nouveaufichier.txt’)
  • files : retourne un tableau avec les noms des fichiers files(‘dossier’)
  • directories : retourne un tableau avec les noms des dossiers directories(‘dossier’)
  • prepend : ajoute un contenu au début d’un fichier prepend(‘fichier.txt’, ‘texte’)
  • append: ajoute un contenu à la fin d’un fichier append(‘fichier.txt’, ‘texte’)

Un exemple

Dans l’application d’exemple on a cette configuration :

'disks' => [

    'local' => [
        'driver' => 'local',
        'root' => storage_path('app'),
    ],

    'public' => [
        'driver' => 'local',
        'root' => storage_path('app/public'),
        'url' => env('APP_URL').'/storage',
        'visibility' => 'public',
    ],

    'files' => [
        'driver' => 'local',
        'root' => public_path() . ('/files'),
        'visibility' => 'public',
    ],

    'thumbs' => [
        'driver' => 'local',
        'root' => public_path() . ('/thumbs'),
        'visibility' => 'public',
    ],

    ...

],

On a les valeurs par défaut pour local et public et on a deux disques supplémentaires :

  • files : pour les médias
  • thumbs : pour les petites images des articles dans l’administration

On voit que les deux sont placés dans public :

Les médias

Dans l’application les médias sont gérés par elFinder :

Par l’intermédiaire de ce package pour l’adaptation à Laravel.

La configuration du package se situe dans ce fichier :

J’ai juste ajouté ces middlewares parce qu’il faut être au moins rédacteur pour accéder à la gestion des médias :

'route' => [
    'prefix' => 'elfinder',
    'middleware' => ['web', 'redac'],
],

Un administrateur a accès à tous les médias mais un rédacteur doit être limité à ses propres médias pour éviter qu’il aille perturber les autres.

Si vous regardez le contenu du dossier public/files vous trouvez ceci :

Chaque rédacteur a un dossier de la forme user{id}. Par défaut on a un seul rédacteur avec l’identifiant 2 donc son dossier est user2 :

Dans le modèle app\Models\User on trouve cette méthode :

/**
 * Get user files directory
 *
 * @return string|null
 */
public function getFilesDirectory()
{
    if ($this->role === 'redac') {
        $folderPath = 'user' . $this->id;
        if (!in_array($folderPath , Storage::disk('files')->directories())) {
            Storage::disk('files')->makeDirectory($folderPath);
        }
        return $folderPath;
    }
    return null;
}

Elle retourne le nom du dossier du rédacteur et au besoin on le crée s’il n’existe pas. On voit l’utilisation de la méthode directories de Storage sur le disque files pour trouver tous les dossiers :

Storage::disk('files')->directories()

Si le dossier n’existe pas alors on le crée avec makeDirectory :

Storage::disk('files')->makeDirectory($folderPath);

Les thumbs

Lorsqu’on liste les articles dans l’administration on visualise une miniature de l’image d’illustration de l’article :

Il serait lourd de charger les images en haute résolution, même s’il ne s’agit que de l’administration alors l’application génère des miniatures dans le dossier thumbs :

J’ai déjà évoqué ce sujet dans le chapitre sur les événements. J’ai alors dit que c’est le service app\Services\Thumb qui est chargé de la génération des miniatures lorsqu’on crée ou modifie un article :

Voici la principale méthode de ce service :

public static function makeThumb(Model $model)
{
    if ($model instanceof Post) {
        $path = $model->image;
        $dir = dirname ($path);
        if ($dir != '\files') {
            $dir = substr_replace ($dir, '', 0, 7);
            if (!in_array($dir , Storage::disk('thumbs')->directories())) {
                Storage::disk('thumbs')->makeDirectory($dir);
            }
        }
        $image = Image::make(url($model->image))->widen(100);
        Storage::disk('thumbs')->put(substr_replace (self::makeThumbPath($path), '', 0, 7), $image->encode());
    }
}

On retrouve la routine de création du dossier telle qu’on l’a vue pour les médias puisque la structure de thumbs doit être la même que celle de files :

if (!in_array($dir , Storage::disk('thumbs')->directories())) {
    Storage::disk('thumbs')->makeDirectory($dir);
}

On crée la miniature avec le superbe package intervention/image :

$image = Image::make(url($model->image))->widen(100);

Ensuite on utilise la méthode put de Storage pour mémoriser la miniature sur le disque thumbs :

Storage::disk('thumbs')->put(substr_replace (self::makeThumbPath($path), '', 0, 7), $image->encode());

Le cache

Laravel fournit une API unique pour tous les drivers de cache gérés comme Memcached ou Redis. Ça fonctionne un peu comme les sessions avec un système clé-valeur. Par défaut le cache utilise un fichier.

La configuration

Le fichier de configuration est ici :

On voit que par défaut on utilise un fichier (file) :

'default' => env('CACHE_DRIVER', 'file'),

Ensuite vous avez tous les systèmes disponibles :

'stores' => [

    'apc' => [
        'driver' => 'apc',
    ],

    'array' => [
        'driver' => 'array',
    ],

    'database' => [
        'driver' => 'database',
        'table' => 'cache',
        'connection' => null,
    ],

    'file' => [
        'driver' => 'file',
        'path' => storage_path('framework/cache/data'),
    ],

    'memcached' => [
        'driver' => 'memcached',
        'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
        'sasl' => [
            env('MEMCACHED_USERNAME'),
            env('MEMCACHED_PASSWORD'),
        ],
        'options' => [
            // Memcached::OPT_CONNECT_TIMEOUT  => 2000,
        ],
        'servers' => [
            [
                'host' => env('MEMCACHED_HOST', '127.0.0.1'),
                'port' => env('MEMCACHED_PORT', 11211),
                'weight' => 100,
            ],
        ],
    ],

    'redis' => [
        'driver' => 'redis',
        'connection' => 'default',
    ],

],

Donc par défaut ça aboutit ici :

Utilisation

Vous disposez de la façade Cache et de l’helper cache() pour utiliser le cache.

Mettre en cache

Pour mettre en cache on utilise la méthode put :

Cache::put('clé', 'valeur', $minutes);
   ou
cache(['clé' => 'valeur'], $minutes);

Pour vérifier que la clé n’existe pas déjà et risquer de l’écraser utilisez add au lieu de put.

Il faut obligatoirement indiquer une durée en minutes. Si vous volez une durée infinie alors utilisez la méthode forever :

Cache::forever('clé', 'valeur');
ou cache()->forever('clé', 'valeur');

Récupérer une valeur en cache

Pour récupérer une valeur en cache on utilise get :

$valeur = Cache::get('clé');
   ou
$valeur = cache('clé');

Si la clé n’existe pas la méthode retourne null, ce qui peut être parfois gênant. Il est possible d’indiquer une valeur par défaut :

$valeur = Cache::get('clé', 'défaut'); 
    ou
$valeur = cache('clé', 'défaut');

Mais vous pouvez avoir envie si la clé n’existe pas de mémoriser une valeur, voici la syntaxe à utiliser :

$value = Cache::remember('clé', $minutes, function () {
    return genereCle();
});
    ou
$value = cache()->remember('clé', $minutes, function () {
    return genereCle();
});

Et si vous avez envie de récupérer la valeur et de la supprimer en même temps alors utilisez pull.

Supprimer une valeur du cache

Pour supprimer une valeur du cache on utilise forget :

Cache::forget('clé');
    ou
cache()->forget('clé');

Et si vous voulez carrément vider tout le cache utilisez flush :

Cache::flush();
    ou
cache()->flush();

Vérifier qu’une clé existe

Enfin on peut vérifier l’existence d’un clé avec has :

if (Cache::has('clé')) {
    //
}
    ou
if (cache()->has('clé')) {
    //
}

Un exemple

Vous pouvez trouver un exemple expliqué avec une démonstration en ligne ici. L’article date un peu mais reste pertinent.

Il y a encore pas mal de possibilités avec ce cache et vous pouvez trouver tout ça dans la documentation.

Les routes et la configuration

Les routes

A chaque requête Laravel doit parcourir le fichier des routes pour trouver la bonne. Cette action peut être accélérée si on crée un cache des routes :

php artisan route:cache

Le fichier est créé ici :

Mais attention ! Il ne faut pas qu’il y ait de closure dans votre fichier des routes sinon ça ne fonctionnera pas !

Maintenant votre application sera plus rapide mais… si vous apportez une modification au fichier des routes celle-ci ne sera plus prise en compte puisque c’est le cache qui sert de référence. vous avez alors le choix entre supprimer le cache :

php artisan route:clear

Ou tout simplement le récréer directement avec la commande vue plus haut.

Mais de toute façon je ne vois pas trop l’intérêt d’utiliser un cache de routes en cours de développement. Réservez donc cette action pour la production parce que là vous n’aurez normalement plus de modification à apporter à vos route mais si vous devez ensuite faire une mise à jour n’oubliez pas ce cache !

La configuration

Ce que j’ai dit ci-dessus pour les routes est tout aussi valable pour la configuration et on dispose de ces deux commandes :

config:cache
config:clear

En résumé

  • Laravel est équipé d’un système de gestion de fichiers unifié qui permet des manipulations en local ou sur le cloud
  • Laravel est équipé d’un système de cache unifié
  • Laravel autorise la mise en cache des routes

 

 

Laisser un commentaire