VIII. Les fichiers et le cache▲
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 les mémoriser et de les retrouver. De plus, 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, ainsi que le système de cache qui peut booster vos applications.
VIII-A. 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…
VIII-A-1. La configuration▲
La configuration se trouve dans le fichier config/filesystem.php avec ce code par défaut :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
'
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. Il faut alors ajouter un lien symbolique (symlink) pour pointer quelque part dans le dossier public. On dispose d'une 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
'
),
VIII-A-2. 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
'
).
VIII-B. Un exemple▲
Dans l'application d'exemple, on a cette configuration :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
'
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 :
VIII-B-1. 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 :
2.
3.
4.
'
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'ils aillent perturber les autres.
Si vous regardez le contenu du dossier public/files, vous trouverez 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 », et donc son dossier est user2 :
Dans le modèle app\Models\User, on trouve cette méthode :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
/**
* 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
);
VIII-B-2. 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énementsLes é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 :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
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());
VIII-C. 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.
VIII-C-1. 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 :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
'
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 :
VIII-C-2. Utilisation▲
Vous disposez de la façade Cache et de l'helper cache() pour utiliser le cache.
VIII-C-2-a. 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 voulez une durée infinie, alors utilisez la méthode forever :
Cache::
forever('
clé
'
,
'
valeur
'
);
ou cache()->
forever('
clé
'
,
'
valeur
'
);
VIII-C-2-b. 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 :
Et si vous avez envie de récupérer la valeur et de la supprimer en même temps, alors utilisez « pull ». |
VIII-C-2-c. 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 :
VIII-C-2-d. Vérifier qu'une clé existe▲
VIII-C-3. 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.
VIII-C-4. Les routes et la configuration▲
VIII-C-4-a. Les routes▲
À 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
et tout simplement le récréer directement avec la commande vue plus haut.
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 routes. Toutefois, si vous devez ensuite faire une mise à jour, n'oubliez pas ce cache !
VIII-C-4-b. 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
VIII-D. 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.