cesurapp / media-bundle
Symfony Media Bundle
Package info
github.com/cesurapp/media-bundle
Type:symfony-bundle
pkg:composer/cesurapp/media-bundle
Requires
- php: >=8.4
- ext-fileinfo: *
- ext-gd: *
- cesurapp/storage-bundle: ^1.2
- claviska/simpleimage: ^4.4
- doctrine/dbal: ^4.4
- doctrine/doctrine-bundle: ^3.3
- doctrine/orm: ^3.7
- symfony/console: ^8.1
- symfony/dependency-injection: ^8.1
- symfony/framework-bundle: ^8.1
- symfony/http-client: ^8.1
- symfony/http-kernel: ^8.1
- symfony/mime: ^8.1
- symfony/uid: ^8.1
- symfony/validator: ^8.1
Requires (Dev)
- php-cs-fixer/shim: ^3.95
- phpstan/phpstan: ^2.2
- phpunit/phpunit: ^13.3
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- dev-main
- 1.2.25
- 1.2.24
- 1.2.23
- 1.2.22
- 1.2.21
- 1.2.20
- 1.2.19
- 1.2.18
- 1.2.17
- 1.2.16
- 1.2.15
- 1.2.14
- 1.2.13
- 1.2.12
- 1.2.11
- 1.2.10
- 1.2.9
- 1.2.8
- 1.2.7
- 1.2.6
- 1.2.5
- 1.2.4
- 1.2.3
- 1.2.2
- 1.2.1
- 1.2.0
- 1.1.01
- 1.1.0
- 1.0.18
- 1.0.17
- 1.0.16
- 1.0.15
- 1.0.14
- 1.0.13
- 1.0.12
- 1.0.11
- 1.0.10
- 1.0.09
- 1.0.08
- 1.0.07
- 1.0.06
- 1.0.04
- 1.0.03
- 1.0.02
- 1.0.01
- 1.0.0
This package is auto-updated.
Last update: 2026-10-01 20:57:12 UTC
README
Media management bundle for Symfony, built on top of cesurapp/storage-bundle.
Features:
- Uploads from HTTP files, base64, remote URLs and raw content
- Image compression, resizing and PNG → JPG conversion
- Media columns stored as JSONB id lists on your entities
- Storage object deleted when the
Mediarow is deleted (after commit) - Safe defaults: active content (html, svg, php…) refused, SSRF-protected downloads, image size limits
Installation
Requirements: PHP 8.4+, Symfony 8.1+, ext-gd, ext-fileinfo
composer require cesurapp/media-bundle
Quick Start
1. Add a Media Column to an Entity
use Cesurapp\MediaBundle\Entity\MediaSuperClass; use Cesurapp\MediaBundle\Entity\Traits\MediaTrait; use Doctrine\ORM\Mapping as ORM; #[ORM\Entity] #[ORM\HasLifecycleCallbacks] class Post extends MediaSuperClass { use MediaTrait; // Columns whose media are removed together with the entity public function getMediaColumns(): array { return ['media']; } }
MediaTrait, AvatarTrait and LogoTrait ship with the bundle. Copy one and rename it for another column.
2. Upload
use Cesurapp\MediaBundle\Manager\MediaManager; // HTTP multipart files, optional key filter and allowed types per key $medias = $manager->uploadHttpFile($request, ['photos'], [], ['photos' => ['image/jpeg', 'image/png']]); // Base64 fields $medias = $manager->uploadHttpBase64($request, ['image'], ['image' => ['image/png', 'image/jpeg']]); // Remote links (private networks refused, 20 MB default limit) $medias = $manager->uploadHttpLink($request, ['imageUrl'], ['imageUrl' => ['image/png']]); // Single sources $media = $manager->uploadFromUploadedFile($file, ['image/png']); $media = $manager->uploadFromBase64($base64, ['image/png']); $media = $manager->uploadFromUrl($url, ['image/png']); $media = $manager->uploadFromContent($content, 'image/png', 'png'); $media = $manager->uploadFromData($dto->validated('avatar')); // from Base64FileValidator $manager->save($medias, $em); // persist + flush $post->addMedia($medias);
3. Options
Every upload method takes an $options array:
| Option | Default | |
|---|---|---|
imageCompress |
true |
Re-encode jpg/png |
imageConvertJPG |
true |
Convert png/jpeg to jpg |
imageQuality |
75 |
Encoder quality |
imageWidth / imageHeight |
720 / 1280 |
Best-fit box |
imageMaxPixels |
40000000 |
Images above this pixel count are refused before decoding |
private |
false |
Write to the device's private bucket |
storage |
default device | Device key; unknown keys fall back to the default |
maxSize |
null (links: 20 MB) |
Max bytes |
maxFiles |
20 |
Max files per request key (HTTP helpers) |
allowUnsafe |
false |
Accept html, svg, xml, js, php, executables |
allowPrivateNetwork |
false |
Let link downloads reach private/loopback addresses |
downloadTimeout / downloadMaxDuration |
10 / 30 |
Seconds |
4. Access
$media = $post->getMedia(); // array<string, Media> $first = $user->getAvatarFirst(); $media->toString($storage); // public or signed URL $media->getResponse($storage); // HTTP response (nosniff, private files never shared-cached) $media->getContent($storage); // Media columns hold lazy references: load a whole list in one query $mediaRepository->preload(array_map(fn (User $u) => $u->getAvatar(), $users));
5. Delete
$em->remove($media); $em->flush(); // object deleted from storage once the transaction commits $em->remove($post); $em->flush(); // MediaSuperClass removes the post's media in the same flush
There is no reference counting. removeMedia(), setMedia() and clearMedia() only change the column;
remove the replaced Media yourself or it stays in storage.
Commands
bin/console media:status # file count and total size
Upgrading
media.mimeis nowVARCHAR(255)(was 40, too short for Office types): generate a migration.sizeis the stored byte count (after compression). Rows written before keep the original upload size.uploadHttpFile()throwsFileValidationExceptionfor invalid files instead of logging and returning the rawUploadedFile.uploadHttpLink()leaves failed links out of the result instead of returning the URL string.MediaSuperClass::postRemoveMedia()is nowpreRemoveMedia()and no longer flushes.
See GUIDELINES.md for details.