Star is a lightweight, fast PHP template engine with support for template inheritance, includes, loops, conditionals, and automatic escaping. It compiles templates to pure PHP for optimal performance.
Simply include the star.php file in your project:
<?php
require 'star.php';
<?php
require 'star.php';
// Define your context (data)
$ctx = [
'title' => 'My Page',
'user' => [
'name' => 'Alice',
'age' => 30
],
'items' => ['apple', 'banana', 'cherry']
];
// Render the template
echo star_render_template(
'page.tpl', // Template file (relative to templates folder)
$ctx, // Context data
'./templates', // Templates directory (optional, defaults to __DIR__.'/templates')
'./cache' // Cache directory (optional, defaults to __DIR__.'/cache')
);
Templates use standard HTML comments:
<!-- This is a comment -->
Use hyphens to trim whitespace around template tags:
{{- variable -}} <!-- Trim whitespace before and after -->
{%- if condition -%} <!-- Trim whitespace before and after -->
<!doctype html>
<html>
<head>
<title>{% block title %}Default Title{% endblock %}</title>
</head>
<body>
<header>
{% block header %}{% endblock %}
</header>
<main>
{% block content %}{% endblock %}
</main>
<footer>
{% block footer %}Default Footer{% endblock %}
</footer>
</body>
</html>
{% extends "layout.tpl" %}
{% block title %}My Page Title{% endblock %}
{% block header %}
<h1>Welcome to My Site</h1>
{% endblock %}
{% block content %}
<p>This is the main content.</p>
{% endblock %}
Access parent block content with {{ super() }}:
{% block footer %}
{{ super() }}
<p>Additional footer content</p>
{% endblock %}
<p>Hello, {{ user.name }}!</p>
Use dot notation to access nested properties:
<p>{{ user.address.city }}</p>
All variables are automatically HTML-escaped for security:
$ctx = ['text' => '<script>alert("xss")</script>'];
{{ text }} <!-- Outputs: <script>alert("xss")</script> -->
URLs are URL-escaped for security:
<p>The site is at {{@ user.website
}}!</p>
Raw variables are not HTML-escaped for security:
<p>Good {{! timeofday }}!</p>
Never use untrusted (user-supplied) data in a raw template variable.
{% if user.age >= 18 %}
<p>Adult content</p>
{% elif user.age >= 13 %}
<p>Teen content</p>
{% else %}
<p>Child content</p>
{% endif %}
Supported operators: ==, !=, <,
>, <=, >=, &&,
||, !
<ul>
{% for item in items %}
<li>{{ item }}</li>
{% endfor %}
</ul>
Inside loops, you have access to special variables:
{% for product in products %}
<p>Index: {{ product_key }}</p>
<p>Name: {{ product.name }}</p>
{% endfor %}
The _key variable contains the array key or index.
{% for category in categories %}
<h2>{{ category.name }}</h2>
<ul>
{% for item in category.items %}
<li>{{ item }}</li>
{% endfor %}
</ul>
{% endfor %}
{% include "partials/header.tpl" %}
Pass specific variables to included templates:
{% for item in items %}
{% include "partials/item.tpl" with entry=item %}
{% endfor %}
In partials/item.tpl:
<li>{{ entry }}</li>
{% include "partials/card.tpl" with title=product.name price=product.price %}
Use raw() or {{! var }} to output unescaped
HTML:
$ctx = [
'html_content' => star_raw_marker('<strong>Bold text</strong>')
];
{{ raw(html_content) }} <!-- Outputs: <strong>Bold text</strong> -->
Important: Only use raw() with trusted
content to prevent XSS attacks.
Use esc_url() or {{@ var }} for URLs:
<a href="{{ esc_url(user.profile_url) }}">Profile</a>
Star automatically compiles templates to pure PHP and caches them for performance. The cache is automatically invalidated when:
extends) is modifiedSpecify a cache directory when rendering:
echo star_render_template('page.tpl', $ctx, './templates', './cache');
The cache directory will be created automatically if it doesn't exist.
Cached files are stored as MD5 hashes of the template name:
cache/
74a7dbf2831ed066620d84d1a43dd346.php
a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6.php
To clear the cache, simply delete the cache directory contents:
rm -rf cache/*
Or programmatically:
array_map('unlink', glob('./cache/*.php'));
star_render_template($templatename, $ctx, $templatesbase,
$cacheDir)Renders a template with caching.
Parameters:
$templatename (string): Template file relative to
templates directory (e.g., 'page.tpl')$ctx (array): Context data to pass to the template$templatesbase (string, optional): Absolute path to
templates directory. Default: __DIR__.'/templates'$cacheDir (string, optional): Path to cache directory.
Default: __DIR__.'/cache'Returns: (string) Rendered HTML
Example:
$html = star_render_template('page.tpl', ['title' => 'Home'], './templates', './cache');
star_render_string_recursive($src, $ctx, $templatesbase,
$currentdir)Renders a template string without caching (for dynamic templates).
Parameters:
$src (string): Template source code$ctx (array): Context data$templatesbase (string): Absolute path to templates
directory$currentdir (string): Current working directoryReturns: (string) Rendered HTML
Example:
$template = '<p>Hello, {{ name }}!</p>';
$html = star_render_string_recursive($template, ['name' => 'Alice'], './templates', getcwd());
star_e($s)HTML-escapes a string for safe output.
Parameters:
$s (string): String to escapeReturns: (string) Escaped string
Example:
echo star_e('<script>alert("xss")</script>');
// Outputs: <script>alert("xss")</script>
star_esc_url($s)URL-escapes a string.
Parameters:
$s (string): URL to escapeReturns: (string) Escaped URL
Example:
$url = star_esc_url('http://example.com/?q=<test>');
// Returns: http://example.com/?q=<test>
star_raw_marker($s)Marks a string as safe HTML (won't be escaped).
Parameters:
$s (string): HTML contentReturns: (array) Raw marker array
Example:
$ctx = [
'safe_html' => star_raw_marker('<strong>Bold</strong>')
];
star_ctx_get($ctx, $path)Gets a value from context using dot notation.
Parameters:
$ctx (array): Context array$path (string): Dot-separated path (e.g., 'user.name')Returns: (mixed) Value or NULL if not found
Example:
$ctx = ['user' => ['name' => 'Alice']];
$name = star_ctx_get($ctx, 'user.name'); // Returns: 'Alice'
raw() with user input -
Always escape user-provided contentesc_url() for URLs - Especially for
user-provided URLstemplates/
layout.tpl # Base layout
page.tpl # Page template
partials/
header.tpl # Reusable header
footer.tpl # Reusable footer
item.tpl # List item component
admin/
dashboard.tpl # Admin templates
.tpl extension for template filesuser-profile.tplproduct-card.tpl not card.tplPHP Code (index.php):
<?php
require 'star.php';
$ctx = [
'page_title' => 'Product Catalog',
'user' => [
'name' => 'Alice',
'is_admin' => true
],
'products' => [
['name' => 'Laptop', 'price' => 999.99],
['name' => 'Mouse', 'price' => 24.99],
['name' => 'Keyboard', 'price' => 79.99]
]
];
echo star_render_template('catalog.tpl', $ctx, './templates', './cache');
Base Layout (templates/layout.tpl):
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>{% block title %}My Store{% endblock %}</title>
</head>
<body>
<header>
<h1>My Store</h1>
{% if user.is_admin %}
<a href="/admin">Admin Panel</a>
{% endif %}
</header>
<main>
{% block content %}{% endblock %}
</main>
<footer>
<p>© 2025 My Store</p>
</footer>
</body>
</html>
Catalog Template (templates/catalog.tpl):
{% extends "layout.tpl" %}
{% block title %}{{ page_title }} - My Store{% endblock %}
{% block content %}
<h2>Welcome, {{ user.name }}!</h2>
<div class="products">
{% for product in products %}
{% include "partials/product-card.tpl" with item=product %}
{% endfor %}
</div>
{% endblock %}
Product Card (templates/partials/product-card.tpl):
<div class="product-card">
<h3>{{ item.name }}</h3>
<p class="price">${{ item.price }}</p>
<button>Add to Cart</button>
</div>
Error: Template not found: templates/page.tpl
Solution: Check that:
Error: Cannot write to cache directory
Solution:
mkdir cache
chmod 755 cache
Error: Parse error in cached PHP
Solution:
{% if %} have matching {% endif %}{% for %} have matching {% endfor %}If templates render slowly:
$cacheDir parameter)Star is licensed GPL 2.0 or later.