{"id":23360306,"url":"https://github.com/piedweb/splates","last_synced_at":"2026-02-28T18:19:07.112Z","repository":{"id":268458879,"uuid":"904275447","full_name":"PiedWeb/Splates","owner":"PiedWeb","description":"Native PHP template engine","archived":false,"fork":false,"pushed_at":"2026-02-27T16:52:10.000Z","size":122,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-02-27T21:27:44.536Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/PiedWeb.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2024-12-16T15:21:10.000Z","updated_at":"2025-12-19T09:43:47.000Z","dependencies_parsed_at":null,"dependency_job_id":"431fb64f-2296-49c7-85cf-b30b429bc830","html_url":"https://github.com/PiedWeb/Splates","commit_stats":null,"previous_names":["piedweb/splates"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/PiedWeb/Splates","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PiedWeb%2FSplates","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PiedWeb%2FSplates/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PiedWeb%2FSplates/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PiedWeb%2FSplates/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/PiedWeb","download_url":"https://codeload.github.com/PiedWeb/Splates/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PiedWeb%2FSplates/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29946482,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-28T17:57:52.716Z","status":"ssl_error","status_checked_at":"2026-02-28T17:57:31.974Z","response_time":90,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-21T11:13:49.580Z","updated_at":"2026-02-28T18:19:07.104Z","avatar_url":"https://github.com/PiedWeb.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Splates: Type-Safe PHP Template Engine\n\nA native PHP template engine with full IDE autocompletion and PHPStan support. Fork of [league/plates](https://github.com/thephpleague/plates), redesigned for modern PHP development.\n\n[![Latest Version](https://img.shields.io/github/release/piedweb/splates.svg?style=flat-square)](https://github.com/piedweb/splates/releases)\n[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE)\n[![Build Status](https://img.shields.io/github/actions/workflow/status/piedweb/splates/php.yml?style=flat-square)](https://github.com/piedweb/splates/actions)\n\n## Why Splates?\n\n- **Full IDE support** - Constructor parameters with `#[TemplateData]` provide autocomplete everywhere\n- **PHPStan max level** - Every template is statically analyzable\n- **No magic** - No string-based template names, no runtime errors from typos\n- **Slots pattern** - Layouts are just components with `Closure` properties (no magic sections)\n- **Value objects** - `Text`, `Html`, `Attr`, `Js` for context-aware escaping\n- **Global services** - Inject dependencies via `Engine::addGlobal()` with `#[Inject]`\n\n## Installation\n\n```bash\ncomposer require piedweb/splates\n```\n\nRequires PHP 8.2+.\n\n## Quick Start\n\n### 1. Create a template\n\n```php\n\u003c?php\n// src/Templates/ProfileTpl.php\n\nnamespace App\\Templates;\n\nuse PiedWeb\\Splates\\Template\\Attribute\\TemplateData;\nuse PiedWeb\\Splates\\Template\\TemplateAbstract;\nuse PiedWeb\\Splates\\Template\\Value\\Text;\n\nclass ProfileTpl extends TemplateAbstract\n{\n    public function __construct(\n        #[TemplateData]\n        public Text $name,       // Auto-escapes on output\n        #[TemplateData]\n        public Text $email,      // No need to call $this-\u003ee()\n    ) {}\n\n    public function __invoke(): void\n    { ?\u003e\n        \u003cdiv class=\"profile\"\u003e\n            \u003ch1\u003e\u003c?= $this-\u003ename ?\u003e\u003c/h1\u003e\n            \u003cp\u003eEmail: \u003c?= $this-\u003eemail ?\u003e\u003c/p\u003e\n        \u003c/div\u003e\n    \u003c?php }\n}\n```\n\n### 2. Render it\n\n```php\n\u003c?php\n\nuse PiedWeb\\Splates\\Engine;\nuse PiedWeb\\Splates\\Template\\Value\\Text;\nuse App\\Templates\\ProfileTpl;\n\n$engine = new Engine();\n\necho $engine-\u003erender(new ProfileTpl(\n    name: new Text('John Doe'),\n    email: new Text('john@example.com'),\n));\n```\n\n---\n\n## Core Concepts\n\n### Template Creation Options\n\nTemplates implement `TemplateClassInterface` and can be created in several ways:\n\n#### 1. Minimal (no helpers needed)\n\n```php\nclass Hello implements TemplateClassInterface\n{\n    public function __construct(public string $name) {}\n\n    public function __invoke(): void\n    {\n        echo \"Hello, {$this-\u003ename}!\";\n    }\n}\n```\n\n#### 2. With `__invoke()` parameter injection\n\n```php\nclass Profile implements TemplateClassInterface\n{\n    public function __construct(public string $name) {}\n\n    public function __invoke(TemplateFetch $f, TemplateEscape $e): void\n    {\n        echo '\u003ch1\u003e' . $e($this-\u003ename) . '\u003c/h1\u003e';\n        echo $f(new SidebarTpl());\n    }\n}\n```\n\n#### 3. With `#[Inject]` property injection\n\n```php\nuse PiedWeb\\Splates\\Template\\Attribute\\Inject;\n\nclass Profile implements TemplateClassInterface\n{\n    #[Inject]\n    protected TemplateFetch $f;\n\n    #[Inject]\n    protected TemplateEscape $e;\n\n    public function __construct(public string $name) {}\n\n    public function __invoke(): void\n    {\n        echo '\u003ch1\u003e' . ($this-\u003ee)($this-\u003ename) . '\u003c/h1\u003e';\n        echo ($this-\u003ef)(new SidebarTpl());\n    }\n}\n```\n\n#### 4. Extending `TemplateAbstract` (full helper methods)\n\n```php\nclass MyTemplate extends TemplateAbstract\n{\n    public function __construct(\n        #[TemplateData]\n        public User $user,\n        #[TemplateData]\n        public array $items = [],  // Optional with default\n    ) {}\n\n    public function __invoke(): void\n    {\n        echo $this-\u003erender(new LayoutTpl(...));\n        echo $this-\u003ee($this-\u003euser-\u003ename);\n    }\n}\n```\n\n### Helper Methods\n\nInside templates, you have access to:\n\n| Method                        | Description                          |\n| ----------------------------- | ------------------------------------ |\n| `$this-\u003ee($value)`            | Escape value for HTML output         |\n| `$this-\u003erender(new Tpl())`    | Render a child template              |\n| `$this-\u003ecapture(fn() =\u003e ...)` | Capture output as string             |\n| `$this-\u003eslot(fn() =\u003e ...)`    | Create a lazy slot (syntactic sugar) |\n\n### Escaping\n\nAlways escape user data:\n\n```php\n\u003ch1\u003e\u003c?= $this-\u003ee($this-\u003etitle) ?\u003e\u003c/h1\u003e\n```\n\nOr use auto-escaping value objects:\n\n```php\nuse PiedWeb\\Splates\\Template\\Value\\Text;\n\n// Text auto-escapes when converted to string\necho new Text('\u003cscript\u003ealert(\"XSS\")\u003c/script\u003e');\n// Output: \u0026lt;script\u0026gt;alert(\u0026quot;XSS\u0026quot;)\u0026lt;/script\u0026gt;\n```\n\n### IDE Syntax Highlighting with Heredoc\n\nWhen mixing PHP and HTML inside closures, VS Code's syntax highlighter often loses context. For better highlighting, use **heredoc syntax**:\n\n```php\nprivate function renderSidebar(): string\n{\n    // Extract and escape values first\n    $dashboardUrl = $this-\u003ee($this-\u003eapp-\u003eurl('/dashboard'));\n    $usersUrl = $this-\u003ee($this-\u003eapp-\u003eurl('/users'));\n\n    // Heredoc provides proper HTML highlighting in VS Code\n    return \u003c\u003c\u003cHTML\n        \u003cnav class=\"sidebar-nav\"\u003e\n            \u003ch3\u003eQuick Links\u003c/h3\u003e\n            \u003cul\u003e\n                \u003cli\u003e\u003ca href=\"{$dashboardUrl}\"\u003eDashboard\u003c/a\u003e\u003c/li\u003e\n                \u003cli\u003e\u003ca href=\"{$usersUrl}\"\u003eAll Users\u003c/a\u003e\u003c/li\u003e\n            \u003c/ul\u003e\n        \u003c/nav\u003e\n        HTML;\n}\n```\n\n**Trade-offs:**\n\n- Heredoc: Better IDE highlighting, but requires pre-computing variables\n- Closure with `?\u003e`: Can use inline PHP expressions (`\u003c?php if ?\u003e`), but inconsistent highlighting\n\nSee `exampleTemplateClass/Templates/Profile.php` for a complete heredoc example.\n\n---\n\n## Layouts with Slots Pattern\n\nInstead of magic sections, Splates uses typed `Closure` properties (slots):\n\n### Layout Template\n\n```php\n\u003c?php\n// src/Templates/LayoutTpl.php\n\nnamespace App\\Templates;\n\nuse Closure;\nuse PiedWeb\\Splates\\Template\\Attribute\\TemplateData;\nuse PiedWeb\\Splates\\Template\\TemplateAbstract;\n\nclass LayoutTpl extends TemplateAbstract\n{\n    public function __construct(\n        #[TemplateData]\n        public string $title,\n        #[TemplateData]\n        public Closure $content,           // Required slot\n        #[TemplateData]\n        public ?Closure $scripts = null,   // Optional slot\n    ) {}\n\n    public function __invoke(): void\n    { ?\u003e\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n\u003chead\u003e\n    \u003ctitle\u003e\u003c?= $this-\u003ee($this-\u003etitle) ?\u003e\u003c/title\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n    \u003cmain\u003e\u003c?= ($this-\u003econtent)() ?\u003e\u003c/main\u003e\n\n    \u003c?php if ($this-\u003escripts): ?\u003e\n    \u003c?= ($this-\u003escripts)() ?\u003e\n    \u003c?php endif ?\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n    \u003c?php }\n}\n```\n\n### Page Template\n\nUse the `$this-\u003eslot()` helper for clean inline slots:\n\n```php\n\u003c?php\n// src/Templates/UserPageTpl.php\n\nnamespace App\\Templates;\n\nuse PiedWeb\\Splates\\Template\\Attribute\\TemplateData;\nuse PiedWeb\\Splates\\Template\\TemplateAbstract;\n\nclass UserPageTpl extends TemplateAbstract\n{\n    public function __construct(\n        #[TemplateData]\n        public User $user,\n    ) {}\n\n    public function __invoke(): void\n    {\n        // Clean syntax with $this-\u003eslot() helper\n        echo $this-\u003erender(new LayoutTpl(\n            title: $this-\u003euser-\u003egetName(),\n            content: $this-\u003eslot(function() { ?\u003e\n\n                \u003ch1\u003e\u003c?= $this-\u003ee($this-\u003euser-\u003egetName()) ?\u003e\u003c/h1\u003e\n                \u003cp\u003eEmail: \u003c?= $this-\u003ee($this-\u003euser-\u003egetEmail()) ?\u003e\u003c/p\u003e\n                \u003c?= $this-\u003erender(new SidebarTpl()) ?\u003e\n\n            \u003c?php }),\n            scripts: $this-\u003eslot(function() { ?\u003e\n\n                \u003cscript\u003econsole.log(\"loaded\")\u003c/script\u003e\n\n            \u003c?php }),\n        ));\n    }\n}\n```\n\nFor complex slots, you can still use private methods:\n\n```php\npublic function __invoke(): void\n{\n    echo $this-\u003erender(new LayoutTpl(\n        title: $this-\u003euser-\u003egetName(),\n        content: fn() =\u003e $this-\u003erenderContent(),  // Delegate to method\n    ));\n}\n\nprivate function renderContent(): string\n{\n    return $this-\u003ecapture(function() { ?\u003e\n        \u003ch1\u003e\u003c?= $this-\u003ee($this-\u003euser-\u003egetName()) ?\u003e\u003c/h1\u003e\n        \u003c!-- Complex content... --\u003e\n    \u003c?php });\n}\n```\n\n### Or use the `Slot` value object for cleaner syntax\n\n```php\nuse PiedWeb\\Splates\\Template\\Value\\Slot;\n\nclass LayoutTpl extends TemplateAbstract\n{\n    public function __construct(\n        #[TemplateData]\n        public string $title,\n        #[TemplateData]\n        public Slot $content,  // Slot instead of Closure\n    ) {}\n\n    public function __invoke(): void\n    { ?\u003e\n        \u003cmain\u003e\u003c?= $this-\u003econtent ?\u003e\u003c/main\u003e\n    \u003c?php }\n}\n\n// Usage\necho $this-\u003erender(new LayoutTpl(\n    title: 'My Page',\n    content: new Slot(fn() =\u003e '\u003cp\u003ePage content\u003c/p\u003e'),\n));\n```\n\n---\n\n## Global Services\n\nInject services that are available to ALL templates:\n\n### Setup\n\n```php\n$engine = new Engine();\n\n// Register global services\n$engine-\u003eaddGlobal('ext', $templateExtension);\n$engine-\u003eaddGlobal('router', $router);\n```\n\n### Usage in Templates\n\n```php\nuse PiedWeb\\Splates\\Template\\Attribute\\Inject;\n\nclass MyTemplate extends TemplateAbstract\n{\n    // Auto-injected from globals\n    #[Inject]\n    public TemplateExtension $ext;\n\n    #[Inject]\n    public RouterInterface $router;\n\n    public function __construct(\n        #[TemplateData]\n        public User $user,\n    ) {}\n\n    public function __invoke(): void\n    { ?\u003e\n        \u003ca href=\"\u003c?= $this-\u003eext-\u003eurl('user_profile', ['id' =\u003e $this-\u003euser-\u003egetId()]) ?\u003e\"\u003e\n            \u003c?= $this-\u003ee($this-\u003euser-\u003egetName()) ?\u003e\n        \u003c/a\u003e\n    \u003c?php }\n}\n```\n\n### App-Specific Base Template\n\nCreate your own base class for app-wide helpers:\n\n```php\n\u003c?php\n// src/Templates/AppTemplate.php\n\nnamespace App\\Templates;\n\nuse PiedWeb\\Splates\\Template\\Attribute\\TemplateData;\nuse PiedWeb\\Splates\\Template\\TemplateAbstract;\n\nabstract class AppTemplate extends TemplateAbstract\n{\n    // Auto-injected to ALL templates\n    #[Inject]\n    public TemplateExtension $ext;\n\n    // Convenience helpers\n    protected function url(string $route, array $params = []): string\n    {\n        return $this-\u003eext-\u003eurl($route, $params);\n    }\n\n    protected function user(): ?User\n    {\n        return $this-\u003eext-\u003egetUser();\n    }\n}\n\n// All app templates extend AppTemplate\nclass DashboardTpl extends AppTemplate\n{\n    public function __invoke(): void\n    { ?\u003e\n        \u003cp\u003eWelcome, \u003c?= $this-\u003ee($this-\u003euser()?-\u003egetName() ?? 'Guest') ?\u003e\u003c/p\u003e\n    \u003c?php }\n}\n```\n\n---\n\n## Value Objects for Safe Output\n\nSplates provides context-aware value objects:\n\n| Class  | Use Case          | Example                                |\n| ------ | ----------------- | -------------------------------------- |\n| `Text` | HTML text content | `\u003cp\u003e\u003c?= $text ?\u003e\u003c/p\u003e`                  |\n| `Html` | Pre-escaped HTML  | `\u003c?= $html ?\u003e`                         |\n| `Attr` | HTML attributes   | `\u003cdiv class=\"\u003c?= $attr ?\u003e\"\u003e`           |\n| `Js`   | JavaScript values | `\u003cscript\u003evar x = \u003c?= $js ?\u003e;\u003c/script\u003e` |\n\n### Examples\n\n```php\nuse PiedWeb\\Splates\\Template\\Value\\{Text, Html, Attr, Js};\n\n// Text - auto-escapes for HTML content\n$name = new Text('\u003cscript\u003ebad\u003c/script\u003e');\necho \"\u003cp\u003e$name\u003c/p\u003e\";  // \u003cp\u003e\u0026lt;script\u0026gt;bad\u0026lt;/script\u0026gt;\u003c/p\u003e\n\n// Html - for trusted pre-escaped content\n$content = Html::trusted('\u003cstrong\u003eSafe HTML\u003c/strong\u003e');\necho $content;  // \u003cstrong\u003eSafe HTML\u003c/strong\u003e\n\n// Attr - escapes for HTML attributes\n$class = new Attr('my-class\" onclick=\"bad');\necho \"\u003cdiv class=\\\"$class\\\"\u003e\";  // \u003cdiv class=\"my-class\u0026quot; onclick=\u0026quot;bad\"\u003e\n\n// Js - JSON-encodes for JavaScript\n$data = new Js(['user' =\u003e 'John', 'count' =\u003e 42]);\necho \"\u003cscript\u003evar config = $data;\u003c/script\u003e\";\n// \u003cscript\u003evar config = {\"user\":\"John\",\"count\":42};\u003c/script\u003e\n```\n\n---\n\n## Caching\n\nFor production, enable reflection caching:\n\n```php\n$engine = new Engine(cacheDir: '/path/to/cache');\n\n// Warm cache on deploy\n$engine-\u003egetInjectResolver()-\u003ewarmCache([\n    ProfileTpl::class,\n    LayoutTpl::class,\n    // ... all template classes\n]);\n```\n\n---\n\n## Development\n\n```bash\ncomposer test    # Run tests\ncomposer stan    # Run PHPStan\ncomposer format  # Format code\n```\n\n---\n\n## Migrating from league/plates\n\nSplates is a fork of [league/plates](https://github.com/thephpleague/plates), redesigned around PHP classes, attributes, and PSR-4 autoloading instead of string-based template names.\n\n### Key Differences\n\n| league/plates | Splates |\n|---|---|\n| `new Engine('/templates', 'php')` | `new Engine(templateDir: '/templates')` |\n| `$engine-\u003erender('profile', ['name' =\u003e 'John'])` | `$engine-\u003erender(new ProfileTpl(name: 'John'))` |\n| `$engine-\u003eaddData(['key' =\u003e 'val'])` | `$engine-\u003eaddGlobal('key', $val)` |\n| `$engine-\u003eregisterFunction('upper', ...)` | Removed - use plain PHP |\n| `$engine-\u003eloadExtension(new Asset(...))` | Removed - use `#[Inject]` |\n| `$engine-\u003eaddFolder('emails', '/path')` | Removed - use PSR-4 namespaces |\n| `$this-\u003ee($value)` in templates | `$this-\u003ee($value)` (same) |\n| `$this-\u003efetch('partial')` | `$this-\u003erender(new PartialTpl())` |\n| `$this-\u003elayout('layout')` | `echo $this-\u003erender(new LayoutTpl(content: ...))` |\n| `$this-\u003esection('content')` | `($this-\u003econtent)()` in layout |\n| `$this-\u003estart('content')` ... `$this-\u003estop()` | `content: $this-\u003eslot(function() { ... })` |\n\n### Migration Steps\n\n#### 1. Convert string templates to classes\n\n**Before (league/plates):**\n\n```php\n// templates/profile.php\n\u003ch1\u003e\u003c?= $this-\u003ee($name) ?\u003e\u003c/h1\u003e\n\u003cp\u003e\u003c?= $this-\u003ee($bio) ?\u003e\u003c/p\u003e\n```\n\n**After (Splates):**\n\n```php\nclass ProfileTpl extends TemplateAbstract\n{\n    public function __construct(\n        #[TemplateData] public string $name,\n        #[TemplateData] public string $bio,\n    ) {}\n\n    public function __invoke(): void\n    { ?\u003e\n\u003ch1\u003e\u003c?= $this-\u003ee($this-\u003ename) ?\u003e\u003c/h1\u003e\n\u003cp\u003e\u003c?= $this-\u003ee($this-\u003ebio) ?\u003e\u003c/p\u003e\n    \u003c?php }\n}\n```\n\nFor simple templates that don't need type safety, file-based templates are still supported:\n\n```php\n$engine-\u003erender('path/to/template.php', ['name' =\u003e 'John']);\n```\n\n#### 2. Convert layouts to slots\n\n**Before (league/plates):**\n\n```php\n// templates/layout.php\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n\u003chead\u003e\u003ctitle\u003e\u003c?= $this-\u003ee($title) ?\u003e\u003c/title\u003e\u003c/head\u003e\n\u003cbody\u003e\u003c?= $this-\u003esection('content') ?\u003e\u003c/body\u003e\n\u003c/html\u003e\n\n// templates/page.php\n\u003c?php $this-\u003elayout('layout', ['title' =\u003e $title]) ?\u003e\n\u003c?php $this-\u003estart('content') ?\u003e\n\u003ch1\u003e\u003c?= $this-\u003ee($title) ?\u003e\u003c/h1\u003e\n\u003c?php $this-\u003estop() ?\u003e\n```\n\n**After (Splates):**\n\n```php\nclass LayoutTpl extends TemplateAbstract\n{\n    public function __construct(\n        #[TemplateData] public string $title = 'App',\n        #[TemplateData] public ?Closure $content = null,\n    ) {}\n\n    public function __invoke(): void\n    { ?\u003e\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n\u003chead\u003e\u003ctitle\u003e\u003c?= $this-\u003ee($this-\u003etitle) ?\u003e\u003c/title\u003e\u003c/head\u003e\n\u003cbody\u003e\n    \u003c?php if ($this-\u003econtent): ?\u003e\n        \u003c?= ($this-\u003econtent)() ?\u003e\n    \u003c?php endif ?\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n    \u003c?php }\n}\n\nclass PageTpl extends TemplateAbstract\n{\n    public function __construct(\n        #[TemplateData] public string $title,\n    ) {}\n\n    public function __invoke(): void\n    {\n        echo $this-\u003erender(new LayoutTpl(\n            title: $this-\u003etitle,\n            content: $this-\u003eslot(function() { ?\u003e\n\u003ch1\u003e\u003c?= $this-\u003ee($this-\u003etitle) ?\u003e\u003c/h1\u003e\n            \u003c?php }),\n        ));\n    }\n}\n```\n\n#### 3. Replace extensions with globals\n\n**Before (league/plates):**\n\n```php\n$engine-\u003eloadExtension(new Asset('/assets'));\n// In template: $this-\u003easset('logo.png')\n```\n\n**After (Splates):**\n\n```php\n$engine = new Engine();\n$engine-\u003eaddGlobal('assetPath', '/assets');\n\n// In template class:\nclass MyTpl extends TemplateAbstract\n{\n    #[Inject] public string $assetPath;\n\n    public function __invoke(): void\n    {\n        echo $this-\u003eassetPath.'/logo.png';\n    }\n}\n```\n\n#### 4. Verify\n\n```bash\ncomposer stan     # PHPStan will catch most type errors\ncomposer test     # Run your tests\n```\n\n---\n\n## Credits\n\n- [Robin D. / Pied Web](https://piedweb.com) - Current Maintainer\n\nOriginal **league/plates** contributors:\n\n- [RJ Garcia](https://github.com/ragboyjr) - Original Plates Maintainer\n- [Jonathan Reinink](https://github.com/reinink) - Original Plates Author\n- [All Contributors](https://github.com/piedweb/splates/contributors)\n\n## License\n\nMIT License. See [LICENSE](LICENSE) for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpiedweb%2Fsplates","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpiedweb%2Fsplates","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpiedweb%2Fsplates/lists"}