, (*1)
endobox
minimal template engine., (*2)
, (*3)
, (*4)
:seedling: Native PHP syntax |
:pencil: Markdown on-board |
:rocket: Minimal API |
Write templates in vanilla PHP. No need to learn a new syntax. |
A full-blown Markdown parser is built right in. Yes, it can be combined with PHP! |
Do powerful things with just a handful of elementary methods. |
Documentation
Installation
Install endobox is via Composer:, (*5)
composer require younishd/endobox
You will need PHP 7.0+., (*6)
Get started
The typical way to configure endobox to load templates for an application looks like this:, (*7)
require_once '/path/to/vendor/autoload.php';
use endobox\Endobox;
$endobox = Endobox::create('path/to/templates');
You can add additional template locations:, (*8)
$endobox->addFolder('another/path/to/templates');
Render templates
Instantiate a Box
for your template:, (*9)
$welcome = $endobox('welcome');
Render the template with some variables by calling render()
:, (*10)
echo $welcome->render([ 'name' => "Alice" ]);
The template file itself could look like this:, (*11)
welcome.php
<h1>Hello, <?= $name ?>!</h1>
File extensions
endobox decides how to render a template based on the file extension., (*12)
When you instantiate the template Box
however, the extension is omitted., (*13)
$members = $endobox('members'); // no file extension
PHP: .php
PHP templates are processed by evaluating the code between PHP tags (i.e., <? … ?>
) and returning the result., (*14)
members.php
<h1>Members</h1>
<ul>
<?php foreach ($users as $u): ?>
<li><?= $u->name ?></li>
<?php endforeach ?>
</ul>
:information_source: Protip: The <?=
is syntactic sugar for <?php echo
., (*15)
Markdown: .md
Markdown templates are processed by a Markdown parser (Parsedown) which produces the corresponding HTML code. This can be used for static content., (*16)
members.md
# Members
- Alice
- Bob
- Carol
PHP+Markdown: .md.php
As the name suggests, this template type combines both PHP and Markdown: The template gets evaluated as PHP first, then parsed as Markdown. Pretty neat., (*17)
members.md.php
# Members
- <?= $u->name ?>
HTML: .html
HTML templates are always printed as is. No further processing takes place., (*18)
members.html
<h1>Members</h1>
<ul>
<li>Alice</li>
<li>Bob</li>
<li>Carol</li>
</ul>
Data
Data is accessible inside a template as simple __variables__ (e.g., $foo
) where the variable name corresponds to the assigned array key or property., (*19)
<h1>Hello, <?= $username ?>!</h1>
Assign data
There are several ways to assign data to a template box:, (*20)
// via assign(…)
$welcome->assign([ "username" => "eve" ]);
// via object property
$welcome->username = "eve";
// via render(…)
$welcome->render([ "username" => "eve" ]);
// implicitly
$welcome([ "username" => "eve" ]);
Shared data
Usually, template boxes are isolated from each other. Data that's been assigned to one box, will not be visible from another., (*21)
$welcome->username = "eve"; // not accessible to 'profile'
$profile->email = "eve@example.com"; // not accessible to 'welcome'
If they should share their data however, you can link them together:, (*22)
$welcome->link($profile);
Now, these template boxes are linked and they share the same data., (*23)
welcome.php
<h1>Hello, <?= $username ?>!</h1>
<p>Your email address is: <code><?= $email ?></code></p>
profile.php
<h1>Profile</h1>
<ul>
<li>Username: <strong><?= $username ?></strong></li>
<li>Email: <strong><?= $email ?></strong></li>
</ul>
Notice how welcome.php
prints out $email
which was initially assigned to $profile
and profile.php
echoes $username
even though it was assigned to $welcome
., (*24)
:information_source: Protip: You can create template boxes using an existing Box
object (instead of using the BoxFactory
object) with $box->create('template')
which has the advantage of linking the two boxes together by default., (*25)
Default values
Sometimes it can be useful to supply a default value to be printed in case a variable has not been assigned. You can easily achieve that using PHP 7's null coalescing operator: ??
, (*26)
<title><?= $title ?? "Default" ?></title>
Escaping
Escaping is a form of data filtering which sanitizes unsafe, user supplied input prior to outputting it as HTML., (*27)
endobox provides two shortcuts to the htmlspecialchars()
function: $escape()
and its shorthand version $e()
, (*28)
Hello, = $escape($username) ?>!
Hello, = $e($username) ?>!
Escaping HTML attributes
:warning: Warning: It's VERY important to always double quote HTML attributes that contain escaped variables, otherwise your template will still be open to injection attacks (e.g., XSS)., (*29)
<img src="portrait.jpg" alt="<?= $e($name) ?>">
<img src="portrait.jpg" alt='<?= $e($name) ?>'>
<img src="portrait.jpg" alt=<?= $e($name) ?>>
Chaining & Nesting
Since you're rarely dealing with just a single template you might be looking for a method that combines multiple templates in a meaningful way., (*30)
Chaining
By chaining we mean concatenating templates without rendering them., (*31)
Chaining two templates is as simple as:, (*32)
$header($article);
Now, calling ->render()
on either $header
or $article
will render both templates and return the concatenated result., (*33)
:information_source: Protip: The benefit of not having to render the templates to strings right away is flexibility: You can define the layout made out of your templates before knowing the concrete values of their variables., (*34)
The general syntax for chaining a bunch of templates is simply:, (*35)
$first($second)($third)($fourth); // and so on
Neat., (*36)
The more explicit (and strictly equivalent) form would be using append()
or prepend()
as follows:, (*37)
$first->append($second)->append($third)->append($fourth);
Or…, (*38)
$fourth->prepend($third)->prepend($second)->prepend($first);
:information_source: Protip: Note that the previously seen Box::__invoke()
is simply an alias of Box::append()
., (*39)
You have now seen how you can append (or prepend) Box
es together., (*40)
Notice however that the variables $first
, $second
, $third
, and $fourth
were objects of type Box
which means they needed to be brought to life at some point —
probably using the BoxFactory
object created in the very beginning (which we kept calling $endobox
in this document)., (*41)
In other words the complete code would probably look something like this:, (*42)
$first = $endobox('first');
$second = $endobox('second');
$third = $endobox('third');
echo $first($second)($third);
It turns out there is a way to avoid this kind of boilerplate code: You can directly pass the name of the template (a string
) to the append()
method instead of the Box
object!, (*43)
So, instead you could just write:, (*44)
echo $endobox('first')('second')('third');
It looks trivial, but there is a lot going on here. The more verbose form would look as follows:, (*45)
echo $endobox->create('first')->append('second')->append('third');
This is – in turn – equivalent to the following lines:, (*46)
echo ($_ = $endobox->create('first'))
->append($endobox->create('second')->link($_))
->append($endobox->create('third')->link($_));
Notice that unlike before these (implicitly created) boxes are now all linked together automatically, meaning they share the same data., (*47)
The rule of thumb is: Boxes created from other boxes are linked by default., (*48)
Nesting
A fairly different approach (probably the template designer rather than the developer way) would be to define some sort of layout template instead:, (*49)
layout.php
<html>
<head></head>
<body>
<header><?= $header ?></header>
<article><?= $article ?></article>
<footer><?= $footer ?></footer>
Then somewhere in controller land:, (*50)
$layout = $endobox('layout');
$header = $endobox('header'); // header.html
$article = $endobox('article'); // article.php
$footer = $endobox('footer'); // footer.html
echo $layout->render([
'header' => $header,
'article' => $article->assign([ 'title' => "How to make Lasagna" ]),
'footer' => $footer
]);
This should be fine, but we can get rid of some boilerplate code here: $header
and $footer
really don't need to be variables., (*51)
That's where nesting comes into play!, (*52)
Use the $box()
function to instantiate a template Box
from inside another template:, (*53)
layout.php
<html>
<head></head>
<body>
<header><?= $box('header') ?></header>
<article><?= $article ?></article>
<footer><?= $box('footer') ?></footer>
Then simply…, (*54)
echo $endobox('layout')->render([
'article' => $endobox('article')->assign([ 'title' => "How to make Lasagna" ])
]);
This is already much cleaner, but it gets even better: By using $box()
to nest a template Box
inside another these two boxes will be linked by default!, (*55)
That allows us to condense this even further. Check it out:, (*56)
layout.php
<html>
<head></head>
<body>
<header><?= $box('header') ?></header>
<article><?= $box('article') ?></article>
<footer><?= $box('footer') ?></footer>
All three templates are now nested using $box()
and therefore linked to their parent (i.e., $layout
)., (*57)
This reduces our controller code to one line:, (*58)
echo $endobox('layout')->render([ 'title' => "How to make Lasagna" ]);
Notice how we are assigning a title to the layout
template even though the actual $title
variable occurs in the nested article
template., (*59)
:information_source: Protip: The $box()
function is also available as a method of Box
objects (i.e., outside templates): You can instantiate new boxes with $box->create('template')
where $box
is some Box
object that has already been created., (*60)
Functions
Functions are a cool and handy way of adding reusable functionality to your templates (e.g., filters, URL builders…)., (*61)
Registering functions
You can register your own custom function (i.e., closure) by simply assigning it to a template Box
just like data!, (*62)
Here is a simple function $day()
which returns the day of the week:, (*63)
$calendar->day = function () { return date('l'); };
Inside your template file you can then use it in the same fashion as any variable:, (*64)
<p>Today is <?= $day ?>.</p>
This would look like this (at least sometimes):, (*65)
<p>Today is Tuesday.</p>
You can go even further and actually invoke the variable just like any function and actually pass some arguments along the way., (*66)
Below is a simple closure $a()
that wraps and escapes some text in a hyperlink tag:, (*67)
$profile->a = function ($text, $href) {
return sprintf('<a href="%s">%s</a>',
htmlspecialchars($href),
htmlspecialchars($text));
};
Calling this function inside your template would look like this:, (*68)
<p>Follow me on <?= $a("GitHub", "https://github.com/younishd") ?></p>
This would produce something like this:, (*69)
<p>Follow me on <a href="https://github.com/younishd">GitHub</a></p>
Default functions
There are a couple of default helper functions that you can use out of the box (some of which you may have already seen):, (*70)
Function |
Description |
Example |
$box() or $b()
|
Instantiate a Box from within another template. (See Nesting.) |
<article><?= $box('article') ?></article> |
$markdown() or $m()
|
Render some text as Markdown. Useful when the text is user input/stored in a database. |
<?= $markdown('This is some _crazy comment_!') ?> |
$escape() or $e()
|
Sanitize unsafe user input using htmlspecialchars() . (See Escaping.) |
<img src="portrait.jpg" alt="<?= $e($name) ?>"> |
Cloning
You can easily clone a template Box
using the built-in clone
keyword., (*71)
$sheep = $endobox('sheep');
$cloned = clone $sheep;
The cloned box will have the same content and data as the original one. However, chained or linked boxes are discarded., (*72)
License
endobox is open-sourced software licensed under the MIT license., (*73)