ForgeTemplates

The composition capability your app uses to render message bodies — most often notification and email content — from plain PHP template files, with layouts and named regions for a consistent look.

Overview

ForgeTemplates is a deliberately small template composer. A template is just a PHP file that echoes text. You hand it some data, it renders, and you get a string back that you can send however you like — an email body through ForgeNotification, a text message, an email fragment.

Because templates are plain PHP, there's no new syntax to learn and no compiled markup to wrangle. You write the output directly, and the composer takes care of finding the file, extracting your data into the template's scope, and wrapping the result in a layout when you ask for one.

Two directories matter:

  • App templates — live under app/Common/Templates/.
  • Module templates — live under a module's src/Common/Templates/, referenced by a module name prefix.

Framing: this is a component of your app, not a framework templating system. You keep your message markup in files, pass them your data, and render — the composition details stay in your hands.

Installation

php forge.php package:install-module --module=forge-templates

Installation registers the template manager and its helper. It has no database work and stands on its own — though it's most useful alongside the notification capability, whose messages you'll be composing.

Template Files

A template is a regular PHP file whose output becomes the rendered body. To compose with data, the passed values are made available as variables in the template, and a $props object too:

<?php /* app/Common/Templates/emails/welcome.php */ ?>
<h1>Welcome, <?= $name ?>!</h1>
<p>Thanks for joining <?= $props->appName ?>.</p>

You echo whatever bytes you want in the body. The earliest directory wins when the same template could live in more than one place, and the path is resolved from the same structure layout your app already uses.

Rendering a Template

Render any template by name with the useTemplate() helper or the manager itself:

$body = useTemplate('emails/welcome', [
    'name' => 'Ada',
    'appName' => 'Acme',
]);

The name resolves to app/Common/Templates/emails/welcome.php. You can also reach a module's templates by prefixing the name with the module — a handy way to ship reusable message layouts alongside a capability:

$body = useTemplate('ForgeTemplates:emails/welcome', $data);

The returned string is ready to hand to whatever consumes it. If a template can't be found you get a clear error naming the file it searched for.

Passing Data

Data comes as an array or an object. Either way it's extracted into the template scope as named variables, and also exposed through a $props object. That gives you two convenient ways to reach a value:

useTemplate('receipt', [
    'orderId' => 1024,
    'total' => '19.99',
    'currency' => 'USD',
]);

In the template you can echo $orderId, $total, and $currency directly, or reach the object with $props->orderId. Which style you use is up to you — both are always in scope.

Layouts & Namespaces

Rather than repeat the chrome of every message — the email envelope, the header and footer — you can declare a layout inside a template and let it provide the wrapper. Inside the template you name the layout:

<?php /* app/Common/Templates/emails/welcome.php */ ?>
<?php $this->layout('email'); ?>

<h1>Welcome, <?= $name ?>!</h1>

The rendered body then becomes $content inside the matching layout file, found at app/Common/Templates/layouts/email.php:

<?php /* app/Common/Templates/layouts/email.php */ ?>
<html>
<body>
    <header><h1>Acme</h1></header>
    <main><?= $content ?></main>
    <footer>Thanks for reading.</footer>
</body>
</html>

This lets every welcome, receipt, or notice share one wrapper while each supplies its own body — update the layout once and the whole family of messages follows.

With Notifications

The most common pairing is with the notification capability. Compose an email body or HTML with a template, then pass it to a notification channel:

$html = useTemplate('emails/welcome', ['name' => 'Ada']);

$notifications->email()
    ->to('ada@example.com')
    ->subject('Welcome to Acme')
    ->html($html)
    ->queue();

Because the template returns a plain string, it drops straight into any channel that takes content. Your message markup lives in files, your sending logic stays in your code, and a single template change updates every message that uses it.

From Your Code

You can depend on the template manager directly instead of the global helper. It's registered as an injectable, so it's available wherever your dependencies are:

use Modules\ForgeTemplates\Injectable\TemplateManager;

final class WelcomeMailer
{
    public function __construct(
        private readonly TemplateManager $templates,
    ) {}

    public function send(string $email, string $name): void
    {
        $body = $this->templates->useTemplate('emails/welcome', [
            'name' => $name,
        ]);

        // ... hand $body to your email channel
    }
}

If your class prefers traits, a TemplateHelper trait exposes the same useTemplate() method directly. Between the global helper, the trait, and the injectable, the rendering path stays the same no matter how you reach it.