laminas/laminas-view
laminas-view is the view layer for Laminas MVC and standalone PHP applications, providing PHP-based templating, view models, helpers, and rendering strategies for composing layouts and safely presenting escaped output.
Install the library directly; it does not require a Laminas application:
composer require laminas/laminas-view
v3 requires PHP 8.2+ and laminas/laminas-servicemanager v4. If you are coming from a Zend/Laminas MVC app, read the migration guide (docs.laminas.dev/laminas-view/v3/migration/v2-to-v3) before touching code — v3 is a deliberately decoupled, breaking release.
First place to look is the entry point: Laminas\View\Renderer\PhpRenderer implements Laminas\View\ViewInterface and is what you actually call. It owns three things you configure: a resolver (how template names map to files), a helper plugin manager (what $this->foo() means inside a template), and the variables passed to render().
Minimal standalone bootstrap:
use Laminas\View\Renderer\PhpRenderer;
use Laminas\View\Resolver\TemplatePathStack;
$renderer = new PhpRenderer();
$resolver = new TemplatePathStack();
$resolver->addPath(__DIR__ . '/views');
$renderer->setResolver($resolver);
$html = $renderer->render('emails/welcome', ['user' => $user]);
Your first realistic use case in a Laravel codebase is rendering outside Blade: queue workers, CLI commands generating HTML/PDF bodies, mail fragments, or a package that must not depend on the framework's view layer. Treat it as a templating library, not a framework integration — there is no service provider shipped for Laravel.
Bind one renderer per container, not per render. Register PhpRenderer as a singleton in a small service provider and inject it. Renderers hold helper state (placeholders, escaping strategy); constructing one per call is wasteful and hides state bugs.
Layer resolvers deliberately. Use TemplateMapResolver for explicit, auditable template names and TemplatePathStack for directory scanning; combine with AggregateResolver when you have vendor templates plus app templates. In production, prefer the map — a typo becomes an exception instead of a silently-wrong file.
Compose with partials rather than inheritance. $this->partial('name', $vars) and PartialLoop for iterables give you Blade @include/@foreach equivalents. For layout behaviour, either build a ViewModel with children and a setTemplate(), or do it manually: render the body, then render the layout with ['content' => $body]. The manual form is easier to reason about and to test.
Register helpers through the plugin manager config, not by mutating the renderer at runtime. Custom helpers extend AbstractHelper (or implement HelperInterface); expose them via factories so they get dependencies. Use the shipped IDE helper interface to get autocompletion on $this->myHelper() in templates.
Escape explicitly. There is no auto-escaping. Every interpolation needs $this->escapeHtml(), escapeHtmlAttr(), escapeJs(), or escapeUrl(). Bake this into code review; it is the single largest correctness risk.
Turn on strict_vars globally in your view config so a misspelled template variable throws instead of rendering an empty string. This is the highest-value config toggle in the package.
Use ViewModel::setTerminal(true) when a child should suppress parent/layout rendering — useful for partial/JSON-ish responses. v3 also allows view model mutation prior to rendering, so you can decorate and adjust in a listener-free pipeline.
Isolate behind your own interface. In Laravel, wrap the renderer in a thin TemplateRenderer contract (render(string $template, array $data): string) so a future switch to Blade, Twig, or Mailables is a one-class change — the rest of your app never imports a Laminas\ symbol.
Test templates directly. Render to a string in PHPUnit and assert on output; no HTTP kernel needed. This makes laminas-view templates far more unit-testable than Blade views in practice.
RenderChildModel was deprecated in 2.44 and removed in 3.0; TreeRendererInterface, the HtmlPage and Json helpers, AbstractHtmlElement, and all Laminas\Feed code are gone. The Laminas\EventManager dependency was dropped, so listener-based ViewEvent workflows no longer exist — anything you did on renderer.post must become explicit code.view_helpers, and factories must accept iterables (fixed in 3.0.1). If you hand-roll factories, handle both arrays and Traversable or you will break on merged config.PhpRenderer::render rejects ambiguous arguments in v3 — pass an explicit ViewModel or an array, not something nullish.include. Opcache is what makes this fast; do not expect Blade-style compiled/cached views, and do not put expensive work in template headers.strict_vars and nulls: variables present but null can still trip checks in some paths — verify with ViewModel iteration before assuming the config is broken.resolver()->resolve($name) output to see which candidate won; the stack order is the usual culprit.EscapeHtml etc. are opt-in calls in templates; the strategy only governs helpers that delegate to it.HeadScript/InlineScript-style HTML helpers survived to v3 — check the migration guide for each one you use and plan an alternative.How can I help you explore Laravel packages today?