Weave 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

netbull/media-bundle

Netbull Media Bundle is a Symfony bundle for managing media assets and file attachments. It helps organize uploaded images and documents, associate them with application entities, and serve them through configurable helpers for simpler media workflows.

View on GitHub
Deep Wiki
Context7

Getting Started

netbull/media-bundle is a Symfony bundle for attaching uploaded media to Doctrine entities, now at 7.x — actively patched (7.2.14 landed 2026-08-14) but still low-adoption (1 star, 0 known dependents). Treat it as a readable, maintained-but-unloved dependency: the source is the documentation, but you're not adopting something abandoned.

Bootstrap sequence:

composer require netbull/media-bundle

Register the bundle in config/bundles.php (Symfony Flex may do this automatically — if not, add it manually):

Netbull\MediaBundle\NetbullMediaBundle::class => ['all' => true],

Where to look first, in order:

  1. src/Resources/config/ — service definitions and the bundle's Configuration tree. This tells you exactly what is configurable (storage paths, entity classes, adapter names, thumbnail sizes) far faster than any README.
  2. src/Entity/ (or src/Model/) — find the Media/File entity and its interface. You need to know whether it ships a mapped entity or an abstract class/base interface you're expected to extend.
  3. src/DependencyInjection/ — the extension class reveals default parameter values you'd otherwise guess at.
  4. src/Form/ and src/Controller/ — the upload path: what form type you attach, and which route receives the file.
  5. src/Provider/ / src/Thumbnail/ — the bundle handles video as well as images, and generates thumbnails for both. This is where the type-specific behaviour lives.

Config skeleton (validate the exact keys with bin/console config:dump-reference netbull_media — the config tree is authoritative, not the docs):

# config/packages/netbull_media.yaml
netbull_media:
    db_driver: orm
    class:
        media: App\Entity\Media

First use case to prove the wiring: attach a single Media to one entity (e.g. a Post with a cover image), upload it through the bundle's form type, persist, and render the public URL in Twig. That single vertical slice exercises the entity mapping, the storage adapter, the upload handler, and the URL resolver at once — if any of those four is misconfigured, this flow fails loudly and points you at it. Once that works, repeat with a short video file to confirm the thumbnail pipeline runs — the two paths (image vs. video) diverge inside the bundle, and only the video path exercises derivative generation.

Implementation Patterns

Entity attachment via interface, not inheritance. The durable pattern for these bundles is to have your entity implement the bundle's media-aware interface and hold a ManyToOne to the media class:

class Post implements MediaAwareInterface
{
    #[ORM\ManyToOne(targetEntity: Media::class, cascade: ['persist'])]
    private ?Media $cover = null;

    public function getCover(): ?Media { return $this->cover; }
    public function setCover(?Media $cover): void { $this->cover = $cover; }
}

Keeping the relation on your entity (rather than relying on the bundle to own the join) means you control cascade and orphan-removal semantics, which matters when you later need to delete media files alongside rows.

Form-driven upload. The typical workflow is a dedicated MediaType on the bundle's media class, embedded into your entity form. This lets Symfony's PropertyAccessor and Doctrine's change tracking do the work:

$builder->add('cover', MediaType::class, ['required' => false]);

Use the bundle's form type rather than hand-rolling a FileType — it's what triggers the upload handler, metadata extraction (mime type, dimensions, size), and thumbnail generation for both images and video.

Video is a first-class media type. The bundle distinguishes media kinds (image, video, file) and generates derivative thumbnails per kind. Model them as separate media entities or via a provider/type discriminator column — whichever the Configuration.php tree offers — but decide before you have rows in production, because migrating a flat media table to typed media later means rewriting every association and every thumbnail path.

Service-layer uploads for non-form paths. For API endpoints, console imports, or queue workers, bypass the form layer and call the bundle's media manager/uploader service directly. Inject it by its service ID (confirm the exact ID in config/services.xml) and call its create/persist method with an UploadedFile or a local path. This is the pattern that lets you reuse the same storage adapter across web and CLI — and it's the only sane way to bulk-import existing media, where you want to skip form validation entirely.

Deletion goes through the manager, not $em->remove(). Thumbnails are separate files with separate paths derived from the parent media. As of 7.2.14 the bundle correctly cleans up thumbnails when a video is deleted — but that fix only holds if the deletion flows through whatever service the bundle uses. Removing the entity directly via the EntityManager bypasses it and leaves files on disk.

