Sourced Pages
Serve pages from an API, a database or any other source through the normal routing, template and editing pipeline.
On this page
What is a sourced page?
A page whose content comes from somewhere other than pages/: an API, a database, a file. Its parent is a real page whose view-bound class implements PageSource, and that class builds each child from a record.
A sourced page renders, routes and edits like any other page. It is never in the registry, and it costs no API call until something asks for it.
The source
PageSource has one method. record($name) returns the child's record, with its uuid, view and content, or null when the source has none:
// site/views/events/events.php, bound to the real page at site/pages/events/
namespace Phlat;
class EventsPage extends Page implements PageSource
{
public function record(string $name): ?array
{
$event = EventsApi::find($name);
if ($event === null)
return null;
return [
'uuid' => 'event-' . $event['id'],
'view' => 'event',
'title' => $event['name'],
'starts' => $event['starts_at'],
'venue' => $event['venue'],
];
}
}
Keep the uuid stable for a record: it is what the page's files and any relation to it are keyed by.
$page->child('summer-fest') on the source returns the page built from its record. $app->resolve('events/summer-fest') finds it from a path, and relation and pages fields resolve a path through the source the same way. $app->page() never asks a source, so routing and 404s stay cheap.
Routing
The source's router answers its children's URLs and returns the page:
// site/views/events/router.php
use Phlat\App;
use Phlat\Exceptions\NotFoundException;
use Phlat\Page;
return function (App $app, Page $page): void {
$page->router->get('{slug}', fn($slug) => $page->child($slug) ?? throw new NotFoundException());
};
Editing: the view decides
The child's view decides what an editor owns. An editor can open a sourced page in the admin whether or not it has been saved. The first save makes its folder, and drafts start from then on.
A save stores only the fields the view declares. Anything else in the record always comes live from the source, so with the event view declaring title and a note, an editor's title stays theirs while starts and venue follow the API:
// site/views/event/_index.json
{
"fields": {
"title": {},
"note": {}
}
}
Saving a key the view doesn't declare throws a ValidationException, on every page, sourced or not.
The same view can render a sourced page under a source and an ordinary page anywhere else. Whether a page is sourced comes from its parent, which isSourced() reports.
Listing
A source lists its own children. Build each one with child() and collect them in a PageCollection, which takes appended pages and a total of your own for paging:
$events = new PageCollection($app, EventsApi::count());
foreach (EventsApi::page(1) as $event)
$events->append($page->child($event['slug']));
Sourced or written in bulk?
A sourced page is live: it always shows what the source holds now, and it stays out of find(), search and sitemaps.
When the pages should be searchable and listed by the registry, write them as ordinary pages instead. A script or command creates and updates them with new() and save(), and one History batch makes the whole run one commit:
$parent = $app->page('events');
$app->history()->batch('import events', function () use ($parent, $records) {
foreach ($records as $slug => $record) {
if ($parent->has($slug))
$parent->child($slug)->save(['title' => $record['title']]);
else
$parent->new($slug, ['view' => 'event', 'title' => $record['title']]);
}
});
phlat docs:api, which writes this site's API reference, is one such command.