Guide
Use cases and worked examples for FluentPHP.
Table of contents
Use cases
Common scenarios where teams reach for FluentPHP:
| Use case | Example scenario |
|---|---|
| Localized web app UI | Serve a web application in each user’s language — pick the locale bundle per request and format page copy, labels, and notices with live data. |
| Transactional emails & notifications | Produce localized emails, SMS, and push messages — order confirmations, alerts, receipts — with names, amounts, and dates filled in. |
| Multi-tenant SaaS | Share one set of translation files across tenants and render every request in the tenant’s own locale. |
| Localized API responses | Return messages and error text in the caller’s language, chosen from an Accept-Language header or account preference. |
| CLI tools, invoices & reports | Localize command-line output and generated documents such as invoices, statements, and PDFs. |
| Count- and gender-aware copy | Cart counts, unread badges, gendered phrasing — let translators handle the variants in FTL instead of branching in PHP. |
The features that power these scenarios — variables, selectors, custom functions, and error handling — are shown in the examples below.
Variables
Inject request data into localized messages. Placeables ({ $name }) are
replaced with the matching argument passed to formatPattern().
$bundle = new FluentPhp\FluentBundle('en');
$bundle->addResource("welcome = Welcome, { $user }\n");
echo $bundle->formatPattern('welcome', [
'user' => 'Asha',
]);
// Welcome, Asha
Selectors
Selectors keep copy rules beside the translated text instead of branching in controllers. Translators can adjust wording without PHP code changes.
$bundle->addResource(<<<'FTL'
status = { $state ->
[active] Account active
[blocked] Account blocked
*[other] Status unknown
}
FTL);
echo $bundle->formatPattern('status', ['state' => 'active']);
// Account active
The variant marked with * is the default, used when no other branch matches.
PHP functions
Register PHP callables as Fluent functions so messages can use domain-specific formatting without forcing that logic into translation files.
$bundle = new FluentPhp\FluentBundle('en');
$bundle->addResource(<<<'FTL'
today = Today is { FORMAT_DATE($date) }
FTL);
$bundle->addFunction('FORMAT_DATE', function (DateTimeInterface $date): string {
return $date->format('Y-m-d');
});
echo $bundle->formatPattern('today', ['date' => new DateTimeImmutable()]);
// Today is 2026-06-23
Reusing resources across bundles
FluentResource is a parsed FTL resource. Parse once, then add the same
resource to multiple bundles.
$resource = FluentPhp\FluentResource::fromFile(__DIR__ . '/messages.ftl');
$en = new FluentPhp\FluentBundle('en');
$en->addResource($resource);
$enGb = new FluentPhp\FluentBundle('en-GB');
$enGb->addResource($resource);
In a long-running PHP runtime (PHP-FPM, Swoole, RoadRunner, FrankenPHP), use
ResourceCache instead so the parse is reused
across requests, not just across bundles within one request.
Values
Message parameters may be strings, integers, floats, booleans, null, or
objects.
- Stringable objects are formatted through
__toString(). - Non-stringable objects format as
[Object]. - Unsupported values — such as arrays and resources — raise
FluentPhp\Exception.
$bundle->addResource("label = { $value }\n");
echo $bundle->formatPattern('label', ['value' => 42]); // 42
echo $bundle->formatPattern('label', ['value' => true]); // true
Objects that implement Stringable are formatted with their __toString():
class Money implements Stringable
{
public function __construct(private int $cents) {}
public function __toString(): string
{
return number_format($this->cents / 100, 2);
}
}
$bundle->addResource("price = Total: { $amount }\n");
echo $bundle->formatPattern('price', ['amount' => new Money(1299)]);
// Total: 12.99
Error handling
All extension-specific exceptions extend FluentPhp\Exception, so a single
catch can cover everything, or you can target specific failures.
use FluentPhp\FluentBundle;
use FluentPhp\ParserException;
use FluentPhp\ResolverException;
try {
$bundle = new FluentBundle('en');
$bundle->addResource($maybeInvalidFtl);
echo $bundle->formatPattern('greeting', ['name' => 'Sam']);
} catch (ParserException $e) {
// Invalid FTL syntax — structured details available:
foreach ($e->getErrors() as $err) {
fprintf(STDERR, "line %d, col %d: %s\n", $err['line'], $err['col'], $err['source']);
}
} catch (ResolverException $e) {
// Missing variables, unknown functions, etc.
foreach ($e->getErrors() as $message) {
fprintf(STDERR, "%s\n", $message);
}
}
See the API Reference for the full
exception hierarchy and the shape of getErrors().
Runnable examples
The example/
directory contains runnable scripts:
hello-world.phphas-message.phpfunction.phpselectors.phpadvanced.php
Run one with the built extension:
php -d extension=target/debug/libfluent.so example/selectors.php