Image courtesy of Zoran SalamunToday I Learnt — Twig and Services
Recently, I was working on a feature and needed to generate and mail a PDF document whenever a payment was made. I dispatched a Message and in the message handler, I used the KNP Snappy Bundle to generate a PDF from a twig template. It was pretty straightforward (or so I thought) until I saw the mayhem that had been let loose in my failed transport.
After a bit of digging, I saw a message like this.
The exit status code '2' says something went wrong:
stderr: "Loading pages (1/6)
[> ] 0%
[======> ] 10%
[============================> ] 48%Done
Exit with code 2 due to http error: 404 Page not found
"
stdout: ""
command: /usr/bin/wkhtmltopdf --lowquality --no-outlineArmed with my error I headed to Stackoverflow in search of a kind programmer to help with my lowly case. Turns out someone had the same issue and from the suggestions, I understood that something was missing — either CSS, Javascript or an image. It wasn’t the cut-and-paste solution I would have liked, but it did give me an idea of what the problem was.
Since I was generating a simple certificate, there wasn’t any Javascript in my twig template. I used internal CSS as well so the 404 had to be from one of the images I was using for the certificate. It didn’t take long to find the offending image. In the twig template it was defined as shown below.
<img src={{ absolute_url(path('get_seal')) }}/>Basically, I was trying to access the image from a route in my application. This may have worked if the PDF generation was being handled by a controller but when the message is being handled, trying to access a route in the application was causing the 404 error. The more I think of it, the more I wonder why I expected anything different 🙈.
The most obvious solution was to move the image to the public folder and be done with it. But that would also have meant that the image would have been available for anybody to download — I didn’t want that. So it was back to google to see what I could find. It turns out I could write a service, make it available as a Twig global and get the image I needed as a base64 string. This was next level stuff for me and I just had to try it for myself.
This is what my project folder structure looked like.

The seal was located in the
imagesfolder.
The first thing I did was to bind the image directory to the service container. In config/services.yaml, I added a bind key under defaults as shown below.
services:
_defaults:
bind:
$imagesDirectoryPath: '%kernel.project_dir%/images'With this, I can specify string $imagesDirectoryPath as the constructor for my service and the service container will know what I’m referring to.
Next, I created the service. The service constructor takes one parameter — $imagesDirectoryPath. In the service, I had a public function named getSeal which retrieves the seal and returns it as a base64 string. This is what the service looked like.
<?php
namespace App\Service;
class ImageRetriever {
public function __construct(
private string $imagesDirectoryPath
) {
}
public function getSeal(): string {
$image = "{$this->imagesDirectoryPath}/seal.png";
$type = pathinfo($image, PATHINFO_EXTENSION);
$data = base64_encode(file_get_contents($image));
return "data:image/$type;base64,$data";
}
}With the service done, I made it available as a Twig global by updating the twig configuration in config/packages/twig.yaml as follows.
twig:
globals:
ImageRetriever: '@App\Service\ImageRetriever'All that was left was to use the newly declared global in my twig template as follows:
<img src="{{ ImageRetriever.seal }}" alt="seal"/>The rest was history… I dispatched some messages which were handled with gusto…
Looking back, I prefer this solution. It’s a lot more code and it was definitely a lot of work figuring out what was going on while linking the service with twig (which somehow I keep forgetting is still PHP at heart), but the fact that the assets stays in a location that isn’t publicly accessible makes it worth it.
If you get stuck at any point, you can consult this repo. I would also love to hear your thoughts on this approach and even alternatives to this — I’ve already heard something interesting which involves Twig extensions 🤯.
Until next time, make peace not war ✌🏾