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!

  • 2020-06-08 Diego Aguiar

    Hey Brandon Peterson

    I'm glad to hear it. If you don't mind could you please tell us how you fixed your problem so others can benefit from your answer?

    Cheers!

  • 2020-06-08 Brandon Peterson

    Figured out my problem

  • 2020-06-08 weaverryan

    Hey Brandon Peterson!

    Oh wow That's an interesting error :). I think I see the error - and it's SO subtle!


    -$html = $this->twig-$this->render('daily_report/showdailypdf.html.twig', [
    +$html = $this->twig->$this->render('daily_report/showdailypdf.html.twig', [

    See the difference? The "+" line is the one I've corrected - you were missing a tiny >, which made it look like you were trying to do subtraction (hence the "converted to number" error).

    Let me know if this fixes it :).

    Cheers!

  • 2020-06-05 Brandon Peterson

    I get an error:
    Notice: Object of class Symfony\Component\HttpFoundation\Response could not be converted to number
    $html = $this->twig-$this->render('daily_report/showdailypdf.html.twig', [
    'daily' => $dailyreport <-----ERROR HERE

    ]);
    $pdf = $this->pdf->getOutputFromHtml($html);
    Any thoughts?

  • 2019-12-30 AbelardoLG

    I think so.

    My pleasure!

    Brs.

  • 2019-12-30 Victor Bocharsky

    Hey AbelardoLG,

    Hm, this sounds like a workaround: you kill the old instance and create a new one. Sounds like a bug? Anyway, thank you for sharing this tip with others.

    Cheers!

  • 2019-12-29 AbelardoLG

    Hi there.

    If you are using the Dompdf library (like me), and it outputs the following message:

    No block-level parent found. Not good.

    at the end of the foreach (inside its body), you should write the following lines of code:
    - - unset($this->pdf); // $this->pdf is the object belongs to the PDF library that you use
    - - $this->pdf = new Dompdf(); // you should recreate again the object.

  • 2019-12-02 Diego Aguiar

    Oh boy, Windows been Windows... Thanks for sharing it!

  • 2019-12-02 donchev

    For Windows 10 Users (if there are others besides me :D):

    I had to move the installation of the wkhtmltopdf from Program Files to C:/ so there are not any spaces in the path eg: "Program FIles"

    My .env config:

    ###> knplabs/knp-snappy-bundle ###
    WKHTMLTOPDF_PATH='C:\wkhtmltopdf\bin\wkhtmltopdf.exe'
    WKHTMLTOIMAGE_PATH='C:\wkhtmltopdf\bin\wkhtmltoimage.exe'
    ###< knplabs/knp-snappy-bundle ###