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 Object Bundle Laravel Package

x-one/media-object-bundle

Symfony bundle for managing media objects in your application: attach files and images to entities, organize uploads, and handle storage through a flexible media object layer. Simplifies reusable media management and Doctrine integration.

View on GitHub
Deep Wiki
Context7

Getting Started

x-one/media-object-bundle is a Symfony bundle whose entire job is to ship a known-good VichUploaderBundle configuration: a MediaObject-style entity, a Doctrine mapping, a storage/namer setup, and the wiring so you can attach files to your own entities without re-deriving the YAML every project. Treat it as a starter configuration, not a framework — you get a working upload path, not a media library.

Minimal steps:

  1. Add the repository first. The package lives on Bitbucket, not Packagist, so plain composer require x-one/media-object-bundle will fail. Add a VCS entry to your root composer.json, then require it:
    "repositories": [
      { "type": "vcs", "url": "https://bitbucket.org/majchw/media-object-bundle/" }
    ]
    
    Then composer require x-one/media-object-bundle:dev-master (pin the commit hash you tested — see Gotchas).
  2. Register the bundle. Symfony 4+/5+/6+: add it to config/bundles.php. Also confirm VichUploaderBundle is registered — this bundle depends on it but may not pull it in transitively.
  3. Create the table. Run bin/console doctrine:migrations:diff and inspect the generated migration before executing it. The entity's table name and columns are decided by the bundle, so read the diff.
  4. Set the storage paths. Configure the upload destination and public URI prefix (in config/packages/vich_uploader.yaml, or wherever the bundle expects its config — check DependencyInjection/Configuration.php in the repo for the exact tree).
  5. First use case: upload one file, persist the MediaObject, then render its URL in Twig with vich_uploader_asset(mediaObject, 'file'). If that renders and the file appears under your web-accessible directory, the bundle did its job.

Where to look first, in order:

  • README / Resources/doc/ — may be the only documentation.
  • DependencyInjection/Configuration.php — the authoritative list of config keys and defaults.
  • Resources/config/*.yaml (or services.php) — shows the mapping name, the entity class, and which services are public/aliased.
  • Entity/ or Model/ — the MediaObject class, its file property, and the Vich mapping annotations/attributes.
  • composer.json — required Symfony and Vich versions; this tells you immediately whether it will even install on your stack.

Implementation Patterns

Attach media to your domain entity. The intended pattern is a Doctrine relation to the bundle's media entity (or to a mapped superclass you extend):

#[ORM\ManyToOne(targetEntity: MediaObject::class, cascade: ['persist', 'remove'])]
#[ORM\JoinColumn(nullable: true)]
private ?MediaObject $image = null;

Cascade persist so the file row is written with your entity, and remove so deleting the owner cleans up the row. Whether the physical file is deleted depends on Vich's Doctrine listener being active for the mapping — verify it, don't assume.

Controller + form. The everyday flow is a standard Symfony form with a FileType bound to the media object's file property, then $form->handleRequest() → $em->persist() → $em->flush(). The interesting part happens in Vich's prePersist/preUpdate listeners, so your controller stays boring — that's the whole value proposition.

Rendering. Use vich_uploader_asset(media, 'file') rather than hand-building the path from uri_prefix. Hand-built paths are the #1 source of broken images when the prefix or a LiipImagine filter changes. If you need thumbnails, layer LiipImagineBundle on top and point the filter's loader at the same Vich mapping.

Collections of media. If your bundle's entity supports a mapping discriminator, use it for multiple collections (avatars vs. documents) so each gets its own directory and namer. If the configuration is hard-coded to a single mapping name, you're better off overriding the Vich config in your app rather than fighting the bundle.

Storage abstraction. For local dev this bundle almost certainly targets the local filesystem. For S3/GCS, swap Vich's storage service for the Flysystem implementation in your own config; keep the change at the config layer so the entity and controllers don't care.

Testing. Point the mapping at a temporary directory in config/packages/test/. Create uploads with new UploadedFile($path, 'name.jpg', 'image/jpeg', null, true) (test mode = true). Assert on the persisted row and on file_exists() of the stored path; don't assert on the exact generated filename unless you've pinned the namer.

Keep an exit hatch. Because the package is config-only, wrap access behind your own service or repository (e.g. MediaManager::attach()), and type-hint against your domain rather than importing MediaObject everywhere. If the bundle is abandoned, you inline ~40 lines of Vich YAML and delete your wrapper implementation.

Gotchas and Tips

  • Distribution risk is the headline issue. Zero stars, zero dependents, Bitbucket-only, no Packagist presence: expect no semver tags, no CI, no issue tracker activity. Pin to an exact commit ("x-one/media-object-bundle": "dev-master#abc1234") so a force-push doesn't silently change your upload behavior. Vendoring the config into config/packages/vich_uploader.yaml and dropping the dependency is a legitimate first-week decision.
  • uri_prefix vs. upload_destination mismatch produces images that render a 200-looking path but 404. Dump the resolved config (bin/console config:dump-reference vich_uploader for the schema, and inspect your container parameters) and cross-check against where files actually landed on disk.
  • inject_on_load. Without it, re-saving an entity that already has a file can blank the property, because the form's file field is empty on edit. Enable it so Vich re-hydrates the existing file object during load.
  • Namer collisions and path traversal. If the configured namer preserves the original client filename, you inherit duplicate-name overwrites and hostile filenames. Switch to a hash/UUID namer and always validate with Symfony's File constraint (max size + mimeTypes), since clients lie about extensions.
  • Orphaned files. Removing the row and deleting the bytes on disk are two separate concerns in Vich. Test the delete path explicitly — especially when media is referenced by several owners or removed via cascade. Also note files written before a transaction rollback stay on disk; a periodic reconciler is cheap insurance.
  • Mapping name coupling. The classic errors are Mapping not found for field "file" and "file could not be found for field". Both mean your entity's Vich annotation and the bundle's configured mapping name disagree. Use bin/console debug:container to find the storage/namer service IDs actually registered.
  • Symfony version drift. A bundle with no dependents is likely written against an older Symfony. Watch for annotation-only Doctrine mappings if you're on PHP 8 attributes, AppKernel-era service definitions, and any use of removed UploadedFile APIs or getClientOriginalName() sanitization assumptions.
  • Config precedence. If the bundle prepends its Vich config, your app config wins and you can override paths and namers cleanly. If it sets it in an extension load(), your overrides may silently lose. Read DependencyInjection/*Extension.php before spending an hour debugging why your uri_prefix change did nothing.
  • Extending the entity. If the bundle maps a concrete MediaObject rather than a MappedSuperclass, subclassing it with Doctrine inheritance is painful. Prefer composition (a relation) over inheritance unless the repo explicitly documents the superclass approach.
  • Security default. Files under a public uri_prefix are world-readable by URL. For invoices, ID scans, or anything tenant-scoped, store outside the web root and stream through a controller with a voter check — don't rely on unguessable filenames.
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.
frankyso/ipaymu
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