Skip to content

Take the debugbar out of the app flow and the packages that feed it #539

Closed

Description

@armanist

Summary

Take the debugbar out of the app flow and out of the packages that feed it: no web stage, no view, no logger and no mailer calls the debugger any more. Each package keeps its own state, and the debugger collects what it shows when the toolbar renders: the registered event listeners, the matched route together with the rendered view, the database query log, the log messages and the mailer transport log.

It also brings back three tabs that lost their data: the routes tab has shown only the view path since 3.0.0, the queries tab has been empty since 2.6.0, and the SMTP exchange never reaches the mails tab because SmtpAdapter checks a config key that is never set.

Why

The App package runs the boot and the request, the View package renders templates, the Logger writes logs and the Mailer sends mail; none of them should know about one optional integration. Today two web stages, View, the debug-mode logger and the mailer call or target the debugger directly, so the debugbar is wired into every request, every rendered view, every log line and every mail whether a project uses it or not.

Before the 3.0 routing rewrite, the router wrote the matched route (method, pattern, module, controller, action, middlewares, params) into the routes tab, and View added the view path to that entry. The rewrite dropped the router's write, so the tab now holds only View => <module>/Views/<file>.

The queries tab was filled with Database::queryLog() next to the route data until the 2.6.0 hooks rework removed that write. Since then nothing writes to it, although Idiorm still logs the queries whenever app.debug is on.

Current state (checked on master)

Place What it does
InitDebuggerStage (web boot) calls debugbar()->initStore(), which creates the five tabs as empty arrays
PrepareRequestStage (web runtime, matched routes only) writes event()->getRegistered() into the events tab when the debug bar is enabled
View (gets the Debugger injected by ViewFactory) after rendering a view, merges View => <module>/Views/<file> into the routes tab
MessageAdapter (the logger in debug mode) calls debugbar() and writes each log line into messages, or into the tab named by $context['tab']
MailerTrait::send() logs the transport errors of a failed send with warning(..., ['tab' => Debugger::MAILS])
SmtpAdapter sends the SMTP exchange to the same tab, but only when config()->get('debug') is set, a key that does not exist (app.debug is the debug flag), so it never does
nothing writes to the queries tab
  • The store does not need initStore(): DebuggerStore::set() creates a missing tab on the first write, and get() and render() read a missing tab as []. Nothing in src relies on the tabs existing beforehand.
  • The toolbar is rendered by the module layouts (debugbar()->render()), while the request is still being dispatched, so the matched route is still on the request at that point.

Proposed change

  1. Remove InitDebuggerStage from the web boot, with no replacement.

  2. Remove the debugbar write from PrepareRequestStage.

  3. View no longer depends on the debugger: it keeps the name of the last rendered view (getViewFile(), like getLayout()), and ViewFactory no longer passes debugbar().

  4. MessageAdapter becomes an in-memory logger: report() keeps [level => message] and getMessages() returns them. It no longer imports or calls the debugger.

  5. The mailer keeps its own transport log: MailerInterface::getTransportLog(), implemented in MailerTrait, returns the transport errors of failed sends and, in debug mode, the SMTP exchange. The failed-send warning stays in the log, without the tab. SmtpAdapter checks is_debug_mode().

  6. Debugger::render(), when the debug bar is enabled, fills these tabs itself right before it builds them:

    • events: the listeners from event()->getRegistered()
    • routes: from request()->getMatchedRoute() the methods, pattern, module, controller and action (or a closure), name, group, prefix, middlewares and params, and the view path from the existing View (<module>/Views/<file>); with no matched route and no rendered view the tab stays empty
    • queries: each query from Database::queryLog(), only when the request already used the database (db() was resolved) with a relational adapter; the debugbar never opens a connection itself, and SleekDB has no query log
    • messages: the messages of the debug-mode logger (the boot always builds the logger for the error handler)
    • mails: the transport log of mailer(), only when the request used the mailer (like the messages tab reads logger())

    The collected entries go straight to the tab's collector, after the tab's stored entries. The store keeps only what code writes into it directly (debugbar()->addToStoreCell()), so those writes are never cleared or mixed with collected data. The debugger never builds a mailer or database connection the request did not use.

After this, no package outside src/Debugger references Quantum\Debugger; only the module layout templates call debugbar()->render().

No event listener is involved. A listener would need a place to be registered: the framework helper files load in alphabetical order (Debugger before Event) and only once per process, and a boot stage that registers the listener would be the same hardcoding in another form. Reading the state at render time needs none.

