Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Php View Laravel Package

slim/php-view

slim/php-view is a lightweight view renderer for Slim Framework 4 that uses plain PHP templates with no compilation step or custom syntax. It supports layouts and partials through the PhpRenderer class, making server-side rendering simple and fast for developers who prefer native PHP over template e...

View on GitHub
Deep Wiki
Context7

Getting Started

Install and wire it up (Slim 4)

composer require slim/php-view
// public/index.php
use Slim\Views\PhpRenderer;

$container->set(PhpRenderer::class, function () {
    return new PhpRenderer(__DIR__ . '/../templates', [
        'siteName' => 'Acme',
    ]);
});
// templates/home.php
<h1><?= htmlspecialchars($siteName, ENT_QUOTES, 'UTF-8') ?></h1>
<p>Hello, <?= htmlspecialchars($name, ENT_QUOTES, 'UTF-8') ?></p>
// src/Application/Actions/HomeAction.php
public function __invoke(Request $request, Response $response): Response
{
    return $this->view->render($response, 'home', ['name' => 'Josh']);
}

First things to read

  1. Slim\Views\PhpRenderer itself — it is roughly 100 lines. Reading it end to end (constructor, render(), getTemplateFile(), fetch()) tells you everything the package does, including exactly how data becomes variables.
  2. The README's "Rendering a template" section — that's basically the whole API surface.
  3. Your own templates/ directory layout. There is no convention imposed, so decide one now (flat files vs. pages/, partials/, layouts/ subfolders).

First real use case: replacing hand-rolled ob_start(); include ...; ob_get_clean() in a controller with $view->render($response, 'template', $data). The win is that the renderer holds the template path and shared attributes, so controllers stop caring about filesystem paths.

Slim 3 variant (for older apps): register $container['view'] with the same class and call $this->view->render($response, 'home.phtml', $data) from a route callable. Slim 3's default slim/twig-view-style ViewInterface contract is satisfied by __invoke()/render(), so the wiring is drop-in.

Implementation Patterns

Constructor injection beats container lookups. Bind PhpRenderer as a service and type-hint it in action constructors. If you use PHP-DI with autowiring, define the binding once with the template path so no action ever needs __DIR__ gymnastics.

Global attributes for cross-cutting view data. Anything every template needs — current user, CSRF token, flash messages, app version, locale — belongs in addAttribute()/setAttributes(), populated from a middleware rather than repeated in each action:

$app->add(function (Request $request, Handler $handler) {
    $this->view->addAttribute('currentUser', $request->getAttribute('user'));
    $this->view->addAttribute('flash', $this->flash->getMessages());
    return $handler->handle($request);
});

Note the merge order: attributes are merged first, then route data, so per-render data wins — a good default, but it means an action can accidentally shadow siteName.

Layouts via include. The package has no layout/inheritance support. The idiomatic pattern is a two-file split where the layout includes the page:

// templates/layout.php
<!doctype html><html><body>
<?php include $templatePath . '/partials/header.php'; ?>
<?= $content ?>
</body></html>

In practice most people invert this: each page template does include $this->templatePath . '/partials/header.php' at the top and footer.php at the bottom, with templatePath exposed as an attribute. Exposing templatePath as a global attribute is the single most useful trick with this package.

Partials. include $templatePath . '/partials/card.php' inside a loop works because include inherits the calling scope's variables — so $item from your foreach is visible. Keep partials small and document which variables they require in a comment at the top; there's no compiler to catch a missing variable.

Escaping helper. Since nothing is escaped automatically, define one function and use it religiously:

