Skip to content

kynx/v8js-handlebars

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

v8js-handlebars

A thin wrapper around the Handlebars javascript library, utilising the V8Js extension to compile and render templates from PHP with Googles V8 javascript engine.

Features

  • Pre-compile templates for fast rendering
  • Use the same javascript helpers client-side as on the server
  • Use existing PHP code for helpers server-side
  • Faithfully mirrors the handlebars API: less stuff to learn

Quickstart

Handlebars::registerHandlebarsExtension(file_get_contents('/path/to/handlebars.js'));

$handlebars = new Handlebars();
$template = $handlebars->compile('<h1>Hello {{ name }}!</h1>');
echo $template(['name' => 'world']);

Outputs <h1>Hello world!</h1>.

Requirements

You must have the "ext-v8js" extension installed to use this library. This is available from the pecl. Mac users should be able to install via brew:

brew install php56-v8js

(Or at least they will once my pull request is merged).

Installing v8js on other platforms is a little more complicated: none of the major Linux distros ship with a version of V8 > 3.24.6, which V8Js depends on. See the V8Js README.Linux or README.Win32 for details.

Installation

Install via composer:

composer require "kynx/v8js-handlebars"

Usage

Before the library can be used, register the handlebars source with V8Js. This must be done before you instantiate the handlebars class. The default composer install pulls in handlebars.js under components/handlebars:

Handlebars::registerHandlebarsExtension(file_get_contents('components/handlebars/handlebars.js'));

This will register the full handlebars class which you will need to both compile and render templates. If all your templates and partials are pre-compiled, you can just load the runtime instead:

Handlebars::registerHandlebarsExtension(file_get_contents('components/handlebars/handlebars.runtime.js'), true);

The Handlebars class contains (almost) all the API operations detailed in the Handlebars reference. The only difference is that you can use PHP arrays and objects as arguments, as well as strings containing valid javascript.

The test suite is the best place to look for examples of usage, but below is a taster.

compile($template, $options = [])

Compiling a template returns a callable object that can be used for immediate rendering:

$handlebars = new Handlebars();
$template = $handlebars->compile('<h1>{{ name.first }} {{ name.last }}</h1>');
echo $template(['name' => ['first' => 'Slarty', 'last' => 'Bartfast']]);

// outputs "<h1>Slarty Bartfast</h1>"

See the handlebars API documentation for the options that can be passed.

precompile($template, $options = [])

Pre-compiling a template does all the hard work. The returned javascript string can be used from both PHP on the server and by handlebars running in the client browser, so is a good candidate for caching. Once done, call the template() method to get a callable you can pass your data to:

$handlebars = new Handlebars();
$compiled = $handlebars->precompile('<h1>{{ name.first }} {{ name.last }}</h1>');

// cache template for later use...

template($template, $options = [])

If you've got a compiled template, call template() to get a callable that will render it. While compile() and precompile() require the full handlebars source, this method (and the others below) can be used with just the runtime, which you get by passing true as the first parameter to the constructor. The full handlebars will work as well.

$handlebars = new Handlebars(true);
$template = $handlebars->template($compiled);
echo $template(['name' => ['first' => 'Slarty', 'last' => 'Bartfast']]);

registerPartial($name, $partial = false)

Registering a single partial:

$handlebars = new Handlebars();
$handlebars->registerPartial('my_partial', '<h1>{{ test }}</h1>');

Registering multiple partials at once, using a javascript string:

$handlebars = new Handlebars();
$handlebars->registerPartial('{
    partial1 : "<h1>{{ test }}</h1>",
    partial2 : "<h2>{{ test }}</h2>"
}');

Doing the same thing with a PHP array:

$handlebars = new Handlebars();
$handlebars->registerPartial([
    'partial1' => '<h1>{{ test }}</h1>',
    'partial2' => '<h2>{{ test }}</h2>'
]);

As with full templates, partials can be pre-compiled and cached. If you are using the runtime, only pre-compiled partials will work:

$handlebars = new Handlebars(true);
$handlebars->registerPartial('my_partial', $compiled);

registerHelper($name, $helper = false)

Helpers can be either javascript functions, PHP callables or a PHP class containing callable methods. If you want to re-use them client-side, use javascript functions.