Storage abstraction for environment parity. Configure a local filesystem adapter in dev and an object-storage adapter in prod, driven by env vars. Do this before you write code that builds URLs — hardcoded path construction is the single hardest thing to retrofit, and thumbnails multiply the number of paths you'd have to fix.

Twig presentation. Resolve URLs through the bundle's helper or a small Twig extension of your own rather than concatenating the storage path. For additional derivatives (responsive srcsets, webp conversion), pair with liip/imagine-bundle and feed it the media's relative path — the media bundle stores the file and its thumbnail, Imagine handles anything further. Keep those responsibilities separate.

Integrating with existing CRUD. Add the media field to the entity form, add a Voter/access check on media download routes if files are not fully public, and add the media relation to your Doctrine fixtures so tests have real files on disk.

Gotchas and Tips

Maintained, but thinly adopted. The 7.x line is being patched (this release is a real bugfix, not a version bump for its own sake), so the "abandoned" risk is lower than the star count suggests. The support risk is unchanged: zero dependents means no community answers when something breaks. Pin the minor ("netbull/media-bundle": "^7.2") rather than floating — you want the bugfixes, not a surprise major.

Orphaned files — narrower than it used to be, still real. 7.2.14 fixed orphan thumbnails when deleting a video, which confirms the general shape of the problem: deleting media leaves files behind unless the bundle's own deletion path runs. Don't assume the fix generalises to images, to files replaced in place, or to entities you delete without cascading. Add a postRemove listener or a scheduled reconciliation command that diffs the media table against the storage directory — this remains the highest-value piece of glue code you'll write.

Thumbnails double your storage surface. Every video now implies at least one generated thumbnail file. Anything that enumerates storage (backups, CDN invalidation, cleanup jobs, quota calculations) must account for both the original path and the derived path. If you're mirroring the upload directory to a CDN, make sure the thumbnail directory is in the same mirror.

Doctrine schema drift. Bundles that ship mapped entities often cause doctrine:schema:validate complaints about mapping outside the entity path. If you hit "The association refers to the inverse side field which does not exist," your App\Entity\Media is missing #[ORM\Entity] or the bundle's mapping isn't registered. Check doctrine.orm.mappings in config/packages/doctrine.yaml and add the bundle's mapping block explicitly if needed.

After changing config, clear the cache. bin/console cache:clear — Symfony config trees are compiled, and a stale container will silently use old parameters. Debug with bin/console debug:config netbull_media.

File permissions and storage paths. The most common first-run failure is a write permission error on the upload directory, surfacing as a generic RuntimeException from Flysystem rather than a clear "permission denied." Verify the configured path exists, is writable by the PHP user, and is outside public/ if you plan to gate access through a controller. Thumbnail generation runs as a second write to a sibling directory — check both, not just the originals path.

Metadata is captured at upload time only. Mime type, size, and dimensions are stored in the DB row, and thumbnails are generated once at upload. Replacing a file in place (same row, new bytes) leaves stale metadata and a stale thumbnail. Either enforce replace-through-delete, or explicitly refresh metadata and regenerate derivatives in your update path.

Config key names may not match the docs. The Configuration.php class is the only truth. When a YAML key is silently ignored, config:dump-reference will reveal the real name — an unknown key under a non-ignoreExtraKeys node throws at compile time, but typos nested under a prototype-like node can pass silently.

Extension points to prefer. Rather than editing vendor code: implement the bundle's interface on your own entity, decorate its uploader service (decorates: in services.yaml), and add a Doctrine event listener for lifecycle hooks. If the bundle's storage adapter is too thin, swap the service binding for your own Flysystem instance — that's a cleaner seam than subclassing.

Test with real temp files, including a video. Media bundles interact with the filesystem in ways mocks hide. Use Symfony's KernelTestCase with a temp upload directory in config/packages/test/ and assert the file physically exists after persist, not just that the entity has a non-null path. With video in the mix, also assert the thumbnail file exists — that's the code path that silently no-ops when FFmpeg or the thumbnail provider isn't configured.

Have an exit plan. Write your own thin MediaManager interface in front of the bundle's services. If you later replace it with vich/uploader-bundle (far larger ecosystem, though image-centric) or plain Flysystem plus a thumbnail job, only that adapter class needs rewriting.

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.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib