PHP method chaining or fluent interface?

PhpOopMethod ChainingFluent Interface

Php Problem Overview


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?

Php Solutions


Solution 1 - Php

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!](https://tio.run/##lZBBDoIwEEX3PUVDuoAFXgDF6BU8ABlLgUZTmnbQhfHsOLSSYIwa/6KTzO/7kxnb2XG9tfTKM3jPGzipAzptWnZjnGSdvgAqLjy6InSawUjUveFVJXtD7UFimgUrIpMEdtrnJbl8w5Mkknf2GgB1vftGrgiFJzvJKRyciT8@Ju5/JR7/SmwV0j3eMpdgiJ5p4pkAWtqo6@KaaVaQoWTXcwF5GTcPdT/VeUoxjg8 "PHP – Try It Online")

Solution 2 - Php

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 - Php

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'

?>

Solution 4 - Php

Another Way for static method chaining :

class Maker 
{
    private static $result      = null;
    private static $delimiter   = '.';
    private static $data        = [];
    
    public static function words($words)
    {
        if( !empty($words) && count($words) )
        {
            foreach ($words as $w)
            {
                self::$data[] = $w;
            }
        }        
        return new static;
    }

    public static function concate($delimiter)
    {
        self::$delimiter = $delimiter;
        foreach (self::$data as $d)
        {
            self::$result .= $d.$delimiter;
        }
        return new static;
    }

    public static function get()
    {
        return rtrim(self::$result, self::$delimiter);
    }    
}

Calling

echo Maker::words(['foo', 'bob', 'bar'])->concate('-')->get();

echo "<br />";

echo Maker::words(['foo', 'bob', 'bar'])->concate('>')->get();

Solution 5 - Php

Method chaining means that you can chain method calls:

$object->method1()->method2()->method3()

This means that method1() needs to return an object, and method2() is given the result of method1(). Method2() then passes the return value to method3().

Good article: http://www.talkphp.com/advanced-php-programming/1163-php5-method-chaining.html

Solution 6 - Php

There are 49 lines of code which allows you to chain methods over arrays like this:

$fruits = new Arr(array("lemon", "orange", "banana", "apple"));
$fruits->change_key_case(CASE_UPPER)->filter()->walk(function($value,$key) {
     echo $key.': '.$value."\r\n";
});

See this article which shows you how to chain all the PHP's seventy array_ functions.

http://domexception.blogspot.fi/2013/08/php-magic-methods-and-arrayobject.html

Solution 7 - Php

A fluent interface allows you to chain method calls, which results in less typed characters when applying multiple operations on the same object.

class Bill { 

    public $dinner    = 20;

	public $desserts  = 5;

	public $bill;

	public function dinner( $person ) {
		$this->bill += $this->dinner * $person;
		return $this;
	}
	public function dessert( $person ) {
		$this->bill += $this->desserts * $person;
		return $this;
	}
}

$bill = new Bill();

echo $bill->dinner( 2 )->dessert( 3 )->bill;

Solution 8 - Php

I think this is the most relevant answer.

<?php

class Calculator
{
  protected $result = 0;

  public function sum($num)
  {
    $this->result += $num;
    return $this;
  }

  public function sub($num)
  {
    $this->result -= $num;
    return $this;
  }

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

$calculator = new Calculator;
echo $calculator->sum(10)->sub(5)->sum(3)->result(); // 8

Solution 9 - Php

Below is my model that is able to find by ID in the database. The with($data) method is my additional parameters for relationship so I return the $this which is the object itself. On my controller I am able to chain it.

class JobModel implements JobInterface{
    
    	protected $job;
    
    	public function __construct(Model $job){
    		$this->job = $job;
    	}
    
    	public function find($id){
    		return $this->job->find($id);
    	}
    
    	public function with($data=[]){
    		$this->job = $this->job->with($params);
    		return $this;
    	}
}

class JobController{
    protected $job;

    public function __construct(JobModel $job){
        $this->job = $job;
    }

    public function index(){
        // chaining must be in order
        $this->job->with(['data'])->find(1);
    }
}

Solution 10 - Php

If you mean method chaining like in JavaScript (or some people keep in mind jQuery), why not just take a library that brings that dev. experience in PHP? For example Extras - https://dsheiko.github.io/extras/ This one extends PHP types with JavaScript and Underscore methods and provides chaining:

You can chain a particular type:

<?php
use \Dsheiko\Extras\Arrays;
// Chain of calls
$res = Arrays::chain([1, 2, 3])
    ->map(function($num){ return $num + 1; })
    ->filter(function($num){ return $num > 1; })
    ->reduce(function($carry, $num){ return $carry + $num; }, 0)
    ->value();

or

<?php
use \Dsheiko\Extras\Strings;
$res = Strings::from( " 12345 " )
            ->replace("/1/", "5")
            ->replace("/2/", "5")
            ->trim()
            ->substr(1, 3)
            ->get();
echo $res; // "534"

Alternatively you can go polymorphic:

<?php
use \Dsheiko\Extras\Any;

$res = Any::chain(new \ArrayObject([1,2,3]))
    ->toArray() // value is [1,2,3]
    ->map(function($num){ return [ "num" => $num ]; })
    // value is [[ "num" => 1, ..]]
    ->reduce(function($carry, $arr){
        $carry .= $arr["num"];
        return $carry;

    }, "") // value is "123"
    ->replace("/2/", "") // value is "13"
    ->then(function($value){
      if (empty($value)) {
        throw new \Exception("Empty value");
      }
      return $value;
    })
    ->value();
echo $res; // "13"

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionSanjay KhatriView Question on Stackoverflow
Solution 1 - PhpKristoffer Sall-StorgaardView Answer on Stackoverflow
Solution 2 - PhpBoltClockView Answer on Stackoverflow
Solution 3 - PhpmwangabenView Answer on Stackoverflow
Solution 4 - PhpRashedul Islam SagorView Answer on Stackoverflow
Solution 5 - PhpalexnView Answer on Stackoverflow
Solution 6 - PhpLukas DongView Answer on Stackoverflow
Solution 7 - PhpDevsaifulView Answer on Stackoverflow
Solution 8 - PhpBiswajit BiswasView Answer on Stackoverflow
Solution 9 - PhpJuanBrunoView Answer on Stackoverflow
Solution 10 - PhpDmitry SheikoView Answer on Stackoverflow