phpaml / engine
Client-side state, actions and rendering engine for AML View.
Requires
- php: ^8.2
This package is auto-updated.
Last update: 2026-08-19 15:04:53 UTC
README
Beta architecture note: the current inline runtime remains supported during the beta. Moving it to a modular, versioned JavaScript asset is a stable-release blocker; see the runtime modularization plan.
phpaml/engine is the client-side execution engine for AML View. It owns local
state, local actions and targeted bindings in the browser. Local interactions
do not contact the PHP server.
The first beta supports local actions, explicit API requests, navigation, lifecycle events, reactive presentation, collections, form bindings and accessible synchronous or asynchronous validation. AML View remains the public declarative API and installs this package as an internal dependency.
Form controls can use data-aml-model to update state locally. The engine
immediately refreshes every matching data-aml-bind element without a network
request.
The client router intercepts same-origin links, fetches the next server-rendered document, replaces only the AML application root, updates active links and uses the browser History API. External links, downloads and links explicitly marked for native navigation are left untouched.
Backend communication is explicit through Api::get(), post(), put(),
patch() and delete(). API actions are restricted to same-origin paths and
can bind their loading, result and error values to client state. Ordinary local
actions never pass through this request client.
Every AML root emits aml:mount, aml:update and aml:unmount. The public
AMLEngine.on(root, phase, handler) helper returns a disposer that is called
automatically during unmount. In-flight API requests use AbortController and
are cancelled when navigation removes their page.
Actions::sequence() executes instructions in declaration order and awaits API
instructions before continuing. Actions::when() selects a then or
otherwise instruction using eq, neq, gt, gte, lt, lte, truthy
or falsy against current client state.
Reactive presentation bindings update visibility, CSS classes and disabled state whenever their referenced value changes. They are evaluated during the initial mount and after every local, form or API state update.
Reactive collections support append, prepend, removeAt, removeBy,
updateBy, filterBy, sortBy, move, reverse and clear local actions.
merge updates part of an object without replacing its other properties. Nested paths such as
profile.address.city and tasks.0.title are reactive. State references inside
action values are resolved at click time. List nodes are rebuilt safely with
textContent, never raw HTML.
Actions::transaction() groups several state changes into one browser render
and emits one aml:transaction event containing the changed paths. Failures
restore the original snapshot and emit aml:transaction-error. This avoids
intermediate UI states when several related values must change together.
Local-only Actions::sequence() instructions are batched automatically in one
microtask render. Computed manifests propagate concat, sum, count, all
and any values from explicit dependencies. Twenty guarded passes prevent a
malformed computed graph from looping indefinitely.
For development tools, AMLEngine.inspect(root) returns an isolated snapshot
of the current frontend state without exposing the mutable internal object.
AMLEngine.history(root) retains the latest 100 snapshots and
AMLEngine.restore(root, index) restores one without contacting the server.
Persistence supports local storage, session storage and IndexedDB. Versioned
objects can declare deterministic rename, defaults and remove migrations.
The engine reports missing, newer, expired or corrupt data through explicit
aml:storage-* events and keeps the server-rendered fallback state usable.
The reproducible browser fixture in tests/browser-fixture.php covers nested
transactions, custom collection templates, multiple AML roots, history and
restore, IndexedDB migrations, corrupt storage and live cross-tab updates.
Playwright executes the critical interaction, transaction and CSRF scenarios
in Chromium, Firefox and WebKit on every CI run.
For same-origin mutations, Engine reads the token from
<meta name="csrf-token" content="…">, sends it through X-CSRF-Token, and
updates the meta element when the response provides a renewed token in the
same header. GET requests never receive this header.
Deployments with a strict Content Security Policy may pass the request nonce to
EngineRuntime::script($nonce). The nonce is validated before it is inserted;
applications do not need to enable unsafe-inline for the AML engine.
Effects runtime
The engine consumes effect manifests generated by #[Effect]. It schedules
one-shot actions, debounced dependency reactions, timeouts, intervals and
window/document listeners. Every effect owns its timers, listeners and abort
controllers, so rerunning or unmounting performs deterministic cleanup. Effect
errors are isolated and observable through aml:effect-error; rapid reactive
cycles are stopped and reported through aml:effect-cycle.
Async effects support latest, exhaust, queue and parallel scheduling.
The default latest strategy aborts stale requests and prevents their results,
errors, or loading cleanup from modifying the current execution. Dynamic keyed
components own isolated runtimes, and diagnostic state restoration reruns the
dependencies it changed.
Listener actions may consume sanitized event snapshots through EventRef.
Plans support local declarative cleanup and dependency triggers support
throttle. AMLEngine.effects(), pauseEffect(), resumeEffect() and
runEffect() provide copied diagnostics and explicit development controls.
Rich component runtime
The engine enhances AML View’s rich manifests entirely in the browser. It opens and closes native dialogs, traps modal focus, implements keyboard tabs and accordions, updates ARIA state, and sorts data tables locally. Keyed lists can be reordered with drag and drop without a server request.
Virtual lists calculate a bounded visible window from scroll position, row height, viewport height, and overscan. Newly inserted elements may use declarative fade, slide, or scale transitions through the Web Animations API. All behavior is scoped to the mounted AML root.
The initial virtual window remains server rendered. A ResizeObserver refreshes
visible windows when their viewport changes. Drag payloads are bound to their
source collection, keyboard moves use Alt+ArrowUp and Alt+ArrowDown, table
sort state stays local to its table, and animations honor reduced motion.
Context and navigation runtime
Nested context providers are resolved locally and can follow reactive state or
persist through local storage. Theme and locale contexts synchronize the root
document. AMLEngine.context() returns a copied context value and
AMLEngine.route() exposes an immutable path, query, and hash snapshot.
Declarative navigation supports push and replacement history, same-origin fetching, loading/error/not-found boundaries, active links, title updates, focus restoration, scrolling, and reduced-motion-aware transitions. Unsafe URL schemes are rejected, and external HTTP(S) destinations use native navigation.