4.4. Handlebars

PHP Handlebars matches the JavaScript Version and has compile time helper support and super nice compile time error reporting. This version of Handlebars is based on caching the compiled templates and inherently made the overall compile times faster. Loading at ~50ms uncached and ~30ms cached.

Let’s create a new file in the /public directory called templating.php and paste the following code in public/templating.php.

Figure 4.4.1.A. Basic Handlebars
require_once '../vendor/autoload.php';

use Cradle\Handlebars\HandlebarsHandler as Handlebars;

//load up handlebars instance
$handlebars = new Handlebars();

//compile the template
$template = $handlebars->compile('<h1>{{article_title}}</h1><p>{{article_detail}}</p>');

//bind the values to the template and echo out
echo $template([
    'article_title' => 'Article 1',
    'article_detail' => 'A story about Article 1...'
]);

After running http://127.0.0.1:8888/templating.php, you should see the article’s output. Let’s go the Front End’s static controller and create a playful route like the following code in /app/www/src/controller/static.php.

Figure 4.4.1.B. Handlebars in a Route
...
$this->get('/handlebars/is/fun', function($req, $res) {
    $global = $this->package('global');

    //load up handlebars instance
    $handlebars = $global->handlebars();

    //compile the template
    $template = $handlebars->compile('<h1>{{article_title}}</h1><p>{{article_detail}}</p>');

    //bind the values to the template and echo out
    $page = $template([
        'article_title' => 'Article 1',
        'article_detail' => 'A story about Article 1...'
    ]);

    $res->setContent($page)
});
...

In the JavaScript Version of Handlebars, we are merely following the guidelines which do not handle files explicitly. Instead we create wrapper functions to do this in /bootstrap/handlebars.php.

Figure 4.4.1.C. Global Handlebars Methods
...
/**
 * Returns the global handlebars object
 *
 * @return Handlebars
 */
->addMethod('handlebars', function () {
    static $handlebars = null;

    if (is_null($handlebars)) {
        $handlebars = HandlebarsHandler::i();
    }

    return $handlebars;
})

/**
 * Makes a rendered  template
 *
 * @return string
 */
->addMethod('template', function ($file, array $data = [], array $partials = []) {
    if (!file_exists($file)) {
        return null;
    }

    $template = file_get_contents($file);
    $handlebars = cradle('global')->handlebars();

    foreach ($partials as $name => $content) {
        if (file_exists($content)) {
            $content = file_get_contents($content);
        }

        $handlebars->registerPartial($name, $content);
    }

    $compiled = $handlebars->compile($template);
    return $compiled($data);
});
...

There are two methods here, one called handlebars() which returns the Handlebars singleton instance and one called template() that accepts a template file path (amongst other things).

Let’s create a template in /app/www/src/template/handlebars/template.html with the following code.

Figure 4.4.1.C. Basic Handlebars Template File
<h1>{{article_title}}</h1>
<p>{{article_detail}}</p>

