Leverage Dependency Inversion In Laravel Package Development
Last week end I started working on an external package for my startup. Even though it's still in Work In Progress, the goal of the package is to be able to create and manage Printable Document on which you precisely position texts and images.
Once the HTML of the document is ready, the next step is to convert the HTML to PDF. And there are several PHP packages to achieve that.
But which one should I choose??
Answer: Why not all of them? And let the user choose the one that works best for them!
This is something that Laravel already does everywhere. See the Mail
component for exemple. In the config file in your laravel app, you can choose the driver you wish to use (log, mailgun, mandrill, smtp, ...)
Let's go step by step
I assume that you've already setup the base of your Laravel Package. The most important piece of code starts in the PackageServiceProvider
. It should look like this.
I took the package service provider from my print package
<?php
namespace ABCreche\Printer;
use Illuminate\Support\ServiceProvider;
class PrintServiceProvider extends ServiceProvider
{
/**
* Bootstrap any package services.
*
* @return void
*/
public function boot()
{
//
}
/**
* Register any package services.
*
* @return void
*/
public function register()
{
$this->mergeConfigFrom(
__DIR__ . '/../config/printer.php',
'printer'
);
$this->app->bind('printer', function ($app) {
return new Printer;
});
}
Now, I didn't inject anything in the Printer
class. The goal is to achieve something like this in the code:
<?php
namespace ABCreche\Printer;
use ABCreche\Printer\Interfaces\PDFConverter;
class Printer
{
public function __construct(PDFConverter $converter)
{
$this->converter = $converter;
}
public function print(string $html, string $fileName)
{
return $this->converter->convert($html, $fileName);
}
}
In the constructor you see that I inject an Interface. If you are familiar with Dependency Inversion, you immedialtely understand.
For the others, I already explained on this blog what is Dependency Inversion and how it works.
We will make a Dompdf Converter class that will handle the conversion. But Dompdf is going to be one of the available drivers that the package will support, so we cannot inject the DompdfConvert
class in the Printer
class.
You get it, we will code the interface and inject that interface into the Printer
class
In my Printer class I inject a PDFConverter
Interface in the constructor. Let's create that:
<?php
namespace ABCreche\Printer\Interfaces;
use ABCreche\Printer\PrintTemplate;
interface PDFConverter
{
public function convert(string $html, $filename): PDFConverter;
}
Then we make the first driver:
<?php
namespace ABCreche\Printer\Converters;
use ABCreche\Printer\Interfaces\PDFConverter;
class DompdfConverter implements PDFConverter
{
protected $path;
public function convert(string $html, $path): PDFConverter
{
// Code to generate the pdf with Dompdf code
return $this;
}
public function getLocalPath()
{
return $this->path;
}
}
// config/printer.php
return [
// 'dompdf' or anyother drive,
'converter' => 'dompdf'
];
Laravel provides a class allowing you to guess the class to instanciate based on the config file. This class is called Manager
. I don't know why it's called like that.
You'll have to create a new class that extends this Manager
class.
<?php
namespace ABCreche\Printer;
use Illuminate\Support\Manager;
use ABCreche\Printer\Converters\DompdfConverter;
class ConverterManager extends Manager
{
/**
* Create an instance of the Dompdf Converter driver
*
* @return DompdfConverter
*/
protected function createDompdfDriver()
{
return new DompdfConverter;
}
/**
* Get the default driver
*
* @return string
*/
public function getDefaultDriver()
{
return $this->app['config']['printer.converter'];
}
}
And finally, back in the PackageServiceProvider:
$this->app->bind('printer', function ($app) {
$manager = new ConverterManager($app);
return new Printer($manager->driver());
});
And there you go!
As this Manager class was surprisingly missing from the documentation, I thought this tutorial might help a few people.
Hope it helps
I consider myself as an IT Business Artisan. Or Consultant CTO. I'm a self-taught Web Developper, coach and teacher. My main work is helping and guiding digital startups.
more about meBTC
18SY81ejLGFuJ9KMWQu5zPrDGuR5rDiauM
ETH
0x519e0eaa9bc83018bb306880548b79fc0794cd08
XMR
895bSneY4eoZjsr2hN2CAALkUrMExHEV5Pbg8TJb6ejnMLN7js1gLAXQySqbSbfzjWHQpQhQpvFtojbkdZQZmM9qCFz7BXU
2024 © My Dynamic Production SRL All rights Reserved.