ju1ius/twig-buffers-extension

Like Blade stacks? Use Twig buffers!

v0.7.1 2022-05-21 16:14 UTC

This package is auto-updated.

Last update: 2024-04-05 06:40:56 UTC


README

codecov

This extension allows you to define buffers into which you can insert content from another part of the template or from included, embedded or child templates. It is similar in functionality to Blade stacks.

Installation

composer require ju1ius/twig-buffers-extension
use Twig\Environment;
use ju1ius\TwigBuffersExtension\TwigBuffersExtension;

$twig = new Environment();
$twig->addExtension(new TwigBuffersExtension());

Basic usage

Let's start with what will probably be the most frequent use case.

Given the following templates:

{# base.html.twig #}
<head>
  <link rel="stylesheet" href="main.css">
  {% buffer stylesheets %}
</head>
<body>
  <main>
    {% block content '' %}
  </main>
  <script src="main.js"></script>
  {% buffer scripts %}
</body>
{# page.html.twig #}

{% extends 'base.html.twig' %}

{% block content %}

  {{ greeting }}

  {% append to stylesheets %}
    <link rel="stylesheet" href="page.css">
  {% endappend %}
  {% append to scripts %}
    <script src="page.js"></script>
  {% endappend %}

{% endblock content %}

Rendering page.html.twig

$twig->display('page.html.twig', ['greeting' => 'Hello buffers!']);

Will output:

<head>
  <link rel="stylesheet" href="main.css">
  <link rel="stylesheet" href="page.css">
</head>
<body>
  <main>
    Hello buffers!
  </main>
  <script src="main.js"></script>
  <script src="page.js"></script>
</body>

The buffer tag

The buffer tag does two things:

  • it opens a named buffer if a buffer of the same name doesn't exist.
  • it references the named buffer so that it's content can be displayed at the tag's location.

A named buffer can therefore be referenced several times:

<p>{% buffer my_buffer %}</p>
<p>{% buffer my_buffer %}</p>
{% append to my_buffer 'bar' %}
<p>bar</p>
<p>bar</p>

By default, the contents of the buffer are joined using the empty string, but you can customize the joining string:

<p>{% buffer my_buffer joined by ', ' %}</p>
{% append to my_buffer 'foo' %}
{% append to my_buffer 'bar' %}
{% append to my_buffer 'baz' %}
<p>foo, bar, baz</p>

You can also provide a «final glue», similarly to the join twig filter:

<p>{% buffer my_buffer joined by '<br>', '<hr>' %}</p>
{% append to my_buffer 'foo' %}
{% append to my_buffer 'bar' %}
{% append to my_buffer 'baz' %}
<p>foo<br>bar<hr>baz</p>

As we just saw, even when using automatic escaping, the joining strings will not be automatically escaped. You'll have to escape them yourself if they come from untrusted sources:

{% buffer my_buffer joined by some_variable|escape('html') %}

Inserting content into buffers

This is done via the append and prepend tags. They share the same syntax apart from their respective tag names.

As with the block tag, you can use a short or a long syntax:

{# short syntax #}
{% append to my_buffer 'Some content' %}
{# long syntax #}
{% append to my_buffer %}
  Some other content
{% endappend %}

As the name implies, the append tag appends content to the buffer.

The prepend tag however, doesn't prepend content to the buffer, but instead appends content to the head of the buffer:

<p>{% buffer my_buffer %}</p>
{% append to my_buffer '1' %}
{% append to my_buffer '2' %}
{% prepend to my_buffer '3' %}
{% prepend to my_buffer '4' %}
<p>3412</p>

Trying to insert content into a buffer that does not exist or is not in scope will throw an UnknownBuffer exception.

Inserting to potentially undefined buffers

If you want to insert content into a buffer that may not exist or may not be in scope, you have two solutions:

  1. {% append or ignore to my_buffer '...' %}

    If my_buffer doesn't exist or is not in scope, this is a no-op.

  2. {% append or create to my_buffer '...' %}

    If my_buffer doesn't exist or is not in scope, first open the buffer, then insert content into it.

Unique insertions

You can tag insertions with a unique id in order to prevent the same content to be inserted more than once.

<p>{% buffer my_buffer %}</p>

{% for character in 'A'..'Z' %}
  {% append to my_buffer as some_unique_id character %}
{% endfor %}
<p>A</p>

Clearing the contents of a buffer

You can use the clear_buffer function:

{% buffer my_buffer %}
{% append to my_buffer 'some content' %}
{% do clear_buffer('my_buffer') %}

Attempting to clear the content of a buffer that does not exist or is not in scope will throw an UnknownBuffer exception.

Checking if a buffer exists

You can use the buffer test:

{% if 'my_buffer' is buffer %}
  {% append to my_buffer 'some content' %}
{% endif %}

The buffer test will return true if the buffer exists and is in scope.

Checking if a buffer is empty

You can use the empty_buffer test:

{% buffer my_buffer %}
My buffer is {{ 'my_buffer' is empty_buffer ? 'empty' : 'not empty' }}!

Or for a more practical example:

{% if 'my_buffer' is not empty_buffer %}
  <div>
    {% buffer my_buffer %}
  </div>
{% endif %}

The empty_buffer test will return true if:

  • the buffer exists and is empty
  • the buffer doesn't exist or is not in scope.

The scope of a buffer

When a template contains a {% buffer %} tag, the corresponding buffers are opened as soon as the template begins to render.

Once opened, a buffer remains available until the end of the topmost render call.

Therefore, a buffer is available:

  1. in the whole template it is referenced in and in all it's included, embedded or child templates.
  2. in all subsequent siblings of the template it is referenced in.

To clarify, lets look at some examples.

The following works because of rule n°1:

{% append to my_buffer '' %}
{% buffer my_buffer %}

The following also works because of rule n°1:

{# partial: insert-into-buffer.html.twig #}
{% append to my_buffer 'some content' %}
{% include 'insert-into-buffer.html.twig' %}
{% buffer my_buffer %}

The following works because of rule n°2:

{# partial: reference-buffer.html.twig #}
{% buffer my_buffer %}
{% include 'reference-buffer.html.twig' %}
{% append to my_buffer 'some content' %}

However, because of rule n°2, the following does not work:

{#
  This throws an `UnknownBuffer` exception beacause `my_buffer`
  is not in scope until the 'reference-buffer.html.twig'
  partial is included.
#}
{% append to my_buffer 'some content' %}
{% include 'reference-buffer.html.twig' %}