Skip to content

Released version 3.1.6

Latest

Choose a tag to compare

@dg dg released this 27 Jul 15:34

A substantial correctness and security release born from a deep audit of the engine. It hardens context-sensitive escaping in two spots, fixes a long list of filter and compiler edge cases, adds the handy |map filter and an afterRender() extension hook, and brings back a performance regression fix. A recommended upgrade for everyone.

💥 Behavior Changes & Deprecations

  • {$interval|date} without an explicit format now throws instead of silently printing the literal format string j. n. Y. Pass a format for DateInterval, e.g. {$interval|date: '%d days'}.
  • {first}, {last}, {sep} used outside a {foreach} now emit a deprecation warning – they rely on an ambient $iterator that only a loop provides. This becomes a compile-time error in Latte 3.2.

✨ New Features

  • New |map filter – transform every item with a callback, the natural partner to |filter: {foreach ($items|map: fn($x) => $x->name) as $name}.
  • New Extension::afterRender() hook – called in a finally, so it runs even when rendering ends early via {exitIf} or is interrupted by an exception. Perfect for cleanup or measurement.
  • The Tracy panel now shows render time per template, both total and self (excluding nested templates), so you can finally see which template actually eats the time.
  • |last, |random, |implode/|join, |commas now accept any iterable, not just arrays – hand them a generator or iterator directly, just like |first, |slice and |filter already allowed.

🐛 Bug Fixes

  • Security: <script type=" text/javascript "> with surrounding whitespace was classified as raw text while browsers execute it as JavaScript. The type is now trimmed the way browsers do, so context-sensitive escaping kicks in correctly.
  • Security: a template compiled under a permissive sandbox policy could be silently reused from cache under a stricter one, because part of the sandbox is enforced at compile time. Policy rules are now part of the cache key.
  • Security: xlink:href in inline SVG is now treated as a URL attribute and sanitized like href.
  • {capture $x|filter} no longer double-escapes its content. Adding an innocent filter like |trim used to strip the "HTML-ness" of the captured markup, escaping it on output; captured HTML now stays intact (while a filter that changes the content type still yields a plain string).
  • {switch} with no expression used to compile to invalid PHP; it now works as a switch-true idiom ({case $cond}).
  • <script type="a{$dyn}"> with a partially dynamic type attribute no longer fails compilation with a cryptic "Unhandled match case" – it's treated as JavaScript.
  • The empty comment {**} no longer breaks compilation.
  • |reverse and |column no longer drop elements from generators or iterators that yield duplicate keys.
  • |indent no longer interprets $ or \ in the indentation string as regular-expression backreferences.
  • |explode/|split (and |random) now raise a clear RuntimeException on malformed UTF-8 instead of a confusing TypeError.
  • |padLeft/|padRight with an empty pad string, and the divisibleBy() function with a zero divisor, now throw a helpful message instead of a raw DivisionByZeroError.
  • Compiled template class names now use a 64-bit hash (up from 40-bit), removing a small but real risk of two templates colliding so that one silently renders the other.
  • Custom n:attributes whose name contains tag- or inner- (e.g. n:my-tag-attr) are no longer mangled by the prefix stripper.
  • Extensions that subclass core nodes no longer silently break {iterateWhile}, {rollback} and {include parent}Tag::closestTag() now matches subclasses, as its documentation always promised.
  • A compile-time failure without a cache directory now surfaces as a proper CompileException pointing at the generated source, instead of a bare ParseError.

⚡ Performance

  • The engine no longer recomputes its entire configuration signature on every {include}, {extends} or {embed} – a memoization lost in the 3.1 cache refactor is restored (and properly invalidated whenever you reconfigure the engine). This also fixes a crash when createTemplate(clearCache: false) was the very first call.
  • Passing a parameters object to render() is roughly 2.6× faster – the reflection scan of the parameter class is memoized per class instead of being repeated on every render.