Engine Configuration #

Set up your engine once and focus on building templates. You need three things to get started: where templates live, a cache directory, and whether to reload during development.

Build from a single SugarConfig instance so parser/compiler behavior stays in sync.

Quick Start #

These three things get you running:

  1. A template loader — where Sugar finds your .sugar.php files
  2. A cache directory — speeds up renders by compiling once
  3. Debug mode — auto-reload templates during development

Here’s the minimal setup:

use Sugar\Core\Cache\FileCache;
use Sugar\Core\Engine;
use Sugar\Core\Loader\FileTemplateLoader;

$engine = Engine::builder()
    ->withTemplateLoader(new FileTemplateLoader(
        templatePaths: __DIR__ . '/templates'
    ))
    ->withCache(new FileCache(__DIR__ . '/cache/templates'))
    ->withDebug(true) // Enable during development
    ->build();

// Now render
echo $engine->render('home', ['title' => 'Welcome']);

That’s it. Sugar compiles templates to pure PHP and caches them for you.

Core Configuration #

Template Loaders #

Tell Sugar where to find templates.

FileTemplateLoader #

The standard choice: load templates from the filesystem.

use Sugar\Core\Loader\FileTemplateLoader;

$loader = new FileTemplateLoader(
    templatePaths: __DIR__ . '/templates'
);

Multiple paths:

$loader = new FileTemplateLoader(
    templatePaths: [
        __DIR__ . '/templates',
        __DIR__ . '/vendor/package/templates',
    ],
);

Enforce absolute paths:

$loader = new FileTemplateLoader(
    templatePaths: __DIR__ . '/templates',
    absolutePathsOnly: true // Prevents ../ navigation
);

Enable absolutePathsOnly: true to enforce root-relative paths and prevent ../ navigation for includes and extends.

StringTemplateLoader #

Load templates from memory. Perfect for tests or dynamic content:

use Sugar\Core\Loader\StringTemplateLoader;

$loader = new StringTemplateLoader(
    templates: [
        'email/welcome' => '<h1>Welcome <?= $name ?>!</h1>',
        'components/s-button.sugar.php' => '<button class="btn"><?= $slot ?></button>',
    ]
);

Caching #

Sugar compiles templates to pure PHP once and caches the result. Subsequent renders execute the cached code directly via include, leveraging PHP’s opcache for maximum performance. No re-compilation happens until the source template changes (in debug mode) or the cache is cleared.

See File Caching Flow for details on how Sugar’s compilation and caching pipeline works.

Compile templates once, cache them, render fast.

use Sugar\Core\Cache\FileCache;

$cache = new FileCache(__DIR__ . '/cache/templates');

$engine = Engine::builder()
    ->withTemplateLoader($loader)
    ->withCache($cache)
    ->build();

Place cache outside your templates folder and ensure it’s writable.

Development vs Production #

Development (debug mode): Check file timestamps on every render so you see template changes immediately.

Production (no debug): Trust the cache — no filesystem checks, maximum speed.

$engine = Engine::builder()
    ->withTemplateLoader($loader)
    ->withCache($cache)
    ->withDebug(true)
    ->build();
$engine = Engine::builder()
    ->withTemplateLoader($loader)
    ->withCache($cache)
    ->withDebug(false)
    ->build();

Customization #

SugarConfig controls engine internals like directive prefixes, fragment element names, and parser behavior. Use it to adjust Sugar’s syntax when the defaults don’t fit your project or to avoid conflicts with other systems.

Directive Prefix #

Sugar uses s: by default (s:if, s:foreach, etc.). Change it if needed:

use Sugar\Core\Config\SugarConfig;

$config = SugarConfig::withPrefix('v');
// Now use v:if, v:foreach, v:cache, etc.

$engine = Engine::builder()
    ->withSugarConfig($config)
    ->withTemplateLoader($loader)
    ->build();

Template Suffixes #

Configure file extensions on the loader:

use Sugar\Core\Loader\FileTemplateLoader;

$loader = new FileTemplateLoader(
    templatePaths: __DIR__ . '/templates',
    suffixes: ['.sugar.tpl'],
);
use Sugar\Core\Loader\StringTemplateLoader;

$loader = new StringTemplateLoader(
    templates: [
        'pages/home.sugar.tpl' => '<h1><?= $title ?></h1>',
    ],
    suffixes: ['.sugar.tpl'],
);

Fragment Element Name #

Override the wrapperless fragment tag (default <s-template>):

use Sugar\Core\Config\SugarConfig;

$config = (new SugarConfig())
    ->withFragmentElement('s-fragment');

$engine = Engine::builder()
    ->withSugarConfig($config)
    ->withTemplateLoader($loader)
    ->build();

Now use:

<s-fragment s:if="$condition">...</s-fragment>

Self-Closing Tags #

Sugar recognizes HTML void elements automatically. Add custom self-closing tags:

use Sugar\Core\Config\SugarConfig;

$config = (new SugarConfig())
    ->withSelfClosingTags(['meta', 'link', 'custom']);
