Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

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!

25
Login or Register to join the conversation
Mark K. Avatar
Mark K. Avatar Mark K. | posted 3 years ago | edited

When running the console command to send the PDFs, I was getting the error: "Failed to load about:blank, with network status code 301 and http status code 0 - Protocol "about" is unknown". It seems to be related to https links being accessed during the rendering of the PDF (my local site uses https).

I found that adding: $this->pdf->setOption("enable-local-file-access", true);
right before the line that contains: $pdf = $this->pdf->getOutputFromHtml($html);
solved the problem!

I hope this helps anyone else that runs in the same problem. And a huge thanks to Ryan and Victor for these great tutorials!!

7 Reply
Wiola D. Avatar

Thanks a lot! You solved my problem.

Reply

Hey Mark,

Hm, not sure about this, we also use Wkhtmltopdf on SymfonyCasts that works only by HTTPS, and we don't have any problems with generating PDFs.

I noticed 301 status code in the error you mentioned, probably somewhere you're referencing to HTTP but your server/website redirects to HTTPS? I'd recommend you to fix all your links to be HTTPS, then no redirects will be set and PDF generation should work fine even wihtout "enable-local-file-access" I think.

I hope this helps. Anyway, I'm glad you found a working solution for yourself. And thank you for sharing the solution with others!

Cheers!

Reply
Abelardo Avatar
Abelardo Avatar Abelardo | posted 3 years ago | edited

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.
1 Reply

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!

2 Reply
Abelardo Avatar

I think so.

My pleasure!

Brs.

Reply
Rana Avatar
Rana Avatar Rana | posted 8 months ago | edited

Hello,
I was trying the exact same but have the following error:
In DefinitionErrorExceptionPass.php line 54:

Cannot autowire service "App\Service\MailerServiceStaff": argument "$pdf" of method "__construct()" references class "Knp\Snappy\Pdf" but no such service exists.

I have installed "composer require knplabs/knp-snappy"

But errors remain the same.
when i try to see "debug:autowiring pdf"
It does not show Knp\Snappy\PDF. What is the solution?

I am using contao 4.12 (which is with symfony 5)

Help will be appreciated!

Reply

Hey Rana,

I believe you installed the wrong library, you need to install the bundle as mentioned in this chapter https://symfonycasts.com/screencast/mailer/snappy-wkhtmltopdf

Cheers!

Reply

To me

{{ encore_entry_link_tags('app') }}

in

author-weekly-report-pdf.html.twig

generated an error when calling php bin/console app:author-weekly-report:send :

exit with code 1 due to network error: Protocolunknownerror .

I found , after lot of search and testings , that the pb was from twig file and I changed it to
{% for path in encore_entry_css_files('app') %}
<link rel="stylesheet" href="{{ absolute_url(path) }}">
{% endfor %}

Now email are sending correctly and without error.

Maybe can be usefull to someone this information.

Reply

Hey Stefan,

Hm, that encore_entry_link_tags() function should be valid, that's weird. But I'm happy you were able to find a workaround by manually iterating and render CSS files with encore_entry_css_files(), well done! And thanks for sharing it with others!

Most probably that problem is coming from the Wkhtmkltopdf - it might require absolute URL instead of just a path without the host, probably might depend on its version.

Cheers!

Reply
SamuelVicent Avatar
SamuelVicent Avatar SamuelVicent | posted 2 years ago

Hi,
Do you know any working Wkhtmltopdf alternative? Such as Weasyprint?
My sysadmin that uses the gentoo linux distribution told me that Wkhtmltopdf is being deprecated because of the qtwebkit dependency, which seems to not be maintained anymore ( https://bugs.gentoo.org/692532 ).
Would be very thankful if someone can share some knowledge about this matter.
Thanks a lot!

Reply

Hey Samuel,

We do use Wkhtmltopdf for generating PDFs on SymfonyCasts and it works good for us. Not sure about the deprecated dependency, but it's an issue that should be workaround by Wkhtmltopdf I suppose. Anyway, I don't see any information that the Wkhtmltopdf tool is deprecated, the latest stable release was 0.12.6 on June 11, 2020.

I personally do not have any experience with alternatives to Wkhtmltopdf tools so can't help with this, but it should be easy to find some similar projects on GitHub I suppose. From my side, I can say that Weasyprint sounds interesting, also you could take a look at some others:
- https://github.com/dompdf/d...
- https://github.com/MrRio/jsPDF
- https://github.com/RelaxedJ...

2 latest seems JS solutions, I hope this helps!

Cheers!

Reply
SamuelVicent Avatar
SamuelVicent Avatar SamuelVicent | Victor | posted 2 years ago

Hi Victor,
Sorry for the late response.
We finally used Weasyprint, was easy to implement and can generate pdfs on the fly without creating temporary files.
We are thinking about creating a small vendor bundle wrapping a few command line parameters. If so, I'll let you know for your info.
Thanks a lot!

Reply

Hey Samuel,

No problem! Thank you for letting us know what you chose. Fairly speaking, I haven't worked with Weasyprint yet, but that sounds really interesting! And yeah, having a bundle that will make the integration even smoother is awesome, good luck with it! If you could share the link to the bundle later - it would be useful I think ;)

Cheers!

Reply

Hi,

I'm using Docker and, for some reason, I get a blank pdf.

I made a public repo should anyone would be so nice to look into it.

https://bitbucket.org/j_bon...

To get the project running, you could fire run.sh on the root directory or alternatively running the commands inside that script manually.

I would really like to get this working.

Best,

- J

Reply

Hey Julien,

I'd recommend you to "ssh" into that container, i.e. where you have Wkhtmltopdf installed, and run it manually. You can try to run the default command recommended by Wkhtmltopdf to see if it works as expected:

wkhtmltopdf http://google.com google.pdf

Then try to look into that google.pdf - do you see a Google start page PDF-ed? Also, look closer to the output of that command, it may contain some tips or errors if something is missing.

I hope this helps!

Cheers!

Reply

Hi Victor,

I figured it out, I was missing xvbf package. I added it and voilĂ !

Thanks

Reply

Hey Julien,

Nice! Thanks for sharing your solution with others!

Cheers!

Reply
Brandon Avatar
Brandon Avatar Brandon | posted 3 years ago

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?

Reply

Hey Brandon!

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 &gt;, 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!

Reply
mr_d Avatar
mr_d Avatar mr_d | posted 3 years ago | edited

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 ###<br />WKHTMLTOPDF_PATH='C:\wkhtmltopdf\bin\wkhtmltopdf.exe'<br />WKHTMLTOIMAGE_PATH='C:\wkhtmltopdf\bin\wkhtmltoimage.exe'<br />###< knplabs/knp-snappy-bundle ###

Reply
Emil K. Avatar

It's not needed, you can achieve this by treating C:\Program Files directory in the old MS-DOS wa, where there was 8 character limit per folder/file

so C:\Program Files becomes "C:\Progra~1"

Here's the source (i've checked it on my windows 10 and it works)

https://superuser.com/quest...
```
Note for Windows users on 64-bit systems
Progra~1 = 'Program Files'
Progra~2 = 'Program Files(x86)'
```

1 Reply

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

Reply
Brandon Avatar
Brandon Avatar Brandon | posted 3 years ago

Figured out my problem

-1 Reply

Hey Brandon

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!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

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.17.6
        "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
    }
}
userVoice