Aller au contenu principal

Module 11 – Fichiers et upload

Niveau 5.2 – Laravel Inertia (React & Vue)


Objectif

À la fin de ce module vous saurez :

  • Inclure un champ fichier dans un formulaire Inertia (useForm) et envoyer la requête en multipart/form-data vers Laravel.
  • Valider et stocker le fichier côté Laravel (règles image, file, max, mimes), puis rediriger ou renvoyer une réponse avec l’URL du fichier.
  • Afficher la progression de l’upload (barre de progression) côté React ou Vue lorsque Inertia expose un objet progress.
  • Gérer les erreurs de validation (fichier trop lourd, type non autorisé) et les afficher dans l’UI.

Nous donnons un exemple complet : formulaire de profil avec avatar (Laravel + React et Vue).


Comment Inertia gère les fichiers

Avec useForm, si une des valeurs du formulaire est un File (ou une FileList), Inertia détecte la présence d’un fichier et envoie la requête en multipart/form-data (comme un formulaire HTML classique avec enctype="multipart/form-data"). Vous n’avez pas à changer manuellement le Content-Type : Inertia s’en charge. Côté Laravel, vous récupérez le fichier avec $request->file('nom_du_champ') et vous appliquez les règles de validation (required, image, max:2048, mimes:jpg,png, etc.). Après validation, vous stockez le fichier (store(), Storage::put()) et vous enregistrez le chemin ou l’URL en base si besoin (ex. colonne avatar sur la table users).


Côté Laravel – contrôleur (upload avatar)

Route : POST /profile/avatar (ou inclus dans une route PUT /profile pour mettre à jour le profil).

Contrôleur :

public function updateAvatar(Request $request)
{
$validated = $request->validate([
'avatar' => 'required|image|max:2048', // 2 Mo max, type image
], [
'avatar.required' => 'Veuillez sélectionner une image.',
'avatar.image' => 'Le fichier doit être une image (jpeg, png, bmp, gif, svg, webp).',
'avatar.max' => 'L’image ne doit pas dépasser 2 Mo.',
]);

$path = $request->file('avatar')->store('avatars', 'public');
$request->user()->update(['avatar' => $path]);

return redirect()->back()->with('success', 'Photo de profil mise à jour.');
}
  • store('avatars', 'public') : enregistre le fichier dans storage/app/public/avatars (et crée un lien symbolique public/storage vers storage/app/public pour que les images soient accessibles via URL). $path sera une chaîne du type avatars/xyz123.jpg.
  • Si vous voulez exposer l’URL complète en prop (pour afficher l’avatar), vous pouvez utiliser Storage::url($path) et la passer à la page ou la mettre en shared data. Pour l’affichage, souvent on utilise asset('storage/' . $user->avatar).

Formulaire avec fichier – React

resources/js/Pages/Profile/EditAvatar.jsx (exemple) :

import React from 'react';
import { useForm } from '@inertiajs/react';

export default function EditAvatar({ user }) {
const { data, setData, post, processing, errors, progress } = useForm({
avatar: null,
});

const handleSubmit = (e) => {
e.preventDefault();
post(route('profile.avatar.update'));
};

return (
<div>
{user.avatar_url && (
<img src={user.avatar_url} alt="Avatar" className="w-24 h-24 rounded-full" />
)}

<form onSubmit={handleSubmit} className="mt-4">
<div>
<label htmlFor="avatar">Nouvelle photo</label>
<input
id="avatar"
type="file"
accept="image/*"
onChange={(e) => setData('avatar', e.target.files?.[0] ?? null)}
className="block w-full"
/>
{errors.avatar && (
<p className="text-sm text-red-600">{errors.avatar}</p>
)}
</div>

{progress && (
<div className="mt-2">
<div className="w-full bg-gray-200 rounded-full h-2">
<div
className="bg-indigo-600 h-2 rounded-full transition-all"
style={{ width: `${progress.percentage}%` }}
/>
</div>
<p className="text-sm text-gray-600">{progress.percentage} %</p>
</div>
)}

<button type="submit" disabled={processing} className="mt-2">
{processing ? 'Envoi...' : 'Enregistrer'}
</button>
</form>
</div>
);
}
  • setData('avatar', e.target.files?.[0] ?? null) : on ne garde qu’un seul fichier (le premier). Si l’utilisateur annule la sélection, files peut être vide ; on met null.
  • progress : fourni par useForm pendant l’envoi (si votre version d’Inertia le supporte). progress.percentage permet d’afficher une barre de progression. Vérifier la doc Inertia pour le format exact (parfois progress est un nombre entre 0 et 100).
  • accept="image/*" : limite le sélecteur de fichier aux images (UX uniquement ; la vraie validation est côté Laravel).

