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 -->
An Star Template 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.
The {% set %} tag allows you to define and assign variables
within templates. This is useful for computed values, storing loop
results, or simplifying template logic.
Assign a single value to a variable:
{% set greeting = 'Hello, World!' %}
<p>{{ greeting }}</p>
Assign existing variables to new names:
{% set username = user.name %}
<p>Welcome, {{ username }}!</p>
Use expressions with operators like +, -, *,
/, %:
{% set total = item.price * item.quantity %}
<p>Total: ${{ total }}</p>
{% set count = items.length %}
<p>We have {{ count }} items</p>
Assign multiple values in one statement:
{% set first, last = 'Jane', 'Doe' %}
<p>{{ first }} {{ last }}</p>
Capture template output into a variable:
{% set content %}
<div class="card">
<h3>{{ product.name }}</h3>
<p>{{ product.description }}</p>
</div>
{% endset %}
<!-- Use the captured content multiple times -->
{{ content }}
{{ content }}
Variables set inside loops are scoped to that loop and not accessible outside:
{% for item in items %}
{% set formatted = item.name %}
<p>{{ formatted }}</p>
{% endfor %}
{# formatted is NOT accessible here #}
<p>{{ formatted }}</p> <!-- Will be empty -->
{% set subtotal = 0 %}
{% for item in cart %}
{% set subtotal = subtotal + item.price %}
<tr>
<td>{{ item.name }}</td>
<td>${{ item.price }}</td>
</tr>
{% endfor %}
<p>Subtotal: ${{ subtotal }}</p>
{% for product in products %}
{% set is_on_sale = product.discount_price != null %}
<div class="product {% if is_on_sale %}sale{% endif %}">
<h3>{{ product.name }}</h3>
{% if is_on_sale %}
<p>On sale: ${{ product.discount_price }}</p>
{% else %}
<p>${{ product.price }}</p>
{% endif %}
</div>
{% endfor %}
{% set badge %}
<span class="badge badge-primary">NEW</span>
{% endset %}
<h2>Featured Products {{ badge }}</h2>
{% for product in featured %}
<div>
<p>{{ product.name }} {{ badge }}</p>
</div>
{% endfor %}
Supported operators: ==, !=, <, >, <=, >=, &&, ||, !, %
{% if user.age >= 18 %}
<p>Adult content</p>
{% elif user.age >= 13 %}
<p>Teen content</p>
{% else %}
<p>Child content</p>
{% endif %}
Supported operators: ==, !=, <,
>, <=, >=, &&,
||, !, %
Odd/Even rows for numeric keys: {% if product_key % 2 %}ODD{% else
%}EVEN{% endif %}
Zero is treated as even for this purpose, because 0 % 2
returns 0. Odd numbers return 1.
<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.