use Sugar\Core\Config\SugarConfig;

$config = (new SugarConfig())
    ->withSelfClosingTags([
        ...SugarConfig::DEFAULT_SELF_CLOSING_TAGS,
        'custom',
    ]);

Template Helpers #

Template Context #

Expose helper methods to every template via $this. Keep context lightweight and stateless.

use Sugar\Core\Engine;

$context = new class { // [!code focus]
    public function url(string $path): string { // [!code focus]
        return '/app' . $path; // [!code focus]
    } // [!code focus]
// [!code focus]
    public function asset(string $file): string { // [!code focus]
        return '/assets/' . ltrim($file, '/'); // [!code focus]
    } // [!code focus]
}; // [!code focus]

$engine = Engine::builder()
    ->withTemplateLoader($loader)
    ->withTemplateContext($context) // [!code focus]
    ->build();

In templates:

<link rel="stylesheet" href="<?= $this->asset('app.css') ?>">
<a href="<?= $this->url('/profile') ?>">Profile</a>
  • URL builders and asset helpers shared across templates
  • Formatting helpers (dates, numbers, currency)
  • Framework integration points
  • Anything stateless templates frequently call

Advanced Features #

Fragment Caching (s:cache) #

Cache expensive fragments using a PSR-16 store (Redis, Memcached, etc.) through the optional FragmentCache extension.

use Sugar\Core\Engine;
use Sugar\Extension\FragmentCache\FragmentCacheExtension;

$fragmentCache = getYourCacheStore(); // PSR-16 compatible  // [!code focus]

$engine = Engine::builder()
    ->withTemplateLoader($loader)
    ->withExtension(new FragmentCacheExtension($fragmentCache, defaultTtl: 300)) // [!code focus]
    ->build();

In templates, wrap expensive content:

<section s:cache="'homepage:hero'">
    <?= renderExpensiveHtml() ?>
</section>

Override TTL per fragment:

<section s:cache="['key' => 'users:list', 'ttl' => 60]">
    <?= renderUserList() ?>
</section>

See s:cache directive for full syntax.

PHP Syntax Validation #

Optionally validate PHP syntax during compilation for earlier error detection. Requires nikic/php-parser:

$engine = Engine::builder()
    ->withTemplateLoader($loader)
    ->withDebug(true)
    ->withPhpSyntaxValidation(true)  // [!code focus]
    ->build();

Benefits: - Catch PHP syntax errors at compile time - Faster feedback loop during development - Zero overhead when disabled

Enable in development, disable in production.

Exception Rendering #

Extending the Engine #

Extensions #

Bundle directives, compiler passes, and hooks into reusable packages:

use Sugar\Core\Engine;

$engine = Engine::builder()
    ->withTemplateLoader($loader)
    ->withExtension(new AnalyticsExtension()) // [!code focus]
    ->withExtension(new UiComponentLibrary()) // [!code focus]
    ->build();

See Creating Extensions for the full workflow.

Custom Directive Registry #

For quick, one-off custom directives without creating a full extension. Extensions are preferred for reusable or complex directive bundles.

Add your custom directive to the existing registry:

use Sugar\Core\Extension\DirectiveRegistry;

$registry = DirectiveRegistry::default(); // [!code focus]
$registry->register('custom', CustomDirective::class); // [!code focus]

$engine = Engine::builder()
    ->withTemplateLoader($loader)
    ->withDirectiveRegistry($registry) // [!code focus]
    ->build();

// Now s:custom is available alongside built-in directives

Use cases: - Quick prototypes or experiments - Project-specific directives - Adding one or two custom directives without extension overhead

For reusable directive bundles, create an Extension instead. Extensions provide better organization and can include multiple directives, compiler passes, and hooks.

Advanced: Empty Registry

Start from scratch and register only the directives you want. This removes all built-in directives:

use Sugar\Core\Directive\ForeachDirective;
use Sugar\Core\Directive\IfDirective;
use Sugar\Core\Extension\DirectiveRegistry;

$registry = DirectiveRegistry::empty();
$registry->register('if', IfDirective::class);
$registry->register('foreach', ForeachDirective::class);

$engine = Engine::builder()
    ->withTemplateLoader($loader)
    ->withDirectiveRegistry($registry)
    ->build();

// Now only s:if and s:foreach are available

Useful for sandboxed/untrusted templates or feature flags.

For directive authoring, see Custom Directives.

Complete Reference #

All builder methods at a glance:

Method Purpose
withTemplateLoader() Where templates are loaded from
withCache() File-based compiled-template caching
withDebug() Development checks and auto-reload
withSugarConfig() Parser/compiler configuration (prefix, suffixes, etc.)
withTemplateContext() Helpers available as $this in templates
withPhpSyntaxValidation() Validate PHP syntax at compile time
withExceptionRenderer() Custom exception rendering
withHtmlExceptionRenderer() Built-in HTML exception renderer
withExtension() Register reusable directive/pass bundles
withDirectiveRegistry() Override available directives

Next Steps #