Extensions #

Extensions let you attach custom PHP logic to your Glaze project. There are two independent mechanisms, both activated by the #[GlazeExtension] attribute:

  • Template helpers — invokable classes callable as $this->extension('name') from any Sugar template.
  • Build event subscribers — classes with methods decorated with #[ListensTo] that react to specific moments in the build pipeline.

A single class can do both at the same time.

Auto-discovery #

Create an extensions/ directory at your project root. Any class decorated with #[GlazeExtension] placed there is discovered and registered automatically.

The directory is extensions/ by default. Override it in glaze.neon:

extensionsDir: src/Extensions

Classes that do not carry #[GlazeExtension] are silently skipped, so helper files can live alongside extension classes without issue.


Template helpers #

A template helper is a named, invokable class. Provide a non-empty name in the attribute and implement __invoke().

extensions/LatestRelease.php:

<?php

use Glaze\Template\Extension\GlazeExtension;

#[GlazeExtension('version')]
final class LatestRelease
{
    public function __invoke(): string
    {
        return trim((string)file_get_contents(__DIR__ . '/../VERSION'));
    }
}

Call it from any Sugar template:

<footer>Version <?= $this->extension('version') ?></footer>

Extension results are memoized per build – however many templates call the same extension, the underlying PHP runs exactly once.

Arguments are forwarded to __invoke() on its first invocation:

#[GlazeExtension('asset')]
final class AssetExtension
{
    public function __invoke(string $path): string
    {
        return '/assets/' . ltrim($path, '/');
    }
}
<link rel="stylesheet" href="<?= $this->extension('asset', 'css/site.css') ?>">

Note: Because results are cached after the first call, passing different arguments on subsequent calls returns the result from the first invocation. Design extensions that take arguments as single-invocation helpers.

Calling an unregistered name throws a RuntimeException:

Glaze extension "versin" is not registered. Available: version, asset

Build event subscribers #

Extensions can also listen to events fired during the static build. Add one or more public methods decorated with #[ListensTo(BuildEvent::X)].

A class that only subscribes to events does not need a name or __invoke() – omit the name argument entirely:

<?php

use Glaze\Build\Event\BuildCompletedEvent;
use Glaze\Build\Event\BuildEvent;
use Glaze\Build\Event\PageWrittenEvent;
use Glaze\Template\Extension\GlazeExtension;
use Glaze\Template\Extension\ListensTo;

#[GlazeExtension]
final class SitemapGenerator
{
    private array $urls = [];

    #[ListensTo(BuildEvent::PageWritten)]
    public function collect(PageWrittenEvent $event): void
    {
        $this->urls[] = $event->page->urlPath;
    }

    #[ListensTo(BuildEvent::BuildCompleted)]
    public function write(BuildCompletedEvent $event): void
    {
        $sitemap = $this->buildSitemap($this->urls, $event->config->site->baseUrl);
        file_put_contents($event->config->outputPath() . '/sitemap.xml', $sitemap);
    }
}

A class with both a name and event listeners is valid – it registers as both a template helper and a subscriber:

#[GlazeExtension('stats')]
final class StatsExtension
{
    private int $pageCount = 0;

    #[ListensTo(BuildEvent::PageWritten)]
    public function count(PageWrittenEvent $event): void
    {
        $this->pageCount++;
    }

    public function __invoke(): int
    {
        return $this->pageCount;
    }
}

Build events reference #

Event Payload Mutable fields When
BuildEvent::BuildStarted BuildStartedEvent Before content discovery
BuildEvent::ContentDiscovered ContentDiscoveredEvent $pages After discovery, before rendering
BuildEvent::PageRendered PageRenderedEvent $html After each page renders, before writing
BuildEvent::PageWritten PageWrittenEvent After each page is written to disk
BuildEvent::BuildCompleted BuildCompletedEvent After all pages and assets are written

Mutable fields let you modify the build in place. Set ContentDiscoveredEvent::$pages to inject or remove pages, or set PageRenderedEvent::$html to post-process rendered output.

BuildStartedEvent #

$event->config    — BuildConfig

Useful for opening file handles, recording a start time, or validating external prerequisites.

ContentDiscoveredEvent #

$event->pages     — ContentPage[]  (mutable)
$event->config    — BuildConfig

Mutate $pages to inject virtual pages, reorder, augment metadata, or filter the list.

PageRenderedEvent #

$event->page      — ContentPage
$event->html      — string  (mutable)
$event->config    — BuildConfig

Mutate $html to minify output, inject analytics snippets, or extract content for a search index.

PageWrittenEvent #

$event->page        — ContentPage
$event->destination — string  (absolute output path)
$event->config      — BuildConfig

Useful for accumulating sitemap entries, search-index records, or per-page statistics.

BuildCompletedEvent #

$event->writtenFiles — string[]  (absolute paths)
$event->config       — BuildConfig
$event->duration     — float  (seconds)

Useful for writing derived files (sitemap, search index, RSS feed) and triggering post-build hooks.