younishd/endobox

This package is abandoned and no longer maintained. No replacement package was suggested.

minimal template engine.

v4.3.0 2020-06-24 12:36 UTC

This package is auto-updated.

Last update: 2023-11-06 04:29:13 UTC


README

endobox

endobox

minimal template engine.

Build Status Code Climate Latest Stable Version Total Downloads License

endobox

🌱 Native PHP syntax 📝 Markdown on-board  🚀 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:

composer require younishd/endobox

You will need PHP 7.0+.

Get started

The typical way to configure endobox to load templates for an application looks like this:

require_once '/path/to/vendor/autoload.php';

use endobox\Endobox;

$endobox = Endobox::create('path/to/templates');

You can add additional template locations:

$endobox->addFolder('another/path/to/templates');

Render templates

Instantiate a Box for your template:

$welcome = $endobox('welcome');

Render the template with some variables by calling render():

echo $welcome->render([ 'name' => "Alice" ]);

The template file itself could look like this:

welcome.php
<h1>Hello, <?= $name ?>!</h1>

File extensions

endobox decides how to render a template based on the file extension.

When you instantiate the template Box however, the extension is omitted.

$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.

members.php
<h1>Members</h1>
<ul>
    <?php foreach ($users as $u): ?>
        <li><?= $u->name ?></li>
    <?php endforeach ?>
</ul>

ℹ️ Protip: The <?= is syntactic sugar for <?php echo.

Markdown: .md

Markdown templates are processed by a Markdown parser (Parsedown) which produces the corresponding HTML code. This can be used for static content.

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.

members.md.php
# Members

<?php foreach ($users as $u): ?>
    - <?= $u->name ?>
<?php endforeach ?>

HTML: .html

HTML templates are always printed as is. No further processing takes place.

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.

<h1>Hello, <?= $username ?>!</h1>

Assign data

There are several ways to assign data to a template box:

// 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.

$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:

$welcome->link($profile);

Now, these template boxes are linked and they share the same data.

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.

ℹ️ 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.

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: ??

<title><?= $title ?? "Default" ?></title>

Escaping

Escaping is a form of data filtering which sanitizes unsafe, user supplied input prior to outputting it as HTML.

endobox provides two shortcuts to the htmlspecialchars() function: $escape() and its shorthand version $e()

<h1>Hello, <?= $escape($username) ?>!</h1>

<h1>Hello, <?= $e($username) ?>!</h1>
Escaping HTML attributes

⚠️ 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).

<!-- Good -->
<img src="portrait.jpg" alt="<?= $e($name) ?>">

<!-- BAD -->
<img src="portrait.jpg" alt='<?= $e($name) ?>'>

<!-- BAD -->
<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.

Chaining

By chaining we mean concatenating templates without rendering them.

Chaining two templates is as simple as:

$header($article);

Now, calling ->render() on either $header or $article will render both templates and return the concatenated result.

ℹ️ 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.

The general syntax for chaining a bunch of templates is simply:

$first($second)($third)($fourth); // and so on

Neat.

The more explicit (and strictly equivalent) form would be using append() or prepend() as follows:

$first->append($second)->append($third)->append($fourth);

Or…

$fourth->prepend($third)->prepend($second)->prepend($first);

ℹ️ Protip: Note that the previously seen Box::__invoke() is simply an alias of Box::append().

You have now seen how you can append (or prepend) Boxes together.

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).

In other words the complete code would probably look something like this:

$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!

So, instead you could just write:

echo $endobox('first')('second')('third');

It looks trivial, but there is a lot going on here. The more verbose form would look as follows:

echo $endobox->create('first')->append('second')->append('third');

This is – in turn – equivalent to the following lines:

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.

The rule of thumb is: Boxes created from other boxes are linked by default.

Nesting

A fairly different approach (probably the template designer rather than the developer way) would be to define some sort of layout template instead:

layout.php
<html>
<head></head>
<body>
<header><?= $header ?></header>
<article><?= $article ?></article>
<footer><?= $footer ?></footer>

Then somewhere in controller land:

$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.

That's where nesting comes into play!

Use the $box() function to instantiate a template Box from inside another template:

layout.php
<html>
<head></head>
<body>
<header><?= $box('header') ?></header>
<article><?= $article ?></article>
<footer><?= $box('footer') ?></footer>

Then simply…

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!

That allows us to condense this even further. Check it out:

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).

This reduces our controller code to one line:

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.

ℹ️ 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.

Functions

Functions are a cool and handy way of adding reusable functionality to your templates (e.g., filters, URL builders…).

Registering functions

You can register your own custom function (i.e., closure) by simply assigning it to a template Box just like data!

Here is a simple function $day() which returns the day of the week:

$calendar->day = function () { return date('l'); };

Inside your template file you can then use it in the same fashion as any variable:

<p>Today is <?= $day ?>.</p>

This would look like this (at least sometimes):

<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.

Below is a simple closure $a() that wraps and escapes some text in a hyperlink tag:

$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:

<p>Follow me on <?= $a("GitHub", "https://github.com/younishd") ?></p>

This would produce something like this:

<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):

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.

$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.

License

endobox is open-sourced software licensed under the MIT license.