jms/debugging-bundle
JMS Debugging Bundle adds a powerful debug toolbar and detailed exception pages to Symfony apps, with request profiling, database and log panels, and cleaner error output to speed up development and troubleshooting.
jms/debugging-bundle is a developer-only Symfony2 (2.0–2.2 era) bundle that layers introspection tooling on top of the standard framework. Its most useful surface is console-driven: dumping the service/object graph so you can see what was instantiated and what it references, rather than reverse-engineering that from XML/YAML service definitions. Treat it as a dev-console companion to the profiler, not a replacement.
Minimal setup — pin it as a dev dependency so it never lands in a production build:
// composer.json
"require-dev": {
"jms/debugging-bundle": "dev-master"
}
// app/AppKernel.php
public function registerBundles()
{
$bundles = array(/* ... */);
if (in_array($this->getEnvironment(), array('dev', 'test'))) {
$bundles[] = new JMS\DebuggingBundle\JMSDebuggingBundle();
}
return $bundles;
}
# app/config/config_dev.yml
jms_debugging: ~
Then clear the cache and enumerate what your pinned version actually ships:
php app/console cache:clear --env=dev
php app/console list jms
Where to look first: README.md, then Command/ — that directory is the documentation for a bundle this old, and it tells you the real command names and options. Follow with Resources/config/, which shows what gets wired into the container and under which service IDs (useful if you want to reuse the introspection services yourself). The CLI surface changed across releases, so always trust the installed code over blog posts or the README.
First use case: you inherit a service — a controller, a "manager", a repository wrapper — and want to know which collaborators it actually holds, how deep the dependency chain runs, and which services got dragged in as a side effect. Point the graph dump at that service ID and read the topology instead of sprinkling five var_dump()s and re-running the request.
Dev/test-only registration, always. This is the one non-negotiable pattern. Register the bundle inside an environment guard in AppKernel::registerBundles() and keep it in require-dev. The introspection plumbing registers services and does reflection work with no production value, and the commands assume a writable cache plus a bootable debug-mode kernel. Combine it with composer install --no-dev in your deploy pipeline so the class isn't even present in production autoload maps.
Investigate-before-refactor loop. The highest-value daily workflow is answering "why is this in my container?":
--env=dev --debug so the container is built in debug mode and not optimized away.php app/console debug:container <id>. The split matters: core shows you the definition (arguments, tags, class), the bundle shows you the live object and its actual references after instantiation. Discrepancies between the two are usually where the bug is — a tag, a compiler pass, or a setter injection you forgot about.Pair it with the profiler, don't replace it. The profiler gives you a per-request view (timing, queries, events, the container for one request). This bundle gives you a static/introspective view of object topology. Practical division of labour: use the profiler to find the slow or surprising request, then use the bundle to understand the object graph behind it — especially when the slowness is constructor-time work in a service that shouldn't have been instantiated at all.
Use it as a post-refactor auditing step. After moving services between files, splitting a "god" manager, or changing compiler-pass ordering, run the dump and diff the output against a saved snapshot from before the refactor. It catches services that are still instantiated eagerly (or now instantiated when they weren't) in a way that grepping config never will. Store the snapshots outside the repo, or the diffs become noise.
Reuse the command classes directly. If the shipped commands are close to what you need, instantiate them from your own console command or a small dev-only script rather than shelling out. They're plain Symfony Console commands, so setContainer() / run() on a CommandTester gives you the dump output as a string — handy for asserting "this service is no longer in the graph" in a smoke test.
Integration tip — keep the debug env honest. Because the bundle's value depends on debug mode, don't let it drift into a config.yml that applies everywhere. Keep the jms_debugging: ~ stanza in config_dev.yml only, and if you have a staging env that mirrors production, decide deliberately whether it goes there — usually not.
Cache clearing is not optional. Symfony2 compiles the container; changes to bundle registration, service definitions, or compiler passes will silently not appear until you cache:clear --env=dev. If the dump doesn't reflect your edit, that's the first thing to check — before assuming the bundle is broken.
Command names are version-dependent. The set of jms:* commands and their options changed across releases. Never hardcode a command invocation in a script or CI config without confirming it against the installed version via php app/console list jms. If you write internal docs, pin them to a commit.
PHP 8 and modern Symfony will break it. This targets PHP 5.3/5.4, PSR-0 autoloading, and Symfony 2.0/2.1 APIs removed in 3.0 and 4.0. On PHP ≥ 8.0 expect fatal errors from removed functions and legacy syntax if you vendor a copy. As a Composer dependency it will also conflict with anything requiring symfony/console ^5|^6 — resolution typically fails before runtime. If you need the behaviour on a modern stack, this repo is a specification, not a starting point: rebuild the commands against current Symfony Console or Artisan.
Namespace collision risk. The package lives under JMS\. If the same project ever pulls in jms/serializer (or other JMS packages), check for autoload and class-name collisions — PSR-0 resolution across two JMS\ roots is a real source of confusing "class not found" or wrong-class-loaded errors.
Never let the output reach a production log or HTTP response. Container dumps expose service class names and, depending on the command, constructor arguments — which can include DSNs, API keys, and mailer credentials passed as scalar arguments. Gate the bundle in code (registerBundles() guard, require-dev), not in a wiki page. A stray app/console invocation on a staging box with APP_DEBUG=true is enough to leak credentials.
Memory and speed on large containers. Reflecting over a few hundred services is fine; a container with thousands of definitions plus a populated object graph can be slow and memory-hungry on 2012-era PHP defaults. If the dump OOMs, raise memory_limit for that command only, or narrow the target to a single service ID instead of dumping the whole graph.
Expect thin docs — read the source. There is no maintained documentation and no community to escalate to (159 stars, zero dependents). When the README is ambiguous, Command/ and Resources/config/ are authoritative. Read composer.json constraints before attempting any install; the answer is almost always "hard incompatible."
Extension points are narrow but real. The introspection services are registered in the DI container, so you can fetch them by ID from your own dev-only command and format the output however you like (JSON, Graphviz .dot, a Blade/Twig partial). That's the cheapest way to get value if the built-in output doesn't fit — you're reusing the walking logic, not forking the bundle.
Set expectations with the team. For a Laravel codebase the day-to-day equivalents are php artisan about, php artisan route:list -v, php artisan config:show, app()->getBindings() in Tinker, and Telescope/Debugbar/Ray for request tracing. Keep this bundle as a pattern reference for container-graph and event-tracing UX; don't put it in composer.json and don't budget integration weeks for it.
How can I help you explore Laravel packages today?