function e(?string $v): string {
    return htmlspecialchars((string) $v, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}

Autoload it via composer.json files, or expose it as a closure attribute. For raw HTML fragments use <?= $body ?> deliberately and only on trusted, sanitized content.

Setting the content type yourself. PhpRenderer writes the body and returns the response — it does not touch headers. Add a Content-Type: text/html; charset=utf-8 header either in the action or, better, in a response-header middleware so you never forget it.

Rendering outside Slim. It only depends on Psr\Http\Message\ResponseInterface, so it works with Nyholm, Laminas Diactoros, or Guzzle PSR-7:

$response = (new \Nyholm\Psr7\Response())->withHeader('Content-Type', 'text/html');
$response = $view->render($response, 'report', ['rows' => $rows]);

Testing. Templates are PHP, so you can assert on rendered output without a browser:

$response = $view->render(new \Slim\Psr7\Response(), 'home', ['name' => 'Josh']);
$this->assertStringContainsString('Hello, Josh', (string) $response->getBody());

Golden-file tests over a templates/ fixtures directory catch accidental Undefined variable warnings before users do. Run the suite with error_reporting(E_ALL) and convert warnings to exceptions so a typo'd variable name fails the test instead of rendering an empty string.

Custom extension. For .phtml templates pass the third constructor argument: new PhpRenderer($path, [], 'phtml').

Gotchas and Tips

  • The extension is appended for you. render($response, 'home') → templates/home.php. Passing 'home.php' yields home.php.php on most versions, because getTemplateFile() blindly concatenates path . DIRECTORY_SEPARATOR . template . '.' . extension. Always pass the bare name.
  • No auto-escaping, by design. Every <?= $foo ?> is an XSS hole if $foo is user data. This is the number-one source of bugs with this package. Audit templates with a grep for <?= and confirm each one is either wrapped in e() or provably safe.
  • extract() collisions. fetch() runs extract($data) after ob_start() in a scope that already has $templateFile and $data. A data key named templateFile or data silently clobbers those locals. Also, extract() skips keys that aren't valid PHP identifiers — 'user-name' or 'user.name' simply won't exist as a variable. Use underscore keys.
  • write() appends, it doesn't replace. If anything upstream already wrote to the response body (streaming middleware, a debug dumper, a previous render() call), your HTML is concatenated onto it. Return a fresh response or use $response->getBody()->rewind()/->truncate(0) before rendering.
  • Missing template throws RuntimeException, not a 404. In production with displayErrorDetails => false you get an opaque 500. If template names ever come from data, translate a missing-file exception into a 404 explicitly.
  • Never pass a user-controlled template name straight through. The renderer just concatenates strings — '../../etc/passwd' resolves happily. Whitelist template names against a known array.
  • fetch() visibility varies by release. In some versions it's public and you can render a partial to a string; in others it's protected. If you need a renderToString(), subclass and expose a method, or just use ob_start() + include in your own helper — don't build architecture around an inherited method whose visibility has changed.
  • Single template path. There's no multi-path search like Twig's paths array. Organize with subdirectories ('admin/users') rather than expecting fallback directories. Trailing slashes in the constructor path produce double separators — harmless on Linux/macOS but sloppy; normalize with rtrim($path, '/\\').
  • Windows paths. Because the separator is DIRECTORY_SEPARATOR, a template name written with forward slashes still works (Windows accepts both), but a path built in a test with forward slashes and compared as a string will fail on Windows. Use DIRECTORY_SEPARATOR in assertions or normalize.
  • No caching layer, no compilation. Every render is a require plus output buffering. That's cheap, but pair it with OPcache (opcache.validate_timestamps=0 in prod after deploy) and be aware there is no fragment caching — if a partial is expensive, cache the data, not the template.
  • Debugging Undefined variable warnings is easier if you dump the merged data first: var_dump(array_merge($view->getAttributes(), $data)) in a dev-only middleware. Nine times out of ten the key you set in the action doesn't match the variable name in the template.
  • Extension points are minimal and that's the point: setTemplatePath(), setTemplateExtension(), addAttribute()/setAttributes(), and the protected fetch()/getTemplateFile(). If you need filters, sections, or auto-escaping, you've outgrown this package — that's the signal to move to Twig or Blade, not to bolt features onto PhpRenderer.
  • Zero dependents is a feature here. The package is a thin, stable shim with no transitive risk. Treat it as a small piece of infrastructure you own a copy of mentally — you can read the entire source in five minutes, which is exactly why it's a reasonable choice for template-light APIs and internal tools.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager