Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Media Bundle Laravel Package

dahovitech/media-bundle

View on GitHub
Deep Wiki
Context7

Guide de démarrage rapide

Ce guide vous permet de commencer rapidement avec le DahoviTech Media Bundle après l'installation.

Premier upload

Via l'interface d'administration

  1. Accédez à l'interface d'administration : http://your-domain/admin/media
  2. Cliquez sur "Télécharger" dans la navigation
  3. Glissez-déposez vos fichiers ou cliquez pour les sélectionner
  4. Les fichiers sont automatiquement uploadés et traités

Via l'API

curl -X POST "http://your-domain/api/media/upload" \
  -F "file=@/path/to/your/image.jpg" \
  -F "name=Ma première image" \
  -F "description=Test d'upload"

Utilisation dans vos contrôleurs

Service MediaManager

<?php

namespace App\Controller;

use DahoviTech\MediaBundle\Service\MediaManager;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class MyController extends AbstractController
{
    public function __construct(
        private MediaManager $mediaManager
    ) {
    }

    #[Route('/upload-form', name: 'upload_form')]
    public function uploadForm(): Response
    {
        return $this->render('upload_form.html.twig');
    }

    #[Route('/upload', name: 'upload', methods: ['POST'])]
    public function upload(Request $request): Response
    {
        /** [@var](https://github.com/var) UploadedFile $uploadedFile */
        $uploadedFile = $request->files->get('file');
        
        if ($uploadedFile) {
            $media = $this->mediaManager->createFromUploadedFile(
                $uploadedFile,
                $request->request->get('name', 'Mon fichier'),
                $request->request->get('description'),
                true // public
            );
            
            $this->addFlash('success', 'Fichier uploadé avec succès !');
            
            return $this->redirectToRoute('show_media', ['id' => $media->getId()]);
        }
        
        $this->addFlash('error', 'Aucun fichier sélectionné');
        return $this->redirectToRoute('upload_form');
    }

    #[Route('/media/{id}', name: 'show_media')]
    public function showMedia(int $id): Response
    {
        $media = $this->mediaManager->findById($id);
        
        if (!$media) {
            throw $this->createNotFoundException('Média non trouvé');
        }
        
        return $this->render('show_media.html.twig', [
            'media' => $media
        ]);
    }

    #[Route('/gallery', name: 'gallery')]
    public function gallery(): Response
    {
        // Récupérer toutes les images
        $images = $this->mediaManager->getImages();
        
        return $this->render('gallery.html.twig', [
            'images' => $images
        ]);
    }
}

Templates Twig

Formulaire d'upload simple

