Skip to content
Advertisement

Alternative to template_from_string for processing strings with calls to custom Twig functions

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);
}
User contributions licensed under: CC BY-SA
10 People found this is helpful
Advertisement