<h3>Comments</h3>
<hr />
<h4>Reply to: {{article_detail}}</h4>
{{#if comment.0.comment_thumbs}}
    <em>+1</em>
{{else}}
    <em>-1</em>
{{/if}}
<p>{{comment.0.comment_detail}}</p>
<hr />
<h4>Reply to: {{article_detail}}</h4>
{{#if comment.1.comment_thumbs}}
    <em>+1</em>
{{else}}
    <em>-1</em>
{{/if}}
<p>{{comment.1.comment_detail}}</p>

In the above code, we are trying to set up an output for an article page. We can access multi-dimensional arrays using the dot notation. For example below the following $data if passed to Handlebars can access comments like {{comment.0.comment_detail}}.

$data = [
    'article_title' => 'Article 1',
    'article_detail' => 'A story about Article 1...',
    'comment' => [
        [
            'comment_thumbs' => 1,
            'comment_detail' => 'That was beautiful.'
        ],
        [
            'comment_thumbs' => 0,
            'comment_detail' => 'Um... I dont get it.'
        ]
    ],
];

Handlebars also has control statements like {{#if comment.0.comment_thumbs}}, but their control statements cannot compare values.

This is why we created a custom helper called `when` to solve for comparing
values in a control statement.

Let’s now connect this template file to our fun route in /app/www/src/controller/static.php.

Figure 4.4.1.D. Connecting the Template
...
$this->get('/handlebars/is/fun', function($req, $res) {
    $global = $this->package('global');

    //load up handlebars instance
    $handlebars = $global->handlebars();

    //determine the template file
    $file = dirname(__DIR__) . '/template/handlebars/template.html';

    //determine the data
    $data = [
        'article_title' => 'Article 1',
        'article_detail' => 'A story about Article 1...',
        'comment' => [
            [
                'comment_thumbs' => 1,
                'comment_detail' => 'That was beautiful.'
            ],
            [
                'comment_thumbs' => 0,
                'comment_detail' => 'Um... I dont get it.'
            ]
        ],
    ];

    //compile the template
    $page = $global->template($file, $data);

    //set the content
    $res->setContent($page)
});
...

Handlebars also has looping out of the box and as per their JavaScript Version documentation. In /app/www/src/template/handlebars/template.html, let’s use this loop statement for the comment section in the next example below.

Figure 4.4.1.E. The each Helper
<h1>{{article_title}}</h1>
<p>{{article_detail}}</p>

<h3>Comments</h3>
{{#each comment}}
    <hr />
    <h4>Reply to: {{../article_detail}}</h4>
    {{#if comment_thumbs}}
        <em>+1</em>
    {{else}}
        <em>-1</em>
    {{/if}}
    <p>{{comment_detail}}</p>
{{/each}}

This template also exposes how Handlebars navigates through the original data set passed to the template via {{../article_detail}}. Since that code is in the each loop, it accesses the parent variable by pre-pending a ../. Handlebars can also work with multiple templates at the same time. When a template is called inside of another template it is called a partial. In /app/www/src/template/handlebars/template.html, let’s move the {{#if comment_thumbs}} control statement to a partial.

Figure 4.4.1.F. Adding a Partial
<h1>{{article_title}}</h1>
<p>{{article_detail}}</p>

<h3>Comments</h3>
{{#each comment}}
    <hr />
    <h4>Reply to: {{../article_detail}}</h4>
    {{> thumbs}}
    <p>{{comment_detail}}</p>
{{/each}}

Next let’s create the partial file /app/www/src/template/handlebars/thumbs.html, and paste in the code for comment_thumbs like the following example

Figure 4.4.1.G. Thumbs Partial File
{{#if comment_thumbs}}
    <em>+1</em>
{{else}}
    <em>-1</em>
{{/if}}

Lastly let’s register this partial in Handlebars using our bootstrap helper as in the following example.

Figure 4.4.1.H. /app/www/src/controller/static.php
...
$this->get('/handlebars/is/fun', function($req, $res) {
    $global = $this->package('global');

    //load up handlebars instance
    $handlebars = $global->handlebars();

    //determine the template file
    $file = dirname(__DIR__) . '/template/handlebars/template.html';

    //determine the data
    $data = [
        'article_title' => 'Article 1',
        'article_detail' => 'A story about Article 1...',
        'comment' => [
            [
                'comment_thumbs' => 1,
                'comment_detail' => 'That was beautiful.'
            ],
            [
                'comment_thumbs' => 0,
                'comment_detail' => 'Um... I dont get it.'
            ]
        ],
    ];

    //add partials
    $partials = [
        'thumbs' => dirname(__DIR__) . '/template/handlebars/thumbs.html'
    ];

    //compile the template
    $page = $global->template($file, $data, $partials);

    //set the content
    $res->setContent($page)
});
...

After running http://127.0.0.1:8888/templating.php, you should see the final article’s output using partials. That’s it for the Handlebars intro, but in order to fully utilize its potential on the system you should also go over the following chapters and references.