Formulaire avec fichier – Vue

resources/js/Pages/Profile/EditAvatar.vue :

<template>
<div>
<img
v-if="user.avatar_url"
:src="user.avatar_url"
alt="Avatar"
class="w-24 h-24 rounded-full"
/>

<form @submit.prevent="form.post(route('profile.avatar.update'))" class="mt-4">
<div>
<label for="avatar">Nouvelle photo</label>
<input
id="avatar"
type="file"
accept="image/*"
class="block w-full"
@change="form.avatar = $event.target.files?.[0] ?? null"
/>
<p v-if="form.errors.avatar" class="text-sm text-red-600">
{{ form.errors.avatar }}
</p>
</div>

<div v-if="form.progress" class="mt-2">
<div class="w-full bg-gray-200 rounded-full h-2">
<div
class="bg-indigo-600 h-2 rounded-full transition-all"
:style="{ width: form.progress.percentage + '%' }"
/>
</div>
<p class="text-sm text-gray-600">{{ form.progress.percentage }} %</p>
</div>

<button type="submit" :disabled="form.processing" class="mt-2">
{{ form.processing ? 'Envoi...' : 'Enregistrer' }}
</button>
</form>
</div>
</template>

<script setup>
import { useForm } from '@inertiajs/vue3';

defineProps({ user: Object });

const form = useForm({
avatar: null,
});
</script>
  • form.avatar = $event.target.files?.[0] ?? null : même logique qu’en React. form.progress peut être exposé par useForm selon la version (vérifier la doc Inertia pour Vue).

Plusieurs fichiers (multiple)

Pour un champ multiple (ex. galerie de photos), vous pouvez passer un tableau de File :

React : setData('photos', Array.from(e.target.files))
Vue : form.photos = Array.from($event.target.files)

Côté Laravel : validation avec avatar remplacé par photos et photos.* pour les règles sur chaque fichier :

$request->validate([
'photos' => 'required|array',
'photos.*' => 'image|max:2048',
]);

Puis boucle sur $request->file('photos') pour les stocker.


Gros fichiers et upload direct (avancé)

Pour des fichiers très volumineux (vidéos, etc.), l’upload via Inertia (requête unique vers Laravel) peut atteindre les limites PHP (post_max_size, upload_max_filesize) et bloquer longtemps la requête. Une approche courante est l’upload direct vers un stockage (S3, etc.) : Laravel génère une URL pré-signée (signature temporaire), le front envoie le fichier directement au stockage avec cette URL, puis le front notifie Laravel (petite requête) que l’upload est terminé (Laravel enregistre le chemin en BDD). Inertia ne gère pas cette logique ; vous utiliseriez axios ou fetch pour l’upload vers l’URL signée. Ce sujet dépasse le cadre de ce module ; l’important ici est de savoir gérer l’upload classique (useForm + fichier) pour des images ou des documents de taille raisonnable.


À retenir

  • useForm avec une propriété fichier (File ou null) ; Inertia envoie en multipart/form-data automatiquement.
  • Côté Laravel : validate (required, image/file, max, mimes) puis store() ou Storage::put() ; enregistrer le chemin en BDD si besoin.
  • Afficher errors.avatar (ou le nom du champ) en cas d’erreur de validation.
  • progress (si disponible dans useForm) pour une barre de progression pendant l’upload.
  • Pour plusieurs fichiers : tableau de File et validation photos.* côté Laravel.

Dans le prochain module, nous verrons les modales (confirmation, détail) et le bon usage des flash messages déjà partagés (module 7).