ju1ius / twig-buffers-extension
Like Blade stacks? Use Twig buffers!
Requires
- php: >=8.1
- twig/twig: ^3.0
Requires (Dev)
- symfony/stopwatch: ^6.0
README
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:
-
{% append or ignore to my_buffer '...' %}
If
my_buffer
doesn't exist or is not in scope, this is a no-op. -
{% 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:
- in the whole template it is referenced in and in all it's included, embedded or child templates.
- 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' %}