v0.0.41
3 July 2026
Field/FieldValue split, Session/SessionRoot scoped-access refactor, class renames throughout, Collections namespace, Res::ensureCacheControl backstop, and cache addon.
Added
FieldValueclass : the per-render counterpart toField.Fieldis now definition-only (type, label, options);FieldValuecarries the raw value, decodes it, renders it, and validates it.Record::__getnow returnsFieldValueinstead ofField.FieldValueSet: ordered set ofFieldValueinstances; returned byPage::fields()andField::optionFields(). ReplacesFieldCollectionin rendering contexts.TemplateFields: static utility class backingRecord::resolveTemplate()/templateFieldDefs()andFieldValue::optionFields(), shared so non-Recordvalue objects can reuse the same template resolution logic.SessionRoot: the full session implementation.Sessionis now a thin prefix-scoped wrapper;$app->sessionisSessionRoot::scope('site'). Use$app->session->scope('name')for addon-namespaced sessions.NamespacedSessionis removed.HttpException: base class for all typed HTTP exceptions (NotFoundException,ForbiddenException, etc.).App::execute()now only trusts HTTP status codes from this hierarchy; a stray\RuntimeExceptionwith an arbitrary code defaults to 500.DirectSessionAccessRule: PHPStan rule enforcing that all session data access goes through a named scope rather than directly on$app->session.phpstan.neon: PHPStan config added to the repo.cacheaddon : purges Souin cache onpage.update:after,page.delete:before,file.update:after, andfile.delete:aftervia a PURGE request to the Caddy admin API.Http::purge(string $url, array $headers): PURGE HTTP method support.Res::hasHeader(string $name): checks whether a named response header has already been sent.Res::ensureCacheControl(): setsCache-Control: no-storeif no Cache-Control header is queued. Called as a last-chance backstop in allRes::redirect/html/json/textandHx::navigate/location/refreshexit paths so no response can accidentally fall through to Souin's heuristic caching default.V::allowedExtension(string $filename, array $allowed): validates a filename's extension against an allowed list (case-insensitive, no leading dot).Page::upload(array $file, array $options): validates the uploaded file's extension againstconfig.media.allowed, sanitizes the filename, and stores it viaPageFiles.User::role(): returns theUserRoleinstance for this user's role.Registry::countRows(string $query): count variant ofrows()that returns a row count without loading page objects.Record::templateRoot(): new hook; subclasses can override just this (returning e.g.'fieldtypes') instead of overridingtemplate()directly.
Changed
- Renames:
Addon→AddonPage,AddonMounts→AddonRegistry,Email→Mailer,Permission→UserRole,View→PageView. - All collection classes moved to the
Phlat\Collections\namespace (RecordCollection,PageCollection,UserCollection,FieldCollection,AddonCollection). Field::metaFields()renamed toField::optionFields()(returnsFieldValueSet).Page::fragmentreadonly property →Page::fragment()method; now lazy (reads the view template on demand instead of at construction).Page::tail()removed (was an alias forremainder()).Page::siblings()andPage::children()accept a$querystring as first parameter.Page::find()no longer has a$recursiveparameter.Page::fileSourcePath()changed fromprotectedtopublic.PageView(Viewrenamed) gains PHP 8.4 property hooks fortitle,description, andcache.F::mime(): extension override map added for CSS, JS, JSON, and SVG, sincemime_content_type()cannot distinguish text-based formats.DataFile::write()now pretty-prints JSON.- Registry
find()/rows()/countRows()all route through a single internalcompile()method instead of duplicating SQL assembly. - Admin media view removed; the browse view replaces it with a tabbed picker/files interface.
- Non-image public files now served directly from
Page::route().