Data Objects

Record, Page, Field, FieldValue, FieldType, DataFile and PageCollection.

Record

Record (Phlat\Data\Record) wraps an index.json and a colocated data.json, and exposes content fields through __get. It is the abstract base for Page, User and Field.

Properties

Property Type Description
$path string Absolute folder path, no trailing slash
$uri string App-relative folder URI
$uuid string Persisted identity, from index.json
$locked bool Structural flag
$created / $updated ?string ISO 8601 timestamps

Methods

Method Returns Description
exists() bool True if index.json exists on disk
path() string The folder path, with a trailing slash
name() string The folder name
folder() string App-relative folder URI
index() DataFile The index.json
data() DataFile The data.json. On a page this honours the logged-in editor's draft autosave
publishedData() DataFile The canonical data.json, bypassing any draft
save(array $data) void Writes structuralKeys() to index.json and the rest to data.json

Field access

A key that isn't a typed property goes through __get, which reads data.json and returns a FieldValue:

$raw   = $page->data()->get('body'); // string from data.json
$field = $page->body;                // FieldValue
echo $field;                          // decoded string

__call resolves the field via __get, then invokes it if callable. That is how {$page->hero('w=400')} works as shorthand for src().


Page

Page extends Record and is one page in the site tree. Get one from $app->page($uri), never new.

Page adds $view (?string, default null), $published (bool, false until published), $access (string, '' for anyone) and $router. title is not a structural property; it is a content field like any other.

Methods

Method Returns Description
url(string $path = '', array $query = []) string Public URL, e.g. /blog/my-post/
slug() string The page's own slug
isViewable() bool Whether the visitor may open it. See Publishing and access
parent() ?Page Parent, or null at the root
root() Page The root page
section() Page The top-level ancestor
depth() int Nesting depth below the root
siblings(string $query = '') PageCollection Parent's other children
next() / prev() ?Page Neighbouring sibling in order
has(string $slug) bool True if a direct child has this slug
hasChildren() bool True if there are any children
contains(Page $other) bool True if $other is this page or a descendant
isActive() / isOpen() bool The current page / the current page or an ancestor of it
new(string $name, array $data = []) Page Create a child page
delete() void Delete the page and its descendants
parents() PageCollection All ancestors, root first
child(string $slug) ?Page A direct child by slug; doesn't check it exists
children(string $query = '') PageCollection Direct children, hidden excluded, filtered and sorted by $query
find(string $query = '') PageCollection Query across this page's subtree
files() array PageFile objects for the non-JSON files in the folder
file(string $uri) / image(string $uri) PageFile / PageImage One of the page's files, by filename
upload(array $file, array $options = []) PageFile Store an uploaded file
view() PageView The resolved view
render(array $data = [], ?string $block = null) string Render to HTML, running controllers
save(array $data) void Content to data.json, structural keys to index.json

Field

Field is a named, reusable field definition at site/fields/{name}/index.json, not what template field access returns. It is read-only: definitions are edited as files.

Property Type Description
$name string The field's name, e.g. body
$type string The field type, e.g. markdown
$label string Admin-facing label
$options array Default options (required, max_length, ...)

FieldValue

What $page->fieldname returns: the per-render value, combining a definition (name, type, label, options) with a FieldType instance. It is never persisted or indexed.

Method Returns Description
decode() mixed The decoded value, via the inner FieldType
encode(mixed $value) mixed Encode a value for storage
empty() bool True if the raw value is null, '' or []
type() FieldType The inner FieldType
render() string Render through the type's render template
url() string The admin edit-fragment URL, /page/@fieldname
withKey(string $key) FieldValue New instance scoped to a collection record key
__toString() string The decoded string

Any other method call (src(), srcset() on an image) is forwarded to the inner FieldType. url() is the exception: it is declared on FieldValue, so it never reaches a file's own url(). For a file or image URL, output the field bare ({$page->cover}) or use ->src(...).


FieldType

Holds a field's encode, decode, empty and validate logic. No page context, rendering or persistence. Subclasses are FieldType plus the type name (FieldTypeMarkdown, FieldTypeImage), and four primitives cover every JSON shape: FieldTypeText, FieldTypeNumber, FieldTypeList and FieldTypeObject. To write one, see Fields.

Member Description
$value The raw stored value
$type The type name
decode() Override to return the decoded value
encode(mixed $value) Override to encode a value for storage
empty() True if value is null, '' or []
create(...) Factory; override to consume $options
__toString() (string) $this->decode()

DataFile

DataFile extends File and wraps a JSON file, decoding it into $data on construction.

Member Description
$data Decoded JSON as an array
get(string $key, mixed $default = null) Dot-notation access: get('meta.title')
merge(DataFile $other) New DataFile with deeply merged data
write(array $data) Merge, write, and return a fresh DataFile
__get(string $key) Shorthand for get($key)

PageCollection

An iterable list of Page instances, returned by Page::children(), parents() and find(). (RecordCollection is the generic base.) It implements IteratorAggregate and Countable, so it works directly in {foreach}. Pages load from the registry lazily, on iteration.

Method Returns Description
count() int Number of items
isEmpty() bool True if there are no items
first() / last() ?Page First / last item
nth(int $index) ?Page Item at an index
reverse() static Reversed copy
limit(int $n) / offset(int $n) static Cap at N items / skip the first N
contains(Record $item) bool True if the item is in the collection
toArray() array The items as a plain array
each(callable $fn) static Call the callback for every item
any(callable $fn) / all(callable $fn) bool True if any / every item passes
append(Record $item) static Add an item
filter(callable $fn) static Items where the callback returns true
sort(callable $fn) static Sorted by a comparison callback
pluck(string $field) array One field's value across all items

The collection has no find(). The query DSL lives on Page::children(), Page::find() and Registry::find(), so pass the query there:

$page->children('published=1, sort=-created')  // right
$page->children()->find('published=1')          // no such method

Query syntax and operators are in Registry.