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

Fluid Laravel Package

typo3fluid/fluid

typo3fluid/fluid is a standalone PHP templating engine and the rendering core behind TYPO3. It provides secure, logic-light HTML templates with layouts, partials, sections, view helpers, and caching for maintainable server-rendered views.

View on GitHub
Deep Wiki
Context7

Getting Started

Fluid is a standalone PHP template engine (the one TYPO3 uses, extracted into a reusable package). If you've used Blade or Twig, the concepts map: templates + layouts + partials + a registry of typed helper objects ("ViewHelpers", the f: namespace).

Install and pick a major version deliberately — the package ships parallel majors with different PHP floors and different argument-validation semantics:

composer require typo3fluid/fluid:^5.3   # new projects, strict argument processing
# ^4.6 for libraries still pinned there, ^2.14 only for legacy stacks

The three objects you interact with daily:

Object Role
TYPO3Fluid\Fluid\View\TemplateView Entry point: assign(), assignMultiple(), render()
RenderingContext Config hub: paths, cache, ViewHelper namespaces, escaping
TemplatePaths Template/layout/partial root paths and file resolution

Smallest working setup:

use TYPO3Fluid\Fluid\View\TemplateView;

$view = new TemplateView();
$context = $view->getRenderingContext();
$context->getTemplatePaths()->setTemplateRootPaths([__DIR__ . '/templates/']);
$context->getTemplatePaths()->setLayoutRootPaths([__DIR__ . '/templates/Layouts/']);
$context->getTemplatePaths()->setPartialRootPaths([__DIR__ . '/templates/Partials/']);

$view->assignMultiple(['user' => $user, 'items' => $items]);
echo $view->render('Index'); // resolves Index.fluid.html or Index.html
<f:layout name="Default" />
<f:section name="Main">
    <h1>Hello {user.name}</h1>
    <ul>
        <f:for each="{items}" as="item" iteration="it">
            <li>{it.cycle}. {item.title}</li>
        </f:for>
    </ul>
</f:section>

Look first at: the ViewHelpers/ directory in the package (that is the real API reference), the TYPO3 Fluid documentation for syntax (the release notes explicitly cross-reference it — the standalone docs are thin), and TemplatePaths if a template "cannot be found".

First real use case: transactional email and HTML fragment rendering in a non-TYPO3 app — assign DTOs, keep markup in .fluid.html files, render to string.

Implementation Patterns

Custom ViewHelper (the 90% task). Declare the API in initializeArguments(); Fluid validates and type-casts inputs before render() is called:

use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;

final class BadgeViewHelper extends AbstractViewHelper
{
    protected $escapeOutput = false; // render() produces trusted HTML

    public function initializeArguments(): void
    {
        $this->registerArgument('label', 'string', 'Badge text', true);
        $this->registerArgument('variant', 'string', 'Style variant', false, 'default');
        $this->registerArgument('count', 'int|null', 'Optional counter', false, null);
    }

    public function render(): string
    {
        return sprintf('<span class="badge badge--%s">%s</span>',
            htmlspecialchars($this->arguments['variant']), htmlspecialchars($this->arguments['label']));
    }
}

Register a namespace once, at bootstrap:

$context->getViewHelperResolver()
    ->addNamespace('ui', 'Acme\\Ui\\ViewHelpers');
// template: <ui:badge label="New" variant="info" />

Wrap the engine in one factory/service. Don't scatter path and cache configuration across controllers. Build a FluidRenderer that owns a pre-configured RenderingContext, exposes render(string $template, array $data): string plus renderPartial() / renderSection() for AJAX fragments. In Laravel, bind it as a singleton in a service provider so the compiled-template cache survives across requests.

Composition patterns that scale:

  • Layout + f:section for page chrome; partials for repeated fragments.
  • <f:render partial="Row" arguments="{_all}" /> to pass the whole scope (documented reserved variable in Fluid 5).
  • <f:argument name="title" type="string" /> at the top of partials/templates to get typed, validated contracts instead of guessing what {title} is.
  • <f:variable name="total" value="{items -> f:count()}" /> for derived values; avoid PHP-side pre-formatting that belongs in templates.
  • f:for + iteration.isFirst/isLast/total instead of passing precomputed flags from PHP.

Escaping policy as a pattern: variables are escaped by default. Only mark trusted output with f:format.raw or by passing objects that implement the UnsafeHTML marker interface (4.6/5.1+). Make this a code-review rule — it's the single most common security regression in Fluid codebases.

Caching in production, off in dev:

$context->setCache(new \TYPO3Fluid\Fluid\Core\Cache\SimpleFileCache(
    __DIR__ . '/storage/cache/fluid'
));

Keep a fluid:clear console command / Makefile target; without a cache configured, Fluid reparses every template on every request.

Testing pattern: point templateRootPaths at a tests/fixtures/ directory, render, assert on the string. This is far cheaper than mocking a ViewHelper-heavy view layer. On 5.3+ also run the shipped CLI template analyzer in CI to catch broken templates without rendering every entry point.

Component libraries (4.6/5.2+): the metadata/annotations API and component-listing API let you generate a styleguide from your ViewHelper set — useful if you're replacing a design-system's Twig components.

Gotchas and Tips

  • No cache by default. The biggest perf surprise in standalone deployments. Also ensure the cache directory is writable and template identifiers/names used for compiled class names are stable — renaming templates orphans cache entries.
  • Double escaping. An $escapeOutput = true ViewHelper that returns markup gets its output escaped again when nested. Set escapeOutput/escapeChildren consciously; audit any ViewHelper that emits tags.
  • Fluid 5 argument validation is strict. StrictArgumentProcessor enforces declared types, supports unions (string|int), iterable, countable, callable. Passing null to a string argument throws — declare ?string/string|null. Stringable objects are cast early (5.1), but that's a convenience, not a license to skip types.
  • <f:argument> does not work for sections rendered via f:render section="..." — a documented limitation. Use it in partials/templates, pass section data via arguments.
  • Template resolution fallback chain (Fluid 5): myTemplate.fluid.html → myTemplate.html → myTemplate → capitalized variants. Lowercase names are now legal, but watch case sensitivity on Linux and be aware that the first match across all template root paths wins — verify your override order when layering package templates under app templates.
  • Exceptions got much better recently. Template file paths, true line numbers, and exception codes landed across 4.6.1, 5.2, 5.3 (TagBuilder, ViewHelper argument validation). If you're debugging blind on an older minor, upgrading is the fix.
  • Namespace registration happens at parse time. Registering a ViewHelper namespace after a template is already compiled has no effect; clear the cache. Cached templates share resolver delegates since 4.6, so config changes need a flush.
  • AbstractTemplateView shortcut methods are deprecated (since 2.14) — write against getRenderingContext() now, even if you're on 2.x, or the 4→5 upgrade bites.
  • {foo.bar} resolves getters on objects and array keys on arrays; {foo.bar.baz} on a null intermediate silently yields empty rather than throwing. Use f:if condition="{foo.bar}" guards or f:debug when output is mysteriously blank.
  • Debugging toolkit: f:debug (dump the variable provider), the 5.3 CLI analyzer, and catching TYPO3Fluid\Fluid\Core\Parser\Exception vs Core\ViewHelper\Exception separately — parser failures are template syntax, ViewHelper failures are argument/API misuse.
  • Upgrade path discipline: 5.0 breaking changes were pre-announced via deprecations in 4.x. If you're starting on 2.x because a dependency pins it, budget that migration now; the escape/type behavior differences are behavioral, not cosmetic.
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.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route