diff --git a/v3.0/advanced-features/request-lifecycle.md b/v3.0/advanced-features/request-lifecycle.md index 0cd7964..22cb86d 100644 --- a/v3.0/advanced-features/request-lifecycle.md +++ b/v3.0/advanced-features/request-lifecycle.md @@ -23,7 +23,7 @@ Follow one request, `GET /dashboard`, from the browser to the response: 6. If the page is in the view cache, that copy is the response. Otherwise the controller action runs. 7. The response is sent with the CORS headers from `config/cors`. -Each of these steps is a stage in a pipeline. The stage names and their exact order are listed in [App Architecture](../packages/app/architecture.md#web-boot-order). +Each of these steps is a stage in a pipeline. The stage names and their exact order are listed in [App Architecture](../packages/app/architecture.md#web-boot-order), and the events each step dispatches for your own listeners are in [Hooking into the running app](../packages/app/architecture.md#hooking-into-the-running-app). ## 1. The request enters the application diff --git a/v3.0/cli/console-commands.md b/v3.0/cli/console-commands.md index 4a2e75c..d67ecf6 100644 --- a/v3.0/cli/console-commands.md +++ b/v3.0/cli/console-commands.md @@ -80,7 +80,7 @@ That base class gives commands: - optional help text - argument definitions - option definitions -- `$requiresEnvironment` (`true` by default), which says whether the command needs `.env` and the app config; see [When the boot fails](qt-command-line-tool.md#when-the-boot-fails) +- `$requiresEnvironment` (`true` by default), which lets a framework command such as `core:env` run when `.env` is missing or malformed; see [Commands without a working environment](qt-command-line-tool.md#commands-without-a-working-environment) - helper methods such as: - `getArgument()` - `getOption()` diff --git a/v3.0/cli/qt-command-line-tool.md b/v3.0/cli/qt-command-line-tool.md index c8ecc83..b30b0f0 100644 --- a/v3.0/cli/qt-command-line-tool.md +++ b/v3.0/cli/qt-command-line-tool.md @@ -51,13 +51,8 @@ Before anything else, it sets up: So `qt` is not a shell wrapper around random scripts. It boots a real console application. -### 3. Quantum boots the project -Next, Quantum loads what commands usually rely on: helper functions, `.env`, the app config, and the error handler. It also names the console application from `app.name` and `app.version`. - -This boot is the same for every command. If one of its steps fails, Quantum does not stop here; see [When the boot fails](#when-the-boot-fails). - -### 4. Core commands are registered -Quantum then registers the framework's built-in commands from: +### 3. Helpers load and core commands are registered +Next, Quantum loads the helper functions and registers the framework's built-in commands from: ```text Quantum\Console\Commands @@ -73,14 +68,17 @@ These include commands such as: - `core:env` - `core:key` +### 4. Quantum loads the project +Then Quantum loads what commands usually rely on: `.env`, the app config, and the error handler. It also names the console application from `app.name` and `app.version`. + ### 5. App commands are registered -After that, Quantum registers project-level commands from: +Last, Quantum registers project-level commands from: ```text shared/Commands/ ``` -in the starter project. +in the starter project. They come after the environment and the config, so a command's constructor can use `env()` and `config()`. That means `qt` can run both: @@ -90,8 +88,7 @@ That means `qt` can run both: ### 6. The command is validated and executed The console app reads the first CLI argument as the command name. -If the command does not exist, Quantum throws an error. -If the boot failed and the command requires the environment, Quantum throws the original boot error. +If the command does not exist, Quantum throws ``Command `` is not defined``. Otherwise, Symfony Console runs it. ## What `qt` does for developers @@ -155,7 +152,7 @@ this is roughly what happens: The same pattern applies to other commands. -## When the boot fails +## Commands without a working environment Picture a fresh clone of a project: there is no `.env` yet, and the first thing you run is: @@ -163,36 +160,16 @@ Picture a fresh clone of a project: there is no `.env` yet, and the first thing php qt core:env ``` -That command creates `.env` from `.env.example`, so it has to work exactly when loading `.env` cannot. This is why the console boot keeps going when one of its steps fails: Quantum remembers the error and still registers and runs commands. +That command creates `.env` from `.env.example`, so it has to work exactly when loading `.env` cannot. When `.env` is missing or cannot be parsed, Quantum therefore looks at the requested command before it loads the rest of the project: -Each command then decides whether it can run without the failed part: +- `core:env`, and Symfony's `list` and `help`, run. The boot stops before the environment, so nothing that needs it is loaded: no app config, no error handler, and none of your own commands. +- Every other command stops right away with the error of the environment file, for example ``The file `.env` not found.`` or ``Failed to parse dotenv file``, instead of failing later with a confusing one. -- `core:env`, and Symfony's `list` and `help`, run normally. -- A command that needs the environment, such as `migration:migrate`, stops with the original error (for example ``The file `.env` not found.``) instead of failing later with a confusing one. - -Your own commands need the environment by default. If a command must also run without it, for example a setup check, set `$requiresEnvironment` to `false`: - -```php -namespace Shared\Commands; +Once `.env` works again, for example after `php qt core:env --yes` has recreated it from `.env.example`, every command runs as usual. -use Quantum\Console\CliCommand; - -class DoctorCommand extends CliCommand -{ - protected ?string $name = 'app:doctor'; - - protected ?string $description = 'Checks the project setup'; - - protected bool $requiresEnvironment = false; - - public function exec(): void - { - $this->info('Checking the project setup...'); - } -} -``` +Your own commands are registered last ([step 5](#5-app-commands-are-registered)), after the point where the boot stops, so `php qt list` shows only the framework's commands and running one of yours reports the environment error. Which framework commands can run without the environment is set with `CliCommand::$requiresEnvironment`, which `core:env` sets to `false`. -After a failed boot the application name is not read from your config, so `php qt list` shows it as `UNKNOWN`. +Without a working `.env` the application name is not read from your config, so `php qt list` shows it as `UNKNOWN`. ## In one sentence diff --git a/v3.0/packages/app/adapters.md b/v3.0/packages/app/adapters.md index 7f05b09..339d073 100644 --- a/v3.0/packages/app/adapters.md +++ b/v3.0/packages/app/adapters.md @@ -2,7 +2,7 @@ Quantum ships two built-in app adapters: `web` and `console`. -Both satisfy the same `start(): ?int` contract, but they boot different runtime services and handle different entry-point work. +Both follow the same lifecycle, `boot()` and then `start(): ?int` (see [Contracts](contracts.md#appinterface)), but they boot different runtime services and handle different entry-point work. ## Web adapter @@ -52,9 +52,9 @@ exit($app->start() ?? 0); ### What it does -Every `php qt ` goes the same way: the adapter boots the project (helpers, `.env`, app config, error handler), registers the framework commands and your commands from `shared/Commands`, checks that the requested command exists, runs it through Symfony Console, and returns its exit code. +Every `php qt ` goes the same way: the adapter boots the project (helpers, the framework commands, `.env`, app config, error handler, then your commands from `shared/Commands`), checks that the requested command exists, runs it through Symfony Console, and returns its exit code. -The boot is the same for every command. If it fails, for example on a fresh clone without `.env`, commands are still registered and can run; see [When the boot fails](../../cli/qt-command-line-tool.md#when-the-boot-fails). +Without a working `.env`, only a few commands such as `core:env` can run; see [Commands without a working environment](../../cli/qt-command-line-tool.md#commands-without-a-working-environment). The adapter does not make the environment writable. A command that changes `.env` through `environment()->updateRow()` calls `setMutable(true)` first, as `core:key` does. diff --git a/v3.0/packages/app/architecture.md b/v3.0/packages/app/architecture.md index 0f3bae3..22f1446 100644 --- a/v3.0/packages/app/architecture.md +++ b/v3.0/packages/app/architecture.md @@ -12,26 +12,24 @@ This page follows that path: the factory that creates the app, the context every $app = AppFactory::create(AppType::WEB, __DIR__); ``` -It does three things: +It does four things: 1. validates the requested adapter type 2. creates a new `DiContainer` and `AppContext` -3. wraps the selected adapter in `Quantum\App\App` +3. creates the adapter and boots it with `boot()` +4. wraps the adapter in `Quantum\App\App` -The factory also stores the created app in a static cache keyed by app type. +So the app you get back is already booted, and `start()` handles the run. The factory also stores the created app in a static cache keyed by app type. ## 2) Shared context -`AppContext` carries two pieces of runtime state: - -- the application base directory -- the DI container used to resolve shared services - -From that context, code can resolve the objects both the web and the console boot provide: +`AppContext` carries the application base directory and the DI container used to resolve shared services. From that context, code can resolve the objects both the web and the console boot provide: - `Environment` - `Config` +It also carries the state of the run it is passed through: whether a stage ended the run early (`stop()` / `isStopped()`) and the exit code. + `App` also stores the current context statically. Helpers like `base_dir()` depend on that global context being initialized first. Each run also gets a runtime context that extends `AppContext` with what only that kind of app has. See [Runtime contexts](#runtime-contexts). @@ -40,30 +38,43 @@ Each run also gets a runtime context that extends `AppContext` with what only th Each adapter runs two pipelines, both with the generic `Quantum\App\Pipeline`: -- the **boot** pipeline, during construction, with the shared `AppContext` -- the **runtime** pipeline, in `start()`, with a runtime context (`WebContext` or `ConsoleContext`) +- the **boot** pipeline, in `boot()`, with the shared `AppContext` +- the **runtime** pipeline, in `start()`, with a new runtime context (`WebContext` or `ConsoleContext`) + +The stages run in order, and each receives the context. A stage can end the run early: an `OPTIONS` request, for example, is fully answered by the first web stage, so it calls `$context->stop()` and the stages that would match a route and dispatch it are skipped. Some work still has to happen after such an early end, so a pipeline can have a **closing stage** that runs last in any case: the web runtime uses it to send the response. -The pipeline contract is simple: every stage receives the context and runs once, in order. A stage may declare `BEFORE` / `AFTER` event names that the pipeline dispatches around it; see [Contracts](contracts.md#stage-and-pipeline-contract). +A stop ends only its own pipeline. When a boot stage stops, the rest of the boot is skipped, but `start()` still runs; the console uses this when `.env` cannot be loaded, see [Console boot order](#console-boot-order). -### Hooking into the boot +The exact rules are in [Contracts](contracts.md#stage-and-pipeline-contract). -Say you want to register something right after the modules are loaded, or measure how long the config takes to load. The boot stages dispatch events around themselves, such as `boot.modules.before` and `boot.modules.after`, with the context in the payload. +### Hooking into the running app -Listen in a file under your project's `helpers/` directory. Helpers load first, so the listener is in place before the other boot stages run: +Say you want to add a header to every response, or log which console command ran. The runtime stages dispatch events around themselves, such as `web.send.before` and `console.command.before`, with the runtime context in the payload. + +Listen in a file under your project's `helpers/` directory. Helpers load early in the boot, so the listener is in place before the first request or command is handled: ```php -event()->listen('boot.modules.after', function (array $payload) { - $baseDir = $payload['context']->getBaseDir(); +// helpers/events.php +event()->listen('web.send.before', function (array $payload) { + $payload['context']->getResponse()->setHeader('X-Served-By', 'Quantum'); +}); + +event()->listen('console.command.before', function (array $payload) { + $command = $payload['context']->getInput()->getFirstArgument(); - // the module routes are loaded at this point + // ... }); ``` -The full list of events and when they are skipped is in [Contracts](contracts.md#stage-and-pipeline-contract). +A stage that does not run fires no events, so a listener never has to check whether the request was stopped. The send stage is the closing stage, so the header above is also added to a `404` and an `OPTIONS` response. + +The boot stages dispatch no events: the boot is the framework's own setup, and app code hooks in once it is finished. The first event of a request is `web.preflight.before`, and the first event of a command is `console.command.before`. + +The full list of events and the order they fire in is in [Contracts](contracts.md#lifecycle-events). ## Web boot order -The web adapter runs these stages during construction: +`boot()` runs these stages: 1. `LoadHelpersStage` 2. `LoadEnvironmentStage` @@ -75,46 +86,56 @@ The web adapter runs these stages during construction: ## Web runtime order -After boot, `start()` runs these stages with a `WebContext`: +`start()` runs these stages with a `WebContext`: -1. `HandlePreflightStage`: `OPTIONS` requests get `204 No Content`, and the remaining stages up to sending are skipped +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 remaining stages up to sending are skipped +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 5. `DispatchRequestStage`: middleware wraps the route dispatch or the cached response lookup -6. `SendResponseStage`: appends the CORS headers, sends the response, and clears the request's route and input state, also when sending fails + +`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. ## Console boot order -The console adapter runs these stages during construction: +`boot()` runs these stages: -1. `InitConsoleStage` +1. `InitConsoleStage`: registers the Symfony application, the input and the output 2. `LoadHelpersStage` -3. `LoadEnvironmentStage` -4. `LoadAppConfigStage` -5. `SetupErrorHandlerStage` -6. `SetupConsoleApplicationStage` +3. `RegisterCoreCommandsStage`: registers the framework's commands +4. `ResolveEnvironmentStage`: decides what happens when `.env` cannot be loaded +5. `LoadEnvironmentStage` +6. `LoadAppConfigStage` +7. `SetupErrorHandlerStage` +8. `SetupConsoleApplicationStage`: names the application from `app.name` and `app.version` +9. `RegisterAppCommandsStage`: registers your commands from `shared/Commands` + +`ResolveEnvironmentStage` loads `.env` once to see whether it works. If it does not, the stage stops the boot for a command that can run without the environment, and lets every other command fail with the error; which commands those are is in [Commands without a working environment](../../cli/qt-command-line-tool.md#commands-without-a-working-environment). -`InitConsoleStage` runs first, so the console application, input and output exist even when a later stage fails. A failure does not stop the adapter: it is stored on the `ConsoleContext` and `RunCommandStage` decides per command. What the user sees is described in [When the boot fails](../../cli/qt-command-line-tool.md#when-the-boot-fails). +The framework's commands are registered before the environment so that `core:env` can be found when `.env` cannot be loaded; their constructors therefore use neither the environment nor the config. Your commands are registered last, after the environment, the config and the error handler, so their constructors can use `env()` and `config()`. ## Console runtime order -`start()` runs these stages with a `ConsoleContext`: +`start()` runs one stage with a `ConsoleContext`: -1. `RegisterCoreCommandsStage` -2. `RegisterAppCommandsStage` -3. `RunCommandStage`: rejects a missing or unknown command (``Command `` is not defined``), re-throws the boot failure for a command that requires the environment, and otherwise runs the command and keeps its exit code +1. `RunCommandStage`: rejects a missing or unknown command (``Command `` is not defined``), and otherwise runs the command and keeps its exit code ## Runtime contexts `WebContext` and `ConsoleContext` extend `AppContext`, with the same base directory and container. -- `WebContext` reads the request, the routes, and the view cache from the container, and the matched route from the request. It stores what the run produces: the response to send and whether the run was stopped early. -- `ConsoleContext` reads the Symfony application, input, and output from the container. It stores the boot failure, if any, and the command's exit code. +- `WebContext` reads the request, the routes, and the view cache from the container, and the matched route from the request. It stores the response to send. +- `ConsoleContext` reads the Symfony application, input, and output from the container. + +Both keep the stop flag and the exit code of their run, which they inherit from `AppContext`. + +## Where the stages live + +A stage that both adapters run is in `Quantum\App\Stages\Boot`: `LoadHelpersStage`, `LoadEnvironmentStage`, `LoadAppConfigStage` and `SetupErrorHandlerStage`. A stage that only one adapter runs lives next to that adapter, in `Quantum\App\Adapters\Web\Stages\Boot`, `...\Web\Stages\Runtime`, `...\Console\Stages\Boot` or `...\Console\Stages\Runtime`. ## Command discovery model -The console runtime registers commands from two places: +The console boot registers commands from two places: - framework commands under `vendor/quantum/framework/src/Console/Commands` (namespace `Quantum\Console\Commands`) - application commands under `shared/Commands` (namespace `Shared\Commands`) diff --git a/v3.0/packages/app/contracts.md b/v3.0/packages/app/contracts.md index 4d05938..a48a77e 100644 --- a/v3.0/packages/app/contracts.md +++ b/v3.0/packages/app/contracts.md @@ -13,7 +13,7 @@ Creates or returns the shared `App` instance for the requested type. ### Guarantees -- the returned object wraps the selected adapter +- the returned object wraps the selected adapter, already booted - a new DI container is created only for the first instance of each type - the created `AppContext` is stored globally on `App` @@ -48,6 +48,23 @@ without exposing the adapter directly. If the adapter does not implement the requested method, the call throws an app exception instead of silently ignoring it. +## `AppInterface` + +Both adapters implement `Quantum\App\Contracts\AppInterface`: + +- `boot(): void` runs the boot pipeline +- `start(): ?int` runs the runtime pipeline and returns the exit code + +Creating an adapter does not boot it; the constructor only stores the context. `AppFactory` calls `boot()` for you. Code that creates an adapter directly must call it before `start()`: + +```php +$adapter = new WebAppAdapter($context); +$adapter->boot(); +$adapter->start(); +``` + +No adapter catches exceptions: a failing stage, in the boot or in the run, throws to the caller. + ## `AppContext` `AppContext` is the bridge between bootstrap and runtime packages. @@ -58,15 +75,18 @@ If the adapter does not implement the requested method, the call throws an app e - DI container (`getContainer()`) - DI-resolved `Environment` (`getEnvironment()`) - DI-resolved `Config` (`getConfig()`) +- the run state: `stop()` / `isStopped()` and the exit code (`getExitCode()` / `setExitCode()`, `ExitCode::SUCCESS` by default) -These are what both the web and the console boot provide. +The services are what both the web and the console boot provide. ### Runtime contexts `WebContext` and `ConsoleContext` extend `AppContext` and add what only their kind of run has: -- `WebContext`: `getRequest()`, `getRoutes()` and `getViewCache()` from the container, `getMatchedRoute()` from the request, the response to send (`getResponse()` / `setResponse()`), and `stop()` / `isStopped()` -- `ConsoleContext`: `getApplication()`, `getInput()` and `getOutput()` from the container, `getBootFailure()`, and the exit code (`getExitCode()` / `setExitCode()`) +- `WebContext`: `getRequest()`, `getRoutes()` and `getViewCache()` from the container, `getMatchedRoute()` from the request, and the response to send (`getResponse()` / `setResponse()`) +- `ConsoleContext`: `getApplication()`, `getInput()` and `getOutput()` from the container + +A runtime context is built from the `AppContext` with the same base directory and container, but its own run state: a stop in the boot does not carry over into the run. ### Constraint @@ -92,15 +112,43 @@ class MyStage implements StageInterface } ``` -`Quantum\App\Pipeline` runs stages in order with one context: +`Quantum\App\Pipeline` runs stages in order with one context, and optionally a closing stage: ```php -(new Pipeline([new FirstStage(), new SecondStage()]))->run($context); +(new Pipeline([new FirstStage(), new SecondStage()], new ClosingStage()))->run($context); ``` - The context type is a PHPDoc template (`Pipeline`, `StageInterface`), checked by PHPStan: running web stages with a console context, mixing stages of different kinds, or a stage without its `@implements` line fails static analysis. PHP itself only requires an `AppContext`. - The constructor throws `InvalidArgumentException` immediately when an item does not implement `StageInterface`. -- A stage may declare `BEFORE` and `AFTER` class constants holding event names. The pipeline dispatches them around the stage with `['context' => $context]` as the payload, skips them while the `event()` helper is not loaded yet, and does not dispatch `AFTER` when the stage throws. Today only boot stages declare events (`boot.environment`, `boot.config`, `boot.error_handler`, `boot.http`, `boot.modules`, each with `.before` / `.after`). +- A stage that calls `$context->stop()` ends the run after it finishes: no later stage runs or dispatches events. +- The closing stage runs last, also after a stop. It does not run when a stage throws: the exception reaches the caller. +- A stage may declare `BEFORE` and `AFTER` class constants holding event names. The pipeline dispatches them around the stage with `['context' => $context]` as the payload, the closing stage included. A stage that throws dispatches no `AFTER`; a stage that stops dispatches its `AFTER` before the run ends. + +## Lifecycle events + +Only the runtime stages declare events; the boot stages declare none. + +| Stage | Events | +|---|---| +| `HandlePreflightStage` | `web.preflight.before`, `web.preflight.after` | +| `ResolveRouteStage` | `web.route.before`, `web.route.after` | +| `HandleRouteNotFoundStage` | `web.not_found.before`, `web.not_found.after` | +| `PrepareRequestStage` | `web.prepare.before`, `web.prepare.after` | +| `DispatchRequestStage` | `web.dispatch.before`, `web.dispatch.after` | +| `SendResponseStage` (closing stage) | `web.send.before`, `web.send.after` | +| `RunCommandStage` | `console.command.before`, `console.command.after` | + +The payload's `context` is the `WebContext` or the `ConsoleContext` of the run. Which events a request fires: + +| Request | Events, in order | +|---|---| +| a matched route | every `web.*` pair above, from `web.preflight` to `web.send` | +| `OPTIONS` | `web.preflight`, `web.send` | +| no matching route | `web.preflight`, `web.route`, `web.not_found`, `web.send` | +| a stage throws | everything before it and its `.before`; no `.after`, no `web.send` | + +- `web.send.after` fires after the response is sent and the request's route and input are cleared; read what you need from the request earlier. +- `console.command.after` fires only with a Symfony application that does not exit after the command. The default one ends the process inside `Application::run()`, so in `php qt` only `console.command.before` fires. ## Exit codes diff --git a/v3.0/packages/debugger/architecture.md b/v3.0/packages/debugger/architecture.md index 18aac5e..899961d 100644 --- a/v3.0/packages/debugger/architecture.md +++ b/v3.0/packages/debugger/architecture.md @@ -11,7 +11,7 @@ The Debugger package follows a simple pipeline: ### 1. Boot stage -`Quantum\App\Stages\Boot\InitDebuggerStage` runs in the web boot pipeline and calls `debugbar()->initStore()`. +`Quantum\App\Adapters\Web\Stages\Boot\InitDebuggerStage` runs in the web boot pipeline and calls `debugbar()->initStore()`. 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). @@ -20,7 +20,7 @@ It runs after helpers, environment, config, error handler, and HTTP initializati Several framework components write diagnostics into the same store: - `Quantum\Logger\Adapters\MessageAdapter` writes log messages into `messages` or a caller-provided tab -- `Quantum\App\Stages\Web\PrepareRequestStage` writes the registered event listeners (`event()->getRegistered()`) into `events` when the debug bar is enabled, once a route has matched +- `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 diff --git a/v3.0/packages/debugger/overview.md b/v3.0/packages/debugger/overview.md index 5065a01..32d8ec4 100644 --- a/v3.0/packages/debugger/overview.md +++ b/v3.0/packages/debugger/overview.md @@ -17,7 +17,7 @@ That means every caller writes into the same `DebuggerStore` and the same underl ### Boot-time initialization -Web apps initialize the package through `Quantum\App\Stages\InitDebuggerStage`, which calls: +Web apps initialize the package through `Quantum\App\Adapters\Web\Stages\Boot\InitDebuggerStage`, which calls: ```php debugbar()->initStore(); diff --git a/v3.0/packages/event/architecture.md b/v3.0/packages/event/architecture.md index 3687285..987b360 100644 --- a/v3.0/packages/event/architecture.md +++ b/v3.0/packages/event/architecture.md @@ -22,7 +22,7 @@ Both methods reject an empty name with `EventException::emptyEventName()` (`The ## Who dispatches inside the framework -- The app pipeline dispatches the boot stage events, such as `boot.modules.before` and `boot.modules.after`. The full list and the rules are on [App Contracts](../app/contracts.md#stage-and-pipeline-contract). +- 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). Everything else is up to your application. diff --git a/v3.0/packages/event/overview.md b/v3.0/packages/event/overview.md index 443207d..525d3f4 100644 --- a/v3.0/packages/event/overview.md +++ b/v3.0/packages/event/overview.md @@ -8,7 +8,7 @@ Say a user signs up. The code that creates the account should not also have to k event()->dispatch('user.registered', ['user' => $user]); ``` -The framework uses the same mechanism for its boot: you can listen to `boot.modules.after` and similar events to run code at a fixed point of the boot. See [Hooking into the boot](../app/architecture.md#hooking-into-the-boot). +The framework uses the same mechanism around each request and command: you can listen to `web.send.before`, `console.command.before` and similar events to run code at a fixed point of the run. See [Hooking into the running app](../app/architecture.md#hooking-into-the-running-app). ## Package shape diff --git a/v3.0/packages/event/usage.md b/v3.0/packages/event/usage.md index 809eddb..f117dc9 100644 --- a/v3.0/packages/event/usage.md +++ b/v3.0/packages/event/usage.md @@ -22,7 +22,7 @@ A listener can be any PHP callable: a closure, an invokable object, or `[$object ## Where to register listeners -A listener only runs if it was registered before the event is dispatched. The safe place is a file in your project's `helpers/` directory, or in a module's `helpers/` directory. Quantum loads those files first, before the rest of the boot, so their listeners are in place for every event, the [boot events](../app/architecture.md#hooking-into-the-boot) included: +A listener only runs if it was registered before the event is dispatched. The safe place is a file in your project's `helpers/` directory, or in a module's `helpers/` directory. Quantum loads those files early in the boot, so their listeners are in place for every event, the framework's [request and command events](../app/architecture.md#hooking-into-the-running-app) included: ```php // helpers/events.php