Scape/SPECS.md

11 KiB

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:

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:
      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)