Installation:
composer require braunstetter/media-bundle
Register the bundle in config/bundles.php:
return [
// ...
Braunstetter\MediaBundle\MediaBundle::class => ['all' => true],
];
Create a Media Entity:
Extend BaseFile for your media type (e.g., Image, Document):
// src/Entity/Image.php
namespace App\Entity;
use Braunstetter\MediaBundle\Entity\BaseFile;
class Image extends BaseFile {}
Run migrations:
php bin/console make:migration
php bin/console doctrine:migrations:migrate
First Use Case: Upload a file via a form (see FormTypes for details). Example controller:
use Braunstetter\MediaBundle\Form\Type\FileType;
use Symfony\Component\HttpFoundation\Request;
public function upload(Request $request): Response
{
$image = new Image();
$form = $this->createForm(FileType::class, $image);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$image->upload(); // Persist and upload
$this->getDoctrine()->getManager()->flush();
}
return $this->render('upload.html.twig', ['form' => $form->createView()]);
}
Entity Design:
BaseFile for all media types (e.g., Image, Video, Document).getMimeType() or getAllowedMimeTypes() to enforce constraints:
public function getAllowedMimeTypes(): array
{
return ['image/jpeg', 'image/png'];
}
Form Integration:
FileType for file uploads in forms. Customize with options:
{{ form_start(form) }}
{{ form_widget(form.file) }} {# Renders file input #}
{{ form_errors(form) }}
{{ form_end(form) }}
$builder->add('file', FileType::class, [
'allowed_mime_types' => ['image/jpeg', 'image/png'],
'allowed_extensions' => ['jpg', 'png'],
]);
Upload Handling:
FilesystemUploader (default) or implement UploaderInterface for custom storage (e.g., S3):
$uploader = new FilesystemUploader($this->getParameter('kernel.project_dir').'/public/uploads');
$image->upload($uploader); // Persists and uploads
$image->upload($localUploader);
$image->upload($cloudUploader, 'cloud_path');
File Processing:
BaseFile methods for metadata:
$image->getOriginalName(); // Original filename
$image->getWebPath(); // Public URL (e.g., `/uploads/filename.jpg`)
$image->getSize(); // Filesize in bytes
resize() for images).Validation:
FileType constraints or entity logic:
public function validate(): void
{
if ($this->getSize() > 5 * 1024 * 1024) {
throw new \RuntimeException('File too large');
}
}
Mime Type Validation:
File constraint in forms:
$builder->add('file', FileType::class, [
'mime_types' => ['image/jpeg'],
'mime_types_message' => 'Only JPEG images allowed',
]);
Filesystem Permissions:
public/uploads.chmod -R 775 /path/to/uploads
Entity Lifecycle:
$entity->upload() after setting the file field. This:
BaseFile handle it.Deletion Quirks:
// src/EventSubscriber/MediaSubscriber.php
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\PreRemoveEventArgs;
class MediaSubscriber implements EventSubscriber
{
public function getSubscribedEvents(): array
{
return ['preRemove'];
}
public function preRemove(PreRemoveEventArgs $args): void
{
$entity = $args->getObject();
if ($entity instanceof BaseFile && $entity->getFile()) {
$entity->deleteFile(); // Custom method to delete file
}
}
}
Large Files:
max_file_size in php.ini and adjust post_max_size.symfony/ux-upload).Custom Uploaders:
UploaderInterface for cloud storage (e.g., AWS S3):
class S3Uploader implements UploaderInterface
{
public function upload(BaseFile $file, string $path = null): void
{
$s3->putObject([
'Bucket' => 'your-bucket',
'Key' => $path ?? $file->getFile()->getClientOriginalName(),
'Body' => fopen($file->getFile()->getPathname(), 'r'),
]);
$file->setPath($path ?? $file->getFile()->getClientOriginalName());
}
}
Dynamic Paths:
getUploadRoot() to customize upload directories per entity:
public function getUploadRoot(): string
{
return $this->getUser()->getId() . '/uploads';
}
Soft Deletes:
BaseFile to support soft deletes:
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Timestampable\Traits\TimestampableEntity;
#[ORM\Table(name: 'images')]
class Image extends BaseFile
{
#[ORM\Column(options: ['default' => false])]
private bool $isDeleted = false;
public function delete(): void
{
$this->isDeleted = true;
$this->setUpdatedAt(new \DateTime());
}
}
Testing:
$uploader = $this->createMock(UploaderInterface::class);
$uploader->expects($this->once())
->method('upload')
->with($this->isInstanceOf(Image::class));
$image->upload($uploader);
Performance:
$uploader = new FilesystemUploader($uploadDir);
foreach ($images as $image) {
$image->upload($uploader); // Reuse uploader instance
}
How can I help you explore Laravel packages today?