typo3fluid/fluid
TYPO3Fluid is a standalone PHP templating engine from TYPO3, providing the Fluid syntax for building secure, reusable templates with view helpers, layouts, partials, and caching. Use it in any PHP project without the full TYPO3 CMS stack.
Installation:
composer require typo3fluid/fluid
Fluid is dependency-agnostic but requires PHP 8.1+.
Basic Rendering:
use Fluid\Fluid\View\TemplateView;
use Fluid\Fluid\Core\Parser\Syntax\TemplateParser;
// Initialize parser and template view
$parser = new TemplateParser();
$templateView = new TemplateView();
$templateView->setParser($parser);
// Render a simple template
$templateView->setTemplateSource('Hello {name}!');
$templateView->assign('name', 'Laravel');
echo $templateView->render();
Output: Hello Laravel!
First Use Case:
Create a resources/views/fluid/hello.fluid.html file:
<h1>{greeting}</h1>
<p>Current time: {f:format.date(format: 'Y-m-d H:i:s')}</p>
Render it in a Laravel controller:
use Fluid\Fluid\View\TemplateView;
use Fluid\Fluid\Core\Parser\Syntax\TemplateParser;
public function showGreeting()
{
$parser = new TemplateParser();
$templateView = new TemplateView();
$templateView->setParser($parser);
$templateView->setTemplatePathAndFilename(resource_path('views/fluid/hello.fluid.html'));
$templateView->assignMultiple([
'greeting' => 'Welcome to Fluid in Laravel',
'date' => now(),
]);
return response($templateView->render());
}
Partial Templates:
Use <f:render partial="partialName" arguments="{...}"> to modularize templates.
Example: resources/views/fluid/partials/alert.fluid.html
<div class="alert {severity}">
{message}
</div>
Render in parent template:
<f:render partial="Alert" arguments="{severity: 'error', message: 'Something went wrong'}" />
Layouts:
Define a base layout (resources/views/fluid/layouts/base.fluid.html):
<!DOCTYPE html>
<html>
<head>
<title>{title}</title>
</head>
<body>
{content}
</body>
</html>
Extend in child templates:
<f:layout name="Base" />
<f:section name="title">My Page</f:section>
<f:section name="content">
<h1>Hello, {name}!</h1>
</f:section>
Core ViewHelpers:
Use built-in ViewHelpers like f:format.date, f:for, f:if, etc.
Example:
<f:for each="{users}" as="user">
<li>{user.name} ({f:format.date(date: user.createdAt)})</li>
</f:for>
Custom ViewHelpers:
Extend AbstractViewHelper to create reusable components.
Example: app/ViewHelpers/FormatCurrencyViewHelper.php
namespace App\ViewHelpers;
use Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
use Fluid\Fluid\Core\Rendering\RenderingContextInterface;
class FormatCurrencyViewHelper extends AbstractViewHelper
{
public function render(): string
{
$amount = $this->arguments['amount'] ?? 0;
$currency = $this->arguments['currency'] ?? 'USD';
return '$' . number_format($amount, 2) . ' ' . $currency;
}
}
Register in config/fluid.php:
'viewHelpers' => [
'App\\ViewHelpers\\' => true,
],
Use in template:
<p>Price: {f:formatCurrency(amount: 1999.99, currency: 'EUR')}</p>
Template Paths:
Configure multiple template paths in config/fluid.php:
'templatePaths' => [
resource_path('views/fluid'),
base_path('resources/views/fluid/overrides'),
],
Fluid resolves templates in order, supporting fallback chains (e.g., page.fluid.html → page.html).
Component-Based Rendering:
Use the Annotations API (Fluid 5+) to auto-generate component listings.
Example: app/Components/UserCard.php
namespace App\Components;
use Fluid\Fluid\Core\Component\AbstractComponent;
#[Component('user-card')]
class UserCard extends AbstractComponent
{
public function render(): string
{
return $this->renderTemplate('UserCard');
}
}
Template: resources/views/fluid/Components/UserCard.fluid.html
<div class="user-card">
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
Render in template:
<f:component.name="user-card" arguments="{user: user}" />
Automatic Caching:
Enable caching in config/fluid.php:
'cache' => [
'enabled' => true,
'directory' => storage_path('framework/cache/fluid'),
],
Warm up the cache during deployment:
php artisan fluid:warmup
Or manually:
use Fluid\Fluid\Core\Cache\CacheFactory;
use Fluid\Fluid\Core\Parser\Syntax\TemplateParser;
$cacheFactory = new CacheFactory();
$cache = $cacheFactory->create();
$parser = new TemplateParser();
$parser->setCache($cache);
Cache Invalidation: Clear cache when templates change:
php artisan cache:clear
Or programmatically:
\Illuminate\Support\Facades\Cache::forget('fluid');
resources/views/fluid/partials/blade.fluid.html
@if($showWelcome)
<p>Welcome back, {user.name}!</p>
@endif
Note: Blade directives (@) are parsed by Laravel, not Fluid.Variable Escaping:
{variable} (escaped) vs. {variable.raw} (unescaped).UnsafeHTML interface:
$safeHtml = new \Fluid\Fluid\Core\Rendering\UnsafeHtmlString('<strong>Bold</strong>');
Render in template:
{safeHtml}
Case Sensitivity in Template Names:
home.fluid.html or Home.fluid.html).kebab-case for files).CDATA Syntax Collisions:
<![CDATA[ ]]> sections, use {{{ ... }}} instead of { ... } to avoid conflicts with JavaScript/CSS.<script>
<![CDATA[
const name = "{{{ user.name }}}"; // Note triple braces
]]>
</script>
ViewHelper Argument Validation:
public function render(): string {
$this->arguments['name']->validateNotEmptyString();
return $this->arguments['name'];
}
InvalidArgumentException for invalid types.MissingArgumentException for required arguments.Template Path Priority:
templatePaths. Overrides must be placed after the base path.'templatePaths' => [
base_path('resources/views/fluid/base'), // Base templates
base_path('resources/views/fluid/overrides'), // Overrides (higher priority)
],
Caching Quirks:
resources/views/fluid/{dynamicFolder}/template.fluid.html) as they break caching.Deprecated Features:
How can I help you explore Laravel packages today?