Andri Andri - 2 months ago 7
PHP Question

Defer execution/completion in Twig

I am using Twig separately from the Symfony2 framework, into something smaller rolled to suit my (perceived) needs better. Part of my framework is an asset management library that manages js/css dependencies, and after preprocessing, combining, minifying, gzipping, outputs the appropriate

<link>
and
<script>
tags to be placed in the
<head>
or in the bottom of
<body>
.

The asset helper has only three public functions:
require_asset($asset)
,
render_head()
and
render_bottom()
. The first one is used sparingly throughout different template parts (I want to keep the loading of the assets outside the application logic, hence inside the twig templates). The two others need to be called after all assets have been required, and (after the asset manager does its thing) return the appropriate and tags to be placed in the template.

All normal templates extend the base template:

base.twig:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{{ title }}</title>
<!--[if lt IE 9]><script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
{{ Assets.headAssets }}
</head>
</body>
{% block body %}
{% endblock %}
{{ Assets.bottomAssets }}
<body>
</html>


here
{{ Assets.headAssets }}
and
{{ Assets.bottomAssets }}
are calls through previously declared Twig global variable
Assets
to the aforementioned
render_head()
and
render_bottom()
methods;

In my instance, this base template is extended from test.twig:

{% extends "base.twig" %}
{% import "forms.twig" as forms %}

{% block body %}
{{ Assets.requires('formtest.css') }}
{{ Assets.requires('testscript.js') }}
<form method="post">

{{ forms.form(form) }}

<input type="submit"/>
</form>

{% endblock %}


Here,
{{ Assets.requires('formtest.css') }}
and
{{ Assets.requires('testscript.js') }}
instruct the assets helper to include the css and js required by this template through calling
require_asset($asset)
.

Normally, if done outside twig, the output functions would be called last, after all needed assets have been required. This works as expected.

The problem I am running into has to do with the order of execution of these functions from the twig template. Since base.twig is called first, and contains the calls to the output functions, these get called before any requirement is made from the extending templates or included blocks/macros. Because of this the result is empty and does not take into account these new requirements.

One way I have though of to solve this problem, would be to calculate the outputs after Twig has returned the rendering, and replace them in the returned result. This works, but i feel is not elegant enough.

I was wondering if there was a way to postpone the execution of
{{ Assets.headAssets }}
and
{{ Assets.bottomAssets }}
until twig is done with everything else. Is there any way for me to achieve this?

Answer

I think this would work:

base.twig

{% set bodyContent %}
    {% block body %}{% endblock body %}
{% endset %}
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>{{ title }}</title>
        <!--[if lt IE 9]><script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
        {{ Assets.headAssets }}
    </head>
    </body>
        {{ bodyContent }}
        {{ Assets.bottomAssets }}
    <body>
</html>

Other files can be unmodified.

As you can see, block body calls very first in base.twig template, so all your assets are already required when execution came to {{ Assets.headAssets }} call.