Ember Template Engine
Ember is LavaLust’s built-in template engine. It compiles lightweight template syntax into cached PHP files, giving you clean, readable templates without sacrificing PHP’s full power. Compiled files are reused on subsequent requests so there is no repeated parsing overhead.
Ember is inspired by Blade and Twig syntax but is purpose-built for LavaLust and has no external dependencies.
Setup
1. Enable Ember in app/config/ember.php
<?php
$config['ember_helper_enabled'] = TRUE;
$config['templates_path'] = APPPATH . 'views/';
$config['cache_path'] = APPPATH . '../runtime/cache/ember/';
$config['auto_escape'] = TRUE;
$config['enable_php_blocks'] = FALSE; // TRUE to allow @php ... @endphp
$config['escape_context'] = 'html'; // 'html', 'attr', 'js', or 'url'
2. Load the library in your controller
<?php
$this->load->library('ember');
Once loaded it is available as $this->ember.
Supported template file extensions
Ember resolves templates by trying the following extensions in order:
Extension |
Example |
|---|---|
(no extension) |
|
|
|
|
|
|
|
|
|
Rendering Templates
Use render() to compile and execute a template, passing variables as an
associative array. The method returns the rendered HTML as a string.
<?php
// Render and output immediately
echo $this->ember->render('home', [
'title' => 'Welcome',
'user' => $user,
]);
// Or pass it to a LavaLust view
$data['content'] = $this->ember->render('partials/hero', ['title' => 'Hello']);
$this->load->view('layout', $data);
Template files live under the configured templates_path
(default: app/views/).
app/views/home.ember.php
app/views/partials/hero.ember.php
app/views/layouts/main.ember.php
Output — Echoing Variables
Escaped output (auto-escapes HTML entities — use this by default)
<h1>{{ title }}</h1>
<p>Hello, {{ user.name }}</p>
Raw / unescaped output (use only for trusted HTML)
{!! html_content !!}
Applying the raw filter inline
{{ body|raw }}
Warning
Always use {{ }} for user-supplied data. Only use {!! !!} or
the raw filter for HTML you have generated or sanitised yourself.
Filters
Filters transform a variable’s value before it is output. Chain multiple
filters with |.
Built-in filters
Filter |
Syntax |
Description |
|---|---|---|
|
|
Convert to uppercase |
|
|
Convert to lowercase |
|
|
Output without escaping |
|
|
Explicitly escape the value |
|
|
Convert newlines to |
|
|
Strip HTML tags |
|
|
Strip leading and trailing whitespace |
Chaining filters
{{ comment|striptags|trim|upper }}
Custom filters
Register your own filter with add_filter() and use it in templates:
<?php
// In your controller or bootstrap
$this->ember->add_filter('currency', function($value, $symbol = '$') {
return $symbol . number_format((float) $value, 2);
});
<!-- Template -->
{{ price|currency }}
{{ price|currency('€') }}
Functions
Call registered functions directly inside {{ }}:
<?php
$this->ember->add_function('asset', function($path) {
return base_url('assets/' . $path);
});
$this->ember->add_function('route', function($name) {
return site_url($name);
});
<link rel="stylesheet" href="{{ asset('css/app.css') }}">
<a href="{{ route('users/index') }}">Users</a>
Global Variables
Global variables are available in every template without being passed
explicitly to render():
<?php
$this->ember->add_global('app_name', config_item('app_name'));
$this->ember->add_global('current_user', $this->session->userdata('user'));
<title>{{ app_name }}</title>
<p>Logged in as: {{ current_user.name }}</p>
Control Structures
if / elseif / else / endif
@if(user.role == 'admin')
<a href="/admin">Admin Panel</a>
@elseif(user.role == 'moderator')
<a href="/moderate">Moderate</a>
@else
<a href="/profile">My Profile</a>
@endif
foreach / endforeach
<ul>
@foreach(posts as post)
<li>{{ post.title }}</li>
@endforeach
</ul>
for / endfor
@for($i = 1; $i <= 5; $i++)
<span>{{ i }}</span>
@endfor
while / endwhile
@while(!queue.isEmpty())
<p>{{ queue.next() }}</p>
@endwhile
Template Inheritance
Ember supports a full extends / section / yield layout system so you can define a base layout once and override named sections per page.
Base layout (layouts/main.ember.php)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>@yield('title') — My App</title>
@yield('head')
</head>
<body>
<nav><!-- navigation --></nav>
<main>
@yield('content')
</main>
<footer><!-- footer --></footer>
@yield('scripts')
</body>
</html>
Child template (pages/dashboard.ember.php)
@extends('layouts/main')
@section('title')
Dashboard
@endsection
@section('head')
<link rel="stylesheet" href="{{ asset('css/dashboard.css') }}">
@endsection
@section('content')
<h1>Welcome, {{ user.name }}</h1>
<p>Here is your dashboard.</p>
@endsection
@section('scripts')
<script src="{{ asset('js/dashboard.js') }}"></script>
@endsection
Rendering the child template
<?php
echo $this->ember->render('pages/dashboard', [
'user' => $user,
]);
Note
@extends() must reference the layout relative to templates_path.
The layout file is rendered automatically — you do not call render()
on it directly.
Including Partials
Use @include() to embed another template inside the current one.
All variables from the current context are available in the included partial.
@include('partials/navbar')
@include('partials/alert')
Partial file (partials/navbar.ember.php):
<nav>
<a href="/">Home</a>
@if(current_user)
<a href="/logout">Logout</a>
@else
<a href="/login">Login</a>
@endif
</nav>
Auto-Escaping and Escape Contexts
When auto_escape = TRUE (the default), all {{ }} output is escaped
according to the configured escape_context. You can also escape explicitly
per expression by passing a context to the escape() method or using the
context-specific filter.
Available escape contexts
Context |
Config value |
Description |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
Escaping in PHP code
<?php
// Default context (from config)
echo $this->ember->escape($user_input);
// Explicit context
echo $this->ember->escape($user_input, 'attr');
echo $this->ember->escape($user_input, 'js');
echo $this->ember->escape($user_input, 'url');
PHP Blocks
Raw PHP blocks inside templates are disabled by default for security. Enable them in config only when absolutely necessary:
<?php
$config['enable_php_blocks'] = TRUE;
When enabled:
@php
$total = array_sum(array_column($items, 'price'));
@endphp
<p>Total: {{ total }}</p>
When disabled, @php ... @endphp blocks are replaced with an HTML
comment and their content is never executed.
Warning
Enabling PHP blocks in templates that accept user-supplied input is a significant security risk. Only enable this option in trusted, developer-controlled template files.
Template Caching
Ember compiles each template to a PHP file in the configured cache_path
the first time it is requested. On subsequent requests the compiled file is
served directly — no re-parsing — unless the source template is modified,
in which case the cache is automatically invalidated and recompiled.
Clearing the cache manually
<?php
$this->ember->clear_cache();
This deletes all *.php files from the cache directory. The next request
will recompile every template from source.
Tip
Add $this->ember->clear_cache() to your deployment script so stale
compiled templates are never served after a template update.
Security
Ember includes several security measures:
Protection |
Details |
|---|---|
Auto-escaping |
All |
Path traversal prevention |
|
PHP blocks disabled |
|
Sandboxed rendering |
Templates execute inside a closure with only extracted context variables, registered functions, and |
Cache file permissions |
Compiled cache files are written with |
Method Reference
Method |
Signature |
Returns |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Complete Example
Controller
<?php
// app/controllers/Blog.php
class Blog extends Controller
{
public function __construct()
{
parent::__construct();
$this->load->library('ember');
$this->load->model('Post_model', 'post_model');
// Global available in all templates
$this->ember->add_global('site_name', 'My Blog');
// Custom filter
$this->ember->add_filter('excerpt', function($text, $length = 100) {
return strlen($text) > $length
? substr($text, 0, $length) . '...'
: $text;
});
// Custom function
$this->ember->add_function('asset', fn($p) => base_url('assets/' . $p));
}
public function index()
{
$posts = $this->post_model->get_published(10);
echo $this->ember->render('blog/index', [
'posts' => $posts,
'page' => 'Blog',
]);
}
}
Layout (app/views/layouts/main.ember.php)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>@yield('title') — {{ site_name }}</title>
<link rel="stylesheet" href="{{ asset('css/app.css') }}">
</head>
<body>
@include('partials/nav')
<main class="container">
@yield('content')
</main>
</body>
</html>
Page template (app/views/blog/index.ember.php)
@extends('layouts/main')
@section('title')
Blog
@endsection
@section('content')
<h1>Latest Posts</h1>
@foreach(posts as post)
<article>
<h2>{{ post.title }}</h2>
<p>{{ post.body|excerpt(150) }}</p>
<a href="/blog/{{ post.slug }}">Read more →</a>
</article>
@endforeach
@if(count(posts) === 0)
<p>No posts yet.</p>
@endif
@endsection
Tips and Best Practices
Always use
{{ }}for user input — never{!! !!}unless you have sanitised the value yourself.Set
auto_escape = TRUEin config (it is the default) and only reach for|rawwhen you deliberately want unescaped HTML.Use
@extendsand@yieldto define a single layout file instead of repeating the<html>shell in every view.Use
@includefor repeated UI fragments (navigation, alerts, footers) rather than copy-pasting markup across templates.Register shared data (logged-in user, site name, CSRF token) as globals in a base controller constructor so it is available in every template.
Clear the cache as part of your deployment pipeline —
$this->ember->clear_cache()— to ensure stale compiled files are never served after a template update.Keep
enable_php_blocks = FALSEin production. If you need PHP logic in a template, move it to the controller or a custom function registered withadd_function().Use the
excerpt,striptags, andtrimfilters to sanitise and shorten text output rather than doing it in the controller.