Skip to main content
Every {{ $expression }} output in a Lex template is routed through an escaper before it reaches the browser. By default Lex uses HtmlEscaper, which calls PHP’s htmlspecialchars. You can replace this with your own implementation by providing a class that satisfies EscaperInterface.

The EscaperInterface contract

namespace Wik\Lexer\Contracts;

interface EscaperInterface
{
    /**
     * Escape a value for safe HTML output.
     *
     * The returned string must be safe to place verbatim inside an HTML document.
     */
    public function escape(mixed $value): string;
}
Your implementation receives any value the template produces — including null, booleans, objects implementing Stringable, or plain strings — and must return a string that is safe to embed verbatim in HTML.

Default behaviour

The built-in HtmlEscaper class converts the five HTML-sensitive characters to their named entity equivalents:
Input characterEscaped output
&&
<&lt;
>&gt;
"&quot;
'&#039;
It also handles the following types before passing the value to htmlspecialchars:
  • null → empty string ''
  • Stringable → casts to string first
  • booltrue becomes '1', false becomes ''
  • array or non-Stringable object → empty string ''
Under the hood it uses:
htmlspecialchars((string) $value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
ENT_SUBSTITUTE replaces malformed UTF-8 byte sequences with the Unicode replacement character instead of returning an empty string, which prevents silent data loss.

Implementing a custom escaper

Create a class that implements EscaperInterface and define the escape() method:
use Wik\Lexer\Contracts\EscaperInterface;

final class MyEscaper implements EscaperInterface
{
    public function escape(mixed $value): string
    {
        // Your escaping logic here
        return htmlspecialchars((string) $value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
    }
}

Registering your escaper

Pass an instance to setEscaper() on the Lexer object:
use Wik\Lexer\Lexer;

$lexer = (new Lexer())
    ->paths([__DIR__ . '/views'])
    ->setEscaper(new MyEscaper());
All {{ expr }} output for every template rendered by this Lexer instance is then routed through MyEscaper::escape().
{!! $expr !!} raw echo is not routed through the escaper — it outputs the value verbatim. Only {{ $expr }} uses the escaper.

Realistic example: null-safe escaper with additional sanitisation

The following escaper adds two behaviours on top of the default:
  1. Coerces null to an empty string so templates never output "" or warnings.
  2. Strips HTML tags that survive htmlspecialchars when the value is already a string containing pre-escaped content (useful when integrating with a CMS that stores partially-sanitised content).
use Wik\Lexer\Contracts\EscaperInterface;

final class SanitisedEscaper implements EscaperInterface
{
    public function escape(mixed $value): string
    {
        // Coerce null and other non-stringable types to empty string
        if ($value === null || is_array($value)) {
            return '';
        }

        if ($value instanceof \Stringable) {
            $value = (string) $value;
        }

        if (is_bool($value)) {
            return $value ? '1' : '';
        }

        // Strip any residual HTML tags before HTML-encoding
        $stripped = strip_tags((string) $value);

        return htmlspecialchars($stripped, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
    }
}
Register it the same way:
$lexer = (new Lexer())
    ->paths([__DIR__ . '/views'])
    ->setEscaper(new SanitisedEscaper());

When to use a custom escaper

Framework integration

Replace the default escaper with your framework’s built-in XSS filter to keep escaping behaviour consistent across the entire application — for example, integrating with Symfony’s twig_escape_filter or Laravel’s e() helper logic.

Custom output encoding

Output templates into contexts other than HTML — for example, XML documents, plain-text emails, or CSV fields — where the escaping rules differ from htmlspecialchars.

Additional sanitisation

Apply extra content policies on top of HTML escaping — strip specific tags, enforce an allowlist of HTML attributes, or run values through an HTML purifier library before output.

Null / type coercion

Centralise how the template engine handles null, booleans, and objects so that individual templates do not need explicit null-guard expressions everywhere.

Using the Lexer::fromConfig() factory

If you use lex.config.json, apply the escaper after the factory call:
use Wik\Lexer\Lexer;

$lexer = Lexer::fromConfig()
    ->setEscaper(new SanitisedEscaper());

Build docs developers (and LLMs) love