Runtime
Magewire specific (since: 3.0.0)
You can build every component you'll ever need without reading this page because Magewire boots itself automatically. But when you're debugging an edge case, writing a Mechanism or Feature, or just curious how a PHP framework wakes up inside a Magento request, this is the background that makes the rest of the architecture click.
The runtime is a small, request-scoped state machine. Once per request it boots Magewire, remembers what kind of request it is handling, and tracks where it is in its own boot lifecycle. Three pieces of vocabulary cover almost everything:
| Term | Question it answers | Type |
|---|---|---|
| mode | What kind of request is this: an initial page load or an update? | RequestMode |
| state | Where is Magewire in its boot lifecycle right now? | RuntimeState |
| boot mode | How eagerly should an individual service boot: early, or only when needed? | ServiceTypeItemBootMode |
The runtime itself is a single object (Magewirephp\Magewire\Runtime) holding just a mode and a
state, reachable through MagewireServiceProvider::runtime(). It lives for one HTTP request and
starts fresh on the next.
Request modes
The mode records which of Magewire's two request shapes is being served:
| Mode | Value | Meaning |
|---|---|---|
UNDEFINED |
0 | No mode set yet: the runtime hasn't been booted. |
PRECEDING |
1 | The initial full page render. Components are constructed and mounted as the page's HTML is built. |
SUBSEQUENT |
2 | A /magewire/update XHR. Components are reconstructed from their snapshot, updated, and re-rendered. |
The mode is write-once: the first call to mode($mode) sets it, and every later call returns
that value unchanged. It can never be unset back to UNDEFINED. This gives the rest of the
framework a single, stable answer to "are we on an update request?":
if ($magewireServiceProvider->runtime()->mode()->isSubsequent()) {
// e.g. decorate the layout for page-less block loading: see the Layout page.
}
PRECEDING and SUBSEQUENT are the same naming you'll see throughout the architecture: a
preceding request renders the page, a subsequent request updates a component that was rendered
on a preceding one.
Runtime state
The state tracks Magewire's own boot lifecycle. It only ever moves forward through the happy path, with two exits:
UNINITIALIZED ──▶ SETUP ──▶ BOOTING ──▶ BOOTED
(0) (1) (2) (3)
any step ──▶ FAILED (4) BOOTED ──▶ STOPPED (5)
| State | Value | Meaning |
|---|---|---|
UNINITIALIZED |
0 | Boot process hasn't started. Initial state. |
SETUP |
1 | Containers are booted and the always-on services have run. |
BOOTING |
2 | Actively executing the boot sequence; remaining services are booting. |
BOOTED |
3 | Fully booted and operational. |
FAILED |
4 | A critical error stopped the boot; the request can't proceed. |
STOPPED |
5 | Was running, then gracefully shut down. |
Because the cases are ordered integers, state checks read like comparisons:
state()->isMinimally(RuntimeState::BOOTED), isAbove(...), isBelow(...), is(...),
isAny([...]). The boot routine uses this to guard against re-entry: if the state is already at
BOOTING or beyond, a second boot() call returns immediately instead of booting twice.
The boot process
Booting happens in two phases: a lightweight setup that runs on every controller action, and a full boot that runs only when Magewire is actually needed.
Phase 1: setup()
Triggered by the ControllerActionPredispatch observer on every controller action, so the
groundwork is in place before anything renders. It:
- Fully boots the Containers service type.
- Fires the
magewire:setupevent. - Boots the persistent-and-above Mechanisms and Features (see boot modes).
- Moves the state to
SETUP.
If setup has already run (state >= SETUP) it's a no-op, so the cost is paid once per request.
Phase 2: boot(mode)
Triggered the first time Magewire is genuinely in play. There are exactly two trigger points:
ViewBlockAbstractToHtmlBefore: when the first block carrying amagewireargument is about to render on a normal page, it boots withRequestMode::PRECEDING.MagewireUpdateRoute: the update endpoint boots withRequestMode::SUBSEQUENT.
The boot routine then:
- Sets the request mode (write-once).
- Runs
setup()first if the runtime is stillUNINITIALIZED. - Moves the state to
BOOTING. - Fires the
magewire:bootevent, which returns afinishcallback. - Boots the remaining (lazy) Mechanisms and Features that setup skipped.
- Invokes the
finishcallback: the "after boot" hook, run only when no exception occurred. - Moves the state to
BOOTED.
Any exception along the way flips the state to FAILED and re-throws. A reset(mode) helper exists
to force a clean re-boot (it sets the state back to UNINITIALIZED and boots again), but you should
rarely need it.
Service types and boot modes
Everything Magewire boots is organised into three service types, each a collection of ordered, individually-bootable items:
| Service type | What it holds |
|---|---|
| Containers | Foundational services; always fully booted first, during setup(). |
| Mechanisms | The required core pipeline steps. See Mechanisms. |
| Features | Optional, swappable capabilities. See Features. |
Each item declares a boot mode that controls when it boots:
| Boot mode | Value | Boots during |
|---|---|---|
LAZY |
10 | The full boot() phase: only when Magewire is actually used. |
PERSISTENT |
20 | setup(): early, on every controller action. |
ALWAYS |
30 | setup(): early, on every controller action. |
A boot call only runs items whose boot mode is at or above the requested level. So setup()'s
boot(PERSISTENT) starts the PERSISTENT and ALWAYS items and skips LAZY ones; the later full
boot() (no minimum) picks up everything that's left. An item without a boot_mode takes its service
type's fallback: ALWAYS for Containers and LAZY for Mechanisms and Features, because most of them only
matter once a component is in play.
Ordering items
Within a service type, items boot in ascending sort_order. An item without a sort_order (or with 0)
gets the previous item's value plus one, in declaration order, and items with the same value keep their
declaration order.
Since Magewire 3.7, an item can also declare a sequence: the items of the same service type it must boot
after, by DI item name.
<type name="Magewirephp\Magewire\Features">
<arguments>
<argument name="items" xsi:type="array">
<item name="vendor_listener_audit" xsi:type="array">
<item name="type" xsi:type="string">Vendor\Module\Magewire\Features\SupportListenerAudit\SupportListenerAudit</item>
<item name="sort_order" xsi:type="number">6000</item>
<item name="sequence" xsi:type="array">
<item name="magewire_events" xsi:type="boolean">true</item>
</item>
</item>
</argument>
</arguments>
</type>
- Keys are item names from the DI array, such as
eventsormagewire_events, not class names. - Only the value
truecounts.false, so another module can switch an entry off, and names that are not in the collection are ignored. - A sequence takes precedence over
sort_orderand applies transitively. An item waits for every item it lists. - A sequenced item is placed after the items that were ready in the same ordering pass, not necessarily directly
after the item it names. Keep using
sort_orderfor the rest of the placement. - An item with boot mode
PERSISTENTorALWAYScannot sequence after aLAZYitem. Because Features and Mechanisms default toLAZY, an eager item usually cannot sequence after a core item. This throwsLogicException: Service type item "…" cannot sequence after lazy item "…". - A cycle, including an item that lists itself, throws
LogicException: Circular service type sequence detected among items: ….
Both exceptions are thrown when the service type is first sorted during Magewire's setup, and the runtime state
becomes FAILED. Core uses a sequence itself: magewire_events boots after events.
In Magewire 3.6 and earlier, sequence was only consulted when two items had the same sort_order, and it did
not check which item was named. Existing sequence entries become real ordering constraints after upgrading to
3.7, so recheck them.
Hooking into the boot
The boot phases are themselves extension points, dispatched through Magewire's event system. From a
Mechanism, Feature, or Component Hook you can listen with on():
use function Magewirephp\Magewire\on;
on('magewire:setup', function ($runtime) {
// Runs once per request, right after containers boot.
});
on('magewire:boot', function ($runtime) {
// Before the remaining services boot.
return function () {
// After a successful boot (the "finish" callback).
};
});
These are the same on() / trigger() primitives the rest of the lifecycle uses. See
Component Hooks for the broader hook pipeline.
Accessing the runtime
Day-to-day component code never touches the runtime. When framework-level code does need it, it goes through the service provider:
$runtime = $magewireServiceProvider->runtime();
$runtime->mode(); // RequestMode: preceding / subsequent
$runtime->state(); // RuntimeState: where in the boot lifecycle
Treat both as read-only from your own code. The framework sets the mode and advances the state
at the right moments; reading them (especially mode()->isSubsequent()) is fine and common, but
setting them by hand will desync the boot machinery.
Related
- Mechanisms: the required core steps booted by the runtime.
- Features: smaller lifecycle services booted alongside them, including both optional and runtime-required capabilities.
- Component Hooks: the
on()/trigger()event pipeline used throughout boot. - Layout: a concrete consumer of
mode()->isSubsequent().