{# templates/upload_form.html.twig #}
{% extends 'base.html.twig' %}

{% block body %}
<div class="container mt-5">
    <h1>Upload de fichier</h1>
    
    {% for flash_error in app.flashes('error') %}
        <div class="alert alert-danger">{{ flash_error }}</div>
    {% endfor %}
    
    {% for flash_success in app.flashes('success') %}
        <div class="alert alert-success">{{ flash_success }}</div>
    {% endfor %}
    
    <form action="{{ path('upload') }}" method="post" enctype="multipart/form-data">
        <div class="mb-3">
            <label for="file" class="form-label">Fichier</label>
            <input type="file" class="form-control" id="file" name="file" required
                   accept=".jpg,.jpeg,.png,.gif,.webp,.pdf,.txt,.doc,.docx">
        </div>
        
        <div class="mb-3">
            <label for="name" class="form-label">Nom</label>
            <input type="text" class="form-control" id="name" name="name" placeholder="Nom du fichier">
        </div>
        
        <div class="mb-3">
            <label for="description" class="form-label">Description</label>
            <textarea class="form-control" id="description" name="description" rows="3" 
                      placeholder="Description du fichier"></textarea>
        </div>
        
        <button type="submit" class="btn btn-primary">
            <i class="bi bi-upload"></i> Télécharger
        </button>
    </form>
</div>
{% endblock %}

Affichage d'un média

{# templates/show_media.html.twig #}
{% extends 'base.html.twig' %}

{% block body %}
<div class="container mt-5">
    <div class="row">
        <div class="col-md-8">
            <h1>{{ media.name }}</h1>
            
            {% if media.isImage %}
                <img src="{{ media.url }}" alt="{{ media.altText ?: media.name }}" 
                     class="img-fluid rounded shadow">
            {% else %}
                <div class="alert alert-info">
                    <h4>
                        {% if media.type == 'pdf' %}
                            <i class="bi bi-file-earmark-pdf"></i> Document PDF
                        {% elseif media.type == 'document' %}
                            <i class="bi bi-file-earmark-text"></i> Document
                        {% else %}
                            <i class="bi bi-file-earmark"></i> Fichier
                        {% endif %}
                    </h4>
                    <p class="mb-0">{{ media.originalFilename }}</p>
                </div>
            {% endif %}
            
            {% if media.description %}
                <p class="mt-3">{{ media.description }}</p>
            {% endif %}
        </div>
        
        <div class="col-md-4">
            <div class="card">
                <div class="card-header">
                    <h5><i class="bi bi-info-circle"></i> Informations</h5>
                </div>
                <div class="card-body">
                    <dl class="row">
                        <dt class="col-sm-5">Type :</dt>
                        <dd class="col-sm-7">{{ media.type|title }}</dd>
                        
                        <dt class="col-sm-5">Taille :</dt>
                        <dd class="col-sm-7">{{ (media.size / 1024)|number_format(1) }} KB</dd>
                        
                        {% if media.isImage and media.width and media.height %}
                            <dt class="col-sm-5">Dimensions :</dt>
                            <dd class="col-sm-7">{{ media.width }} × {{ media.height }} px</dd>
                        {% endif %}
                        
                        <dt class="col-sm-5">Créé le :</dt>
                        <dd class="col-sm-7">{{ media.createdAt|date('d/m/Y H:i') }}</dd>
                        
                        <dt class="col-sm-5">Statut :</dt>
                        <dd class="col-sm-7">
                            {% if media.isPublic %}
                                <span class="badge bg-success">Public</span>
                            {% else %}
                                <span class="badge bg-warning">Privé</span>
                            {% endif %}
                        </dd>
                    </dl>
                    
                    <hr>
                    
                    <div class="d-grid gap-2">
                        <a href="{{ path('dahovi_tech_media_api_download', {id: media.id}) }}" 
                           class="btn btn-primary" download>
                            <i class="bi bi-download"></i> Télécharger
                        </a>
                        
                        {% if media.isImage %}
                            <button type="button" class="btn btn-outline-secondary" 
                                    onclick="copyImageUrl('{{ media.url }}')">
                                <i class="bi bi-link"></i> Copier l'URL
                            </button>
                        {% endif %}
                    </div>
                </div>
            </div>
            
            {% if media.isImage and media.metadata %}
                <div class="card mt-3">
                    <div class="card-header">
                        <h6><i class="bi bi-camera"></i> Métadonnées</h6>
                    </div>
                    <div class="card-body">
                        {% for key, value in media.metadata %}
                            {% if key != 'exif' and key != 'file_permissions' %}
                                <small class="d-block">
                                    <strong>{{ key|replace({'_': ' '})|title }} :</strong> {{ value }}
                                </small>
                            {% endif %}
                        {% endfor %}
                    </div>
                </div>
            {% endif %}
        </div>
    </div>
</div>

<script>
function copyImageUrl(url) {
    navigator.clipboard.writeText(window.location.origin + url).then(function() {
        alert('URL copiée dans le presse-papiers !');
    });
}
</script>
{% endblock %}

Galerie d'images

{# templates/gallery.html.twig #}
{% extends 'base.html.twig' %}

{% block body %}
<div class="container mt-5">
    <h1>Galerie d'images</h1>
    
    {% if images|length > 0 %}
        <div class="row g-4">
            {% for image in images %}
                <div class="col-md-6 col-lg-4 col-xl-3">
                    <div class="card h-100">
                        <a href="{{ path('show_media', {id: image.id}) }}">
                            <img src="{{ image.thumbnailUrl }}" 
                                 alt="{{ image.altText ?: image.name }}"
                                 class="card-img-top" style="height: 200px; object-fit: cover;">
                        </a>
                        
                        <div class="card-body">
                            <h6 class="card-title">{{ image.name }}</h6>
                            
                            {% if image.description %}
                                <p class="card-text text-muted small">
                                    {{ image.description|slice(0, 80) }}...
                                </p>
                            {% endif %}
                            
                            <small class="text-muted">
                                {{ image.createdAt|date('d/m/Y') }}
                            </small>
                        </div>
                        
                        <div class="card-footer bg-transparent">
                            <div class="btn-group w-100" role="group">
                                <a href="{{ path('show_media', {id: image.id}) }}" 
                                   class="btn btn-outline-primary btn-sm">
                                    <i class="bi bi-eye"></i>
                                </a>
                                <a href="{{ path('dahovi_tech_media_api_download', {id: image.id}) }}" 
                                   class="btn btn-outline-success btn-sm" download>
                                    <i class="bi bi-download"></i>
                                </a>
                            </div>
                        </div>
                    </div>
                </div>
            {% endfor %}
        </div>
    {% else %}
        <div class="alert alert-info text-center">
            <h5><i class="bi bi-images"></i> Aucune image trouvée</h5>
            <p>Commencez par télécharger quelques images.</p>
            <a href="{{ path('upload_form') }}" class="btn btn-primary">
                <i class="bi bi-upload"></i> Télécharger des images
            </a>
        </div>
    {% endif %}
</div>
{% endblock %}

Utilisation dans vos entités

Relation avec vos entités

<?php

namespace App\Entity;

use DahoviTech\MediaBundle\Entity\Media;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
class Article
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column(length: 255)]
    private ?string $title = null;

    #[ORM\Column(type: 'text')]
    private ?string $content = null;

    #[ORM\ManyToOne(targetEntity: Media::class)]
    #[ORM\JoinColumn(nullable: true)]
    private ?Media $featuredImage = null;

    #[ORM\ManyToMany(targetEntity: Media::class)]
    #[ORM\JoinTable(name: 'article_media')]
    private Collection $attachments;

    public function __construct()
    {
        $this->attachments = new ArrayCollection();
    }

    // Getters et setters...

    public function getFeaturedImage(): ?Media
    {
        return $this->featuredImage;
    }

    public function setFeaturedImage(?Media $featuredImage): static
    {
        $this->featuredImage = $featuredImage;
        return $this;
    }

    public function getAttachments(): Collection
    {
        return $this->attachments;
    }

    public function addAttachment(Media $attachment): static
    {
        if (!$this->attachments->contains($attachment)) {
            $this->attachments->add($attachment);
        }
        return $this;
    }

    public function removeAttachment(Media $attachment): static
    {
        $this->attachments->removeElement($attachment);
        return $this;
    }
}

Formulaire avec sélection de média

<?php

namespace App\Form;

use App\Entity\Article;
use DahoviTech\MediaBundle\Entity\Media;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class ArticleType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('title', TextType::class, [
                'label' => 'Titre'
            ])
            ->add('content', TextareaType::class, [
                'label' => 'Contenu'
            ])
            ->add('featuredImage', EntityType::class, [
                'class' => Media::class,
                'choice_label' => 'name',
                'placeholder' => 'Sélectionner une image',
                'required' => false,
                'query_builder' => function ($repository) {
                    return $repository->createQueryBuilder('m')
                        ->where('m.type = :type')
                        ->setParameter('type', 'image')
                        ->orderBy('m.name', 'ASC');
                }
            ])
            ->add('attachments', EntityType::class, [
                'class' => Media::class,
                'choice_label' => 'name',
                'multiple' => true,
                'expanded' => false,
                'required' => false
            ]);
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => Article::class,
        ]);
    }
}

