# Scape Templates — Product Specification ## Project Name Scape Templates ## Project Description A lightweight, standalone PHP template engine designed for simplicity, security, and performance. Scape is built as an **Output Engine first**, focusing on marrying pre-processed data with design rather than performing additional business logic or data manipulation. ## Project Purpose To provide a modern, framework-agnostic alternative for template rendering that maintains a strict separation of concerns. Scape enforces a "logic-light" philosophy to ensure templates remain readable and focused purely on presentation. ## Project Installation Instructions Scape can be installed via Composer: ```bash composer require getphred/scape ``` ## Project Features ### 1. File Handling & Configuration - **Template Extensions**: - All templates, layouts, and partials must use the `.scape.php` extension. - **Filter Extensions**: - Custom filters must use the standard `.php` extension. - All custom filters must implement the `Scape\Interfaces\FilterInterface` to ensure they provide the necessary transformation methods. - **Directory Configuration**: Template locations are managed via environment variables: - `SCAPE_TEMPLATES_DIR`: Main directory for application templates. - `SCAPE_LAYOUTS_DIR`: Directory for base layouts and parent templates. - `SCAPE_PARTIALS_DIR`: Directory for reusable snippets/partials. - `SCAPE_FILTERS_DIR`: Directory for user-defined filters. - **Dot Notation Pathing**: All internal paths (extends, includes) use dot notation (e.g., `sidebar.login_form`) relative to their respective directories, omitting the file extension. ### 2. Syntax & White-space - All opening and closing tags are white-space independent (e.g., `{{var}}` is equivalent to `{{ var }}`). - **Variable Interpolation**: - `{{ var }}`: Automatically HTML-escaped output (uses `ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5`). - `{{{ var }}}`: Raw, unescaped output. - **Data Access**: - Use dot-notation for accessing class properties/attributes (e.g., `user.name`). - Use bracket notation for accessing array elements (e.g., `items['title']` or `users[0]`). ### 3. Logic & Control Flow - **Syntax**: Uses `{( ... )}` for logic tags. - **White-space Management**: Logic tags `{( ... )}` and block/inheritance tags `{[ ... ]}` automatically consume one trailing newline immediately following their closing `)}` or `]}` to prevent unintended vertical spacing in the rendered output. - **"Logic-Light" Constraints**: - No generic programming or complex expressions. - No `if` statements or conditional branching (by design). - No manual variable assignment within templates. - **Loops**: - Only bounded iteration is supported: `foreach`. - **Grammar**: - `{( foreach item in collection )} ... {( endforeach )}` - `{( foreach key, item in collection )} ... {( endforeach )}` - Every loop provides access to two local integer variables: - `index`: The current iteration index, 0-indexed (0, 1, 2...). - `pos`: The current human-readable position, 1-indexed (1, 2, 3...). - **Positional Rendering**: Special tags are available within loops to handle presentation based on position: - `{( first )} ... {( endfirst )}`: Renders only on the first iteration. - `{( inner )} ... {( endinner )}`: Renders on all iterations except the first and last. - `{( last )} ... {( endlast )}`: Renders only on the last iteration. ### 4. Inheritance & Reusability - **Syntax**: Uses `{[ ... ]}` for block and inheritance tags. - **Layouts & Inheritance**: - Templates can extend layouts from `SCAPE_LAYOUTS_DIR` using `{[ extends 'path' ]}`. - The `extends` tag must be the very first thing in a template. - **Blocks**: - **Placeholder**: Layouts define placeholders using `{[ block 'name' ]} ... {[ endblock ]}`. - **Override**: Child templates provide content for these placeholders by defining a block with the same name. - **Parent Content**: Within an override block, the child can render the layout's default content using the `{[ parent ]}` tag. - **Default Content**: If a child template does not provide a block, the content within the layout's `block` tags is rendered as a default. - **Nested Blocks**: Blocks can be nested within other blocks. - **Partials**: - Reusable snippets from `SCAPE_PARTIALS_DIR` can be included using `{[ include 'path' ]}`. - **Encapsulation**: Partials are siloed by default and do not inherit the parent template's variables. - **Passing Data**: - Data can be passed as an array: `{[ include 'path' with data_source ]}`. - The `data_source` can be a local variable, a nested array element (`items['meta']`), a class attribute (`user.profile`), or an inline array declaration (e.g., `['user' => user, 'id' => 1]`). - The array keys from the source are expanded into individual local variables within the partial. - The full parent context can be passed explicitly: `{[ include 'path' with context ]}`. - **Nesting**: Partials can include other partials. To prevent infinite recursion, the engine enforces a maximum nesting depth (default: 20). ### 5. Extensibility - **Filters**: - Used with variable interpolation to transform data. - **Piping**: Supports chaining multiple filters: `{{ var | lower | ucfirst }}`. - **Arguments**: Supports passing simple arguments (strings, numbers, or other variables): `{{ price | currency('USD') }}`. - **The `FilterInterface`**: All filters must implement `Scape\Interfaces\FilterInterface`: ```php public function transform(mixed $value, array $args = []): mixed; ``` - **Loading**: Filters must be pre-loaded at the top of the template. - **Internal Libraries**: Engine-provided filters are loaded using the `uses` keyword: - Syntax: `{( uses namespace:library )}` (e.g., `{( uses filters:string )}`). - **Custom Filters**: User-defined filters are loaded from `SCAPE_FILTERS_DIR` using `load_filter`. - Syntax: `{( load_filter('path') )}` where path is dot-notated. ### 6. Security & Error Handling - **Contextual Escaping**: Standard `{{ }}` interpolation ensures XSS protection. - **Missing Assets**: If a layout or partial is missing, the engine looks for a user-provided `404.scape.php` in `SCAPE_TEMPLATES_DIR`. If not found, it renders a built-in "Template 404" placeholder. - **Exceptions**: The engine throws specific exceptions within the `Scape\Exceptions` namespace: - `TemplateNotFoundException`: Main template, layout, or partial missing. - `SyntaxException`: Malformed tags or disallowed logic (e.g. `if`). - `FilterNotFoundException`: Target of `uses` or `load_filter` missing. - `PropertyNotFoundException`: (Debug only) Accessing undefined key/property. - `RecursionLimitException`: Partials exceed nesting limit (default 20). - **Variable Access**: - **Debug Mode**: Accessing a non-existent object property or array key throws a `PropertyNotFoundException`. - **Production Mode**: Accessing a non-existent property or key fails silently and renders an empty string. ### 7. Performance - **AST Caching**: The engine caches the parsed Abstract Syntax Tree (AST) of templates to speed up subsequent renders. - **Cache Location**: Cache files are stored locally in the project directory under `.scape/cache`. - **No Compiling**: Scape does not compile templates into raw PHP files; it interprets the cached AST directly. - **Cache Modes**: - **Development**: Engine checks file modification times (`mtime`) to invalidate the cache when a template changes. - **Production**: Engine skips `mtime` checks and serves the cached AST directly for maximum performance. ### 8. Host Integration (IoC) - **The `host` Namespace**: Scape provides a reserved `host` namespace that can be used with the `uses` keyword or as a filter/function prefix. - **Provider Registration**: Host frameworks (e.g., Phred) can register custom providers to handle calls in this namespace. - **Use Cases**: Used for framework-level features like feature flags (Flagpole), routing, or translations without creating a hard dependency within the engine. - **Default Behavior**: If no provider is registered, calls to the `host` namespace return the input value unchanged or `null`. ### 9. Runtime API - **The `Scape\Engine` Class**: The primary entry point for the library. - **Configuration Precedence**: Programmatic Config > Environment Variables > Defaults. - **Rendering**: - Method: `public function render(string $template, array $data = []): string` - The `$template` argument uses dot notation. - **Mode Control**: The engine operating mode (`debug` vs `production`) can be set via the `SCAPE_MODE` environment variable or explicitly during instantiation. ## Project Dependencies - **PHP**: ^8.2 - **PHPUnit**: ^10.0 (Development)