Search by

nvl / pages

nicolas_vls

Hierarchical, translatable, content-composed pages and dynamic page resources for Laravel

v7.0.0 2026-11-03 15:10 UTC

This package is auto-updated.

Last update: 2026-11-04 13:06:11 UTC


README

Localized hierarchical Pages for PHP 8.4 and Laravel 13. Core and Translatable are required. Plain Pages load without Content, SEO, Filterable, Metafields or application DTO libraries. Lifecycle operations and authenticated reads reload Eloquent actors from their class-defined canonical storage before host policies inspect them. Dirty keys, altered storage, detached actors and unavailable rows reject; caller attributes and loaded relations do not carry policy privileges. External identity providers retain their own freshness contract. Private previews require a persisted Eloquent actor. Explicit system actors remain subject to host policy.

Pages owns structural identities, translations, hierarchy, publication windows and native resource resolution. The consuming application owns Actions, input/output data, editor assembly, routes, controllers and preview HTTP responses.

Native services

Inject services from Nvl\Pages\Services:

Service Operations Results
PageLifecycle create, update, move, delete, restore Refreshed Page models; deletion returns bool
PageReader get, paginate, children, navigation, keyAvailable Models, Laravel collections/paginators or documented arrays
PageResolver resolve, preview ['page' => Page, 'resource' => ?array]
$page = $pages->create([
    'key' => 'about',
    'slug' => 'about',
    'site' => 'default',
    'status' => 'published',
    'translations' => ['en' => ['title' => 'About us']],
], $actor);

$page = $pages->update($page, [
    'slug' => 'about',
    'kind' => 'static',
    'status' => 'draft',
    'translations' => ['en' => ['title' => 'About us']],
], $page->revision, $actor);

$page = $pages->move($page, $parentId, $position, $page->revision, $actor);
$pages->delete($page, $page->revision, $actor);
$page = $pages->restore($page, $page->revision, $actor);

$result = $resolver->resolve($path, $site, $locale);
$preview = $resolver->preview($path, $site, $locale, $persistedActor);

Creation and replacement accept the documented native attribute arrays. Replacement retains the original complete-replacement semantics; omitted defaulted editorial fields reset to their defaults. Key, site, parent identity and ownership cannot be changed through replacement. Move owns parent changes. Expected revisions are separate arguments. Unknown/protected fields are rejected inside the service.

Page keys are globally unique across sites in an unowned installation; use site-prefixed keys when sites share storage. Native Ownership adoption replaces that identity with the documented owner partition. Slugs and paths are site scoped.

Mutations preserve serialized site-tree locks, cycle/depth checks, descendant path rebuilding, unique key/slug constraints, source-connection transactions and committed events. Locale replacement uses Translatable's supported related-row store inside the Page transaction. An enclosing host transaction remains authoritative.

Tree locks and lifecycle transactions use the configured Page model's actual connection. Existing Page inputs supply their identity; the persisted site determines the lock scope, even if the caller edited its in-memory attributes.

Authorization and public resolution

Bind Nvl\Pages\Contracts\PageAuthorization for application policy. It receives PageAbility, an explicit Laravel authenticatable, SystemActor or PublicActor, the canonical Page where applicable, and documented native context facts. Background identity is supplied only by trusted application code and requires explicit consumer authorization for management. The default adapter grants eligible public reads; it does not grant background management authority.

Public resolution always authorizes PublicActor::Anonymous; preview requires a persisted authenticatable and separate Preview authorization. The default policy does not grant private previews. Dynamic resolution first authorizes the matched Page with resourceId => null, before handler validation or data access, then authorizes the resolved resource identity before returning it. Policies must allow the page-level check independently of resource-level permission. Withdrawal from Published or Scheduled to Draft requires Publish authority. Public matching preserves the original not-found failures, publication/expiration windows, exact site, locale and owner isolation. Caller context cannot replace canonical site/path/resource facts.

For isolated configured Page models, enter native Ownership using a verified public owner/site mapping before resolution. PublicOwnerSiteRunner::run($canonicalOrigin, $callback) accepts the canonical origin string. The application owns request parsing. A supplied site must match the verified site; a public admission grants no management rights.

PageReader::children() and navigation() apply public visibility. get() returns a canonical model with localized copy and accepts an explicit PublicActor::Anonymous for public reads: publication/expiration windows and any verified public site constrain its native query before View authorization. Authenticated and trusted Background reads retain their explicit host policy. paginate() authorizes management reads. Pagination accepts fixed native filters for status, kind, parent, navigable and sort; it does not require Filterable. Consumers select safe fields and loaded translations for their response projections.

ConfiguredPageAuthorization is a supported public policy for composition with a host's PageAuthorization adapter. It reads canonical Page storage for public View decisions, retains the verified site, permits navigation and denies other capabilities. Hosts can delegate these public capabilities to it and implement their own management policy without importing an internal class or copying native publication rules.

Dynamic resources

Register stable aliases under nvl-pages.resources. PageResourceHandler retains route patterns, exact route-parameter rules, constrained queries/fetching, sanitized presentation and optional sitemap entries. Request records are array{pageId:string,site:string,locale:string,parameters:array<string,string>}. Presentation records are array{type:string,id:string,payload:array<string,mixed>}.

After a page matches its route and rules, a missing dynamic resource raises NotFoundHttpException; resolution does not try lower-priority pages. Authenticated get resolves a canonical row before its per-page policy, so hosts choose their own public error mapping if existence must be concealed.

Owned handlers implement OwnershipSafePageResourceHandler and declare the exact native model. Pages checks both query and returned model identity under the admitted resource. It does not infer ownership from route parameters or presentation IDs.

Optional composition

nvl-pages.integrations.content, seo and metafields accept null for loaded-provider detection, false to disable, or true to require the provider. Explicitly enabled missing providers fail during boot. The base Page has no hard Content or SEO interface/trait dependency.

When selected, the existing adapters register the configured Page identity, Content group content, scoped SEO profiles/sitemaps and optional model relations. Hard deletion retains Content's atomic owner cleanup. Consumers compose Content rendering and SEO output through those packages' native services; Pages returns its own canonical model and resource record.

Installation and verification

Configure nvl-pages.models.page, connection, table identities, authorization, URL generation and resource handlers. Select one owner for migrations: vendor loading or host-published copies. Preserve existing IDs, revision history and reviewed native Ownership adoption. The legacy Tenancy bridge and package HTTP routes are removed.

Publish canonical nvl-pages-config, nvl-pages-migrations and nvl-pages-skills resources as needed. Run nvl:pages:doctor --strict --format=json for schema, resource, optional-adapter and tree diagnostics. When Ownership is declared, its adoption step provisions the separate owner tree-lock table; doctor verifies that table as part of readiness. nvl-pages.limits exposes maximum_path_bytes (768), maximum_resource_parameters (8), maximum_per_page (100) and maximum_public_children (100). Hierarchy depth is configurable from one to four levels. Host configuration/cache/worker deployment steps remain application-owned.

Shared service contracts, model extensions and composition apply alongside the package-specific rules above.