Install the Package:
composer require cesurapp/media-bundle cesurapp/storage-bundle
Ensure your config/bundles.php includes both bundles.
Configure Storage:
Edit config/packages/cesurapp_storage.yaml to define your storage backends (e.g., local filesystem, S3). Example:
cesurapp_storage:
storages:
default:
type: local
path: '%kernel.project_dir%/public/uploads'
s3:
type: aws_s3
bucket: 'my-media-bucket'
region: 'us-east-1'
Add Media to an Entity:
Extend your entity with MediaTrait and implement MediaInterface. For a single media column (e.g., avatar), this is optional:
use Cesurapp\MediaBundle\Entity\MediaInterface;
use Cesurapp\MediaBundle\Entity\Traits\MediaTrait;
class User implements MediaInterface
{
use MediaTrait;
// Optional: Define media columns for multi-column support
public function getMediaColumns(): array
{
return ['avatar', 'document'];
}
}
Upload Media via Controller:
Use the MediaManager to handle uploads with built-in image processing:
use Cesurapp\MediaBundle\Manager\MediaManager;
use Symfony\Component\HttpFoundation\Request;
class UploadController
{
public function upload(Request $request, MediaManager $manager): void
{
$medias = $manager
->setImageCompress(true)
->setImageQuality(80)
->uploadFile($request, ['avatar']);
$user->addMedias($medias);
$em->persist($user);
$em->flush();
}
}
Serve Media: Access media via entity methods or directly from storage:
// In a controller or service
$response = $user->getAvatarFirst()->getResponse('default');
return $response;
avatar column to User entity using MediaTrait.enctype="multipart/form-data").MediaManager in the controller to process the upload, compress the image, and attach it to the user.getAvatarFirst()->getResponse().For entities requiring multiple media columns (e.g., avatar, document, cover_photo), extend MediaTrait and override getMediaColumns():
class Product implements MediaInterface
{
use MediaTrait;
public function getMediaColumns(): array
{
return ['image', 'thumbnail', 'certificate'];
}
}
AvatarTrait, DocumentTrait) to avoid bloating the entity and improve readability.Configure image transformations in the MediaManager chain:
$medias = $manager
->setImageCompress(true)
->setImageConvertJPG(true) // Convert PNG/GIF to JPG
->setImageQuality(75)
->setImageSize(1200, null) // Preserve aspect ratio
->uploadFile($request, ['image']);
MediaManager calls with different sizes.setImageConvertWebP(true) for modern formats.cesurapp_storage.yaml).Leverage automatic cleanup when media is no longer referenced:
// Remove media from entity (counter decrements)
$user->removeMedia($media);
$em->flush(); // Media deleted if counter reaches 0
// Entity deletion cascades to all media
$em->remove($user);
$em->flush(); // All media auto-deleted
MediaTrait to customize cleanup logic (e.g., log deletions).Handle non-file uploads (e.g., Base64 from frontend or direct URLs):
// Base64 upload with MIME validation
$medias = $manager->uploadBase64(
$request,
['image'],
['image' => ['image/png', 'image/jpeg']]
);
// Remote URL download
$medias = $manager->uploadLink(
$request,
['remoteImage'],
['remoteImage' => ['image/webp']]
);
Retrieve media regardless of storage backend:
// Get response for HTTP serving
$response = $media->getResponse('s3'); // Uses 's3' storage config
// Get file path (storage-agnostic)
$path = $media->getPath(); // e.g., "2025/01/20/abc123.jpg"
// Get raw content
$content = $media->getContent('default');
Use Symfony’s File type in forms:
use Symfony\Component\Form\Extension\Core\Type\FileType;
$builder->add('avatar', FileType::class, [
'mapped' => false, // MediaBundle handles mapping
'label' => 'Profile Picture',
]);
mapped => false to let MediaManager handle file processing.Extend functionality via Symfony events. Example: Log media uploads:
// config/services.yaml
services:
App\EventListener\MediaUploadListener:
tags:
- { name: kernel.event_listener, event: media.pre_upload, method: onPreUpload }
class MediaUploadListener
{
public function onPreUpload(MediaEvent $event): void
{
$media = $event->getMedia();
// Custom logic (e.g., validation, logging)
}
}
Extend cesurapp/storage-bundle to support additional backends (e.g., Backblaze B2):
// src/Storage/BackblazeStorage.php
use Cesurapp\StorageBundle\Storage\StorageInterface;
class BackblazeStorage implements StorageInterface
{
// Implement required methods (e.g., upload, delete, getUrl)
}
Register the adapter in cesurapp_storage.yaml:
cesurapp_storage:
storages:
backblaze:
type: backblaze
bucket: 'my-bucket'
key: '%env(BACKBLAZE_KEY)%'
Use Doctrine events for pre/post media operations:
// src/EntityListener/MediaLifecycleListener.php
use Doctrine\ORM\Events;
class MediaLifecycleListener
{
public function prePersist(Media $media): void
{
// Example: Auto-generate filename
$media->setName('auto_' . uniqid() . '.' . $media->getMimeType());
}
}
Register in config/doctrine.yaml:
doctrine:
orm:
entity_listeners:
Cesurapp\MediaBundle\Entity\Media: [App\EntityListener\MediaLifecycleListener]
Use built-in commands for maintenance:
# Check storage stats
bin/console media:status
# Cleanup orphaned media (run periodically)
bin/console media:cleanup
MediaTrait in multiple entities with the same column names (e.g., avatar) can cause conflicts.UserAvatarTrait, ProductImageTrait) or use unique column names.removeMedia() is called in the same transaction as the entity update. For complex workflows, use Doctrine events:
$em->getEventManager()->addEventListener(
Events::postRemove,
function (LifecycleEventArgs $args) {
$entity = $args->getObject();
if ($entity instanceof MediaInterface) {
$entity->cleanup(); // Force cleanup
}
}
);
$message = new Process
How can I help you explore Laravel packages today?