skaldoe / laravel-media-library
Médiathèque réutilisable (dossiers, upload, attachments polymorphiques) pour projets Laravel
Requires
- php: ^8.2
- illuminate/database: ^11.0|^12.0|^13.0
- illuminate/events: ^11.0|^12.0|^13.0
- illuminate/http: ^11.0|^12.0|^13.0
- illuminate/routing: ^11.0|^12.0|^13.0
- illuminate/support: ^11.0|^12.0|^13.0
- illuminate/validation: ^11.0|^12.0|^13.0
Requires (Dev)
- orchestra/testbench: ^9.0|^10.0|^11.0
- phpunit/phpunit: ^11.5|^12.5
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Package Laravel réutilisable fournissant une médiathèque complète : upload de fichiers, organisation en dossiers (arborescence illimitée), et attachement polymorphique many-to-many entre médias et n'importe quel modèle de l'application (avatar utilisateur, thumbnail de post, galerie d'article, etc.).
Pourquoi ce package
- Un seul fichier peut être attaché à plusieurs modèles grâce à la table pivot
media_attachments(many-to-many polymorphique), contrairement à une relation classique où un média n'appartient qu'à un seul propriétaire. - Collections nommées : un même modèle peut avoir plusieurs types de médias distincts (
avatar,cover,gallery...) via le champcollection_name. - Dossiers hiérarchiques avec protection anti-cycle (impossible de déplacer un dossier dans l'un de ses propres sous-dossiers).
- Config centralisée : disque de stockage, mimes acceptés, taille max, guard d'authentification — tout est ajustable par projet sans toucher au code du package.
Prérequis
- PHP ^8.2
- Laravel ^11.0 ou ^12.0
- Un système d'authentification API compatible avec
auth:sanctum(ou tout autre guard configurable)
Installation
1. Installer le package
composer require skaldoe/laravel-media-library
Composer résout le paquet directement depuis Packagist : aucune configuration de repositories n'est nécessaire.
Installer une version de développement (non publiée sur Packagist)
Pour pointer directement sur le dépôt GitHub — par exemple pour tester une branche ou un correctif pas encore taggé — déclarez le dépôt VCS dans votre composer.json :
{
"repositories": [
{
"type": "vcs",
"url": "https://github.com/skal-doe/laravel-media-library.git"
}
]
}
composer require skaldoe/laravel-media-library:dev-main
En développement local, pointez plutôt vers un chemin local (path) pour voir vos modifications en direct sans re-tag :
{
"repositories": [
{
"type": "path",
"url": "../laravel-media-library",
"options": { "symlink": true }
}
]
}
composer require skaldoe/laravel-media-library:@dev
2. Installation automatique (recommandé)
php artisan media-library:install
Cette commande publie automatiquement la configuration, publie les migrations et exécute migrate.
3. Ou installation manuelle
Publier la configuration (optionnel) :
php artisan vendor:publish --tag=media-library-config
Exécuter les migrations :
php artisan migrate
Le package charge automatiquement ses migrations (media, media_folders, media_attachments) via loadMigrationsFrom() — aucun fichier à copier obligatoirement dans le projet hôte.
⚠️ Si le projet avait déjà des migrations locales pour ces tables (avant l'extraction en package), supprime-les de
database/migrations/du projet hôte pour éviter un conflit "table already exists".
Configuration
// config/media-library.php return [ // Préfixe des routes générées : /api/{route_prefix}/medias 'route_prefix' => 'admin', // Middleware appliqué au groupe de routes (SubstituteBindings est // toujours ajouté automatiquement par le package, inutile de le // déclarer ici) 'middleware' => ['auth:sanctum'], // Disque Laravel utilisé pour stocker les fichiers 'disk' => 'public', // Règles d'upload 'accepted_mimes' => 'jpeg,jpg,png,gif,webp,svg', 'max_file_size' => 2048, // Ko // Modèle User pour la relation uploaded_by. // null = reprend automatiquement auth.providers.users.model 'user_model' => null, ];
Routes exposées
| Méthode | URI | Action |
|---|---|---|
| GET | /api/{prefix}/medias |
Liste paginée des médias + dossiers du niveau courant (ou recherche globale) |
| POST | /api/{prefix}/medias |
Upload de un ou plusieurs fichiers |
| PUT | /api/{prefix}/medias/{media} |
Déplacer un média vers un autre dossier |
| DELETE | /api/{prefix}/medias/{media} |
Mettre un média à la corbeille (soft delete, refusé s'il est attaché) |
| GET | /api/{prefix}/medias/trash |
Liste des médias à la corbeille |
| POST | /api/{prefix}/medias/{id}/restore |
Restaurer un média de la corbeille |
| DELETE | /api/{prefix}/medias/{id}/force |
Supprimer définitivement un média (purge le fichier physique) |
| POST | /api/{prefix}/medias/bulk-delete |
Mettre plusieurs médias à la corbeille |
| POST | /api/{prefix}/medias/bulk-restore |
Restaurer plusieurs médias de la corbeille |
| POST | /api/{prefix}/medias/bulk-force-delete |
Supprimer définitivement plusieurs médias |
| POST | /api/{prefix}/medias/bulk-move |
Déplacer plusieurs médias vers un dossier |
| GET | /api/{prefix}/folders |
Dossiers racine, ou enfants directs de ?parent_id= (chargement par niveau, pas l'arbre entier — voir note ci-dessous) |
| POST | /api/{prefix}/folders |
Créer un dossier |
| PUT | /api/{prefix}/folders/{folder} |
Renommer / déplacer un dossier |
| DELETE | /api/{prefix}/folders/{folder} |
Supprimer un dossier |
GET /foldersne renvoie qu'un seul niveau à la fois (dossiers racine par défaut, ou enfants directs deparent_idsi fourni), avecfolder_count/medias_countsur chaque dossier pour savoir s'il est développable. Ce choix est volontaire : charger tout l'arbre en une fois ne scale pas avec un grand nombre de dossiers. Le layer Nuxt charge donc l'arbre à la demande, niveau par niveau, au dépliage de chaque nœud (voiruseMediaFolderTreedans le layer).
Utilisation dans un modèle
Ajoute le trait HasMedia à n'importe quel modèle Eloquent :
use SkalDoe\MediaLibrary\Concerns\HasMedia; class Post extends Model { use HasUuids, HasFactory, SoftDeletes, HasMedia; }
Média unique par collection (ex. avatar, thumbnail)
// Accès direct au modèle Media public function thumbnail() { return $this->singleMedia('post-thumbnail'); } // Ou via l'attachement (alias rétro-compatible) public function thumbnailAttachment() { return $this->singleMediaAttachment('post-thumbnail'); }
Pour assigner ou remplacer un média unique :
$post->syncMedia($mediaId, 'post-thumbnail'); // ou directement une instance Media (accepté partout où un média est attendu) : $post->syncMedia($media, 'post-thumbnail'); // ou passer null pour détacher : $post->syncMedia(null, 'post-thumbnail');
Collection multiple (galerie)
Le package offre des méthodes pour gérer les collections à éléments multiples. Chaque média peut être passé par son id ou directement par son instance Media (les deux syntaxes sont équivalentes, y compris mélangées dans un même tableau) :
// Attacher un média $post->attachMedia($mediaId, 'gallery'); $post->attachMedia($media, 'gallery'); // instance Media directement // Détacher un média $post->detachMedia($mediaId, 'gallery'); // Remplacer l'ensemble des médias d'une collection $post->syncMedias([$mediaId1, $mediaId2], 'gallery'); $post->syncMedias([$media1, $media2], 'gallery'); // ou des instances Media // Récupérer les médias d'une collection $post->medias()->wherePivot('collection_name', 'gallery')->get();
Création / Mise à jour en transaction
// Créer un modèle avec son média $post = Post::createWithMedia($attributes, $mediaId, 'post-thumbnail'); // Mettre à jour un modèle et son média $post->updateWithMedia($attributes, $newMediaId, 'post-thumbnail');
Eager loading
Toujours charger la relation explicitement pour éviter le N+1 :
Post::with('thumbnail')->paginate(); Post::with('medias')->paginate();
Validation dans les Requests
Pour valider qu'un media_id reçu depuis le front existe bien (et n'est pas à la corbeille), utilise la règle MediaExists fournie par le package plutôt que de réécrire exists:media,id à la main dans chaque FormRequest. Le format (uuid) et l'existence en base sont vérifiés automatiquement en une seule règle, inutile d'ajouter 'uuid' séparément :
use Illuminate\Foundation\Http\FormRequest; use SkalDoe\MediaLibrary\Rules\MediaExists; class StorePostRequest extends FormRequest { public function rules(): array { return [ 'title' => ['required', 'string', 'max:255'], 'thumbnail_id' => ['nullable', MediaExists::make()], 'gallery_ids' => ['array'], 'gallery_ids.*' => [MediaExists::make()], ]; } }
MediaExists::trashed() fait l'inverse : elle n'accepte que les médias actuellement en corbeille (utile par exemple pour valider un id envoyé à une route de restauration).
Dépannage
"Attempt to read property on string" dans une FormRequest → le middleware SubstituteBindings n'est pas actif sur le groupe de routes. Le ServiceProvider du package l'ajoute automatiquement ; si l'erreur persiste après mise à jour du package, vide le cache : php artisan route:clear && php artisan config:clear.
Suppression/déplacement "réussit" sans effet visible → même cause que ci-dessus : sans binding de route, Laravel injecte une instance vide du modèle au lieu de résoudre le bon enregistrement.
Erreur lors de composer require → si vous utilisez la version de développement via le dépôt VCS direct, vérifiez que l'URL du repository dans composer.json est exacte (https://github.com/skal-doe/laravel-media-library.git).
Versionning
Le package suit SemVer. Fixe une contrainte de version explicite dans chaque projet consommateur (^0.1, pas @dev) une fois stabilisé, pour éviter qu'une évolution sur un projet ne casse silencieusement les autres.