diff --git a/v3.0/advanced-features/request-lifecycle.md b/v3.0/advanced-features/request-lifecycle.md index 22cb86d..8a06509 100644 --- a/v3.0/advanced-features/request-lifecycle.md +++ b/v3.0/advanced-features/request-lifecycle.md @@ -15,7 +15,7 @@ The request lifecycle is that connection. Follow one request, `GET /dashboard`, from the browser to the response: -1. Quantum boots the project: helpers, `.env`, config, the error handler, request and response, the debugger, and the modules with their routes. +1. Quantum boots the project: helpers, `.env`, config, the error handler, request and response, and the modules with their routes. 2. A browser's CORS preflight (`OPTIONS`) is answered right away with `204 No Content`. 3. The router looks for a route matching `/dashboard`. If there is none, the visitor gets a 404. 4. The route matched, so Quantum detects the language and prepares the view cache. @@ -39,7 +39,7 @@ Quantum checks the request against the routes the modules registered. If no route matches, the request ends in a 404. A browser gets your HTML error page, and an API client gets JSON; see [`page_not_found_response()`](../packages/http/helpers.md). -If a route matches, Quantum prepares what the rest of the request may need: it detects the language from the request (translations load on the first `t()` call), writes the registered event listeners to the debug bar when it is enabled, and sets up the view cache when it is enabled. A 404 does none of this work. +If a route matches, Quantum prepares what the rest of the request may need: it detects the language from the request (translations load on the first `t()` call) and sets up the view cache when it is enabled. A 404 does none of this work. ## 3. Middleware runs before the controller action diff --git a/v3.0/packages/app/adapters.md b/v3.0/packages/app/adapters.md index 339d073..4476962 100644 --- a/v3.0/packages/app/adapters.md +++ b/v3.0/packages/app/adapters.md @@ -24,7 +24,6 @@ The web adapter: - loads environment and app config - installs the error handler - registers request and response objects in DI -- initializes the debugger store - loads module routes into a `RouteCollection` - matches the current request and dispatches it through middleware diff --git a/v3.0/packages/app/architecture.md b/v3.0/packages/app/architecture.md index 22f1446..035583b 100644 --- a/v3.0/packages/app/architecture.md +++ b/v3.0/packages/app/architecture.md @@ -81,8 +81,7 @@ The full list of events and the order they fire in is in [Contracts](contracts.m 3. `LoadAppConfigStage` 4. `SetupErrorHandlerStage` 5. `InitHttpStage` -6. `InitDebuggerStage` -7. `LoadModulesStage` +6. `LoadModulesStage` ## Web runtime order @@ -91,7 +90,7 @@ The full list of events and the order they fire in is in [Contracts](contracts.m 1. `HandlePreflightStage`: `OPTIONS` requests get `204 No Content`, and the run stops 2. `ResolveRouteStage`: matches the request against the loaded module routes and stores the match on the request 3. `HandleRouteNotFoundStage`: a request without a match gets Quantum's not-found response, and the run stops -4. `PrepareRequestStage`: resolves the Lang package (translations load on first use), logs the registered events to the debugbar in debug mode, and sets up the view cache when it is enabled +4. `PrepareRequestStage`: resolves the Lang package (translations load on first use) and sets up the view cache when it is enabled 5. `DispatchRequestStage`: middleware wraps the route dispatch or the cached response lookup `SendResponseStage` is the closing stage: it appends the CORS headers, sends the response, and clears the request's route and input state, also when sending fails. It runs after a stop too, so the `204` and the not-found response are sent the same way. diff --git a/v3.0/packages/app/overview.md b/v3.0/packages/app/overview.md index 838f182..1123114 100644 --- a/v3.0/packages/app/overview.md +++ b/v3.0/packages/app/overview.md @@ -10,7 +10,7 @@ Use it when you need to start either a web request lifecycle or a console comman - `App` as the adapter-backed application object - `AppContext` for base-directory and DI-backed shared runtime access - built-in `web` and `console` adapters -- bootstrap stages for helpers, environment, config, HTTP, debugger, modules, and error handling +- bootstrap stages for helpers, environment, config, HTTP, modules, and error handling - path and utility helpers such as `base_dir()`, `modules_dir()`, `slugify()`, and `uuid_random()` ## Quick start diff --git a/v3.0/packages/debugger/architecture.md b/v3.0/packages/debugger/architecture.md index 899961d..707cb6f 100644 --- a/v3.0/packages/debugger/architecture.md +++ b/v3.0/packages/debugger/architecture.md @@ -1,34 +1,52 @@ # Debugger Architecture -The Debugger package follows a simple pipeline: +When a layout calls `debugbar()->render()`, the toolbar is built from two sources: the entries your code wrote with `addToStoreCell()`, and what the debugger reads at that moment from the packages that own the data. Nothing is pushed into the debugger while the request runs; each package keeps its own state and does not reference the Debugger package. -1. boot the shared debugger instance -2. initialize tab storage -3. let framework components append structured messages into store cells -4. replay those messages into DebugBar collectors during layout render +Reading at render time needs no wiring: there is no listener to register and no boot step. A tab also shows what is true when the page renders, for example an event listener a controller added late in the request. -## Runtime flow +## Render flow -### 1. Boot stage +`Debugger::render()`: -`Quantum\App\Adapters\Web\Stages\Boot\InitDebuggerStage` runs in the web boot pipeline and calls `debugbar()->initStore()`. +1. returns an empty string when `app.debug` is off, without collecting anything +2. collects the entries of each tab from the packages (see below) +3. for each of the five tabs, gives the tab's `MessagesCollector` its stored entries first and the collected entries after them +4. returns `renderHead()` and `render()` output from the DebugBar JavaScript renderer -It runs after helpers, environment, config, error handler, and HTTP initialization, but before modules are loaded; see the [web boot order](../app/architecture.md#web-boot-order). +The collected entries go straight to the collectors. `render()` never writes them into the store and never clears it, so the store keeps only what code wrote into it directly. -### 2. Producers write into the store +## Where each tab's data comes from -Several framework components write diagnostics into the same store: +| Tab | Collected from | Only when | +|---|---|---| +| `messages` | the debug-mode logger: `logger()->getAdapter()->getMessages()` | always in debug mode; the logger is built during boot for the error handler | +| `queries` | `Database::queryLog()`, one entry per query | the request started the database, it is still connected, and its adapter keeps a query log (SleekDB does not) | +| `routes` | the matched route (`request()->getMatchedRoute()`) and the last rendered view (`View::getViewFile()`) | a route matched or a view was rendered | +| `events` | `event()->getRegistered()` | at least one listener is registered | +| `mails` | the default mailer's `getTransportLog()` | the request used the mailer | -- `Quantum\Logger\Adapters\MessageAdapter` writes log messages into `messages` or a caller-provided tab -- `Quantum\App\Adapters\Web\Stages\Runtime\PrepareRequestStage` writes the registered event listeners (`event()->getRegistered()`) into `events` when the debug bar is enabled, once a route has matched -- `Quantum\View\View::updateDebugger()` rewrites the `routes` tab to include the resolved view file -- mailer code routes failures into the `mails` tab through logger helpers +The debugger never builds a database connection or a mailer the request did not use. -Each write is appended as `[$level => $data]`. +The `routes` entry holds the route's methods, pattern, module, controller and action (`Closure` for a closure route), name, group, prefix, middlewares and params, plus the view path; values that are not set are left out: + +```php +[ + 'Methods' => 'GET', + 'Pattern' => '/[:alpha:2]?/posts', + 'Module' => 'Web', + 'Controller' => 'Modules\Web\Controllers\PostController', + 'Action' => 'posts', + 'Name' => 'posts', + 'Params' => ['_segment0' => 'en'], + 'View' => 'Web/Views/post/post', +] +``` + +The `mails` tab reads only the default mailer (`mailer()`), so the transport log of another adapter does not appear there; a failed send is also logged as a warning, which reaches the `messages` tab. ## Store model -`DebuggerStore` keeps data in a private static array: +`DebuggerStore` keeps the direct writes in a private static array: ```php private static array $store = []; @@ -41,32 +59,10 @@ Important consequences: - `flush()` removes the entire store array - `get($key)` returns `[]` when the key does not exist -## Render model - -`Debugger::render()` loops over the built-in tab names and calls `createTab($tab)` for each one. - -That list is fixed to `messages`, `queries`, `routes`, `events`, and `mails`. - -`createTab()`: - -1. creates a `MessagesCollector` for the tab if it does not already exist -2. fetches stored messages for that tab -3. calls the collector method named by the stored level (`info`, `warning`, and so on) +## Collector replay -Because the level name is executed as a method call, the write side and the collector API must stay in sync. - -It also means store-backed custom tab names are not replayed automatically. Writing to some other cell is only useful if you add your own render path around the package. - -## View-path merge behavior - -The `routes` tab is not append-only. - -`Quantum\View\View::updateDebugger()` reads the first existing `info` payload from `routes`, merges in: - -```php -['View' => request()->getCurrentModule() . '/Views/' . $viewFile] -``` +Each entry, stored or collected, has the shape `[$level => $data]`. `createTab()` creates a `MessagesCollector` for the tab if it does not exist yet and calls `log($level, $data)` for each entry. -and then clears and rewrites the tab. +The collectors live as long as the `Debugger` instance, so calling `render()` twice in one request hands them every entry again and the toolbar lists them twice. -So the final `routes` payload is a merged structure, not a chronological list of route-related events. +Only the five built-in tab names are rendered. Writing to some other store key is only useful if you add your own render path around the package. diff --git a/v3.0/packages/debugger/contracts.md b/v3.0/packages/debugger/contracts.md index c2a0cad..2e1f2a3 100644 --- a/v3.0/packages/debugger/contracts.md +++ b/v3.0/packages/debugger/contracts.md @@ -73,13 +73,13 @@ So this write: $debugger->addToStoreCell('messages', 'warning', 'Disk nearly full'); ``` -is later replayed roughly as: +is later replayed as: ```php -$debugBar['messages']->warning('Disk nearly full'); +$debugBar['messages']->log('warning', 'Disk nearly full'); ``` -The level string is therefore part of the runtime contract, not just metadata. +The level becomes the entry's label in the toolbar. `MessagesCollector::log()` accepts any level string, so a custom level shows up as its own label. ## Built-in tab contract @@ -91,6 +91,10 @@ The package only renders five store-backed tabs: - `events` - `mails` -You can write to any key in `DebuggerStore`, but `Debugger::render()` only replays those built-in names. +You can write to any key in `DebuggerStore`, but `Debugger::render()` only renders those built-in names. + +## The store holds only direct writes + +The store contains exactly what code wrote with `addToStoreCell()`: `render()` never writes into it or clears it, so a direct write is always shown, before the [collected entries](architecture.md#render-flow) of its tab. If you need additional tabs, treat that as package customization rather than normal application usage. diff --git a/v3.0/packages/debugger/helpers.md b/v3.0/packages/debugger/helpers.md index b7923ce..039dd20 100644 --- a/v3.0/packages/debugger/helpers.md +++ b/v3.0/packages/debugger/helpers.md @@ -24,11 +24,6 @@ Behavior: ## Where the helper is used in core -Current framework usage includes: - -- `InitDebuggerStage` for store initialization -- `MessageAdapter` for logging into debug tabs -- default web layout templates for final rendering -- `ViewFactory` for injecting the debugger into `Quantum\View\View` +Outside the Debugger package, the framework calls it in one place: the default web layout templates render the toolbar with `render() ?>`. For application code, this helper is the supported API. The framework does not expose a separate factory class for the package. diff --git a/v3.0/packages/debugger/overview.md b/v3.0/packages/debugger/overview.md index 32d8ec4..c50354a 100644 --- a/v3.0/packages/debugger/overview.md +++ b/v3.0/packages/debugger/overview.md @@ -1,11 +1,11 @@ # Debugger -The Debugger package is Quantum's bridge to PHP DebugBar. It collects request-time diagnostics in an in-memory store and renders them into a debug toolbar at the end of a web response. +The Debugger package is Quantum's bridge to PHP DebugBar. When a layout renders the toolbar, it shows the matched route and view, the queries, the log messages, the mail transport log and the event listeners of the request, together with anything your code added. -In current core code, the package is built around two pieces: +The package is built around two pieces: -- `Quantum\Debugger\Debugger`, which owns the DebugBar instance and rendering flow -- `Quantum\Debugger\DebuggerStore`, which keeps per-process tab data until render time +- `Quantum\Debugger\Debugger`, which owns the DebugBar instance, collects the data of the tabs and renders the toolbar +- `Quantum\Debugger\DebuggerStore`, which keeps the entries code writes directly with `addToStoreCell()` ## Entry points @@ -15,15 +15,9 @@ In current core code, the package is built around two pieces: That means every caller writes into the same `DebuggerStore` and the same underlying `DebugBar` instance. -### Boot-time initialization +### Tabs -Web apps initialize the package through `Quantum\App\Adapters\Web\Stages\Boot\InitDebuggerStage`, which calls: - -```php -debugbar()->initStore(); -``` - -`initStore()` creates five built-in tabs: +The toolbar has five built-in tabs: - `messages` - `queries` @@ -31,7 +25,7 @@ debugbar()->initStore(); - `events` - `mails` -The store is still tolerant of missing keys because reads fall back to `[]`, but this boot stage is what pre-seeds the standard tabs used by the rest of the framework. +The package needs no boot step: a tab exists from its first write. `debugbar()->initStore()` creates the five tabs empty up front if your code wants them before anything is written. ## Enablement @@ -39,8 +33,8 @@ The store is still tolerant of missing keys because reads fall back to `[]`, but Practical effect: -- truthy values such as `true`, `1`, or `'true'` enable rendering and logging -- falsey values return an empty string from `render()` and make framework log adapters skip writes +- truthy values such as `true`, `1`, or `'true'` enable rendering +- falsey values make `render()` return an empty string without collecting anything ## What the package collects @@ -52,24 +46,11 @@ The package ships these default DebugBar collectors on construction: - `TimeDataCollector` - `MemoryCollector` -Framework integrations then add package-specific messages into named tabs: - -- logger messages go to `messages` by default -- mail failures can be routed to `mails` -- for a matched web route, the registered event listeners go to `events` (see [Architecture](architecture.md#2-producers-write-into-the-store)) -- view rendering updates the `routes` tab with the final view path - -The built-in render flow only replays those five tab names. If you write data into some other store key, it stays in the store unless you extend the debugger package yourself. - -## Rendering contract - -`render()` does three things when debugging is enabled: +The five named tabs are filled when the toolbar renders; [Architecture](architecture.md#where-each-tabs-data-comes-from) shows where each tab's data comes from. -1. ensures each built-in tab has a `MessagesCollector` -2. replays stored messages into the matching collector -3. returns `renderHead()` and `render()` output from the DebugBar JavaScript renderer +## Rendering -Quantum's default web module templates print that HTML directly in the main layout with `render() ?>`. +`render()` returns the DebugBar head assets and the toolbar markup, or an empty string when debugging is off. Quantum's default web module templates print it in the main layout with `render() ?>`. ## Asset requirement @@ -83,8 +64,5 @@ The package does not publish those files by itself during normal request handlin ## Important constraints - The store is static in `DebuggerStore`, so data is process-scoped, not request-object-scoped. -- `render()` does not clear the store after output. -- `render()` replays stored entries into the same collector objects each time, so calling it more than once in the same request can duplicate toolbar messages. - `addToStoreCell()` ignores empty payloads, so `''`, `[]`, `0`, and `'0'` are not recorded. -- Message levels are invoked dynamically on `MessagesCollector`; unsupported level names would fail at call time. -- Only `messages`, `queries`, `routes`, `events`, and `mails` are auto-created as store-backed tabs. +- The level of an entry is passed to `MessagesCollector::log()` and shown as its label; any string works. diff --git a/v3.0/packages/debugger/usage.md b/v3.0/packages/debugger/usage.md index 53329a4..cc2a5aa 100644 --- a/v3.0/packages/debugger/usage.md +++ b/v3.0/packages/debugger/usage.md @@ -1,6 +1,6 @@ # Debugger Usage -Most applications use the Debugger package indirectly through Quantum's web stack, but the underlying API is small enough to use directly. +Most applications only render the toolbar, and with `app.debug` on its tabs [fill themselves](architecture.md#where-each-tabs-data-comes-from). The API below adds your own entries. ## Basic logging @@ -9,7 +9,7 @@ debugbar()->addToStoreCell('messages', 'info', 'Booted module'); debugbar()->addToStoreCell('messages', 'warning', 'Slow downstream response'); ``` -Those entries are rendered into the `messages` tab when `app.debug` is enabled. +Those entries are rendered into the `messages` tab when `app.debug` is enabled, before the messages the request logged with `logger()`. ## Writing to a specific tab @@ -21,9 +21,9 @@ use Quantum\Debugger\Debugger; debugbar()->addToStoreCell(Debugger::MAILS, 'warning', 'SMTP handshake failed'); ``` -You can also route logger output into a tab by passing a `tab` context value to the framework's message logger path. +This is the way to put an entry into a tab other than `messages`: the logger lists everything in `messages`. -Stick to the built-in tab constants for normal usage. `render()` only replays `messages`, `queries`, `routes`, `events`, and `mails` from the store. +Stick to the built-in tab constants; other tab names are not rendered. ## Rendering in a layout @@ -35,7 +35,7 @@ When debugging is disabled, this returns an empty string. When debugging is enabled, it returns both the renderer head assets and the toolbar markup, so it should only be emitted once per page. -If you call `render()` more than once on the same shared debugger instance, stored messages are replayed again and can appear duplicated in the toolbar. +If you call `render()` more than once on the same shared debugger instance, every entry is passed to the collectors again and appears twice in the toolbar. ## Custom collectors @@ -48,6 +48,7 @@ That means custom construction replaces the default collector set; it does not a ## Practical caveats - Run `install:debugbar` before expecting the toolbar assets to load in the browser. -- `resetStore()` clears all tabs for the shared store. -- `clearStoreCell($cell)` empties a tab but keeps the tab key available. +- `resetStore()` clears every entry written with `addToStoreCell()`. +- `clearStoreCell($cell)` empties a tab's stored entries but keeps the tab key available. +- `getStoreCell($cell)` returns only the entries written with `addToStoreCell()`. To read the request's log lines, use `logger()->getAdapter()->getMessages()`. - Because the helper is DI-backed, mid-request config changes do not rebuild the debugger instance automatically. diff --git a/v3.0/packages/event/architecture.md b/v3.0/packages/event/architecture.md index 987b360..34c19ec 100644 --- a/v3.0/packages/event/architecture.md +++ b/v3.0/packages/event/architecture.md @@ -23,6 +23,6 @@ Both methods reject an empty name with `EventException::emptyEventName()` (`The ## Who dispatches inside the framework - The app pipeline dispatches the runtime stage events, such as `web.preflight.before` and `console.command.before`; the boot dispatches none. The full list and the order they fire in are on [App Contracts](../app/contracts.md#lifecycle-events). -- When the debug bar is enabled, a matched web request writes `getRegistered()` to the debug bar's `events` tab; see [Debugger Architecture](../debugger/architecture.md). +- The debug toolbar reads `getRegistered()` into its `events` tab when it renders; see [Debugger Architecture](../debugger/architecture.md#where-each-tabs-data-comes-from). Everything else is up to your application. diff --git a/v3.0/packages/logger/adapters.md b/v3.0/packages/logger/adapters.md index e4e4049..f4a7387 100644 --- a/v3.0/packages/logger/adapters.md +++ b/v3.0/packages/logger/adapters.md @@ -58,27 +58,14 @@ Use `daily` when you want automatic file splitting by day. ## `message` -`MessageAdapter` sends accepted log entries to Quantum's debugger message store instead of the filesystem. +`MessageAdapter` is the logger of debug mode. It writes no file: it keeps each message in memory as `[level => message]`, in the order they were logged, and `getMessages()` returns them ([example](usage.md#check-what-was-logged-in-debug-mode)). The debug toolbar reads them into its `messages` tab when it renders ([Debugger Architecture](../debugger/architecture.md#where-each-tabs-data-comes-from)). -### How tab routing works - -The adapter uses: - -- `Debugger::MESSAGES` by default -- `context['tab']` when you pass one - -Example: - -```php -logger()->info('Mailer connected', ['tab' => 'mails']); -``` - -The debugger store receives the level and raw message as separate values. File-style formatting and `trace` appends are specific to the file adapters. +The adapter keeps only the level and the message. The context is not used, so file-style formatting and `trace` appends are specific to the file adapters, and a `tab` key does not move a message to another toolbar tab; to add an entry to another tab, write to the debugger directly ([Debugger Usage](../debugger/usage.md#writing-to-a-specific-tab)). ### Contract - available only in debug mode -- writes only when `debugbar()->isEnabled()` is true +- keeps the messages for the life of the process; nothing clears them - ignores file-path config because it has no constructor parameters Outside debug mode, explicitly requesting `message` causes the factory to throw an adapter-not-supported exception. diff --git a/v3.0/packages/logger/architecture.md b/v3.0/packages/logger/architecture.md index ba12237..31182d7 100644 --- a/v3.0/packages/logger/architecture.md +++ b/v3.0/packages/logger/architecture.md @@ -27,7 +27,7 @@ The practical effect is simple: `LoggerFactory::resolve()` imports the `logging` config only when `config()->has('logging')` is false. -So Logger does not eagerly load configuration during bootstrap. The first logger access can trigger config import. +In practice that first access happens during boot: both the web and the console boot resolve a logger for the error handler (`SetupErrorHandlerStage`), so the config is loaded by the time application code runs. After that, the factory reads: @@ -70,9 +70,4 @@ All other context keys are ignored by file adapters. ## Message adapter flow -`MessageAdapter` does not write files. It sends messages into the debugger store: - -- default tab: `Debugger::MESSAGES` -- custom tab: `context['tab']` - -If `debugbar()->isEnabled()` is false, the adapter drops the message silently. \ No newline at end of file +`MessageAdapter` appends each reported message to its in-memory list. It does not know about the debugger: the debug toolbar reads the list through `logger()` when it renders. See [the `message` adapter](adapters.md#message). \ No newline at end of file diff --git a/v3.0/packages/logger/contracts.md b/v3.0/packages/logger/contracts.md index 4ee7ef9..474d846 100644 --- a/v3.0/packages/logger/contracts.md +++ b/v3.0/packages/logger/contracts.md @@ -51,22 +51,16 @@ It is appended verbatim after the formatted message. No placeholder interpolation or structured context rendering happens. -## Debugger logging uses one routing key +## The `message` adapter ignores the context -For the `message` adapter, the special context key is: - -- `tab` - -It chooses the debugger store cell that receives the message. - -If you omit it, the package uses the default messages tab. +The `message` adapter keeps only the level and the message; no context key, `trace` or `tab` included, changes what it keeps. See [the `message` adapter](adapters.md#message). ## Adapter implementations must satisfy `ReportableInterface` Custom adapters resolved through the factory must implement: ```php -public function report(string $level, string $message, ?array $context = []): void; +public function report(string $level, string|Stringable $message, ?array $context = []): void; ``` If the resolved adapter instance does not implement that contract, the factory throws an adapter-not-supported exception. diff --git a/v3.0/packages/logger/helpers.md b/v3.0/packages/logger/helpers.md index f888811..8609821 100644 --- a/v3.0/packages/logger/helpers.md +++ b/v3.0/packages/logger/helpers.md @@ -50,6 +50,6 @@ A few limits are easy to miss: - there are no global helpers for `critical`, `alert`, or `emergency` - the convenience helpers accept string messages, while `logger()` also lets you send array payloads through the PSR-3 methods - every helper call goes back through the shared factory -- in debug mode, helper output goes to the debugger message store instead of files +- in debug mode, helper output is kept in memory by the [`message` adapter](adapters.md#message) instead of written to files Use `logger()` when you need the full PSR-3 surface, adapter selection, or structured array messages. \ No newline at end of file diff --git a/v3.0/packages/logger/overview.md b/v3.0/packages/logger/overview.md index c1842b6..347ba98 100644 --- a/v3.0/packages/logger/overview.md +++ b/v3.0/packages/logger/overview.md @@ -4,7 +4,7 @@ The Logger package gives Quantum a small PSR-3-compatible logging layer with thr - write to one file - write to a date-based daily file -- send messages to the DebugBar message store during debug mode +- keep messages in memory during debug mode, for the debug toolbar Use it when you want framework-level logging through `logger()` or the global level helpers, while keeping the output backend configurable. @@ -14,7 +14,7 @@ Reach for Logger when you need to: - record application errors or warnings to disk - switch between a single log file and one file per day -- surface debug messages inside Quantum's debugger instead of writing files +- see the messages of a request in the debug toolbar instead of in files If you only need a raw filesystem append, use Storage directly. Logger is useful when you want severity filtering and adapter selection. @@ -38,7 +38,7 @@ Logger supports these adapter names: In normal runtime, the factory uses the requested adapter or the configured default. -In debug mode, the factory always resolves the `message` adapter instead. That means debug runs send logs to the debugger message store, not to file-based adapters. +In debug mode, the factory always resolves the [`message` adapter](adapters.md#message) instead, so nothing is written to files. ## Severity filtering diff --git a/v3.0/packages/logger/usage.md b/v3.0/packages/logger/usage.md index 85560fa..07e3052 100644 --- a/v3.0/packages/logger/usage.md +++ b/v3.0/packages/logger/usage.md @@ -55,17 +55,16 @@ logger()->info([ File adapters JSON-format array messages before writing them. The global level helpers accept string messages, so `logger()` is the better fit for this style. -## Route debug messages into a custom debugger tab +## Check what was logged in debug mode -In debug mode, you can place a message in a specific DebugBar tab: +In debug mode the messages stay in memory, so a test or a debug page can read them back from the [`message` adapter](adapters.md#message): ```php -logger()->info('SMTP transport ready', [ - 'tab' => 'mails', -]); -``` +info('Import started'); -If the debugger is disabled, the message adapter drops the message silently. +$messages = logger()->getAdapter()->getMessages(); +// [['info' => 'Import started']] +``` ## Pick the right backend @@ -82,7 +81,7 @@ If the debugger is disabled, the message adapter drops the message silently. ### Rely on debug-mode `message` when: - you are troubleshooting locally -- you want logs in DebugBar instead of on disk +- you want logs in the debug toolbar instead of on disk ## Practical caveats diff --git a/v3.0/packages/mailer/adapters.md b/v3.0/packages/mailer/adapters.md index 434215c..ffbddc0 100644 --- a/v3.0/packages/mailer/adapters.md +++ b/v3.0/packages/mailer/adapters.md @@ -39,7 +39,7 @@ The method name is literally `setReplay`, not `setReply`. ### Runtime behavior -- in debug mode, PHPMailer SMTP debug output is forwarded to the debugger mails tab through `warning(...)` +- in debug mode, PHPMailer's exchange with the SMTP server is recorded in the [transport log](contracts.md#transport-log) - array bodies are concatenated with `implode('')` - rendered HTML bodies have newline characters stripped before send - after each send, the adapter clears recipients, headers, and attachments on the PHPMailer instance @@ -155,7 +155,7 @@ The method name is literally `setReplay`, not `setReply`. The API adapters all share these limits: - they use `HttpClient` directly and catch transport exceptions internally -- `send()` reports failure as `false`; provider-specific error details come from `HttpClient::getErrors()` +- `send()` reports failure as `false`; the provider's error details (`HttpClient::getErrors()`) are kept in the [transport log](contracts.md#transport-log) - they do not expose reply-to, CC, BCC, file attachments, or string attachments through the package surface - array bodies are concatenated with `implode('')` before send - template and raw body output is trimmed and newline-stripped before being sent as HTML \ No newline at end of file diff --git a/v3.0/packages/mailer/contracts.md b/v3.0/packages/mailer/contracts.md index 6249a0d..3b0ba92 100644 --- a/v3.0/packages/mailer/contracts.md +++ b/v3.0/packages/mailer/contracts.md @@ -19,8 +19,9 @@ That gives you this common API: - `setBody(array|string|null $message)` - `getBody()` - `send(): bool` +- `getTransportLog(): array` -If you want code that works across all mailer adapters, stay within that surface. +If you want code that works across all mailer adapters, stay within that surface. The built-in adapters implement it through `Quantum\Mailer\Traits\MailerTrait`; a custom adapter that implements the interface without the trait has to provide every method, `getTransportLog()` included. ## Adapter-specific methods @@ -56,9 +57,28 @@ If the body is an array, the package extracts it into local variables before req ## Delivery result contract -`send()` reports delivery state as `true` or `false`. +`send()` reports delivery state as `true` or `false`; it does not return a provider response object. -It does not return a provider response object. Built-in transport details are mirrored into the debugger mails tab from the adapter error list. +The API adapters catch transport failures and return `false`. The SMTP adapter runs PHPMailer with exceptions enabled, so a failed SMTP send throws `PHPMailer\PHPMailer\Exception` instead. + +## Transport log + +`getTransportLog()` returns what the adapter recorded while sending, oldest first: + +- the transport errors of every send that returned `false`; they are also logged with `warning()` +- in debug mode, the SMTP adapter's exchange with the server, one entry per PHPMailer debug line. PHPMailer writes `[credentials hidden]` in place of the login lines. + +```php +use Quantum\Mailer\Enums\MailerType; + +$mailer = mailer(MailerType::MAILGUN); + +if (!$mailer->setFrom('noreply@example.com')->setAddress('jane@example.com')->setSubject('Hi')->setBody('Hello')->send()) { + $errors = $mailer->getTransportLog(); +} +``` + +The log belongs to the adapter instance and nothing clears it. `MailerFactory` reuses one adapter per name, so in a long-running process it spans every send through that adapter. In debug mode the debug toolbar shows the default mailer's log in its `mails` tab ([Debugger Architecture](../debugger/architecture.md#where-each-tabs-data-comes-from)). ## Mail trap contract diff --git a/v3.0/packages/mailer/overview.md b/v3.0/packages/mailer/overview.md index 4af1a0a..daa170f 100644 --- a/v3.0/packages/mailer/overview.md +++ b/v3.0/packages/mailer/overview.md @@ -56,6 +56,6 @@ After `send()` finishes, the package clears the sender, recipients, subject, bod - Adapter-specific methods are available only when the underlying adapter implements them. - Only the SMTP adapter supports reply-to, CC, BCC, and attachments. - Template paths are stored without the `.php` suffix. The package always requires `