PHP method chaining or fluent interface?

I am using PHP 5 and I've heard of a new featured in the object-oriented approach, called 'method chaining'. What is it exactly? How do I implement it?


Solution 1:

It's rather simple, really. You have a series of mutator methods that all return the original (or other) object. That way, you can keep calling methods on the returned object.

<?php
class fakeString
{
    private $str;
    function __construct()
    {
        $this->str = "";
    }
    
    function addA()
    {
        $this->str .= "a";
        return $this;
    }
    
    function addB()
    {
        $this->str .= "b";
        return $this;
    }
    
    function getStr()
    {
        return $this->str;
    }
}


$a = new fakeString();


echo $a->addA()->addB()->getStr();

This outputs "ab"

Try it online!

Solution 2:

Basically, you take an object:

$obj = new ObjectWithChainableMethods();

Call a method that effectively does a return $this; at the end:

$obj->doSomething();

Since it returns the same object, or rather, a reference to the same object, you can continue calling methods of the same class off the return value, like so:

$obj->doSomething()->doSomethingElse();

That's it, really. Two important things:

  1. As you note, it's PHP 5 only. It won't work properly in PHP 4 because it returns objects by value and that means you're calling methods on different copies of an object, which would break your code.

  2. Again, you need to return the object in your chainable methods:

    public function doSomething() {
        // Do stuff
        return $this;
    }
    
    public function doSomethingElse() {
        // Do more stuff
        return $this;
    }
    

Solution 3:

Try this code:

<?php
class DBManager
{
    private $selectables = array();
    private $table;
    private $whereClause;
    private $limit;

    public function select() {
        $this->selectables = func_get_args();
        return $this;
    }

    public function from($table) {
        $this->table = $table;
        return $this;
    }

    public function where($where) {
        $this->whereClause = $where;
        return $this;
    }

    public function limit($limit) {
        $this->limit = $limit;
        return $this;
    }

    public function result() {
        $query[] = "SELECT";
        // if the selectables array is empty, select all
        if (empty($this->selectables)) {
            $query[] = "*";  
        }
        // else select according to selectables
        else {
            $query[] = join(', ', $this->selectables);
        }

        $query[] = "FROM";
        $query[] = $this->table;

        if (!empty($this->whereClause)) {
            $query[] = "WHERE";
            $query[] = $this->whereClause;
        }

        if (!empty($this->limit)) {
            $query[] = "LIMIT";
            $query[] = $this->limit;
        }

        return join(' ', $query);
    }
}

// Now to use the class and see how METHOD CHAINING works
// let us instantiate the class DBManager
$testOne = new DBManager();
$testOne->select()->from('users');
echo $testOne->result();
// OR
echo $testOne->select()->from('users')->result();
// both displays: 'SELECT * FROM users'

$testTwo = new DBManager();
$testTwo->select()->from('posts')->where('id > 200')->limit(10);
echo $testTwo->result();
// this displays: 'SELECT * FROM posts WHERE id > 200 LIMIT 10'

$testThree = new DBManager();
$testThree->select(
    'firstname',
    'email',
    'country',
    'city'
)->from('users')->where('id = 2399');
echo $testThree->result();
// this will display:
// 'SELECT firstname, email, country, city FROM users WHERE id = 2399'

?>