JavaScript pour l'upload moderne

Upload avec FilePond

<!-- Dans votre template -->
<link href="https://unpkg.com/filepond/dist/filepond.css" rel="stylesheet">
<link href="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css" rel="stylesheet">

<input type="file" class="filepond" name="files[]" multiple data-max-files="5">

<script src="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.js"></script>
<script src="https://unpkg.com/filepond-plugin-file-validate-type/dist/filepond-plugin-file-validate-type.js"></script>
<script src="https://unpkg.com/filepond-plugin-file-validate-size/dist/filepond-plugin-file-validate-size.js"></script>
<script src="https://unpkg.com/filepond/dist/filepond.js"></script>

<script>
// Enregistrer les plugins
FilePond.registerPlugin(
    FilePondPluginImagePreview,
    FilePondPluginFileValidateType,
    FilePondPluginFileValidateSize
);

// Créer une instance FilePond
const pond = FilePond.create(document.querySelector('.filepond'), {
    acceptedFileTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'application/pdf'],
    maxFileSize: '10MB',
    maxFiles: 5,
    allowMultiple: true,
    instantUpload: false,
    
    server: {
        process: {
            url: '/api/media/upload/multiple',
            method: 'POST',
            headers: {
                'X-Requested-With': 'XMLHttpRequest'
            },
            onload: (response) => {
                const result = JSON.parse(response);
                console.log('Upload réussi:', result);
                return result;
            },
            onerror: (response) => {
                console.error('Erreur upload:', response);
            }
        }
    }
});
</script>

