Une image enregistrée avec Storage::disk('public')->put() est bien dans storage/app/public, mais son adresse /storage/... ne répond pas : le lien public/storage n'existe pas encore. La commande php artisan storage:link le crée, sous Linux comme sous Windows, sans droits administrateur. Sous Windows, elle crée en réalité une jonction, ce qui change la façon de la supprimer. Et pour connaître le chemin d'un fichier sur le disque, storage_path() et Storage::disk(...)->path() remplacent l'ancien getAdapter()->getPathPrefix(), qui ne fonctionne plus.
À quoi sert le lien public/storage ?
Le serveur web ne sert que le dossier public. Le disque public de Laravel range ses fichiers dans storage/app/public ; la documentation demande donc un lien de public/storage vers storage/app/public pour les rendre accessibles depuis le web.
Ce lien ne protège rien, au contraire : il rend public tout ce que contient storage/app/public. N'importe qui connaissant l'adresse peut télécharger le fichier. Les documents privés (factures, pièces d'identité...) vont sur le disque local, dont la racine est storage/app/private. Dans notre test, /storage/test.txt (disque public) répondait 200, et /storage/secret.txt (disque local) 403. Pour donner un accès ponctuel à un fichier privé, Storage::disk('local')->temporaryUrl('secret.txt', now()->addMinutes(5)) produit une adresse signée : 200 avec la signature, 403 sans.
Comment créer le lien avec storage:link ?
php artisan storage:link
La commande répond The [.../public/storage] link has been connected to [.../storage/app/public]. Ensuite, asset('storage/test.txt') ou Storage::disk('public')->url('test.txt') donne l'adresse du fichier (dans notre test, http://localhost:8000/storage/test.txt, à partir de APP_URL).
D'autres liens peuvent être ajoutés dans le tableau links de config/filesystems.php ; storage:link les crée tous :
// config/filesystems.php 'links' => [ public_path('storage') => storage_path('app/public'), public_path('images') => storage_path('app/images'), ],
La commande a deux options : --force recrée un lien existant, --relative crée un lien relatif. --relative exige le paquet symfony/filesystem, absent d'une installation neuve : sans lui, la commande échoue avec To enable support for relative links, please install the symfony/filesystem package. Attention, sous Linux, --relative --force sans ce paquet a d'abord supprimé le lien existant avant d'échouer : nous nous sommes retrouvés sans lien. Après composer require symfony/filesystem, le lien obtenu était storage -> ../storage/app/public/.
php artisan storage:unlink supprime les liens configurés. Le dossier /public/storage figure dans le .gitignore d'une application neuve : le lien n'est pas versionné et doit être créé sur chaque serveur.
Et sous Linux ?
storage:link utilise la fonction symlink() de PHP : dans notre test sous Linux, ls -l public affiche storage -> /app/storage/app/public (le projet était dans /app). Le lien manuel équivalent :
ln -s /app/storage/app/public /app/public/storage
Et sous Windows ?
Sous Windows, storage:link ne crée pas un lien symbolique mais une jonction (mklink /J), ce qu'affiche dir public : <JUNCTION> storage. Une jonction ne demande aucun droit particulier : la commande a fonctionné depuis une session sans droits administrateur. Trois différences sont à connaître :
storage:link --forcerefuse de recréer la jonction (The [...] link already exists.) etstorage:unlinkne la supprime pas, sans aucun message : PHP ne la reconnaît pas comme un lien (is_link()renvoiefalse).storage:link --relative, même avecsymfony/filesystem, a affiché son message de succès sans rien créer.- Pour supprimer la jonction sans toucher aux fichiers, utilisez
rmdirdans l'invite de commandes : dans notre test,storage/app/public/test.txtétait toujours là après.
rmdir public\storage
Pour créer le lien à la main, dans l'invite de commandes (cmd), mklink /J fonctionne sans droits particuliers :
mklink /J public\storage storage\app\public
mklink /D (vrai lien symbolique) a échoué sans droits administrateur : Vous ne disposez pas de privilèges suffisants pour effectuer cette opération. En PowerShell, New-Item -ItemType SymbolicLink a échoué de même (Cette opération nécessite un privilège d'administrateur.), alors que New-Item -ItemType Junction a fonctionné. Selon la documentation de Microsoft, le droit de créer des liens symboliques est réservé par défaut aux administrateurs, et la création sans élévation suppose d'activer le mode développeur de Windows.
Comment obtenir le chemin du storage ?
Le code des anciens tutoriels, Storage::disk('local')->getDriver()->getAdapter()->getPathPrefix(), provoque aujourd'hui l'erreur Call to undefined method League\Flysystem\Filesystem::getAdapter(). Utilisez plutôt :
// N'importe où dans l'application use Illuminate\Support\Facades\Storage; storage_path(); // dossier storage du projet storage_path('app/public'); // racine du disque public Storage::disk('public')->path('avatars/a.jpg'); // chemin absolu d'un fichier Storage::disk('local')->path('factures/f.pdf'); // fichier sous storage/app/private
D'après la documentation, path() renvoie le chemin absolu avec le pilote local, et le chemin relatif dans le bucket avec S3. Sous Windows, le résultat mélange \ et / (...\storage\app/public\avatars/a.jpg), ce que PHP accepte.
Le lien se crée au déploiement, avec les autres commandes : voir dans quel ordre les lancer. Ces réglages font partie des déploiements que nous assurons pour les applications que nous hébergeons et maintenons ou que nous développons sur mesure. Un souci de fichiers introuvables en production ? Parlons-en.
php artisan storage:linkrend public le contenu destorage/app/public, rien d'autre.- Les fichiers privés restent sur le disque
local(storage/app/private), avectemporaryUrl()si besoin. - Sous Windows, c'est une jonction : la supprimer avec
rmdir public\storage, pas avec--force. - Le chemin d'un fichier :
Storage::disk(...)->path(), plusgetAdapter().
Testé avec Laravel 13.37.0 et PHP 8.3.6 sous Windows 11, et PHP 8.3.35 sous Linux (Docker).
Questions fréquentes
Faut-il relancer storage:link à chaque déploiement ?
Le lien n'est pas versionné (/public/storage est dans le .gitignore d'une application neuve) : il faut le créer sur chaque serveur. S'il existe déjà, la commande répond "link already exists" sans rien modifier.
Peut-on ranger des fichiers privés dans storage/app/public ?
Non : tout ce qui s'y trouve est téléchargeable par quiconque connaît l'adresse. Rangez-les sur le disque local (storage/app/private) et donnez un accès ponctuel avec temporaryUrl().
Faut-il être administrateur sous Windows pour lancer storage:link ?
Non. La commande crée une jonction, ce qui a fonctionné dans notre test depuis une session sans droits administrateur. Un vrai lien symbolique (mklink /D) demande en revanche des droits administrateur, ou le mode développeur de Windows.
Que renvoie Storage::path() sans préciser de disque ?
Un chemin sur le disque par défaut, défini par FILESYSTEM_DISK. Dans une application neuve, c'est le disque local : Storage::path('') renvoie le dossier storage/app/private.