Eyrie-Templates/SPECS.md

265 lines
10 KiB
Markdown
Raw Permalink Normal View History

2026-01-06 23:29:10 +00:00
# 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.