Fluent API PHP Class
What is the fluent api? How to apply this design pattern in PHP? How to call a method both statically and not? Let's discover it!
First of all, what is a fluent API?
Fluent interface is an object-oriented API whose design relies extensively on method chaining. Its goal is to increase code legibility.
For example, in Laravel, you can build up an SQL query using the Eloquent DB class (that implements the fluent API):
DB::table('users')->where('age', '>', 18)->limit(100)->orderBy('name')->get();
User::where('age', '>', 18)->limit(100)->orderBy('name')->get();
With the fluent API comes handy calling a method both statically and not. Like this:
FluentMath::add(10)->subtract(3)->result();
FluentMath::subtract(3)->add(10)->result();
In the example above you can see that the add
and the subtract
methods have been called both statically and not.
How to call a non-static method with double-colon (::)
You can achieve that using the magic methods __call
and __callStatic
.
How do the magic methods __call
and __callStatic
work?
When you are invoking a static method which does not exist then the call falls back to the __callStatic
method.
class MagicStaticTest
{
public static function __callStatic($method, $args)
{
echo 'The __callStatic has been triggered!<br>';
}
public static function definedMethod()
{
echo 'The staticMethod has been triggered!<br>';
}
}
// it outputs: The staticMethod has been triggered!
MagicStaticTest::definedMethod();
// it outputs: The __callStatic has been triggered!
MagicStaticTest::notExistingMethod();
Similarly happen with a not static calls: if you invoke a not static method which does not exist then the call falls back to the magic function __call
class MagicTest
{
public function __call($method, $args)
{
echo 'The call fell back to the method __call<br>';
}
public static function definedMethod()
{
echo 'The definedMethod has been triggered!<br>';
}
}
$instance = new MagicTest;
// it outputs: The definedMethod has been triggered!
$instance->definedMethod();
// it outputs: The call fell back to the method __call
$instance->aMethodThatDoesNotExists();
To call a method both statically and not statically you can use a simple trick: you can use the magic methods __call
and __callstatic
to route the call to the method you want.
Let’s explain it with an example.
Let’s suppose we want to build a fluent API to add or subtract a quantity from a number. For example what we want to achieve is this:
$res = FluentMath::add(5)
->subtract(2)
->add(8)
->result();
// $res is 5 - 2 + 8 = 11
We can build the FluentMath
class as per below:
class FluentMath
{
private $result = 0;
public function __call($method, $args)
{
return $this->call($method, $args);
}
public static function __callStatic($method, $args)
{
return (new static())->call($method, $args);
}
private function call($method, $args)
{
if (! method_exists($this , '_' . $method)) {
throw new Exception('Call undefined method ' . $method);
}
return $this->{'_' . $method}(...$args);
}
private function _add($num)
{
$this->result += $num;
return $this;
}
private function _subtract($num)
{
$this->result -= $num;
return $this;
}
public function result()
{
return $this->result;
}
}
When you are calling statically FluentMath::add(10)
the call falls back to the method __callStatic
because the method add
does not exist. The method __callStatic
checks if the method _add
exists (with an underscore _
in the beginning), then it calls the method_add
and finally it returns a new class instance.
Instead, when you are calling a method not statically, for example $instance->add(10)
, then the call falls back to the magic method __call
because the method add
does not exist. Then the method __call
checks if the method _add
exists and then it calls the method _add
and finally it returns the class instance.
In the FluentMath
class all the methods which start with an underscore _
can be called both statically and not. Plus those functions can be chained as well.
Take it further: class for using the fluent API anywhere you want
You can use the class below to extend other classes to build your fluent API class.
On the bottom of this article you can find links to my repo where you can find examples.
class FluentApi
{
public function __call($method, $args)
{
return $this->call($method, $args);
}
public static function __callStatic($method, $args)
{
return (new static())->call($method, $args);
}
private function call($method, $args)
{
if (! method_exists($this , '_' . $method)) {
throw new Exception('Call undefined method ' . $method);
}
return $this->{'_' . $method}(...$args);
}
}
Below you can find an example of usage of the FluentApi
class:
class FluentMath extends FluentApi
{
private $result = 0;
protected function _add($num)
{
$this->result += $num;
return $this;
}
protected function _subtract($num)
{
$this->result -= $num;
return $this;
}
public function result()
{
return $this->result;
}
}
When you are using the FluentApi
class remember that all the methods that start with an underscore _
(like _add
or _subtract
in the example above) can be chained and can be called both statically and not. _
Resources
- https://designpatternsphp.readthedocs.io/en/latest/Structural/FluentInterface/README.html
- https://github.com/danielefavi/php-fluent-api-class