Chainable Classes in PHP
If like me, you use a lot of Laravel, you'll have noticed that many of the core Laravel libraries use this chainable class format like I am about to show you. The database query builder is just one example.
$users = DB::table('users')
->select('id', 'name', 'email')
->where('first_name', 'John')
->orderBy('created_at')
->first();
This way of writing libraries makes them much friendlier than passing an array of options for example. In this write up, I'll show you how easy it is to get started writing your own chainable classes.
First though, here is a completed example of what I'm talking about.
class CarBuilder
{
private $manufactureName;
private $manufactureDate;
private $colour = 'silver';
private $topSpeed = 0;
public static function bmw(): CarBuilder
{
return new static('BMW');
}
public static function mercedes(): CarBuilder
{
return new static('Mercedes');
}
public function __construct($manufacturer)
{
$this->manufactureName = $manufacturer;
$this->manufactureDate = date("d/m/Y");
}
public function setColour($colour)
{
$this->colour = $colour;
return $this;
}
public function setTopSpeed($topSpeed)
{
$this->topSpeed = $topSpeed;
return $this;
}
public function build()
{
return [
'manufacturer' => $this->manufactureName,
'manufactureDate' => $this->manufactureDate,
'colour' => $this->colour,
'topSpeed' => $this->topSpeed
];
}
}
Looks pretty straight forward, no? To use this class, the developers using your library would simply need to use the following.
use App\CarBuilder
$carSpec = CarBuilder::bmw()
->setColour('blue')
->setTopSpeed(160)
->build();
So what's going on in this class? Well, it's broken down into three main elements which I will explain now.
Opener
The opener is an entry point to your class. You can have multiple openers, the only rule is they need to be static methods. In my example above I have two openers, the bmw()
and mercedes()
methods. I use my openers to send the manufacturer name to the constructor.
Chain
Chain methods are great for performing pre-execution logic, in my example I solely use them for setting variables such as speed and colour. The one condition of chain variables is that they can only return $this
. I would advise keeping your chain methods pretty light, and keeping all your logic in the executor methods.
Executors
These are what you've been building up to. Now it's time to run any logic using all the properties you've just set using your chain methods. The executors are just like regular methods, they can call any other private methods within the class, then return whatever you'd like. In my example, the build() method is the executor, it uses all the properties previously set to return a specification for our car.
Why?
You might currently be thinking, why would I use this, what benefit does it give me? Well there are no cold hard facts that make chainable classes better, but personally, I think they're much more readable.
/* Chainable Class */
$carSpec = CarBuilder::bmw()
->setColour('blue')
->setTopSpeed(160)
->build();
/* Standard Class */
$CarBuilder = new CarBuilder();
$carSpec = $CarBuilder->build([
'manufacturer' => 'BMW',
'colour' => 'blue',
'topSpeed' => 160,
]);
If you're building an internal API, or a package for others to use. Chain methods like setTopSpeed() are not only easier to remember and document. But a good IDE will list all of the available methods to the developer, something you wouldn't get with an array of options.