Lets Generate a PDF!

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Let's transform this Twig template into a PDF.

Back in AuthorWeeklyReportSendCommand, right before we create the Email, this is where we'll generate the PDF, so we can attach it. To do that, our command needs two new services: Environment $twig - yes, it looks weird, but the type-hint to get Twig directly is called Environment - and Pdf $pdf. That second service comes from SnappyBundle.

... lines 1 - 6
use Knp\Snappy\Pdf;
... lines 8 - 16
use Twig\Environment;
... line 18
class AuthorWeeklyReportSendCommand extends Command
{
... lines 21 - 28
public function __construct(UserRepository $userRepository, ArticleRepository $articleRepository, MailerInterface $mailer, Environment $twig, Pdf $pdf)
{
... lines 31 - 37
}
... lines 39 - 84
}

As a reminder, if you don't know what type-hint to use, you can always spin over to your terminal and run:

php bin/console debug:autowiring pdf

There it is!

Ok, step 1 is to use Twig to render the template and get the HTML: $html = $this->twig->render(). Oh... PhpStorm doesn't like that... because I forgot to add the properties! I'll put my cursor on the new arguments, hit Alt+Enter, and select "Initialize Fields" to create those 2 properties and set them.

... lines 1 - 6
use Knp\Snappy\Pdf;
... lines 8 - 16
use Twig\Environment;
... line 18
class AuthorWeeklyReportSendCommand extends Command
{
... lines 21 - 25
private $twig;
private $pdf;
public function __construct(UserRepository $userRepository, ArticleRepository $articleRepository, MailerInterface $mailer, Environment $twig, Pdf $pdf)
{
... lines 31 - 35
$this->twig = $twig;
$this->pdf = $pdf;
}
... lines 39 - 84
}

Now, back to work: $this->twig->render() and pass this the template name - email/author-weekly-report-pdf.html.twig - and an array of the variables it needs... which I think is just articles. Pass 'articles' => $articles.

... lines 1 - 18
class AuthorWeeklyReportSendCommand extends Command
{
... lines 21 - 46
protected function execute(InputInterface $input, OutputInterface $output)
{
... lines 49 - 53
foreach ($authors as $author) {
... lines 55 - 59
if (count($articles) === 0) {
continue;
}
$html = $this->twig->render('email/author-weekly-report-pdf.html.twig', [
'articles' => $articles,
]);
... lines 68 - 79
}
... lines 81 - 83
}
}

To turn that HTML into PDF content, we can say $pdf = $this->pdf->getOutputFromHtml($html).

... lines 1 - 18
class AuthorWeeklyReportSendCommand extends Command
{
... lines 21 - 46
protected function execute(InputInterface $input, OutputInterface $output)
{
... lines 49 - 53
foreach ($authors as $author) {
... lines 55 - 64
$html = $this->twig->render('email/author-weekly-report-pdf.html.twig', [
'articles' => $articles,
]);
$pdf = $this->pdf->getOutputFromHtml($html);
... lines 69 - 79
}
... lines 81 - 83
}
}

Cool, right! Behind the scenes, this simple method does a lot: it takes the HTML content, saves it to a temporary file, then executes wkhtmltopdf and points it at that file. As long as wkhtmltopdf is set up correctly... and our HTML generates a nice-looking page, it should work!

If all has gone well, the $pdf variable will now be a string containing the actual PDF content... which we could do anything with, like save to a file or attach to an email. Why, what a wonderful idea!

Adding an Attachment

Adding an attachment to an email... probably looks exactly like you would expect: ->attach(). The first argument is the file contents - so $pdf. If you need to attach something big, you can also use a file resource here - like use fopen on a file and pass the file handle so you don't need to read the whole thing into memory. The second argument will be the filename for the attachment. Let's uses weekly-report-%s.pdf and pass today's date for the wildcard: date('Y-m-d').

... lines 1 - 18
class AuthorWeeklyReportSendCommand extends Command
{
... lines 21 - 46
protected function execute(InputInterface $input, OutputInterface $output)
{
... lines 49 - 53
foreach ($authors as $author) {
... lines 55 - 67
$pdf = $this->pdf->getOutputFromHtml($html);
$email = (new TemplatedEmail())
... lines 71 - 74
->context([
... lines 76 - 77
])
->attach($pdf, sprintf('weekly-report-%s.pdf', date('Y-m-d')));
$this->mailer->send($email);
}
... lines 82 - 84
}
}

