I want to create a set of patterns to use for rendering a PDF Proposal (a set of PDF pages). I have made a good start, but not clear on how to proceed when more complexity is involved.
I am using TCPDF API library for PDF rendering (3rd party library). At first the way to generate the PDF Proposal was in one big file with PDF commands, and it was a mess. Now I began to build patterns over that.
The main idea I have is to have a series of PDF templates that tell TCPDF what to do, and supply the data to these templates separately. To help keep templates consistent, I've added an Interface below:
interface PdfTemplateInterface
{
function runPdfTemplate(TcpdfFpdi $pdf, array $data = null): void;
}
Concrete example:
class TechnicalPageTemplate implements PdfTemplateInterface
{
function runPdfTemplate(TcpdfFpdi $pdf, array $data = null): void
{
//TCPDF commands that draw on PDF
$pdf->SetFont('Helvetica', '', 9);
$pdf->writeHTMLCell(33, '', '', 0, $data['html']);
$pdf->Image($data['fname'], 3, 5, 33, 0, '', '', 'B', true, 300);
}
}
Then I have a renderer, whose purpose is to encapsulate and manage TCPDF object, and to run any templates passed to it, supplying them with the TCPDF instance, and data.
class PdfRenderer
{
/** @var TcpdfFpdi */
protected $pdf;
function __construct(TcpdfFpdi $pdf)
{
$this->pdf = $pdf;
}
function renderTemplate(PdfTemplateInterface $template, array $data = null): void
{
$template->runPdfTemplate($this->pdf, $data);
}
}
To initialize:
$pdf = new TCPDF(...);
$pdfRenderer = new PdfRenderer($pdf);
From within a controller/action/handler you can set up $pdfRenderer as a service, and call the above template like so:
$this->pdfRenderer->renderTemplate(new TechnicalPageTemplate(), array(
'html' => $this->repository->getData($id),
'fname' => $fname
));
Templates are essentially kind of like a page-snippet instructions to TCPDF, and you can plug data into them. I am fairly happy with how that turned out.
From hereon things get a bit less clear.
I need to generate a PDF with multiple pages, where each logical page may need its own data and dependencies, and repository needs.
How can I set that up?
Currently I get something like this from within Controller/RequestHandler...
//function Controller::page1()
$this->pdfRenderer->renderTemplate(new HeaderTemplate(), $header);
$this->pdfRenderer->renderTemplate(new Page1Template(), array(
'html' => $this->repository->getData($id)
));
$this->pdfRenderer->renderTemplate(new FooterTemplate(), $footer);
//function Controller::page2()
$this->pdfRenderer->renderTemplate(new HeaderTemplate(), $header);
$this->pdfRenderer->renderTemplate(new Page2Template(), array(
'data' => $this->repository2->getData($id),
'image' => $this->repository3->getData($id),
));
$this->pdfRenderer->renderTemplate(new FooterTemplate(), $footer);
//function Controller::page3()
$this->pdfRenderer->renderTemplate(new HeaderTemplate(), $header);
$this->pdfRenderer->renderTemplate(new Page3TemplatePart1(), array(
'html' => $this->repository4->getData($id)
));
$this->pdfRenderer->renderTemplate(new Page3TemplatePart2(), array(
'html' => $this->dependency5->getData()
));
$this->pdfRenderer->renderTemplate(new FooterTemplate(), $footer);
It is messy and I feel like there is a good pattern waiting to emerge...
but I don't see it.
One thing I see that above "messy" code relies too much on pdfRenderer. One thought I have is to move code from within individual pageX() methods of Controller into PdfRenderer methods.
i.e.
Direction 1 - extend PdfRenderer
class Proposal extends PdfRenderer
{
function page1()
{
parent::renderTemplate(new HeaderTemplate(), $header);
parent::renderTemplate(new Page1Template(), array(
'html' => $this->repository->getData($id)
));
parent::renderTemplate(new FooterTemplate(), $footer);
}
}
//Then call from Controller
$this->proposal = new Proposal(Repository(), Other20Dependencies());
$this->proposal->page1();
$this->proposal->page2();
What I do not like about this pattern is that it merges concerns. Concern of PdfRenderer with business needs of the Proposal.
Direction 2 - Organize code into Pages
I could define ... supplementary Controller chunks, call them Pages
.
Start with an Interface:
interface PdfPageInterface
{
function runPdfTemplates(PdfRenderer $pdfRenderer);
}
Page 1 becomes
class Page1 extends PdfPageInterface
{
function __construct(... Dependencies for Page1 only ...)
{}
function renderPage(PdfRenderer $pdfRenderer)
{
$pdfRenderer->renderTemplate(new HeaderTemplate(), $header);
$pdfRenderer->renderTemplate(new Page1Template(), array(
'html' => $this->repository->getData($id)
));
$pdfRenderer->renderTemplate(new FooterTemplate(), $footer);
}
}
//from within Controller:
$page1 = new Page1(...deps...);
$this->pdfRenderer->renderPage($page1);
where PdfRenderer::renderPage will be like so:
function renderPage(PdfPageInterface $page): void
{
$page->runPdfTemplates($this);
}
This way each page is its own mini-controller that is initialized with and can handle its own dependencies.
The problem I see with this direction is that... I already have the main MVC style Controller. It seems to me that Pages
will be offshoots of the Controller, not really serving as any particular pattern, but more so being used for code organization. That is, not a true separate pattern but more like they will essentially be part of the Controller, but in different files. That split-up of the Controller is what bothers me.
I feel like there is a better pattern available. But I can't see it. My goal here is to find that pattern. Is there anything you'd recommend?