Search by

skaldoe / laravel-media-library

skal-doe

Médiathèque réutilisable (dossiers, upload, attachments polymorphiques) pour projets Laravel

Package info

github.com/skal-doe/laravel-media-library

pkg:composer/skaldoe/laravel-media-library

Statistics

Installs: 4

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.1.0 2026-09-13 18:57 UTC

This package is auto-updated.

Last update: 2026-09-13 18:57:41 UTC


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 champ collection_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 /folders ne renvoie qu'un seul niveau à la fois (dossiers racine par défaut, ou enfants directs de parent_id si fourni), avec folder_count/medias_count sur 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 (voir useMediaFolderTree dans 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.