zenstruck/media-bundle
Zenstruck Media Bundle integrates file attachments with Doctrine entities in Symfony, letting you associate, store, and serve media through a reusable Media entity and configurable storage providers with minimal setup.
Before writing any integration code, internalize the constraints: this bundle targets Symfony 2.x, its last tagged release is April 2014, and the repository is archived with effectively zero adoption (1 star, 0 dependents). On a genuine Symfony 2.x project it is still installable; on anything newer, treat it as a reference implementation to copy from rather than a dependency to add.
1. Install and pin it.
composer require zenstruck/media-bundle
Pin the version (~1.0 or the exact 2014 tag). There will never be another release, so an unpinned dev-master is a guaranteed future breakage.
2. Register the bundle in app/AppKernel.php:
$bundles = array(
// ...
new Zenstruck\Bundle\MediaBundle\ZenstruckMediaBundle(),
);
3. Where to look first — in this order. The 2014 README will be stale relative to the code, so read the source:
DependencyInjection/Configuration.php — the authoritative list of config keys (upload dir, web path, storage/thumbnail options).Resources/config/routing.* — the routes the GUI exposes; you must import them yourself.Controller/ — the actions behind the browser/upload endpoints, which tell you exactly what the GUI expects and returns.4. Minimal configuration. Point the manager at a writable directory and the URL path that serves it:
# app/config/config.yml
zenstruck_media:
upload_dir: %kernel.root_dir%/../web/media
web_path: /media
(Confirm the exact key names against Configuration.php for your pinned version.)
5. Expose the GUI by importing the bundle routing:
# app/config/routing.yml
zenstruck_media:
resource: "@ZenstruckMediaBundle/Resources/config/routing.xml"
prefix: /admin/media
6. First use case — the core loop. Hit /admin/media, upload a file, and confirm it lands in upload_dir and is served under web_path. Upload-through-the-GUI, get-back-a-usable-URL is the entire value proposition — validate it end to end before wiring anything else.
Standalone admin asset library. The most direct use is an internal media browser. Import the routes under an admin prefix and put the whole thing behind your firewall (access_control requiring ROLE_ADMIN). The bundle has no auth of its own, so this is mandatory.
File picker inside a form. The classic day-to-day workflow was letting an editor attach an existing file to a content entity. Rather than fight the bundle's UI, use its media manager as a backend and build a small custom form type that links to the browser and stores the selected path (a string column), not a file reference. Coupling your entity to a path/URL rather than to bundle internals means you can rip the bundle out later without a data migration.
Upload from your own controllers. Don't route every upload through the GUI. Pull the manager service from the container (verify the id in Resources/config/services.xml) and call it from your own action so you own validation, filename normalization, and persistence:
$manager = $this->get('zenstruck_media.manager'); // confirm id in services.xml
$path = $manager->save($uploadedFile); // confirm signature in source
This keeps the bundle a thin storage abstraction and business logic where it belongs.
WYSIWYG / CKEditor integration. Media bundles of this vintage were typically built to act as a "Browse Server" popup for a rich-text editor, calling back with the chosen URL. Point the editor's file-browser URL at the imported route and ensure your editor config sends the callback parameter the controller actually expects — read the controller rather than guessing.
Consistent URL rendering in Twig. Don't scatter web_path string concatenation across templates. Wrap URL/thumbnail construction in a small Twig extension or macro (or the bundle's own helper, if present) so full-size and thumbnail URLs are generated the same way everywhere.
Template overrides as the extension point. Anything under Resources/views can be overridden by placing a file at the same relative path under app/Resources/ZenstruckMediaBundle/views/. This is the sanctioned way to restyle the GUI without forking the bundle.
Archived and unmaintained — plan for it. No security fixes, no compatibility updates, and 0 dependents means essentially no community-vetted usage. Don't build new functionality on it; if you inherit it, isolate it behind your own service interface so a future replacement is a one-file change.
Symfony 2.x only. It predates Symfony 3's directory layout, ::class constants, and autowiring. It won't install or boot on Symfony 3+/4+, and its constraints will conflict with modern packages. Don't try to force it into a modern app.
PHP version ceiling. Written for PHP 5.3/5.4 — expect failures on PHP 7+ (removed/deprecated functions) and certainly on PHP 8. Run it only on a matching legacy PHP.
Config keys are the source of truth. If a key is rejected or silently ignored, open DependencyInjection/Configuration.php and confirm the exact name and nesting for your pinned commit.
Trailing slashes and web paths bite. Mismatches between upload_dir and web_path are the top cause of "uploaded but 404s in the browser." Confirm the upload directory is inside your document root (or symlinked there) and that web_path matches what the web server actually serves.
Routing is not automatic. Forgetting the routing.yml import is the classic "bundle installed but everything 404s" — the bundle deliberately doesn't inject routes.
No built-in access control. Secure the routes yourself. An unauthenticated upload endpoint is a direct path to stored XSS and disk-fill abuse.
Debugging the manager. Use php app/console container:debug zenstruck_media for real service ids and container:debug --parameters for resolved config. Manager method signatures shifted across the handful of 2014 commits — verify save()/getUrl()-style calls against the source instead of trusting tutorials.
Extension points worth knowing: template overrides under app/Resources/ZenstruckMediaBundle/, the DI service definitions you can decorate or replace, and the config tree for storage/thumbnail settings. There's effectively no event system, so anything beyond these means wrapping or editing the bundle's classes.
Have an exit plan. Given the score and archived status, the honest day-to-day advice is: use it as a design reference for a media browser, but back any production dependency with an abstraction layer — or replace it outright with a maintained alternative — so this 2014 code never becomes load-bearing.
How can I help you explore Laravel packages today?