PHP方法链式调用或流畅接口?

189

我正在使用PHP 5,我听说面向对象编程中有一种新的特性叫做“方法链”,那是什么?如何实现它?


1
我认为,如果不是全部,那么大部分这些问题都涉及到关于链接的技术细节,而这个问题更具体地说是关于如何实现链接。 - Kristoffer Sall-Storgaard
@Kristoffer,这些问题很容易帮助OP找到实现的方法。 - Gordon
2
@Kristoffer 此外,如果在谷歌上搜索 method chaining php,那么第一个结果就是由 Salathe 提供的 教程。我不介意回答简单的问题,但有些人就是太懒了。 - Gordon
6
请您审阅,决定链的最终方法决策树 - user895378
10个回答

366

其实很简单。你有一系列的变异器方法,它们都返回原始(或其他)对象。这样,你就可以在返回的对象上继续调用方法。

<?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();

这会输出“ab”

在线尝试!


13
有时也称之为流畅接口。 - Nithesh Chandra
19
@Nitesh 的说法不正确。 流畅接口 主要使用方法链机制,但两者并不相同。方法链只是返回宿主对象,而流畅接口旨在创建DSL。例如:$foo->setBar(1)->setBaz(2)$table->select()->from('foo')->where('bar = 1')->order('ASC)。后者涉及多个对象。 - Gordon
4
公共函数__toString() { 返回 $this->str; }如果您已经打印输出了该链,则无需使用最后一个方法“getStr()”。 - tfont
9
@tfont 是对的,但这样我们就要引入魔术方法了。一次只介绍一个概念应该就足够了。 - Kristoffer Sall-Storgaard
4
自 PHP 5.4 版本以来,甚至可以在一行中完成所有操作: $a = (new fakeString())->addA()->addB()->getStr(); - Philzen

50

基本上,你需要取一个对象:

$obj = new ObjectWithChainableMethods();

调用一个方法,该方法在末尾有效地执行 return $this;

$obj->doSomething();

由于它返回相同的对象,或者更确切地说,是对相同对象的引用,因此您可以继续通过返回值调用同一类的方法,例如:

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

就这样了,有两个重要的事情:

  1. 正如你所提到的,它只适用于PHP 5。在PHP 4中不会正常工作,因为它按值返回对象,这意味着您正在调用不同副本的对象上的方法,这将破坏您的代码。

  2. 同样,在可链接的方法中需要返回对象:

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

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

在PHP4中,你能执行return &$this吗? - alex
@alex:我现在没有 PHP 4 进行测试,但我非常肯定不支持。 - BoltClock
4
我也不这么认为,但它“应该”能够正常工作,对吗?也许如果PHP4不是那么像PHP4的话就好了。 - alex

33

尝试这段代码:

<?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'

?>

2
这就是我所说的好解释...链式方法总是让我感到兴奋!! - MYNE
我如何在方法内部识别链中的第一个和最后一个元素(调用)。因为有时这不仅是按顺序执行的操作列表,而是在收集所有元素后应该完成的事情。比如在这里执行SQL查询 - 但要注意,您可以在一个对象上进行多个链接调用!每个链中都有第一个和最后一个。 - Andris

16

另一种静态方法链式调用的方式:

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);
    }    
}

呼叫

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

echo "<br />";

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

13

5
解释有些不准确。返回值并没有传递。这些方法只是简单地返回宿主对象。 - Gordon
@Gordon 嗯,主机对象不会被返回。任何对象都可以被返回并链接在一起。 - alexn
2
那么我认为它不是由Fowler定义的方法链,例如使修改器方法返回主机对象,以便可以在单个表达式中调用多个修改器。 - 如果您返回其他对象,则更可能是流畅接口 :) - Gordon
哇,我意识到我正在评论一个将近8年的帖子..但是你放在那里的链接,正在重定向到其他网站。只是提供信息。 - willbeeler
以下是Fowler在2005年的帖子,提到了Fluent Interface和Method Chaining: - Matt Smith

5

以下是49行代码,可让您像这样在数组上链接方法:

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

请看这篇文章,它向你展示了如何链接所有 PHP 的七十个 array_ 函数。 http://domexception.blogspot.fi/2013/08/php-magic-methods-and-arrayobject.html

5
这不是一个真正的答案,而是一个链接到可能有答案的网页。 - faintsignal

3

流畅接口允许您链接方法调用,这样在对同一对象应用多个操作时就会减少输入的字符数。

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;

0

我认为这是最相关的答案。

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

此回答缺少相应的教育性解释。 - mickmackusa

-1
如果您指的是像JavaScript中的方法链接(或某些人记得jQuery),为什么不使用一个将该开发体验带入PHP的库呢?例如Extras-https://dsheiko.github.io/extras/。这个库扩展了PHP类型,具有JavaScript和Underscore方法,并提供链接:
您可以链接特定类型:
<?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();

或者

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

或者您可以选择多态:

<?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"

这并没有真正回答问题(“什么是方法链?”)。此外,原始问题已经8年了,并且已经有了更好的答案。 - GordonM

-1
以下是我的模型,它能够在数据库中通过ID查找。 with($data)方法是我用于关系的附加参数,因此我返回对象本身 $this。 在我的控制器上,我能够链接它。
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);
    }
}

你能解释一下这个代码是做什么的吗? - KD.S.T.
这是做什么的? - Patrick

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接