Purpose
The first event in the page-resolution pipeline. Gives plugins a
shot at claiming the request URL before Scriptor's built-in
lookup runs. Listeners cooperate via the mutable resolution
slot: the first listener to assign a Page to it wins.
This is the event the bundled DbPagesResolverPlugin (Scriptor's
own DB-backed slug resolver) listens to, and the event
scriptor-markdown-pages listens to so a /docs/concepts/...
URL can resolve to a virtual page generated from a markdown file
instead of a DB row.
FQCN + file path
- FQCN:
Scriptor\Boot\Events\Frontend\PageResolving - Source:
boot/Events/Frontend/PageResolving.php
When to use
Subscribe inside Plugin::register() when you want to:
- Resolve a URL pattern to a virtual page (markdown files, generated archive pages, on-the-fly product detail pages).
- Override the default DB resolution for a specific URL shape (e.g. a redirect rule that produces a temporary "stub" page whose template handles the redirect).
You do not use this event for read-only side effects after
resolution; that is what PageResolved is for.
Surface
Public properties
| Property | Type | Purpose |
|---|---|---|
urlSegments |
readonly UrlSegments |
Parsed segments of the current request URL. Pass to your own resolver |
resolution |
?Page (mutable) |
The slot listeners fill. Starts null. First non-null wins |
No methods. The DTO is just data plus the writable slot.
Constructor
public function __construct(public readonly UrlSegments $urlSegments)
Scriptor's Site::execute() constructs the event with the
request's UrlSegments and dispatches it. You do not construct
the event yourself.
Lifecycle
Constructed and dispatched once per request at the top of
Scriptor\Boot\Frontend\Site::execute(). After the event has
run through every listener, Site reads resolution:
- Non-null: the page is taken,
PageResolvedfires with that page,execute()returns. - Null:
SitedispatchesRouteNotFoundas a last-chance resolver; if that also leaves the slot empty,throw404().
Listeners run in registration order (whichever plugin called
PluginContext::subscribe() first). The "first writer wins"
contract is enforced by convention, not by the event: a
misbehaving listener can overwrite a previous resolution. The
conventional listener body checks the slot before writing.
Common patterns
Resolving a virtual page from a custom source
public function register(PluginContext $context): void
{
$context->subscribe(
\Scriptor\Boot\Events\Frontend\PageResolving::class,
function (\Scriptor\Boot\Events\Frontend\PageResolving $event): void {
if ($event->resolution !== null) {
return; // someone already resolved
}
$page = $this->mySource->find($event->urlSegments);
if ($page !== null) {
$event->resolution = $page;
}
},
);
}
The two-line guard at the top (!== null → return) is the
canonical body. Without it, two plugins racing for the same URL
would silently overwrite each other and the slot would always
end up with whichever subscribed last.
Claiming a URL prefix (markdown-pages-style)
$context->subscribe(
PageResolving::class,
function (PageResolving $event): void {
if ($event->resolution !== null) {
return;
}
$path = $event->urlSegments->path();
if (! str_starts_with($path, 'docs/')) {
return; // not our prefix
}
$markdownFile = __DIR__ . '/content/' . substr($path, 5) . '.md';
if (is_file($markdownFile)) {
$event->resolution = $this->virtualPageFactory->fromFile($markdownFile);
}
},
);
Generating a virtual archive page
$context->subscribe(
PageResolving::class,
function (PageResolving $event): void {
if ($event->resolution !== null) {
return;
}
$segments = $event->urlSegments;
if ($segments->first() !== 'archive') {
return;
}
$year = (int) ($segments->at(1) ?? 0);
$month = (int) ($segments->at(2) ?? 0);
if ($year > 0 && $month > 0) {
$event->resolution = $this->archiveFactory->build($year, $month);
}
},
);
See also
PageResolved: fires next when this event produced a pageRouteNotFound: fires next when this event did not produce a pagePage: the DTO listeners assign toresolutionSite: theexecute()method that dispatches this event- Concept: Frontend Events: walks the pipeline shape (resolving → resolved or not-found → 404)