enhavo/app-bundle
Enhavo App Bundle is a subtree split of the open-source Enhavo CMS, a PHP project built on Symfony for flexible software that handles complex data structures through a clean and usable interface.
This repository is a read-only subtree split — install the package, don't clone it. All issues and pull requests belong in enhavo/enhavo.
Start from a real Symfony app. enhavo/app-bundle is not a standalone application; it's the glue layer of the Enhavo CMS. A bare composer require enhavo/app-bundle in an empty project will give you services and routes but nothing usable. Prefer the main enhavo/enhavo skeleton (or copy its config/), then composer require the bundle stack you need.
Install and register.
composer require enhavo/app-bundle
With Flex, registration in config/bundles.php is usually automatic. Without Flex, add it manually:
Enhavo\Bundle\AppBundle\EnhavoAppBundle::class => ['all' => true],
Configure. Create/append config/packages/enhavo_app.yaml:
enhavo_app: ~
Most of the meaningful configuration here is references into other bundles (templates, menu, assets, routes), so expect to fill it in only as you add enhavo/resource-bundle, enhavo/routing-bundle, enhavo/block-bundle, etc.
Wire routes. Import the bundle's routing (the exact filename varies by version — list vendor/enhavo/app-bundle/src/Resources/config/ first):
# config/routes/enhavo_app.yaml
enhavo_app:
resource: '@EnhavoAppBundle/Resources/config/routing.yaml'
prefix: /admin
Build assets. Enhavo's admin UI is Webpack Encore-based. If the backend renders unstyled, you skipped this step. Run the project's yarn install && yarn encore dev (or copy the prebuilt public/build/ manifest) and make sure the asset manifest path matches your framework.assets config.
First real use case: add an admin module. In practice that means (a) a resource definition via enhavo/resource-bundle, (b) a route generated by enhavo/routing-bundle, and (c) a menu entry registered through the menu extension tag. Once that CRUD screen renders and saves, you've exercised every part of AppBundle that matters day-to-day.
Where to look first: the parent repo's docs/, then vendor/enhavo/app-bundle/src/Resources/config/ to see the actual service definitions and tags. The README here is a stub; treat the source as the documentation.
Compose, don't fork. AppBundle is the orchestrator — the Resource, Routing, Block, Media, User, and Dashboard bundles do the work. When you need a feature, the idiomatic move is a small in-house bundle that requires enhavo/app-bundle and hooks in via services and tags, not a patch to vendor code.
Tag-driven UI extension. Menus, toolbars, widgets, and dashboard blocks are conventionally contributed by tagging a service (the tag names are the fastest way to learn the extension surface):
bin/console debug:container --tags | grep enhavo
Then implement the matching extension interface and register:
services:
App\Admin\ReportsMenu:
tags: ['enhavo.menu'] # verify exact tag name against src/DependencyInjection
Template overrides before code overrides. Enhavo resolves views through its viewer/theme layer with Symfony's standard bundle override paths. For look-and-feel changes, shadow a template in templates/bundles/EnhavoAppBundle/... (or your app's theme directory) rather than subclassing controllers. Only dip into services when you need behavior, not markup.
Keep admin and frontend routing separate. Admin routes want the /admin prefix plus your security firewall; frontend routes go through the routing bundle's generators so slugs and redirects stay managed. Mixing them into one import is the usual cause of "why is my login page at /admin/login returning a 404 from a different firewall".
Build features as vertical slices. One resource + one route config + one menu entry + one Twig template, shipped together in a single small bundle. This matches how the upstream bundles are structured and makes upstream upgrades far less painful.
Debug with the container, not with dump(). Metadata in Enhavo is frequently compiled into the container at build time — debug:config, debug:container --tag=…, and debug:router will answer most "where does this come from" questions faster than dumping inside a controller.
"enhavo/app-bundle": "~5.0" or whatever the app skeleton pins) because the community is small and older minor releases do churn APIs.enhavo/app-bundle, resource-bundle, routing-bundle, etc. are released in lockstep. A mismatch is the number-one source of cryptic DI errors. Use composer show 'enhavo/*' and composer why-not enhavo/app-bundle <version> when resolving conflicts.vendor/. Changes vanish on the next composer update and PRs against this split will be closed. If you need a hotfix, use cbaldwinitis/composer-patches-style patching (or symfony/flex's patch support) and open the PR upstream against enhavo/enhavo.bin/console cache:clear is the reflex.public/build/, that framework.assets.json_manifest_path points at it, and that you actually ran the build. This bites nearly every first-time installer.security.yaml pattern/form_login.check_path, and remember the route names are namespaced (bin/console debug:router | grep enhavo).enhavo_app: key against src/DependencyInjection/Configuration.php in the installed version before trusting a blog post or older docs page.services.yaml alias. Avoid the last one unless you've read the upstream service definition; many services are intentionally private or lazily wired.bin/console debug:config enhavo_app, bin/console debug:event-dispatcher, bin/console lint:twig templates/, and bin/console debug:container --tag=enhavo.menu (adjust tag). These resolve ~80% of "how does Enhavo do X" questions without leaving the terminal.How can I help you explore Laravel packages today?