Love it! We're ready to try this thing. Find your terminal and run:

php bin/console app:author-weekly-report:send

As a reminder, even though this looks like it's sending to six authors, it's a lie! It's really looping over 6 possible authors, but only sending emails to those that have written an article within the past 7 days. Because the database fixtures for this project have a bunch of randomness, this might send to 5 users, 2 users... or 0 users. If it doesn't send any emails, try reloading your fixtures by running:

php bin/console doctrine:fixtures:load

If you are so lucky that it's sending more than 2 emails, you'll get an error from Mailtrap, because it limits sending 2 emails per 10 seconds on the free plan. You can ignore the error or reload the fixtures.

In my case, in Mailtrap... yea! This sent 2 emails. If I click on the first one... it looks good... and it has an attachment! Let's open it up!

Oh... ok... I guess it technically worked... but it looks terrible. This definitely did not have Bootstrap CSS applied to it. The question is: why not?

Next, let's put on our debugging hats, get to the bottom of this mystery, and crush this bug.

Leave a comment!

This tutorial is built on Symfony 4.3, but will work well with Symfony 4.4 or 5.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-iconv": "*",
        "aws/aws-sdk-php": "^3.87", // 3.110.11
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "knplabs/knp-markdown-bundle": "^1.7", // 1.7.1
        "knplabs/knp-paginator-bundle": "^2.7", // v2.8.0
        "knplabs/knp-snappy-bundle": "^1.6", // v1.6.0
        "knplabs/knp-time-bundle": "^1.8", // v1.9.1
        "league/flysystem-aws-s3-v3": "^1.0", // 1.0.23
        "league/flysystem-cached-adapter": "^1.0", // 1.0.9
        "league/html-to-markdown": "^4.8", // 4.8.2
        "liip/imagine-bundle": "^2.1", // 2.1.0
        "nexylan/slack-bundle": "^2.1,<2.2.0", // v2.1.0
        "oneup/flysystem-bundle": "^3.0", // 3.1.0
        "php-http/guzzle6-adapter": "^1.1", // v1.1.1
        "sensio/framework-extra-bundle": "^5.1", // v5.4.1
        "stof/doctrine-extensions-bundle": "^1.3", // v1.3.0
        "symfony/asset": "^4.0", // v4.3.4
        "symfony/console": "^4.0", // v4.3.4
        "symfony/flex": "^1.9", // v1.9.10
        "symfony/form": "^4.0", // v4.3.4
        "symfony/framework-bundle": "^4.0", // v4.3.4
        "symfony/mailer": "4.3.*", // v4.3.4
        "symfony/messenger": "4.3.*", // v4.3.4
        "symfony/orm-pack": "^1.0", // v1.0.6
        "symfony/security-bundle": "^4.0", // v4.3.4
        "symfony/sendgrid-mailer": "4.3.*", // v4.3.4
        "symfony/serializer-pack": "^1.0", // v1.0.2
        "symfony/twig-bundle": "^4.0", // v4.3.4
        "symfony/twig-pack": "^1.0", // v1.0.0
        "symfony/validator": "^4.0", // v4.3.4
        "symfony/web-server-bundle": "^4.0", // v4.3.4
        "symfony/webpack-encore-bundle": "^1.4", // v1.6.2
        "symfony/yaml": "^4.0", // v4.3.4
        "twig/cssinliner-extra": "^2.12", // v2.12.0
        "twig/extensions": "^1.5", // v1.5.4
        "twig/inky-extra": "^2.12" // v2.12.0
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.0", // 3.2.2
        "easycorp/easy-log-handler": "^1.0.2", // v1.0.7
        "fzaninotto/faker": "^1.7", // v1.8.0
        "symfony/browser-kit": "4.3.*", // v4.3.5
        "symfony/debug-bundle": "^3.3|^4.0", // v4.3.4
        "symfony/dotenv": "^4.0", // v4.3.4
        "symfony/maker-bundle": "^1.0", // v1.13.0
        "symfony/monolog-bundle": "^3.0", // v3.4.0
        "symfony/phpunit-bridge": "^3.3|^4.0", // v4.3.4
        "symfony/profiler-pack": "^1.0", // v1.0.4
        "symfony/var-dumper": "^3.3|^4.0" // v4.3.4
    }
}