Ce guide vous permet de commencer rapidement avec le DahoviTech Media Bundle après l'installation.
http://your-domain/admin/mediacurl -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"
<?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/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 %}
{# 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 %}
{# 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 %}
<?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;
}
}
<?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,
]);
}
}
<!-- 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>
// 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>`;
}
# 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
# Supprimer les médias expirés
php bin/console dahovi-tech:media:cleanup-expired
# Afficher les statistiques
php bin/console dahovi-tech:media:statistics
Maintenant que vous avez les bases :
How can I help you explore Laravel packages today?