2

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?

2
  • 2
    You seem to be concerned because your code does not follow any named pattern you know. But patterns are no building blocks, they are a means to an end, not an end in itself. If you found a way to organize your code well, use it, don't be concerned if that approach has a well-known name or not.
    – Doc Brown
    Aug 22, 2018 at 10:10
  • alright then. I'm currently following direction #2, as it has a bit more separation of pages compared to #1. I can also inject specific invidivual dependencies into each page. Whether it will be good or not in the long run, time will tell, but it will be near-infinitely better than the original code I've started. A lot more easy to modify/move/amend pages without fear of breaking something somewhere else.
    – Dennis
    Aug 22, 2018 at 14:06

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.