PHP method chaining or fluent interface?
Asked Answered
V

10

190

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?

Vlada answered 16/9, 2010 at 6:4 Comment(4)
I'd say most if not all of those questions are about technicalities regarding chaining, this is more specifically about how to achieve it.Macula
@Kristoffer the OP could easily have found how it is achieved from these questions.Wow
@Kristoffer in addition, searching for method chaining php on Google would have given the OP a tutorial by Salathe as the very first result. I dont mind answering easy questions, but some people are just too lazy.Wow
I submit for your perusal, the definitive method chaining decision treeVannie
M
367

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!

Macula answered 16/9, 2010 at 6:10 Comment(5)
This is also sometimes referred to as Fluent InterfacePettiford
@Nitesh that is incorrect. Fluent Interfaces use Method Chaining as their primary mechanism, but it's not the same. Method chaining simply returns the host object, while a Fluent Interface is aimed at creating a DSL. Ex: $foo->setBar(1)->setBaz(2) vs $table->select()->from('foo')->where('bar = 1')->order('ASC). The latter spans multiple objects.Wow
public function __toString() { return $this->str; } This will not require the last method "getStr()" if you're echoing out the chain already.Guard
@Guard True, but then we're introducing magic methods. One concept at a time should be sufficient.Macula
Since PHP 5.4 it's even possible to everything in one line: $a = (new fakeString())->addA()->addB()->getStr();Antiar
M
50

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;
    }
    
Methaemoglobin answered 16/9, 2010 at 6:12 Comment(3)
Could you do return &$this in PHP4?Cornish
@alex: I don't have PHP 4 to test with right now, but I'm pretty sure not.Methaemoglobin
I didn't think so either, but it should work right? Perhaps if PHP4 wasn't so PHP4-ish.Cornish
D
33

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'

?>
Darg answered 27/9, 2015 at 3:52 Comment(2)
this is what i call a good explanation...chaining methods always gives me goosebumbs!!Unequaled
How I identify (inside the method) the first and last elements (calls) in the chain. Because sometimes this is now just a list of operations to be executed in order, but something that should be done after collecting all elements. Like executing an SQL query here - but beware, you could do multiple chained calls on one object! Firt and last in each.Surface
R
16

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();
Rostock answered 4/4, 2017 at 9:57 Comment(0)
S
13

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

Shuman answered 16/9, 2010 at 6:11 Comment(5)
The explanation is a bit off. The return values are not passed around. The methods simply return the host object.Wow
@Wow Well, the host object is not returned. Any object can be returned and chained.Shuman
Then I would argue it's not method chaining as defined by Fowler, e.g. Make modifier methods return the host object so that multiple modifiers can be invoked in a single expression. - if you return other objects, it's more likely a Fluent Interface :)Wow
Wow, I realize that I'm commenting on an almost 8 year old post.. But your link that you have there, is redirecting to some other website. Just fyi.Anemic
here is the original 2005 post by Fowler, mentioning both Fluent Interface and Method Chaining:Gobetween
M
5

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

Myke answered 6/8, 2013 at 8:45 Comment(1)
This isn't really an answer so much as a link to a webpage with a potential answer.Midge
N
3

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;
Nativity answered 18/9, 2020 at 5:11 Comment(0)
P
0

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
Preposterous answered 12/8, 2020 at 8:8 Comment(1)
This answer is missing its educational explanation.Exerciser
H
-1

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);
    }
}
Heroism answered 2/2, 2017 at 6:2 Comment(2)
can you explain what does this do?Oswell
any explanation what this does?Galacto
C
-1

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"
Cab answered 12/4, 2018 at 9:13 Comment(1)
This doesn't really answer the question ("What is method chaining?"). Also the original question is 8 years old and has already got a number of better answersOxyacetylene

© 2022 - 2024 — McMap. All rights reserved.