10 KiB
10 KiB
Eyrie Templates — Product Specification (Draft)
This specification captures the initial scope for Eyrie Templates, the templating engine used by the Phred Framework.
1. Goals
- Fast, safe, ergonomic server‑side templating for PHP apps in the Phred ecosystem.
- Separation of concerns: business logic in PHP, presentation logic in templates.
- Security by default via auto‑escaping with contextual modes.
- Predictable, minimal syntax optimized for HTML.
- DRY composition with inheritance and partials.
2. Non‑Goals
- No arbitrary code execution in templates; no general programming.
- No dynamic variable creation inside templates (only loop counters).
- No while loops or unbounded iteration constructs.
- No filesystem or network access from templates.
3. Installation
- Composer package:
composer require getphred/eyrie
3.1 Engine configuration
- Directory roots can be configured for Layouts, Pages, Partials, and Components.
- Template names are referenced using dot-notation relative to their configured roots and do not include file extensions.
- Default template file extension:
.eyrie.php.
4. Core concepts
- Template: Text file (typically HTML) with Eyrie syntax.
- Context: Associative array passed at render time.
- Page: A top‑level template representing a view. Pages may extend a Layout and fill named Blocks.
- Layout: A template that defines the overarching structure (shell) and exposes named Blocks with optional defaults.
- Block: A named content region. Pages/components can override block content;
[[ super ]]can include parent content. - Component: A reusable, tag‑like unit rendered with a custom element syntax (e.g.,
<@ Movie id="1" />). Components encapsulate their own rendering and accept props. - Props: Data passed to components (and optionally pages) akin to attributes; values flow from the rendering context and are subject to escaping.
- Helper: Whitelisted callable exposed to templates.
- Filter: Unary transformation applied via
|pipe. - Tag: Control structure/directive (loops, conditionals, includes, etc.).
- Loader: Resolves template names to sources.
- Cache: Compiled/intermediate representation for speed.
5. Syntax overview
- Output:
<< expression >>prints value with auto‑escaping. - Calls/control: control blocks start with
<(and end with)>; examples use<( if ... )>/<( endif )>. - Blocks/extends:
[[ ... ]]for inheritance, blocks, includes. - Components:
<@ ComponentName prop1="value" prop2={ expr } />self‑closing. Support for children/content projection is TBD.
5.1 Expressions
- Property/array access:
user.name,order.items[0]. - Literals: strings, numbers, booleans, null.
- Operators: arithmetic, comparison, logical, ternary.
- Filters:
value | lower | escape('attr') | ....
5.2 Output examples
- Basic:
<< user.name >> - With filters:
<< title | upper >> - Raw (discouraged):
<< user.bio | raw >>
5.3 Control structures (inside <( ... )>)
- If/elseif/else/end:
<( if cond )>...<( elseif other )>...<( else )>...<( endif )>
- Foreach:
<( foreach item in items )>...<( endforeach )>- Loop vars (read‑only):
item
- Loop (range/repeat):
<( loop from 0 to 10 )>...<( endloop )>- Loop vars (read‑only):
loop.index(int),loop.first(bool),loop.last(bool),loop.length(int)
5.4 Inheritance and blocks (inside [[ ... ]])
- Extends:
[[ extends "layouts.base" ]](dot-notated paths, final part is parent file)- Extends syntax does not include the layout file extension.
- Extends must be the first line of a template file.
- Declare/override:
[[ block content ]] ... [[ endblock ]][[ super ]]inside override to include parent content
- Block context (optional):
[[ block sidebar with { user: user } ]]
5.5 Partials
- Basic Include:
[[ include "partials.footer" ]] - Include with optional context:
[[ include "partials.footer" with { x: 1 } ]](dot-notated paths, final part is partial file)- Include syntax does not include the partial file extension.
5.6 Components
- Tag‑based components are referenced by PascalCase names and invoked with the
<@prefix:<@ Movie id="1" />. - Props:
- String literal:
<@ Movie title="Jaws" /> - Expression:
<@ Movie rating={ movie.rating } /> - Boolean shorthand:
<@ Movie featured />→featured=true
- String literal:
- Registration: components are registered in PHP (see API). Unregistered component names are a
SyntaxError(configurable toRuntimeError). - Auto‑escaping: Component outputs are escaped by context unless a component deliberately returns a
SafeHtml‑like value. - Context: Components receive props plus a limited view of the parent context (configurable); they do not mutate outer context.
- Layout/block interaction: Components can be used within Layouts and Pages but do not extend layouts themselves, and they do not define or fill block areas.
5.7 Built‑in tags
if/elseif/else/endif,foreach/endforeach,loop/endloop,include,extends,block/endblock,super
5.8 Helpers and filters
- Helpers are registered by name in PHP and callable in
<( ... )>or<< >>, e.g.<< route('home') >>. - Filters chain with pipes, e.g.
<< text | truncate(120) | escape('attr') >>.
6. Auto‑escaping
- Enabled by default for all
<< >>outputs. - Modes:
html(default),attr,url,js. escape(mode)switches mode;safemarks trusted values to bypass escaping.- Escaping happens after filters unless
safepresent. - Component rendering occurs under the current escaping mode of the insertion site.
7. Template loaders
- FilesystemLoader: rooted directories, normalized paths, no
..traversal. - Names are dot‑notated within configured roots and omit the file extension (default
.eyrie.php). - Optional namespaces (if supported):
@emails.welcome→ resolved under theemailsroot. - Future: StringLoader, ArrayLoader.
8. Caching
- Optional compiled cache keyed by template name + engine version + config hash.
- Stores: filesystem (default), PSR‑16 (future).
- Defaults: dir perms 0700; atomic writes; checksum integrity.
9. Public PHP API (draft)
interface Loader {
public function getSource(string $name): string;
public function getCacheKey(string $name): string; // stable per source
public function exists(string $name): bool;
}
final class Environment {
public function __construct(Loader $loader, array $options = []) {}
public function addHelper(string $name, callable $helper): void {}
public function addFilter(string $name, callable $filter): void {}
/** Register a component renderer by tag name, e.g., 'Movie' */
public function addComponent(string $name, ComponentRenderer $component): void {}
public function render(string $name, array $context = []): string {}
public function compile(string $name): CompiledTemplate {}
}
/** Minimal component contract (draft) */
interface ComponentRenderer {
/**
* @param array $props Props/attributes supplied in the template
* @param array $context A read‑only view of the current render context
* @return mixed A string (escaped later) or a SafeHtml‑like value
*/
public function render(array $props, array $context = []): mixed;
}
final class CompiledTemplate {
public function render(array $context = []): string {}
}
Notes:
strict_variablesoption throws on missing variables.- Helpers/filters return values still subject to escaping unless wrapped in a
SafeHtml‑like type.
10. Errors and diagnostics
- Exceptions:
TemplateError,SyntaxError,RuntimeError,LoaderError. - Messages include template name, line/col, and a snippet when possible.
- Configurable verbosity for dev vs prod.
- Useful diagnostics for components: unknown component name, invalid/unknown props, and type errors include the component tag snippet.
11. Performance targets (initial)
- Cold render p95 < 10ms for a ~5KB template on typical server hardware.
- Warm render p95 < 2ms with cache.
- Watchdog timeouts to limit parse/render and mitigate DoS.
- Component recursion and nesting limits (e.g., max depth) to prevent pathological trees.
12. Security requirements (summary)
- Auto‑escape by default;
safeis explicit opt‑in. - No arbitrary PHP execution; only whitelisted helpers/filters.
- Loader prevents traversal; only configured roots.
- Sandboxed evaluation; no eval/reflection.
- Depth/iteration limits; template size/token limits.
- Restrictive cache dir permissions; validated paths.
- Components are pure render units: no filesystem/network access; no mutation of outer context; bounded recursion/depth.
13. Logging and telemetry
- Hook for timings, cache metrics, loader misses.
- PSR‑3 logger support.
14. Compatibility
- Minimum PHP: propose 8.1+ (finalize).
- UTF‑8 for sources and output.
15. Examples
Base base.eyrie.php:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title><< title | escape('html') >></title>
</head>
<body>
<header>[[ block header ]]Default Header[[ endblock ]]</header>
<main>[[ block content ]][[ endblock ]]</main>
</body>
</html>
Child home.eyrie.php:
[[ extends "base" ]]
[[ block content ]]
<h1>Hello, << user.name >>!</h1>
<ul>
<( foreach item in items )>
<li><< loop.index >>. << item.title | escape('attr') >></li>
<( endforeach )>
</ul>
[[ endblock ]]
Component usage example:
[[ extends "base" ]]
[[ block content ]]
<h2>Featured</h2>
<@ Movie id={ featured.id } title={ featured.title } featured />
<ul>
<( foreach m in movies )>
<li>
<@ Movie id={ m.id } title={ m.title } rating={ m.rating } />
</li>
<( endforeach )>
</ul>
[[ endblock ]]
16. Open questions
- Final grammar (EBNF) for expressions/tags.
- Built‑in filters/helpers set.
- Parser error recovery strategy.
- Safe value interface semantics.
- Component children/content projection: do we allow
<Card>...children...</Card>? - Prop type checking and defaulting strategy.