Request lifecycle overview
Compile-time instrumentation
When Twig compiles a template,
TwigInspectorExtension registers DebugInfoNodeVisitor as a node visitor. The visitor walks the compiled AST and wraps every ModuleNode (template) and BlockNode (block) with NodeStart / NodeEnd nodes that call into HtmlCommentsExtension at render time.Cookie-based activation check
At render time
HtmlCommentsExtension::shouldInspect() runs before any output buffering. It checks:kernel.debugistrue(production is skipped unconditionally)- The current request carries the inspector cookie (default name
twig_inspector_is_active) set totrue - The template extension is in
enabled_extensions(default.html.twig) - The template and block name are not in any exclusion list
HTML comment injection
When the inspector is active, Nesting is tracked via
HtmlCommentsExtension wraps each block’s rendered output with a pair of HTML comments that include the template name, a link to the open-in-IDE route, a unique ID, and a box-drawing nesting prefix:nestingLevel and BoxDrawings so deeply nested blocks get distinct visual indicators in DevTools.Controller comment injection
ControllerCommentSubscriber listens to kernel.response at priority -512. When the inspector cookie is present it injects a controller comment directly after the <body> tag for the main request, and wraps each render(controller(...)) sub-request response with start/end comments:JavaScript overlay
The toolbar panel loads a JavaScript overlay. The overlay scans the live DOM for the injected HTML comments, maps comment pairs to the surrounding HTML elements, and attaches hover highlights and a tooltip popup showing the template name(s) for each element.
Click to open in IDE
Clicking a highlighted element fires a request to the bundle route
/_template/{template}?line={n} (route name: nowo_twig_inspector_template_link). OpenTemplateController validates the path, resolves it through the Twig loader, and returns a redirect to the IDE URL scheme configured in framework.ide (e.g. phpstorm://open?file=…&line=…).Web Profiler data collection
TwigInspectorCollector implements DataCollectorInterface and LateDataCollectorInterface. During collect() it parses the HTML comments from the response body to count template and block render occurrences. During lateCollect() it reads render durations from the Twig ProfilerExtension. ControllerRenderSubscriber listens to kernel.controller to record every controller invocation (main + fragments) so the profiler panel can display them with Main / Fragment badges.Component reference
Twig/
NodeVisitor pipeline.
TwigInspectorExtension registers DebugInfoNodeVisitor with Twig. The visitor injects NodeStart / NodeEnd nodes at compile time. At render time HtmlCommentsExtension is called by those nodes to produce or skip HTML comments based on the current request context.EventSubscriber/
Kernel event listeners.
ControllerCommentSubscriber injects controller-boundary comments into responses when the inspector is enabled.
ControllerRenderSubscriber records every controller call (main request and render(controller(...)) sub-requests) so the profiler panel can display them.DataCollector/
Web Profiler panel data.
TwigInspectorCollector parses injected HTML comments to count template and block renders, fetches render timings from the Twig profiler, and exposes controller usage — all surfaced in the Symfony Web Profiler under the </> icon.Controller/
IDE redirect endpoint.
OpenTemplateController handles GET /nowo-twig-inspector/open/{template}. It validates the template name against path traversal, resolves the absolute file path through the Twig loader, and redirects to the IDE URL produced by Symfony’s FileLinkFormatter.Command/
Installation helper.
InstallCommand (nowo:twig-inspector:install) creates the config/packages/nowo_twig_inspector.yaml configuration file and registers the bundle routes in config/routes.yaml. Run this when Symfony Flex is not available.RequestStack/
Sub-request detection.
MainOrMasterRequestProvider and RequestStackMainOrMasterAdapter abstract over the Symfony RequestStack API to reliably identify the main (master) request across Symfony 6, 7, and 8 — used by both event subscribers to distinguish main requests from fragment sub-requests.Cookie-based activation
The inspector is toggled by a browser cookie rather than a config flag. This means you can switch it on and off in the browser without restarting the server or changing any files."true" (or PHP truthy). The Symfony Web Profiler toolbar injects a checkbox that sets and removes the cookie for you.
When the cookie is absent,
HtmlCommentsExtension::shouldInspect() returns false immediately. The DebugInfoNodeVisitor has already compiled the NodeStart / NodeEnd calls into the template, but those calls are no-ops — Twig’s template cache is unaffected.Performance when disabled
The bundle is designed to impose no measurable cost when the inspector is off:HtmlCommentsExtension::shouldInspect()first checks$this->debug. Ifkernel.debugisfalse(production build), the method returnsfalsebefore touching the request stack.- No output buffering (
ob_start) is started unless the check passes. ControllerCommentSubscriberperforms the samedebugflag check at the top ofonKernelResponse().TwigInspectorCollector::collect()short-circuits immediately when the cookie is absent.