Upload simple avec fetch

// Fonction d'upload simple
async function uploadFile(fileInput) {
    const formData = new FormData();
    
    for (let i = 0; i < fileInput.files.length; i++) {
        formData.append('files[]', fileInput.files[i]);
    }
    
    try {
        const response = await fetch('/api/media/upload/multiple', {
            method: 'POST',
            body: formData
        });
        
        const result = await response.json();
        
        if (response.ok) {
            console.log('Upload réussi:', result);
            displayUploadResults(result);
        } else {
            console.error('Erreur upload:', result);
            displayError(result.error);
        }
    } catch (error) {
        console.error('Erreur réseau:', error);
        displayError('Erreur de connexion');
    }
}

// Afficher les résultats
function displayUploadResults(result) {
    const container = document.getElementById('upload-results');
    
    let html = `<div class="alert alert-success">
        ${result.summary.success} fichier(s) uploadé(s) avec succès
    </div>`;
    
    if (Object.keys(result.uploaded).length > 0) {
        html += '<div class="row">';
        for (const [index, media] of Object.entries(result.uploaded)) {
            html += `<div class="col-md-3 mb-3">
                <div class="card">
                    <div class="card-body">
                        <h6>${media.name}</h6>
                        <small class="text-muted">${media.originalFilename}</small>
                        <br>
                        <a href="/media/${media.id}" class="btn btn-sm btn-primary mt-2">Voir</a>
                    </div>
                </div>
            </div>`;
        }
        html += '</div>';
    }
    
    if (Object.keys(result.errors).length > 0) {
        html += '<div class="alert alert-warning"><h6>Erreurs :</h6>';
        for (const [index, error] of Object.entries(result.errors)) {
            html += `<div>Fichier ${parseInt(index) + 1}: ${error}</div>`;
        }
        html += '</div>';
    }
    
    container.innerHTML = html;
}

function displayError(message) {
    const container = document.getElementById('upload-results');
    container.innerHTML = `<div class="alert alert-danger">${message}</div>`;
}

Commandes CLI utiles

Générer des thumbnails

# Générer tous les thumbnails
php bin/console dahovi-tech:media:generate-thumbnails

# Forcer la régénération
php bin/console dahovi-tech:media:generate-thumbnails --force

# Générer un format spécifique
php bin/console dahovi-tech:media:generate-thumbnails --filter=medium

Nettoyage automatique

# Supprimer les médias expirés
php bin/console dahovi-tech:media:cleanup-expired

Statistiques

# Afficher les statistiques
php bin/console dahovi-tech:media:statistics

Prochaines étapes

Maintenant que vous avez les bases :

  1. Sécurité : Configuration de la sécurité
  2. Performance : Optimisation et cache
  3. Stockage cloud : Configuration AWS S3
  4. API avancée : Documentation API complète
  5. Intégrations : TinyMCE et autres éditeurs

Support

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
nasirkhan/laravel-sharekit
directorytree/privacy-filter-classifier
directorytree/privacy-filter
datacore/hub-sdk
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony
develia/geo-bundle
dreamzy/livewire-charts
touchestate-sdk/php-sdk
22h/doctrine-garbage-collection-bundle
agtp/agtp-php
agtp/mod-php
splash/sonata-admin
splash/metadata
splash/openapi
splash/scopes
splash/toolkit
testo/output-teamcity
testo/bridge-symfony