The weasyprint executable is a python package. There are several ways to make it available to the PHP app:
- Through a docker image (recommended)
- By direct installation
Weasyprint is directly run through a docker image, on the fly, so it doesn't need to be installed at all.
This is true for both development and production environements.
To do so, declare the following env var:
1
WEASYPRINT_PATH='docker run --rm --network=host elao/weasyprint:53'
Typically in your .env for development, and through provisionning in production.
Note: If you're using custom fonts, be sure to make them available in the weasyprint docker container with this option: -v %kernel.project_dir%/assets/fonts:/usr/share/fonts.
<?phpnamespaceApp\Service;useSymfony\Component\Process\Exception\ProcessFailedException;useSymfony\Component\Process\Process;useSymfony\Component\Routing\Generator\UrlGeneratorInterface;classWeasyprintPdfGenerator{publicfunction__construct(privateUrlGeneratorInterface$urlGenerator,privatestring$bin='weasyprint',privatestring$host='http://localhost'){}/** * Print the given route to PDF and save it on the disk. * * @param string $filepath Destination PDF file path * @param string $name Name of the route * @param array $parameters Route parameters * * @return string The file path */publicfunctionprint(string$filepath,string$name,array$parameters=[]):string{$file=fopen($filepath,'w');if(false===$file){thrownew\Exception('Could not write file "$filepath".');}$url=$this->host.$this->urlGenerator->generate($name,$parameters,UrlGeneratorInterface::ABSOLUTE_PATH);$process=newProcess(array_merge(explode(' ',$this->bin),[$url,'-','-f','pdf']));$process->run(function($type,$buffer)use($file):void{if(Process::ERR!==$type){fwrite($file,$buffer);}});fclose($file);if(!$process->isSuccessful()){thrownewProcessFailedException($process);}return$filepath;}}
<?phpnamespaceApp\MessageHandler;useApp\Service\WeasyprintPdfGenerator;useApp\Entity\Order;useApp\Message\PrintOrderCommand;useApp\Repository\OrderRepository;classPrintOrderCommandHandler{publicfunction__construct(privateWeasyprintPdfGenerator$pdfGenerator,privatestring$documentPath){}publicfunction__invoke(PrintOrderCommand$command):string{// Generate the full file path:$filepath=implode('/',[$this->documentPath,"order-{$command->id}.pdf"]);// Ensure destination folder is created:@mkdir($this->documentPath);// Print the route to PDF:return$this->pdfGenerator->print($filepath,'print_order',['id'=>$command->id]);}}
Define document path for all services through variable binding:
<!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title>{%blocktitle'My document'%}</title>{%blockstylesheets%}{{encore_entry_link_tags('print')}}{%endblock%} </head> <body class="print">{%blockbody%} <article class="page"> <h1 class="title">Order #{{order.id}}</h1> </article> <article> <h2 class="title break">Premièrement</h2> <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris elit enim, mattis sit amet ante sit amet, convallis aliquet diam. Nam id neque eget massa vehicula euismod fringilla pellentesque lorem.</p> <h3 class="subtitle">Deuxièmement</h3> <p>Praesent pharetra nibh at elit pharetra pellentesque. Curabitur ipsum ligula, condimentum vel fringilla non, elementum ac magna. Nunc eleifend odio tellus, et feugiat odio dapibus commodo.</p> </article>{%endblock%} </body></html>
@charset"UTF-8";$margin:10mm;$header:20mm;$footer:20mm;@page{size:A4;margin:($header+$margin)$margin($margin+$footer)$margin;background-color:none;font-size:10px;font-family:Arial,sans-serif;@top-right{margin-top:$margin;height:$header;display:block;font-size:8pt;content:string(title);// Dynamic contenttext-align:right;vertical-align:middle;white-space:nowrap;z-index:10;}@bottom-left{margin-top:$margin;height:$footer;display:block;font-size:8pt;content:string(title)' | 'string(subtitle);// Dynamic contenttext-align:left;vertical-align:middle;white-space:nowrap;z-index:10;}@bottom-right{margin-top:$margin;height:$footer;display:block;font-size:16px;content:"Page "counter(page);// Dynamic page counttext-align:right;vertical-align:middle;white-space:nowrap;z-index:10;}}// First page specific style@page:first{margin:$margin$margin($margin+$footer)$margin;background-color:blue;color:white@top-right{content:none;}@bottom-left{content:none;}@bottom-right{content:none;}@bottom-center{margin-top:$margin;height:$footer;display:block;font-size:9pt;content:string(title);// Dynamic contenttext-align:center;vertical-align:middle;white-space:nowrap;z-index:10;}}// Use to force a full page section.page{height:297mm-($margin*2+$footer);width:210mm-($margin*2);}// Set CSS vars from DOM content.title{string-set:titlecontent();}.subtitle{string-set:subtitlecontent();}// Page break managment.break{// Always start this element on a new pagebreak-before:always;}// Not printed.hidden{height:0;color:transparent;}
A more detailed example is available in the Weasyprint repository (both HTML and CSS code).