Skip to content

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:

  1. Fully boots the Containers service type.
  2. Fires the magewire:setup event.
  3. Boots the persistent-and-above Mechanisms and Features (see boot modes).
  4. 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 a magewire argument is about to render on a normal page, it boots with RequestMode::PRECEDING.
  • MagewireUpdateRoute: the update endpoint boots with RequestMode::SUBSEQUENT.

The boot routine then:

  1. Sets the request mode (write-once).
  2. Runs setup() first if the runtime is still UNINITIALIZED.
  3. Moves the state to BOOTING.
  4. Fires the magewire:boot event, which returns a finish callback.
  5. Boots the remaining (lazy) Mechanisms and Features that setup skipped.
  6. Invokes the finish callback: the "after boot" hook, run only when no exception occurred.
  7. 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.

etc/frontend/di.xml
<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 events or magewire_events, not class names.
  • Only the value true counts. 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_order and 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_order for the rest of the placement.
  • An item with boot mode PERSISTENT or ALWAYS cannot sequence after a LAZY item. Because Features and Mechanisms default to LAZY, an eager item usually cannot sequence after a core item. This throws LogicException: 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.

  • 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().