Retornant un PDF des d'un Controller de Drupal 8
En primer lloc afegim el TCPDF al nostre projecte:
composer require tecnickcom/tcpdf
Creem la ruta i el controller al nostre mòdul, coderscat.routing.yml:
coders.cat.testpdf:
path: '/testpdf'
defaults:
_controller: '\Drupal\coderscat\Controller\PDFController::testPDF'
_title: 'Test PDF'
requirements:
_access: 'TRUE'
Creem la funció testPDF dins el nostre PDFController:
public function testPDF() {
$pdf = new \TCPDF();
$pdf->SetAuthor('Coders.cat');
$pdf->SetTitle('Test browser output');
$pdf->addPage();
$pdf->SetFont('helvetica', '', 16);
$pdf->Cell(0, 100, 'Test pdf content');
$pdf->Output('test.pdf', 'I');
}
I aparentment tot funciona correctament, veiem el pdf al navegador, i si mirem als missatges de registre del drupal no apareix cap error ni cap notice. Però si mirem als logs del servidor web trobem els següents missatges:
Got error 'PHP message: Uncaught PHP Exception LogicException: "The controller must return a response (null given). Did you forget to add a return statement somewhere in your controller?"
Got error ' line 169PHP message: RuntimeException: Failed to start the session because headers have already been sent by "/var/www/coders.cat/vendor/tecnickcom/tcpdf/include/tcpdf_static.php" at line 350.
Ens cal doncs tornar una Response des del Controller, mirant la documentació del mètode Output tenim les següents opcions que podríem provar:
- I: send the file inline to the browser (default). Aquesta és la que hem utilitzat en l'exemple
- D: send to the browser and force a file download with the name given by name. Ens trobem amb el mateix error que abans
- F: save to a local server file with the name given by name. Podríem desar-ho en un fitxer al disc i retornar una response amb l'enllaç, però això ens obre un mar de dubtes i opcions, per exemple nom del fitxer únic?, path?, públic o privat?, permanent o temporal? etc.
- S: return the document as a string (name is ignored). Desem l'output en un string i l'escrivim directament a la resposta, diria que ens hi acostem, però és un desgast de memòria innecessari (no entrarem en detalls...)
- La resta d'opcions no ens serveixen tampoc.
Remenant una mica els tipus de response ens topem amb un tal StreamedResponse, la doc diu: A StreamedResponse uses a callback for its content. The callback should use the standard PHP functions like echo to stream the response back to the client.
Mola, bàsicament m'està dient que qualsevol output ;) (veieu per on vaig?) del callback s'envia al client. Ja només ens falta una petita part del puzzle que molts haureu deduït:
php://output is a write-only stream that allows you to write to the output buffer mechanism in the same way as print and echo.
I amb el coneixement adquirit ja podem reescriure la funció anterior de la següent manera:
public function testPDF() {
$pdf = new \TCPDF();
$pdf->SetAuthor('Coders.cat');
$pdf->SetTitle('Test browser output');
$pdf->addPage();
$pdf->SetFont('helvetica', '', 16);
$pdf->Cell(0, 100, 'Test pdf content');
$headers = [
'Content-Disposition' => 'inline; filename="test.pdf"',
'Content-Type' => 'application/pdf'
];
return new StreamedResponse(function() use($pdf) {
$pdf->Output('php://output', 'F');
}, Response::HTTP_OK, $headers);
}
Podem posar attachment en lloc de inline si en volem forçar la descàrrega.
- Inicia sessió o registra't per fer comentaris
Comentaris