Eyrie-Templates/SPECS.md
2026-01-06 17:29:10 -06:00

265 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 serverside templating for PHP apps in the Phred ecosystem.
- Separation of concerns: business logic in PHP, presentation logic in templates.
- Security by default via autoescaping with contextual modes.
- Predictable, minimal syntax optimized for HTML.
- DRY composition with inheritance and partials.
## 2. NonGoals
- 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 toplevel 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, taglike 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 autoescaping.
- 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 } />` selfclosing. 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 (readonly): `item`
- Loop (range/repeat):
- `<( loop from 0 to 10 )>` ... `<( endloop )>`
- Loop vars (readonly): `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
- Tagbased 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`
- Registration: components are registered in PHP (see API). Unregistered component names are a `SyntaxError` (configurable to `RuntimeError`).
- Autoescaping: 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 Builtin 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. Autoescaping
- Enabled by default for all `<< >>` outputs.
- Modes: `html` (default), `attr`, `url`, `js`.
- `escape(mode)` switches mode; `safe` marks trusted values to bypass escaping.
- Escaping happens after filters unless `safe` present.
- Component rendering occurs under the current escaping mode of the insertion site.
## 7. Template loaders
- FilesystemLoader: rooted directories, normalized paths, no `..` traversal.
- Names are dotnotated within configured roots and omit the file extension (default `.eyrie.php`).
- Optional namespaces (if supported): `@emails.welcome` → resolved under the `emails` root.
- Future: StringLoader, ArrayLoader.
## 8. Caching
- Optional compiled cache keyed by template name + engine version + config hash.
- Stores: filesystem (default), PSR16 (future).
- Defaults: dir perms 0700; atomic writes; checksum integrity.
## 9. Public PHP API (draft)
```php
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 readonly view of the current render context
* @return mixed A string (escaped later) or a SafeHtmllike value
*/
public function render(array $props, array $context = []): mixed;
}
final class CompiledTemplate {
public function render(array $context = []): string {}
}
```
Notes:
- `strict_variables` option 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)
- Autoescape by default; `safe` is explicit optin.
- 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.
- PSR3 logger support.
## 14. Compatibility
- Minimum PHP: propose 8.1+ (finalize).
- UTF8 for sources and output.
## 15. Examples
Base `base.eyrie.php`:
```html
<!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`:
```html
[[ 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:
```html
[[ 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.
- Builtin 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.