Skip to content

PDF generation🔗

Required🔗

Installation🔗

KnpLabs/KnpSnappyBundle

1
composer require knplabs/knp-snappy-bundle

Recipe du bundle

Local🔗

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# .manala.yaml
manala:
    system:
        apt:
            packages:
                - libxrender1 # Required by wkhtmltopdf
                - libxext6 # Required by wkhtmltopdf
                - fontconfig # Required by wkhtmltopdf
                - libjpeg62-turbo # Required by wkhtmltopdf
                - xfonts-base # Required by wkhtmltopdf
                - xfonts-75dpi # Required by wkhtmltopdf
                - https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.5/wkhtmltox_0.12.5-1.stretch_amd64.deb

Production🔗

Laisser ansible gérer la version de l'OS pour wkhtmltopdf avec {{ ansible_distribution_release }}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# ansible/group_vars/app.yml
app_patterns:
    apt_packages:
        - libxrender1 # Required by wkhtmltopdf
        - libxext6 # Required by wkhtmltopdf
        - fontconfig # Required by wkhtmltopdf
        - libjpeg62-turbo # Required by wkhtmltopdf
        - xfonts-base # Required by wkhtmltopdf
        - xfonts-75dpi # Required by wkhtmltopdf
        - https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.5/wkhtmltox_0.12.5-1.{{ ansible_distribution_release }}_amd64.deb

Se placer dans votre VM ou sur votre serveur pour récupérer le path pour wkhtmltopdf

1
which wkhtmltopdf
Le path sera très probablement /usr/local/bin/wkhtmltopdf

Usage🔗

Pour les cas d'ussage se référer à la documentation de KnpSnappyBundle.

Exemple

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php

use Knp\Snappy\Pdf;

class PdfGenerator
{
    /** @var Pdf */
    private $KnpSnappy;

    private string $outputDir;

    public function __construct(Pdf $KnpSnappy, string $outputDir) {
        $this->KnpSnappy = $KnpSnappy;
        $this->outputDir = $outputDir;
    }

    private function getPath(string $filename): string
    {
        return rtrim($this->outputDir, '/') . '/' . $filename . '.pdf';
    }

    public function generate($content, $filename) {
        $outputPath = $this->getPath($filename);
        $this->KnpSnappy->generateFromHtml($content, $outputPath);

        return $outputPath;
    }

}

Sécurité Symfony🔗

Si vous générez un pdf à partir d'une url de votre application Symfony, vous pouvez avoir besoin de rendre cette url non public tout en permettant à wkhtmltopdf d'y accéder.

La solution la plus simple est de n'autoriser l'accès à cette url qu'en local grâce un access control sur les IP locales :

1
2
3
4
5
6
parameters:
    env(LOCAL_IPS): '127.0.0.1, 172.16.0.0/12'

security:
    access_control:
        - { path: ^/local, roles: IS_AUTHENTICATED_ANONYMOUSLY, ips: "%env(LOCAL_IPS)%" }

La variable d'environnement LOCAL_IPS permettra à l'infra de modifier les IP considérées comme locales en production.

Utilisez le prefix /local dans le chemin de vos routes dédiées à l'impression que vous souhaitez garder privées.

Par exemple :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class FoobarController
{
  /**
   * @Route("/local/foobar/print", name="foobar_print")
   */
  public function printAction(): Response
  {
    return new Reponse($this->render('foobar.html.twig'));
  }

  /**
   * @Route("/foobar.pdf", name="foobar_pdf")
   */
  public function pdfAction(Pdf $pdfGenerator): Response
  {
    $url = $this->generateUrl('foobar_print', [], RouterInterface::ABSOLUTE_URL);

    return new PdfResponse($pdfGenerator->getOutput($url), 'foobar.pdf');
  }
}

L'action pdfAction sera sécurisé comme le reste de l'application si besoin (firewall, authentification, ...) et l'actions printAction ne sera accessible que par le serveur.

Sécurité🔗

Si vous exposez des urls destinées à l'impression uniquement, vous souhaitez peut-être ne les rendre accessibles qu'à wkhtmltopdf.

Une solution est de n'autoriser que les IPs locales à accéder à vos pages. Utilisez pour cela une règle d'access control sur un préfix d'url que vous utilisez sur les contrôleurs concernés.

1
2
3
4
5
6
7
8
# security.yaml
parameters:
    env(LOCAL_IPS): '127.0.0.1, 172.16.0.0/12'
main:
    pattern: ^/
    anonymous: ~
access_control:
    - { path: ^/local, roles: IS_AUTHENTICATED_ANONYMOUSLY, ips: "%env(LOCAL_IPS)%" }

Il faut pour cela que le nom de domaine utilisé dans vos URLs soit résolu par une IP locale. Normalement, sur nos serveurs, le nom de domaine racine est résolut en 127.0.0.1, mais cela peut ne pas être le cas, notamment pour les sous-domaines ou sur une autre infra.

Vous pouvez alors associer votre nom de domaine à l'ip 127.0.0.1 sur le serveur (/etc/hosts) ou utiliser le décorateur suivant pour forcer wkhtmltopdf à requêter Symfony en local (solution recommandée sur les infra Elao/Rix) :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
use Knp\Snappy\Pdf;

/**
 * This decorator introduce a hack to force wkhtmltopdf to
 * access urls through local network without extra server config.
 *
 * Converts
 *
 * wkhtmltopdf 'http://app.foobar.fr/local/something'
 *
 * to
 *
 * wkhtmltopdf --custom-header 'Host' 'app.foobar.fr' 'http://127.0.0.1/local/something'
 */
class LocalPdf extends Pdf
{
    public function __construct(Pdf $pdf)
    {
        $reflection = new \ReflectionClass($pdf);
        $property = $reflection->getParentClass()->getProperty('env');
        $property->setAccessible(true);

        parent::__construct($pdf->getBinary(), $pdf->getOptions(), $property->getValue($pdf));
    }

    public function generate($input, $output, array $options = [], $overwrite = false)
    {
        if (is_string($input) && $host = parse_url($input, PHP_URL_HOST)) {
            $input = str_replace($host, '127.0.0.1', $input);
            $this->setOption('custom-header', array_merge(
                $this->getOptions()['custom-header'] ?? [],
                ['Host' => $host],
            ));
        }

        parent::generate($input, $output, $options, $overwrite);
    }
}
1
2
3
4
5
# services.yaml
services:
    App\Infra\Snappy\LocalPdf:
        decorates: knp_snappy.pdf
        arguments: ['@.inner']

Options🔗

Wkhtmltopdf permet d'utiliser certaine options pour enrichir le PDF (liste des options) comme la gestion du foorter ou du header de la page.

Liste de tout les options gérer par KnpLabs/snappy (la lib sur laquelle se repose KnpSnappyBundle)

1
2
3
4
5
6
<?php

use Knp\Snappy\Pdf;

$pdf = new Pdf('/path/to/bin/wkhtmltopdf');
$pdf->setOption('footer-right', '[Page] / [toPage]');

Source🔗

Project references🔗


Last update: December 20, 2024