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

Handlebars Php Laravel Package

salesforce/handlebars-php

Handlebars.php is a PHP port of Handlebars.js, bringing logic-less, Mustache-inspired templating to PHP. It supports custom helpers, partials, and context-aware rendering, so templates can be shared between JavaScript and PHP with consistent output.

Deep Wiki
Context7

Getting Started

Install

composer require salesforce/handlebars-php

First look

  • src/Handlebars/Handlebars.php — the engine facade; everything hangs off this class.
  • src/Handlebars/Template.php — compiled template object; render() and renderContent() are the entry points.
  • src/Handlebars/Context.php — how paths like user.name and ../parent are resolved inside helpers.
  • src/Handlebars/Helpers.php — built-in helpers (if, unless, each, with) and how helpers are resolved.
  • src/Handlebars/Loader/ and src/Handlebars/Cache/ — where templates come from and how compiled output is cached.

Minimal render

use Handlebars\Handlebars;

$engine = new Handlebars();

echo $engine->render('Hello {{name}}', ['name' => 'World']);
// Hello World

First real-world use case

The strongest fit is user-authored or database-stored templates: notification emails, SMS bodies, invoice line items, exported reports — anywhere you want non-developers to edit copy without touching PHP. Handlebars is logic-less, so a bad template degrades gracefully instead of executing arbitrary code. For a first integration, render a single email body from a DB column:

echo $engine->render($notification->body_template, [
    'user'    => ['first_name' => $user->first_name],
    'order'   => ['number' => $order->number, 'total' => $order->formatted_total],
    'items'   => $order->items->toArray(),
]);

Then graduate to a FilesystemLoader once templates become files.

Implementation Patterns

String templates vs. filesystem templates

For small, dynamic strings (DB templates, CMS snippets) use the default StringLoader and render($templateString, $data).

For view files, swap the loader:

use Handlebars\Handlebars;
use Handlebars\Loader\FilesystemLoader;

$engine = new Handlebars([
    'loader'          => new FilesystemLoader(__DIR__.'/views', ['extension' => '.hbs']),
    'partials_loader' => new FilesystemLoader(__DIR__.'/views/partials', ['extension' => '.hbs']),
]);

echo $engine->render('emails/welcome', $data);   // loads views/emails/welcome.hbs

Compile once, render many

compile() returns a reusable Template; keep it and render repeatedly. This matters in loops (foreach ($users as $u) { echo $tpl->render($u); }) and in queue workers.

$tpl = $engine->compile($body);
foreach ($recipients as $r) {
    $out[] = $tpl->render(['recipient' => $r]);
}

Custom helpers — the main extension point

Helpers receive the template, the current context, and the raw argument string. You resolve arguments yourself against the context:

$engine->addHelper('money', function ($template, $context, $args) {
    return '$' . number_format((float) $context->get(trim($args)), 2);
});

$engine->addHelper('shout', function ($template, $context, $args) {
    return strtoupper((string) $context->get(trim($args)));
});

Register a small, deliberately closed set of helpers (money, date, urlencode, t, pluralize). This is where you keep template authors from needing logic.

Block helpers / partials for composition

  • {{> partialName}} for reused fragments (headers, footers, address blocks).
  • Register programmatically with $engine->registerPartial('name', $source), or let partials_loader resolve from a directory.
  • Use block helpers ({{#each}}, {{#if}}, {{#with}}) rather than inventing control flow in PHP; the whole point is that the template declares the shape.

Escaping and safe output

{{var}} escapes by default. For HTML fragments produced by a helper, return a SafeString so they aren't double-escaped:

use Handlebars\SafeString;

$engine->addHelper('nl2br', function ($t, $c, $a) {
    return new SafeString(nl2br(htmlspecialchars((string) $c->get(trim($a)))));
});

Caching in production

use Handlebars\Cache\Disk;

$engine = new Handlebars([
    'loader' => new FilesystemLoader(__DIR__.'/views', ['extension' => '.hbs']),
    'cache'  => new Disk(sys_get_temp_dir().'/hbs-cache'),
]);

Disk caching avoids re-parsing templates on every request. Treat the cache directory as disposable and clear it on deploy.

Laravel / framework integration

Bind a configured engine as a singleton in a service provider and inject it into mailers/notifiers. Keep Blade for developer-owned layouts and reserve Handlebars for content users own (email templates, message bodies, PDF sections). A single helper set shared across the app keeps output consistent, and a TemplateRepository (DB or filesystem) becomes the only place that knows how templates are stored.

Custom loader pattern

The cleanest way to support DB-backed templates is a custom LoaderInterface whose load($name) fetches the row and returns its body. The engine, helpers, caching, and partials then work exactly as with files.

Gotchas and Tips

Escaping is on by default. {{bio}} HTML-escapes. Use {{{bio}}} or return a SafeString from helpers. Getting this wrong in email templates is the most common surprise.

Missing variables fail silently. A typo like {{user.frist_name}} renders an empty string, not an error. Add a build-time check (render with sample data and assert non-empty) or a debug helper that logs unresolved paths.

Helper arguments are raw strings, not evaluated values. {{shout user.name}} passes "user.name" as $args; you must call $context->get(trim($args)) yourself. For multi-arg helpers, split and trim explicitly — there is no argument array.

{{else}} / inverse blocks can differ from Handlebars.js. Block helper behavior and inverse rendering in this port have varied by version. Verify {{#if}}...{{else}}...{{/if}} behavior against your installed release, and prefer keeping block structure simple. If you need complex branching, move it into a PHP-side data shape instead.

Truthiness is not PHP's. Empty strings, 0, "0", empty arrays, and null are falsy; but the exact rules inside {{#if}} can surprise you versus empty(). Normalize data (booleans, non-empty strings) before passing it in.

Helper output is not sandboxed. Helpers run plain PHP. Templates may be logic-less, but any helper you expose is a capability. Never expose file/exec/system helpers to user-editable template sources.

Path notation quirks. Dot paths (a.b.c), this, @index, @key, and ../ parent traversal are supported, but mixing them with missing intermediate keys yields empty output rather than an error. Normalize nested arrays before rendering.

each over associative arrays. Iteration works, but @key/@index availability and ordering depend on the PHP array — sort in PHP first if output order matters.

Namespace/API drift. Older releases used underscore classes (Handlebars_*); current code uses the Handlebars\ namespace. Pin a version and consult that version's README/tests, since the last tagged release is January 2023 and semantics have shifted across minor versions.

Disk cache keys and invalidation. Cache is keyed by template identity/source. If you edit a template file and see stale output, clear the cache directory — and make sure it happens in your deploy step (or in a test setup that renders many templates).

Partial resolution differs between loaders. Partials registered by string and partials resolved by partials_loader follow different lookup rules; a partial that works inline may not resolve from a file (and vice versa). Standardize on one mechanism per project.

Performance checklist: compile once and reuse; use FilesystemLoader + Disk cache in production; avoid re-instantiating Handlebars per render; keep helper bodies free of I/O because helpers run per-invocation inside loops.

Maintenance posture. The package is stable and MIT-licensed but has a slow release cadence. It's a fine choice for logic-less templating and user-authored content; if you need active feature development or security responsiveness, wrap it behind a thin TemplateRenderer interface so you can swap engines later without touching call sites.

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.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource