Virtual Pages

Render pages backed by external JSON or API data through the normal routing and template pipeline.

On this page

What is a virtual page?

A Page backed by data that was never read from disk, for exposing an external API (events, products, any JSON) as a renderable page.

A virtual page is never injected globally. It exists only because a real page, with a real folder and _index.json, routes to it explicitly, the same constraint that governs addons.

Creating one

App::page(string $uri, array $data) already supports this. A non-empty $data skips every disk read, and a view in $data still resolves the right view-bound Page subclass:

$app->page('events/summer-fest', $syntheticData);

The result is a normal Page, with standard class and template resolution behavior.

In a router

A real page's router builds the data and returns the page. Handlers may return a different Page:

// site/views/events/router.php, for the real page at site/pages/events/
use Phlat\App;
use Phlat\Page;
use Phlat\Exceptions\NotFoundException;
use Phlat\Toolkit\Hash;

return function (App $app, Page $page): void {
    $page->router->get('{slug}', function ($slug) use ($app, $page) {
        $record = (new EventsSource())->find($slug);
        if (!$record) throw new NotFoundException();

        return $app->page("{$page->url()}/{$slug}", [
            'uuid' => Hash::sha256("events:{$slug}"),
            'view' => 'event',
            'data' => $record,
        ]);
    });
};

Use a conventional URI like "{$page->url()}/{$slug}" and url(), template resolution and everything else derived from the path just work.

Listing

For an index, build each page the same way and collect them in a PageCollection, which accepts appended instances:

$events = new PageCollection($app);
foreach ($source->all() as $slug => $record) {
    $events->append($app->page("{$page->url()}/{$slug}", [
        'uuid' => Hash::sha256("events:{$slug}"),
        'view' => 'event',
        'data' => $record,
    ]));
}

Limits

  • Not in the registry. find(), search and sitemaps don't see virtual pages. List them from their source, as above.
  • No shared source interface. A data source is a plain class with whatever lookups suit it.
  • Don't save them. save() on a virtual page writes real files at its path. Return virtual pages only from GET handlers, so they never reach the admin's edit flow.