# 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` - Registration: components are registered in PHP (see API). Unregistered component names are a `SyntaxError` (configurable to `RuntimeError`). - 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; `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 dot‑notated 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), PSR‑16 (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 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_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) - Auto‑escape by default; `safe` is 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`: ```html