162 lines
11 KiB
Markdown
162 lines
11 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 (with the following defaults when not provided):
|
|
- `SCAPE_TEMPLATES_DIR`: Main directory for application templates.
|
|
- Default: `./templates`
|
|
- `SCAPE_LAYOUTS_DIR`: Directory for base layouts and parent templates.
|
|
- Default: `./templates/layouts`
|
|
- `SCAPE_PARTIALS_DIR`: Directory for reusable snippets/partials.
|
|
- Default: `./templates/partials`
|
|
- `SCAPE_FILTERS_DIR`: Directory for user-defined filters.
|
|
- Default: `./filters`
|
|
- `SCAPE_CACHE_DIR`: Directory for cached AST files.
|
|
- Default: `./.scape/cache`
|
|
- **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 )}`).
|
|
- **filters:string** library includes:
|
|
- `lower`: Converts to lowercase.
|
|
- `upper`: Converts to uppercase.
|
|
- `ucfirst`: Capitalizes the first character.
|
|
- `currency(code)`: Formats numeric value as currency (default: 'USD').
|
|
- `float(precision)`: Formats numeric value as a float with fixed precision (default: 2).
|
|
- `date(format)`: Formats a timestamp or date string (default: 'Y-m-d H:i:s').
|
|
- `truncate(length, suffix)`: Truncates string to length (default length: 100, suffix: '...').
|
|
- `default(fallback)`: Returns fallback if value is empty.
|
|
- `json`: Returns JSON encoded string.
|
|
- `url_encode`: Returns URL encoded string.
|
|
- `join(glue)`: Joins array elements with glue (default glue: '').
|
|
- `first`: Returns first element of a collection.
|
|
- `last`: Returns last element of a collection.
|
|
- `word_count`: Returns word count of a string.
|
|
- `keys`: Returns keys of an associative array.
|
|
- **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. **All exceptions must include helpful, context-rich error messages** (e.g., template name, line number, and specific failure reason) to assist in debugging:
|
|
- `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) & i18n
|
|
- **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.
|
|
- **Localization (i18n)**:
|
|
- **Philosophy**: Scape is "Simple for Blogs, Powerful for Enterprise." Localization is an **opt-in** feature.
|
|
- **Responsibility**: Translation logic and message catalogs reside in the Application/Framework. Scape provides the **access layer**.
|
|
- **Usage**: Templates can use `host.translate('key')` or the `|t` filter alias to fetch localized strings on-demand.
|
|
- **Portability**: If no host provider is registered, i18n calls return the input key/value unchanged, ensuring templates remain portable across environments.
|
|
- **Use Cases**: Used for framework-level features like feature flags (Flagpole), routing, or translations without creating a hard dependency within the engine.
|
|
|
|
### 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)
|