Is there an alternative to using template_from_string()
for evaluating strings that contain calls to custom Twig functions (or any other complex Twig code for that matter)?
This is a simplified version of the code I have right now.
{% set content = { 'item_1' : { 'images' : { 'src': 'assets/images/img_1-600.jpg', 'srcset': "{{ assets('assets/images/img_1-600.jpg') }} 600w, {{ assets('assets/images/img_1-1200.jpg') }} 1200w", } } # additional items follow } {% for data in content %} <img src="{{ data.images.src }}" srcset="{{ include(template_from_string(data.images.srcset)) }}" alt=""> {% endfor %}
The assets()
function simply returns a revisioned version of the static asset for the given path (i.e. assets('assets/images/img_1-600.jpg)
renders as something like 'assets/images/img_1-600-a4c2254a6d.jpg)
.
The problems start with img srcset
attribute which can become pretty complex and usually contains references to multiple assets that need to return revisioned values for static assets.
Now I know that I could modify the assets()
function to support that kind of complex scenario but I’d like to keep things simple and let assets()
only handle 1:1 transformations.
The only way to achieve this by using functionality provided by Twig seems to be template_from_string() combined with include, which in itself is not terrible but it does kind of look bulky for a simple operation as evaluating and rendering a string.
Not to mention that template_from_string
requires StringLoaderExtension()
to be loaded by default, which again I’d like to avoid using for performance reasons.
Advertisement
Answer
While the concatenation approach is not a bad idea, after much consideration I came to the conclusion that this is one of those places where having a custom Twig function makes much more sense.
The main argument in favor of using a function compared to simple concatenation is that with a function there is no need to worry about properly formating the template code (i.e. forget a space between the asset and the size descriptor in the srcset
attribute value).
This approach also completely eliminates any need to use template_from_string()
or additional extension dependencies.
Here is the final solution.
The template code, plain and simple, with plenty of overview.
{% set content = { 'item_1' : { 'images' : { 'src': 'assets/images/img-600.jpg', 'srcset': srcset([ [ assets('assets/images/img-600.jpg'), '600w' ], [ assets('assets/images/img-800.jpg'), '800w' ], [ assets('assets/images/img-1000.jpg'), '1000w' ], [ assets('assets/images/img-1200.jpg'), '1200w' ] ]), } } # additional items follow } {% for data in content %} <img src="{{ data.images.src }}" srcset="{{ data.images.srcset }}" alt=""> {% endfor %}
The actual Twig function called srcset
, that is used to generate a value for the srcset
attribute from provided data in the above template.
public function srcset(array $srcset) { $output = []; foreach ($srcset as $set) { // Both parameters need to exist or the set isn't added to the output... if (!(isset($set[0]) && isset($set[1]))) { // ... just some exception handling that isn't of relevance for the question // ... continue; } $output[] = sprintf('%s %s', $set[0], $set[1]); } return implode(', ', $output); }