aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Développement Web

Upload de fichiers Laravel : Storage et validation

L'upload de fichiers Laravel est l'un de ces sujets qui tient en quelques lignes quand il est bien fait, mais qui engendre failles de sécurité, images cassées et un dossier storage en désordre quand il est mal fait. Dans ce guide, nous construisons un flux d'upload d'image de bout en bout : valider le fichier reçu d'un formulaire, l'écrire sur le disque avec la façade Storage, l'exposer via le lien symbolique public, et enfin confirmer qu'il s'agit bien d'une image valide. Tout ce qui est décrit ici est une approche réelle, prête pour la production — aucun paquet ou commande inventé.

Le modèle de disque : local, public et storage

Laravel abstrait les opérations sur les fichiers via la notion de disque. La configuration se trouve dans config/filesystems.php et fournit deux disques locaux par défaut :

  • local — sous storage/app/private, pour les fichiers privés qui ne doivent pas être accessibles directement depuis le web.
  • public — sous storage/app/public, pour les fichiers destinés à être publics (avatars, images de couverture, pièces jointes).

La distinction cruciale est la suivante : le dossier storage/app/public se trouve en dehors de votre racine web (public/), donc le navigateur ne peut pas l'atteindre directement. Un lien symbolique fait le pont. Il suffit de l'exécuter une seule fois :

php artisan storage:link

Cette commande crée un lien symbolique de public/storage vers storage/app/public. Désormais le fichier storage/app/public/avatars/foo.png devient accessible à l'URL /storage/avatars/foo.png. Sur certains hébergements mutualisés, la fonction PHP symlink() est désactivée ; dans ce cas créez le lien depuis le shell avec ln -sfn storage/app/public public/storage.

Valider l'upload

Aucun fichier ne doit jamais être écrit sur le disque sans être validé. En traitant la requête du formulaire dans un contrôleur, vous pouvez passer les règles directement à validate() :

public function store(Request $request)
{
    $request->validate([
        'avatar' => ['required', 'image', 'mimes:jpg,jpeg,png,webp', 'max:2048'],
    ]);

    // ...
}

Chaque règle a son utilité :

  • image — exige que le fichier soit une image (jpeg, png, bmp, gif, svg ou webp).
  • mimes — limite explicitement les extensions acceptées. Laisser le SVG de côté est généralement sage, car on peut y intégrer des scripts.
  • max:2048 — la limite de taille est en kilo-octets, soit 2 Mo ici.

Pour des formulaires plus complexes, déplacer les règles dans une classe Form Request générée avec php artisan make:request StoreAvatarRequest garde le contrôleur propre.

Écrire sur le disque avec la façade Storage

Une fois la validation passée, enregistrer le fichier tient en une ligne. L'objet de fichier uploadé (Illuminate\Http\UploadedFile) fonctionne directement avec les méthodes du disque :

use Illuminate\Support\Facades\Storage;

$path = $request->file('avatar')->store('avatars', 'public');
// $path = "avatars/9aXk2...png"

store() génère un nom unique et imprévisible pour le fichier et renvoie le chemin relatif. Enregistrez ce chemin (par exemple avatars/9aXk2...png) en base de données — pas l'URL complète. Ainsi vos données ne se cassent pas si votre domaine ou la configuration du disque change.

Si vous voulez définir un nom précis, utilisez storeAs() :

$path = $request->file('avatar')->storeAs(
    'avatars', $user->id.'.'.$request->file('avatar')->extension(), 'public'
);

Plus tard, pour afficher le fichier, transformez le chemin relatif en URL publique :

$url = Storage::disk('public')->url($path);
// /storage/avatars/9aXk2...png

La même logique s'applique côté Blade :

<img src="{{ Storage::url($user->avatar) }}" alt="Avatar">

Est-ce vraiment une image ? Au-delà de la validation

Les règles image et mimes vérifient le type MIME et l'extension, mais ne garantissent pas une image lisible et intacte. Pour contraindre les dimensions de l'image, il existe la règle dimensions :

$request->validate([
    'avatar' => [
        'required', 'image', 'mimes:jpg,jpeg,png,webp', 'max:2048',
        'dimensions:min_width=200,min_height=200,max_width=4000,max_height=4000',
    ],
]);

Si vous voulez aussi redimensionner, recadrer ou optimiser l'image, la bibliothèque Intervention Image est un choix courant et fiable. Elle s'installe avec Composer :

composer require intervention/image

Un usage typique consiste à lire le fichier uploadé en mémoire, à le redimensionner à une largeur donnée et à le réécrire sur le disque :

use Intervention\Image\ImageManager;
use Intervention\Image\Drivers\Gd\Driver;

$manager = new ImageManager(new Driver());
$image = $manager->read($request->file('avatar'));
$image->scaleDown(width: 800);

Storage::disk('public')->put($path, (string) $image->encode());

Cette étape a aussi un avantage de sécurité caché : ré-encoder l'image élimine en grande partie toute donnée malveillante intégrée dans le fichier.

Nettoyer et supprimer les anciens fichiers

Quand un utilisateur change son avatar, l'ancien reste comme déchet. Supprimer le fichier précédent avant d'enregistrer le nouveau est une bonne habitude :

if ($user->avatar) {
    Storage::disk('public')->delete($user->avatar);
}

$user->avatar = $request->file('avatar')->store('avatars', 'public');
$user->save();

Pour vérifier qu'un fichier existe, utilisez Storage::disk('public')->exists($path). Pour des opérations comme la suppression en masse ou le listage de dossier, il y a aussi les méthodes files(), allFiles() et deleteDirectory().

Fichiers privés et téléchargements sécurisés

Tous les fichiers n'ont pas besoin d'être publics. Factures, contrats ou documents personnels doivent rester sur le disque local (storage/app/private) et n'être servis qu'à un utilisateur qui passe un contrôle d'autorisation. Pour cela, vous passez par un contrôleur plutôt que par un lien symbolique :

public function download(Document $document)
{
    $this->authorize('view', $document);

    return Storage::disk('local')->download($document->path);
}

Ici le fichier n'est jamais accessible par une URL directe ; chaque requête est contrôlée via une policy. Un lien symbolique pour les images publiques, un téléchargement autorisé pour les documents sensibles — garder cette distinction claire est le fondement d'une bonne architecture.

Questions fréquentes

J'ai lancé storage:link mais les images ne s'affichent toujours pas — pourquoi ?

Les causes les plus fréquentes sont que le lien symbolique n'a pas été créé sur le serveur (public/storage n'a pas été copié au déploiement, ou la fonction PHP symlink() est désactivée), ou que le mauvais chemin relatif — ou une URL complète — est stocké en base. Créez le lien depuis le shell avec ln -sfn et construisez toujours l'URL avec Storage::url().

Quelle est la limite de taille et de type la plus sûre pour les fichiers uploadés ?

Il n'y a pas de chiffre unique correct ; réglez-le selon vos besoins. La règle générale : n'autorisez via mimes que les types attendus, laissez le SVG de côté, fixez une limite max raisonnable et, si possible, ré-encodez l'image avec Intervention Image.

Déplacer les fichiers vers Amazon S3 changera-t-il mon code ?

Presque pas. Grâce à l'abstraction de disque, dire Storage::disk('s3') ou changer le disque par défaut suffit ; vos appels store(), url() et delete() restent identiques. Il faut juste ajouter le paquet league/flysystem-aws-s3-v3.

Vous voulez renforcer votre flux d'upload de fichiers ? Que vous construisiez une application Laravel de zéro ou que vous sécurisiez une configuration de stockage existante, nous pouvons travailler ensemble — contactez-moi.

Devamı için