Reactive JavaScript
Prisma PHP uses PulsePoint as its reactive engine. There is no bundler step and no hydration ceremony:
a route root plus one plain <script>
is a component, and the runtime handles scope, rendering, diffing and event rebinding.
Prisma PHP
The environment. It handles routing and server-side rendering, injects the
pp-component boundary on every route root and imported partial, normalises component scripts,
and issues the CSRF cookie that secures calls back to PHP.
PulsePoint
The reactive engine. A standalone library (see the link above) that Prisma PHP integrates
deeply: hooks, keyed list diffing, context, portals, controlled form bindings, SPA navigation and the
pp global.
The shape you always write
One parent element as the route boundary, the visible content inside it, and the component script as the last child of that root.
Write bindings at the top level — no DOMContentLoaded, no IIFE, no manual
pp.mount(). And never put a type attribute on the script:
the runtime only recognises a script that has none.
<?php
use PP\MainLayout;
MainLayout::$title = 'Todos';
?>
<div>
<section>
<h1>Todos</h1>
<p>Count: {count}</p>
</section>
<script>
const [count, setCount] = pp.state(0);
</script>
</div>
Zero-API interaction
You do not build REST endpoints to handle a button click. Mark a PHP function with
#[Exposed] and call it with
pp.rpc(...). The runtime handles serialization, the CSRF and origin checks,
auth and role enforcement, rate limiting, multipart uploads and SSE streaming.
For long-lived bidirectional traffic — chat, presence, live feeds — use
pp.socket(...) instead of polling.
What the runtime gives you
PulsePoint is a component runtime, not a sprinkle of directives. A route root plus one script is a real component instance, with a scope, a render pass, keyed reconciliation and effect cleanup.
Hooks
pp.state, pp.effect,
pp.memo, pp.reducer,
pp.ref — plus transitions and optimistic updates.
Keyed lists
pp-for over any iterable, with a stable key driving DOM reuse.
Handlers inside a loop keep resolving after a rerender.
Context and portals
pp.createContext with a Provider tag, and
pp.portal for dialogs and overlays that must escape their parent.
Controlled forms
Two-way value and checked bindings, multi-selects, and
defaultvalue defaults restored on form.reset().
SPA navigation
Link interception is on by default, with a swapped loading region, scroll restoration, and
pp:navigation:* events you can hook.
Two wires to PHP
pp.rpc(...) for request-response and SSE streams,
pp.socket(...) for long-lived bidirectional traffic.
Where to go next
The full hook surface: state, effects, refs, memo, reducer, context, portals, transitions and optimistic updates.
Interpolation rules, keyed lists, reactive inline CSS, attribute spreading and controlled form bindings.
Direct function invocation, the error contract, cancellation, streaming responses and upload progress.
The pp global
Hooks are listed on the State & Hooks page. These are the runtime helpers available anywhere the bundle is loaded.
| Helper | Description |
|---|---|
| pp.rpc(name, data?, options?) |
Calls an #[Exposed] PHP function. Handles JSON, SSE streams, multipart uploads,
transparent redirects and cancellation.
|
| pp.socket(name, args?, options?) |
Opens a named socket for long-lived bidirectional messaging. Returns a handle with
send(...), close() and
readyState.
|
| pp.redirect(url) | SPA navigation for same-origin URLs, a normal browser navigation otherwise. |
| pp.createContext(defaultValue) |
Creates a context token to provide in the template and read with pp.context(token).
|
| pp.mount() |
Bootstraps top-level component boundaries. The shipped bundle already calls it on
DOMContentLoaded and repeat calls are no-ops — you should not need it.
|
| pp.enablePerf() / disablePerf() |
Toggles render timing collection. Set localStorage["pp-perf"] = "1" to profile the
mount phase before any console access.
|
| pp.getPerfStats() / resetPerfStats() | Reads and clears the collected render timings. |