Inheritance - bobthecow/mustache.php GitHub Wiki

Getting started with inheritance

Make sure you're using Mustache.php v3.0.0 or higher.

{
    "require": {
          "mustache/mustache": "~3.0"
    }
}

Install the package with composer install or composer update.

Let's adapt the hello world example from the repo README to use inheritance.

<?php

$m = new \Mustache\Engine([
  'partials' => [
    'parent' => 'Hello {{$ planet }}planet{{/ planet }}'
  ],
]);

echo $m->render('{{< parent }}{{$ planet }}world!{{/ planet }}{{/ parent }}', []);

This returns:

Hello world!

Note

To choose a parent template name from the current context, see Dynamic names.

Deep Dive

Why inheritance?

Say we have a template named layout.mustache. This would be our generic site layout template, consisting of all the sorts of things you'd expect in a layout template. Many of these values in layout.mustache would really be dependent on what is being rendered into it. For example each page probably specifies additional JavaScript files that should be loaded. Beyond that, it is nice to have a convenient way for pages to share common markup, without having to split the common markup into {{> top }} and {{> bottom }} partials.

In other templating languages this behavior is achieved using a feature called template inheritance. Content templates are said "inherit" from layout templates.

Another Example

Let's take a look at a content template that inherits from a layout template.

<!-- layout.mustache -->
<!DOCTYPE html>
<html>
  <head>
    <title>{{$ title }}My Awesome Site{{/ title }}</title>
    {{$ stylesheets }}
      <link rel="stylesheet" href="/assets/css/default.css">
    {{/ stylesheets }}
  </head>
  <body>
    <header>
      {{$ header }}
        <h1>Welcome to My Awesome Site</h1>
      {{/ header }}
    </header>
    <div id="content">
      {{$ content }}
        <p>Hello, World!</p>
      {{/ content }}
    </div>
    {{$ scripts }}
      <script src="/assets/js/default.js"></script>
    {{/ scripts }}
  </body>
</html>
<!-- article.mustache -->
{{< layout }}
{{$ title }}{{ article.title }}{{/ title }}

{{$ stylesheets }}
      <link rel="stylesheet" href="/assets/css/default.css">
      <link rel="stylesheet" href="/assets/css/article.css">
{{/ stylesheets }}

{{$ content }}
<p>{{ article.body }}</p>
{{/ content }}

{{$ scripts }}
      <script src="/assets/js/default.js"></script>
      <script src="/assets/js/article.js"></script>
{{/ scripts }}
{{/ layout }}

Rendering article.mustache with this context:

[
  'article' => [
    'title' => 'How to do x, y and z',
    'body' => '<p>First do x, then do y, then do z.</p>'
  ],
]

would result in this rendered output:

<!-- layout.mustache -->
<!DOCTYPE html>
<html>
  <head>
    <title>How to do x, y and z</title>
      <link rel="stylesheet" href="/assets/css/default.css">
      <link rel="stylesheet" href="/assets/css/article.css">
  </head>
  <body>
    <header>
      <h1>Welcome to My Awesome Site</h1>
    </header>
    <section id="content">
      <p>First do x, then do y, then do z.</p>
    </section>
      <script src="/assets/js/default.js"></script>
      <script src="/assets/js/article.js"></script>
  </body>
</html>

Nested parent blocks

Block overrides are scoped to the parent template they belong to. This means nested layouts can safely reuse common block names like content without leaking overrides into each other:

{{< base }}
  {{$ content }}
    {{< card }}
      {{$ content }}Hello!{{/ content }}
    {{/ card }}
  {{/ content }}
{{/ base }}

The inner content override applies to card, not base, so nested parent templates behave like independent layout boundaries.

Strict extra block checks

When strict_tags includes \Mustache\Engine::STRICT_EXTRA_BLOCKS, Mustache throws an UnknownBlockException for block overrides that cannot be matched to a rendered parent template.

Validation happens as parent templates are resolved for rendering. This means an override is accepted when the resolved parent declares that block, even if the block itself is inside a false section and does not render. If a parent tag is skipped by a section or another runtime condition, Mustache does not validate overrides against templates on that skipped path.

See Strict tags for the full strict tag reference.

Caveats

Many tokens are not allowed directly inside the parent ({{< }}) tags including sections ({{# }}), inverted sections ({{^ }}), partials ({{> }}) and tokenizer directives ({{= }}). For example, all of the following templates will result in compiler errors:

{{< foo }}{{# bar }}{{$ baz }}{{/ baz }}{{/ bar }}{{/ foo }}

{{< foo }}{{^ bar }}{{$ baz }}set by template{{/ baz }}{{/ bar }}{{/ foo }}

{{< foo }}{{> qux }}{{$ baz }}set by template{{/ baz }}{{/ foo }}

{{< foo }}{{=<% %>=}}<%={{ }}=%>{{/ foo }}

More Information

Extensive template inheritance tests are available here.

The template inheritance spec discussion can be found at this mustache spec pull request.

Acknowledgements

Thanks to @sayrer for the template inheritance feature in hogan.js, which we stole, tests and all. Much thanks to my coworkers @dtb, @mdg and @benburry at @Etsy. And thanks to @bobthecow for a lot of feedback and help with this implementation.

⚠️ **GitHub.com Fallback** ⚠️