Behavior changes

Case Today After
routes tab only View => <module>/Views/<file> the matched route's data and the view path
queries tab always empty the queries the request ran
events tab the listeners registered when the request was prepared the listeners registered when the page renders, so listeners added later (for example in a controller) are listed too
messages tab each log line written to the store as it is logged the logger keeps the lines; the tab reads them when the toolbar renders
DebuggerStore after rendering holds the events, routes and log lines holds only the direct writes
mails tab transport errors of failed sends; never the SMTP exchange transport errors and, in debug mode, the SMTP exchange
messages tagged with another tab (['tab' => ...]) written to that tab listed in the messages tab
DebuggerStore before any write the five tabs exist as empty arrays a tab exists once something writes to it; reading or rendering is the same
a request that renders no layout the events tab is filled but never shown nothing is written

Breaking changes

  • InitDebuggerStage is removed (in 3.0.3 it was Quantum\App\Stages\InitDebuggerStage). Code that ran it in its own boot can call debugbar()->initStore() instead; Debugger::initStore() stays.
  • View::__construct() no longer takes a Debugger: it is (Renderer, AssetManager, ViewCache). Code that gets the view through view() or ViewFactory is unaffected; code that creates a View itself drops that argument.
  • MailerInterface gains getTransportLog(): array. The built-in adapters get it from MailerTrait; a custom adapter that implements the interface without the trait adds the method.
  • MessageAdapter no longer reads $context['tab']: code that logged into another debugbar tab that way now lands in messages.

Tests

  • Debugger::render() with the debug bar enabled:
    • lists the registered event listeners in the events tab
    • fills the routes tab for a matched controller route, for a closure route, and with the rendered view path
    • leaves the routes tab empty without a matched route and without a rendered view
    • lists the queries the request ran in the queries tab, and leaves it empty, without connecting, when the database was not used or the adapter has no query log
    • lists the logger's messages in the messages tab, and the mailer's transport log in the mails tab, without building a mailer the request did not use
    • passes a tab's stored entries before the collected ones, and leaves the store unchanged
    • passes nothing to the tabs when the debug bar is disabled
  • View::getViewFile() returns the last rendered view.
  • MessageAdapter::getMessages() returns the reported messages.
  • getTransportLog() returns the transport errors of a failed send, and the SMTP exchange in debug mode.

Acceptance criteria

  • no package outside src/Debugger references Quantum\Debugger or calls debugbar(), except the module layout templates that render the toolbar
  • the routes tab shows the matched route and the view path, the queries tab the queries the request ran, the messages tab the log messages, the mails tab the mailer transport log, and the events tab the listeners registered when the toolbar renders
  • composer test, composer cs:check, composer phpstan and composer rector:check pass

Notes

Relevant code:

  • src/App/Adapters/Web/WebAppAdapter.php
  • src/App/Adapters/Web/Stages/Boot/InitDebuggerStage.php
  • src/App/Adapters/Web/Stages/Runtime/PrepareRequestStage.php
  • src/View/View.php, src/View/Factories/ViewFactory.php
  • src/Logger/Adapters/MessageAdapter.php, src/Logger/Factories/LoggerFactory.php
  • src/Mailer/Contracts/MailerInterface.php, src/Mailer/Traits/MailerTrait.php, src/Mailer/Adapters/SmtpAdapter.php, src/Mailer/Factories/MailerFactory.php
  • src/Debugger/Debugger.php, src/Debugger/DebuggerStore.php
  • src/Database/Database.php, src/Database/Contracts/RelationalInterface.php

Depends on:

  • #536
  • #538

Activity

  1. added this to the 3.1.0 milestone on Jun 18, 2026
  2. added
    debugbarDebugbar and debugger integration
    hooksHook and event system
    on Jun 20, 2026
  3. changed the title [-]Refactor debugbar into an event-driven optional integration and remove hardcoded framework coupling[/-] [+]Take the debugbar out of the app flow[/+] on Oct 21, 2026
  4. self-assigned this
    on Oct 21, 2026
  5. changed the title [-]Take the debugbar out of the app flow[/-] [+]Take the debugbar out of the app flow and the view[/+] on Oct 21, 2026
  6. changed the title [-]Take the debugbar out of the app flow and the view[/-] [+]Take the debugbar out of the app flow and the packages that feed it[/+] on Oct 21, 2026
  7. added 10 commits that reference this issue on Oct 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

debugbarDebugbar and debugger integrationhooksHook and event system

Type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions