aura/view
Aura.View implements the TemplateView and TwoStepView patterns using PHP itself as the template language, with file-based and closure templates, helpers, and sections. No runtime dependencies; requires PHP 8.4+.
TemplateRegistryInterface (set/has/get) and SearchPathInterface (getPaths/setPaths/prependPath/appendPath/setNamespaces/hasNamespace/getNamespaces/getNamespacePaths/setTemplateFileExtension) — a framework can now substitute a module-aware registry without subclassing TemplateRegistry. Previously setTemplateRegistry() type-hinted the concrete class. Note the deliberate split: path management is not on TemplateRegistryInterface, so a precompiled name-to-file registry remains a valid substitution.HelperRegistryInterface — but the View deliberately does not type-hint it (helpers reached only via __call(), parameter typed ?object), so Aura.Html's HelperLocator wiring keeps working with no adapter. This is a compatibility win for existing installs and removes an adapter from our integration surface.parent() — Template Inheritance for Overrides (new capability): Search paths are first-hit-wins, so a shadowed template was previously unreachable. parent() resumes the search past the current template's directory. This is the single most TPM-relevant feature: package/theme overrides no longer require copying whole files — only the diff. Directly applicable to multi-brand or vendor-package template customisation, and the one feature here with a measurable engineering-hours payback.\Closure (breaking, previously unassessed): TemplateRegistry::get() returns \Closure, and non-closure callables passed to set() are coerced so the View can bindTo() them. This fixes a latent bindTo() failure for array callables — but it changes stored identity (new finding, added this review). Consequences we must check: anything comparing registry entries by identity (=== on the callable), type-checking for Closure, or serialising/caching the registry (var_export, config-cache style persistence) now breaks, because closures are not serialisable. This is the only release-note item our prior assessment did not cover.config/Common.php, the extra.aura block, the Aura\View\_Config\ PSR-4 entry, and the aura/di dev dependency are gone (the config extended an Aura\Di\Config that no longer exists). Wiring is now explicitly the consuming framework's job — neutral-to-positive for Laravel (we never used Aura.Di) but it removes any auto-discovery surface and means our service provider owns all wiring.src/** headers plus composer.json are consistent. Clears legal/procurement review faster for redistribution or embedding; copyright 2011-2026, Aura for PHP.Aura\View\ViewFactory does not implement Illuminate\Contracts\View\Factory (no make(), exists(), share() semantics). Aliasing the contract to Aura's factory — as earlier drafts of this assessment suggested — is not viable; it would break every Blade/view() call site.AuraRenderer service, inject it into the specific controllers/jobs that need it. Coexistence with Blade, not substitution of Laravel's view layer.setData() vs View::share(); still manual escaping (no auto-escape, unlike Blade).beginSection()/getSection() map to @section/@yield, but with raw PHP control flow.declare(strict_types=1). Direct callers are unaffected; subclasses fatal on load if their overrides don't match.ViewFactory::newInstance() (breaking): The four flat positional arguments collapse into two ViewSpec objects (map, paths, namespaces, extension grouped per registry). newInstance() and newInstance($helpers) are unaffected — only the long form breaks, with a \TypeError naming the parameter.HelperRegistryInterface::set() Gains an override Parameter (breaking for implementors): Anyone who already implements that interface — including internal test doubles or a framework-specific helper registry — must widen the signature. Cheap, but it is a load-time break, not a runtime one.^8.4, up from >=5.4.0. This is the dominant integration constraint — see risk below.View for unit tests, no Blade plugins required.^8.4 Requirement (highest-severity): Hard floor. Laravel 10 (8.1–8.3) is excluded; Laravel 11/12 support 8.4 but only if our runtime is actually 8.4. Any environment pinned to 8.2/8.3 cannot take 7.x and cannot take 2.x alongside a modern Laravel either. This makes the package viable only where we control the PHP runtime.get() always returns \Closure. Breaks identity comparisons, is_array()-style checks, and any attempt to serialise or export the registry (closures cannot be serialised). If we were tempted to cache a resolved registry to disk for boot speed, that door is now closed — we cache at OPcache level only. Low likelihood in a Laravel integration, but it is a boot-time failure if present.View, TemplateRegistry, or implements HelperRegistryInterface, the strict-types change is a boot-time fatal, not a page-level 500. Must be audited before upgrade.HelperRegistry::set() throws HelperAlreadyRegistered on collision (including registering the identical callable twice). Good for detecting multi-package collisions that were previously silent last-one-wins; bad for any boot sequence that registers without an owner. override: true is the escape hatch and is not an error when the name is free — so asserting our own helper without probing first is safe; use has() only when you need to branch.View with no template set returns '' (layout-wrapped) instead of throwing TemplateNotFound — the old behaviour is not expressible under strict types. Misconfiguration surfaces as a blank page rather than an error; needs a lint/assert step.Aura\View\Exception; InvalidTemplateName (name parsing) and HelperNotFound (unresolvable helper, replacing a raw PHP Error) are explicit. InvalidHelpersObject is deprecated (never thrown — the ?object parameter means PHP's own \TypeError fires first) and slated for removal in 8.0 — existing catch blocks still resolve but should be dropped.void. Any chained call ($view->setData(...)->setLayout(...)) now fatals. Mechanical grep, but must be in the upgrade checklist.^2.0 will not see it.View, extend TemplateRegistry, or implement HelperRegistryInterface / SearchPathInterface anywhere? This determines whether the upgrade is a config change or a code change — and the interface implementations must now also accept set(..., override: …).var_export, or disk-cache the template registry? Closures make that impossible as of 7.0.0; caching stays at OPcache level.parent()-based template overrides? If it's "replace Blade," the answer is no.override: true permitted?parent(), or is copy-and-replace acceptable? (Quantify: how many vendor templates do we currently fork, and how many lines do we actually change?)parent() lets us carry only the diff.parent() for the surfaces where we currently copy a vendor template to change two lines.Illuminate\Contracts\View\Factory. Keep Blade authoritative for HTTP HTML.// app/Providers/AuraViewServiceProvider.php
public function register(): void
{
$this->app->singleton(AuraRenderer::class, function ($app) {
$factory = new \Aura\View\ViewFactory();
return new AuraRenderer($factory->newInstance(
$app->make(HelperLocator::class), // Aura.Html — no adapter needed
new \Aura\View\ViewSpec(map: $viewMap, paths: $viewPaths),
new \Aura\View\ViewSpec(map: $layoutMap, paths: $layoutPaths),
));
});
}
Note the ViewSpec form — the 2.x four-flat-argument call produces a \TypeError. newInstance() / newInstance($helpers) still work unchanged for trivial cases.| Blade Directive | Aura.View Equivalent |
|---|---|
@foreach |
foreach ($this->items as $item) |
@section |
$this->beginSection('name') (must be paired; unpaired endSection() throws) |
@include('partial') |
$this->render('partial') |
@yield |
$this->getSection('name') |
@extends |
layout registry + parent() for shadowed templates |
setData() replaces View::share(); use a ViewHelper wrapper as the single escaper entry point. Setters are not chainable — call them on separate lines.override: true. Treat HelperAlreadyRegistered as a CI signal, not a runtime nuisance — it is the mechanism that surfaces the collisions we previously could not see.^8.4 only. Verify against the Laravel major in use before scheduling.^2.0 pin; there is no 3.x–6.x to step through, so 2.x → 7.x is a single jump.\InvalidArgumentException to Aura\View\Exception\InvalidTemplateName; catch HelperNotFound where helpers are dynamic; drop InvalidHelpersObject catches ahead of 8.0.extends .*View, implements HelperRegistryInterface, implements SearchPathInterface, chained setters (set…->), newInstance( with more than two arguments, array-callable registration passed to TemplateRegistry::set(), and any registry serialisation/export.parent() to a real vendor-template override and measure the diff reduction.php artisan view:clear equivalent, nothing to rebuild in CI.InvalidTemplateName and HelperNotFound explicit.config/Common.php and extra.aura means the service provider is the sole source of truth; nothing is discoverable.getNamespacePaths() getter (which returns [] rather than throwing for an unregistered namespace; hasNamespace() distinguishes "unregistered" from "registered but empty").| Risk | Impact | Mitigation |
|---|---|---|
| XSS via unescaped output | High — permanent design property | Single escaper wrapper (ViewHelper), Aura.Html locators, CI grep for raw <?= |
| PHP < 8.4 in any environment | High — hard boot failure on install | Phase 0 runtime gate; pin composer platform config |
| Subclass signature mismatch | High — fatal on class load, not page-level | Phase 1 audit; add types to every override |
Interface implementor signature drift (HelperRegistryInterface::set() gains override) |
High — fatal on class load | Audit implementations; widen signature with the third parameter |
| Helper name collision at boot | Medium — application fails to start | Designate an owner per helper; override: true only where intentional |
| Registry callable identity / serialisation (new) | Medium — closures break ===, is_array(), and disk caching |
Stop treating registry entries as data; cache via OPcache only |
| Chained setter call | Medium — fatal (void return) |
Grep set->*->; setters on separate lines |
newInstance() long positional form |
Medium — \TypeError naming the parameter |
Migrate to ViewSpec |
| Empty render on unset template | Medium — blank output masks misconfiguration | Assert template set in the wrapper; smoke test renders |
endSection() without beginSection() |
Low–Medium — now throws | Mechanical fix; caught in test |
| Template not found | Medium — runtime error | Validate paths in the registry at boot |
| Unresolvable helper | Medium — now HelperNotFound instead of raw PHP Error |
Catch the package exception at the render boundary |
InvalidHelpersObject catch retained |
Low — dead code, removed in 8.0 | Drop catches now |
| Team adoption resistance | High — Blade familiarity | Cheat sheet (Blade → Aura.View), scoped to non-HTML surfaces so Blade skills stay relevant |
ViewFactory, ViewSpec, templates, setData().parent()-based overrides, escaping discipline.void setters, interface substitution, closure-normalised registry, exception hierarchy.override: true is legitimate.How can I help you explore Laravel packages today?