Purpose
Fires after the page resolution dance is over and Site has
picked the page it is about to render. Read-only by design:
listeners cannot rewrite the result here. They observe, react,
or interrupt by throwing.
The event exists so plugins have a single, late-enough hook for "the page exists, the URL is real, do something now". Typical use is logging a page view, populating a request-scoped cache, or running an authorisation check that raises when the authenticated visitor is not allowed to see this page.
FQCN + file path
- FQCN:
Scriptor\Boot\Events\Frontend\PageResolved - Source:
boot/Events/Frontend/PageResolved.php
When to use
Subscribe inside Plugin::register() when you want a hook that
runs:
- Once per request, but only when a page was actually resolved (no 404).
- After any plugin's
PageResolvinglistener and the built-in fall-throughs have finished. - Before the template renders. You can still throw, return
a redirect, or call
$site->throw404()from here.
If you need to mutate the rendered HTML, this is the wrong
event; use ContentRendering. If you need to handle the
no-match case, listen to RouteNotFound.
Surface
Public properties
| Property | Type | Purpose |
|---|---|---|
page |
Page (readonly) |
The resolved page. Always non-null when this event fires |
The class is final readonly. No mutable state, no methods.
Constructor
public function __construct(public Page $page)
Site::execute() constructs the event with the resolved page.
You do not construct it yourself.
Lifecycle
Fires at most once per request, from inside
Scriptor\Boot\Frontend\Site::execute(), immediately after a
non-null Page lands in $site->page. Both resolution paths
trigger it:
PageResolvingproduced a page →PageResolvedfires.PageResolvingproduced nothing butRouteNotFounddid →PageResolvedfires.- Both produced nothing →
Site::throw404();PageResolveddoes not fire.
Listeners run in registration order. Any uncaught exception from
a listener propagates up; subsequent listeners do not run, and
the rendering pipeline never reaches template.php.
Common patterns
Logging a page view
$context->subscribe(
\Scriptor\Boot\Events\Frontend\PageResolved::class,
function (\Scriptor\Boot\Events\Frontend\PageResolved $event) use ($logger): void {
$logger->info('page resolved: {slug}', [
'slug' => $event->page->slug,
'id' => $event->page->id(),
]);
},
);
Authorisation gate that throws
$context->subscribe(
PageResolved::class,
function (PageResolved $event): void {
$page = $event->page;
if ($page->pagetype === 'members-only' && ! $this->session->isAuthenticated()) {
// Bail out before any template renders.
$this->site->redirect('/login/?next=' . urlencode($_SERVER['REQUEST_URI']), 303);
}
},
);
Priming a request-scoped cache
$context->subscribe(
PageResolved::class,
function (PageResolved $event) use ($cache): void {
$cache->prime(
'related-posts.' . $event->page->id(),
fn() => $this->findRelated($event->page),
);
},
);
See also
PageResolving: the resolution event that fires before this oneRouteNotFound: the fall-through that can still produce a page (and thenPageResolvedfires)ContentRendering: the next event in the pipeline, for mutating rendered HTMLPage: the resolved page exposed on this eventSite: dispatcher of all four frontend events- Concept: Frontend Events: pipeline overview