136 lines
8.8 KiB
Markdown
136 lines
8.8 KiB
Markdown
# 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 `{( ... )}` automatically consume one trailing newline immediately following the closing `)}` tag 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)
|