kunstmaan/media-bundle
KunstmaanMediaBundle adds media management to Symfony CMS applications, letting editors upload, organize, and store images and files through an admin interface. It includes thumbnail generation, media folders, and integration with the Kunstmaan admin bundle for managing assets across your site.
Reality check first. kunstmaan/media-bundle is a Symfony 2.x-era bundle, not a Laravel package. It has no service provider, no config publish tag, and its entities are Doctrine ORM classes with annotations. You cannot composer require it into a Laravel app and get anything useful. Its day-to-day value is: (a) if you maintain a legacy Kunstmaan Bundles CMS project, or (b) as a reference architecture you port into Laravel. The 38 stars are misleading — this is a split-out sub-bundle of Kunstmaan/KunstmaanBundlesCMS, where the real community lives.
Minimal install (Symfony 2.x/3.x host).
composer require kunstmaan/media-bundle — but check composer.json first; the 2014 release pins Symfony 2.3–2.7-era constraints and PHP 5.x. Expect dependency hell on anything modern.AppKernel: new Kunstmaan\MediaBundle\KunstmaanMediaBundle(),kunstmaan_media:
resource: "@KunstmaanMediaBundle/Resources/config/routing.yml"
prefix: /admin/media
bin/console config:dump-reference kunstmaan_media — keys drift between versions):
kunstmaan_media:
aviary_api_key: "%aviary_key%" # see gotchas: this is dead
use_symfony_file_storage: true
# remote_video / soundcloud keys exist in some versions
doctrine:schema:update or a migration) — you need at least the media and folder tables.assets:install --symlink for the media chooser JS/CSS, plus LiipImagineBundle config for your filter sets.Where to look first, in this order:
Entity/Media.php and Entity/Folder.php — the actual data model (name, contentType, location, url, metadata JSON, folder, file transient property).Helper/MediaManager.php (or Service/MediaManager.php depending on version) — the façade: prepareMedia, saveMedia, deleteMedia, getHandler, moveMedia.Helper/*/*Handler.php — one strategy class per media type (File, Image, Video, SlideShare, Snippet). This is the real meat of the bundle.Form/Type/MediaType.php + Controller/ChooserController.php — the upload/chooser UX.Twig/MediaTwigExtension.php — how templates resolve media to URLs.First use case: attach an uploaded image to an entity, persist it through the manager, and render a thumbnail in Twig.
$media = new Media();
$media->setFile($uploadedFile); // transient UploadedFile
$media->setName($uploadedFile->getClientOriginalName());
$media->setContentType($uploadedFile->getMimeType());
$mediaManager->prepareMedia($media); // handler sets location + url
$mediaManager->saveMedia($media); // persist + flush
{# confirm exact filter names with: bin/console debug:twig | grep media #}
<img src="{{ media|media_thumb('pagepart_thumb') }}" alt="{{ media.name }}">
1. Single media relation (the 90% case). Map a ManyToOne to Kunstmaan\MediaBundle\Entity\Media on your entity and use the bundle's form type in the admin. The form type renders the chooser modal plus a file input, so editors can pick existing media or upload new.
/**
* @ORM\ManyToOne(targetEntity="Kunstmaan\MediaBundle\Entity\Media")
*/
protected $image;
$builder->add('image', MediaType::class, ['required' => false]);
2. Programmatic imports (CSV feeds, API sync, migration scripts). Always go through MediaManager, never $em->persist($media) directly — the handler is what computes location/url and generates image derivatives. Use prepareMedia() before persisting; it is safe to call in a loop inside a console command, but flush in batches rather than per item.
3. Galleries. Use ManyToMany with a join table plus a collection form of MediaType, 'allow_add' => true, 'by_reference' => false. Ordering, if you need it, lives in your join entity — the bundle has no ordering concept.
4. The chooser inside custom admin screens. ChooserController exposes index/chooser/bulk-upload actions; the JS opens a modal and hands back the media ID(s). If you build your own controller, follow the pattern of returning JSON with id, url, thumb and let the existing Dropzone endpoints (BulkUploadController) do uploads — don't roll your own uploader beside it, or files land outside Media.location conventions.
5. Custom handler (the main extension point). For SVG, PDF previews, S3-backed files, or a DAM sync, implement MediaHandlerInterface and register it with the manager (via the bundle's compiler pass/service tag, or $mediaManager->addHandler($handler) in old versions). Give it a type alias, then reference that alias in the kunstmaan_media.media_types config so the admin gets an icon, form type, and templates. A cloud handler typically stores the object key in location and overrides getUrl() to return a CDN URL.
6. Twig rendering. Let LiipImagine do the resizing — the image handler just resolves the original and the filter set does the rest. Keep filter sets in config.yml and reference them by name; never render media.url for large images in listings.
7. Laravel port (if that's your actual goal). The bundle's design maps cleanly:
| Kunstmaan | Laravel equivalent |
|---|---|
Media entity + metadata JSON |
Eloquent model with $casts = ['metadata' => 'array'] |
MediaManager façade |
Singleton service / MediaService |
| Handler-per-type strategy | Strategy classes resolved by MIME (container bindings or a MediaHandlerRegistry) |
Gedmo nested-set Folder |
kalnoy/nestedset or plain parent_id |
| LiipImagine filter sets | intervention/image + named presets |
| Chooser modal + Dropzone | Livewire/Blade component |
| Whole bundle | spatie/laravel-medialibrary gets you 80% there |
If your day job is Laravel, the honest leverage is: copy the handler abstraction and media_types config idea, not the code.
getRequest()-style controllers make it non-portable to Symfony 5+ too. Treat the 2014 tag as frozen.aviary_api_key and the "advanced image editing" selling point are non-functional. If you need crop/rotate, use LiipImagine runtime filters or a JS cropper. Budget for removing the Aviary button from the admin templates so editors don't click into a broken iframe.MediaManager::getHandler() resolves by contentType. Uploads producing image/svg+xml, application/pdf, or an empty MIME (common with UploadedFile::getMimeType() on some servers) blow up or fall back to the file handler. Set contentType explicitly from a trusted mapping when importing, and register handlers for every type you accept.Media row does not reliably delete the physical file — it depends on the handler's removeMedia() implementation. Run the bundle's maintenance commands periodically (bin/console list kunstmaan_media for the cleanup/thumbnail-regeneration names in your version) and monitor the upload directory size.liip_imagine.filter_sets throws at render time. After adding filters, clear the cache and regenerate thumbnails; check write permissions on the cache directory (web/media/cache in old setups).Entity/Folder.php relies on the Gedmo tree extension (nested set). Without the listener registered, folder moves silently corrupt treeLeft/treeRight and the chooser shows a broken tree. Verify the annotations in Entity/Folder.php against your stof_doctrine_extensions setup.ROLE_ADMIN at minimum) and add your own MIME/size validation — the config's allowed-type list is not exhaustive and Dropzone posts straight to the controller.assets:install, cache:clear, and hard-refresh.config:dump-reference kunstmaan_media for real config keys → debug:container | grep kunstmaan_media to confirm the manager and handler tags are wired → inspect the media row's contentType, location, url after a failed upload, since 90% of "it doesn't show" bugs are a wrong location or a url pointing at a non-web-accessible path.MediaHandlerInterface, custom form types per media type, and the media_types config map. Everything else in the bundle is UI you'll likely replace.How can I help you explore Laravel packages today?