Registering a javascript helper:

$handlebars = new Handlebars();
$handlebars->registerHelper('bold', 'function(options) {
    return new Handlebars.SafeString(
        "<div class=\\"mybold\\">"
        + options.fn(this)
        + "</div>");
}');

The chances are you'll be keeping your javascript helpers in a separate file. So long as this contains a single object something like { partial1 : function(...) {...}, partial2 : function (...) {...} }, you can register them all at once:

$handlebars = new Handlebars();
$handlebars->registerHelper(file_get_contents('/path/to/helpers.js'));

The signature for PHP helpers is slightly different from javascript ones:

$handlebars = new Handlebars();
$handlebars->registerHelper('bold', function($self, $options) {
    return '<div class="mybold">'
        + $options->fn($self)
        + '</div>';
});

Note the $self variable passed as the first argument. With javascript helpers, this always contains the current context. In PHP it won't be available, so we wrap your helper and pass this as the first argument. Roll on PHP7's Closure::call() syntax :)

And yes, $options->fn($self) is calling a javascript function from PHP. Such is the magic of V8Js.

You can also pass an array of PHP callables or a class to registerHelper().

create($runtime = false, $extensions = [], $report_uncaught_exceptions = true)

In javascript Handlebars, this creates an isolated Handlebars environment with its own partials and helpers. There is an odd feature with V8Js extensions where they can actually persist between requests. This has some advantages: we don't have to reload the handlebars source with each request. But it does introduce a potential problem: things one script added to the main Handlebars object might suddenly be appearing in another request.

To avoid poluting the main Handlebars instance, we always call Handlebars.create() when instantiating a new Handlebars class. This static method is a simple factory that returns a new PHP Handlebars instance. The following are identical:

$handlebars = new Handlebars();
$handlebars = Handlebars::create();

Methods not part of the Handlebars API

__construct($runtime = false, $extensions = [], $report_uncaught_exceptions = true)

The $extensions parameter allows you to specify other extensions registered via V8Js::registerExtension() that should be available to your javascript. $report_uncaught_exceptions specifies whether V8JS will throw javascript errors as PHP exceptions. We recommend leaving it at the default.

setLogger(LoggerInterface $logger)

Enables you to set a PSR-3 compatible logger to capture the output of the {{ log }} built-in helper. For example:

$logger = new Zend\Log\Logger;
$writer = new Zend\Log\Writer\Stream('php://output');
$logger->addWriter($writer);

$handlebars = new Handlebars();
$handlebars->setLogger($logger);
$template = $handlebars->compile('{{ log "Ouch!" level="debug" }}');
$template([], ['data' => ['level' => 'debug']]);

// Would output "Ouch!"

Handlebars::isRegistered($runtime = false)

Static method that returns true if the handlebars source has been registered as V8Js extension. Since the handlebars extension can persist between requests, you can use this to avoid the file-system hit and registration costs:

if (!Handlebars::isRegistered()) {
    Handlebars::registerHandlebarsExtension(file_get_contents('components/handlebars/handlebars.js'));
}
$handlebars = new Handlebars();

Handlebars::registerHandlebarsExtension($source, $runtime)

As demonstrated above, static method that registers the handlebars source as a V8Js extension. Pass true as the second argument if you are registering the runtime version.

Handlebars API not implemented

The following exist in the Handlebars API, but haven't been implemented.

  • Handlebars.noConflict() - this enables loading different versions of Handlebars into the global space. I can't see a use case for this in v8js-handlebars.
  • Handlebars.SafeString() and Handlebars.escapeExpression(). These are for use within helpers and are available to any javascript helpers you write, but are not exposed in the PHP API.
  • Handlebars.log() - see setLogger() above.

Exceptions

This library does not declare any exceptions. It does some mild sanity checking when registering extensions, partials and helpers and will throw the odd InvalidArgumentException if it doesn't like what it sees, or a BadMethodCallException if you're trying to do something with the runtime that requires compilation.

The exceptions you're most likely to want to handle gracefully will occur when you actually render your template. Problems here will throw a V8JsScriptException, which contains useful information on what the engine choked on.

About

Handlebars templates via V8Js

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages