Łańcuch metod PHP?

170

Używam PHP 5 i słyszałem o nowym podejściu obiektowym, zwanym „łączeniem metod”. Co to właściwie jest? Jak to zaimplementować?

Sanjay Khatri
źródło
1
Powiedziałbym, że większość, jeśli nie wszystkie te pytania dotyczą aspektów technicznych związanych z tworzeniem łańcuchów, a dokładniej dotyczy to tego, jak to osiągnąć.
Kristoffer Sall-Storgaard
@Kristoffer the OP mógł z łatwością znaleźć sposób na osiągnięcie tego na podstawie tych pytań.
Gordon,
2
@Kristoffer dodatkowo poszukiwania metoda łańcuchowym PHP na Google dałby PO z samouczka przez Salathe jako pierwszy wynik. Nie mam nic przeciwko odpowiadaniu na proste pytania, ale niektórzy ludzie są po prostu zbyt leniwi.
Gordon,
6
Przekazuję do wglądu

Odpowiedzi:

333

Jest to raczej proste, masz serię metod mutatora, z których wszystkie zwracają oryginalne (lub inne) obiekty, w ten sposób możesz nadal wywoływać metody na zwróconym obiekcie.

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

Wyprowadza „ab”

Wypróbuj online!

Kristoffer Sall-Storgaard
źródło
10
Czasami jest to również określane jako Fluent Interface
Nithesh Chandra
17
@Nitesh, to jest nieprawidłowe. Fluent Interfaces używa Method Chaining jako swojego podstawowego mechanizmu, ale to nie to samo . Tworzenie łańcuchów metod po prostu zwraca obiekt hosta, podczas gdy interfejs Fluent ma na celu utworzenie DSL . Ex: $foo->setBar(1)->setBaz(2)vs $table->select()->from('foo')->where('bar = 1')->order('ASC). Ta ostatnia obejmuje wiele obiektów.
Gordon,
3
public function __toString () {return $ this-> str; } Nie będzie to wymagało ostatniej metody "getStr ()", jeśli już wypowiadasz echo łańcucha.
tfont
6
@tfont Prawda, ale potem wprowadzamy magiczne metody. Jedna koncepcja na raz powinna wystarczyć.
Kristoffer Sall-Storgaard
3
Od PHP 5.4 możliwe jest nawet wszystko w jednej linii:$a = (new fakeString())->addA()->addB()->getStr();
Philzen
48

Zasadniczo bierzesz przedmiot:

$obj = new ObjectWithChainableMethods();

Wywołaj metodę, która skutecznie wykonuje a return $this;na końcu:

$obj->doSomething();

Ponieważ zwraca ten sam obiekt, a raczej odwołanie do tego samego obiektu, możesz kontynuować wywoływanie metod tej samej klasy poza zwracaną wartością, na przykład:

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

To wszystko, naprawdę. Dwie ważne rzeczy:

  1. Jak zauważyłeś, jest to tylko PHP 5. Nie będzie działać poprawnie w PHP 4, ponieważ zwraca obiekty według wartości, a to oznacza, że ​​wywołujesz metody na różnych kopiach obiektu, co mogłoby złamać twój kod.

  2. Ponownie, musisz zwrócić obiekt w swoich metodach łańcuchowych:

    public function doSomething() {
        // Do stuff
        return $this;
    }
    
    public function doSomethingElse() {
        // Do more stuff
        return $this;
    }
BoltClock
źródło
Czy mógłbyś to zrobić return &$thisw PHP4?
Alex
@alex: Nie mam teraz PHP 4 do testowania, ale jestem pewien, że nie.
BoltClock
4
Ja też tak nie myślałem, ale powinno działać dobrze? Być może gdyby PHP4 nie było tak PHP4-ish.
Alex
Pełne proste kroki łączenia metod można znaleźć na stronie techflirt.com/tutorials/oop-in-php/php-method-chaining.html
Ankur Kumar Singh
28

Wypróbuj ten kod:

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

?>
mwangaben
źródło
1
to właśnie nazywam dobrym wytłumaczeniem ... metody łańcuchowe zawsze dają mi gęsią skórkę !!
MYNE
Jak identyfikuję (wewnątrz metody) pierwszy i ostatni element (wywołania) w łańcuchu. Ponieważ czasami jest to teraz tylko lista operacji do wykonania w kolejności, ale coś, co należy zrobić po zebraniu wszystkich elementów. Podobnie jak w przypadku wykonywania zapytania SQL tutaj - ale uwaga, możesz wykonać wiele połączonych wywołań na jednym obiekcie! Pierwszy i ostatni w każdym.
Andris
12

Łańcuch metod oznacza, że ​​możesz łączyć wywołania metod:

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

Oznacza to, że metoda method1 () musi zwrócić obiekt, a metoda method2 () otrzymuje wynik metody method1 (). Method2 () następnie przekazuje wartość zwracaną do method3 ().

Dobry artykuł: http://www.talkphp.com/advanced-php-programming/1163-php5-method-chaining.html

alexn
źródło
5
Wyjaśnienie jest trochę dziwne. Zwracane wartości nie są przekazywane. Metody po prostu zwracają obiekt hosta.
Gordon
@Gordon Cóż, obiekt hosta nie jest zwracany. Każdy obiekt może zostać zwrócony i powiązany.
alexn
2
Wtedy argumentowałbym, że nie jest to łączenie metod w sposób zdefiniowany przez Fowlera, np. Metody modyfikujące Make zwracają obiekt hosta, dzięki czemu wiele modyfikatorów można wywołać w jednym wyrażeniu. - jeśli zwrócisz inne obiekty, jest to bardziej prawdopodobne, że będzie to Fluent Interface :)
Gordon
Wow, zdaję sobie sprawę, że komentuję prawie 8-letni post .. Ale twój link, który tam masz, przekierowuje na inną stronę. Po prostu fyi.
willbeeler
11

Inny sposób tworzenia łańcuchów metod statycznych:

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

Powołanie

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

echo "<br />";

echo Maker::words(['foo', 'bob', 'bar'])->concate('>')->get();
Rashedul Islam Sagor
źródło
6

Istnieje 49 linii kodu, które pozwalają łączyć metody na tablicach takich jak ta:

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

Zobacz ten artykuł, który pokazuje, jak połączyć w łańcuch wszystkie siedemdziesiąt funkcji PHP array_.

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

Lukas Dong
źródło
5
To nie jest tak naprawdę odpowiedź, a raczej link do strony internetowej z potencjalną odpowiedzią.
faintsignal
-1

Jeśli masz na myśli tworzenie łańcuchów metod, jak w JavaScript (lub niektórzy pamiętają o jQuery), dlaczego nie wziąć biblioteki, która oferuje tego programistę. doświadczenie w PHP? Na przykład Dodatki - https://dsheiko.github.io/extras/ Ten rozszerza typy PHP o metody JavaScript i Underscore i zapewnia łańcuchowanie:

Możesz połączyć określony typ:

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

lub

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

Alternatywnie możesz przejść do polimorfii:

<?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"
Dmitry Sheiko
źródło
To tak naprawdę nie odpowiada na pytanie („Co to jest łączenie metod?”). Również oryginalne pytanie ma 8 lat i ma już kilka lepszych odpowiedzi
GordonM
-1

Poniżej znajduje się mój model, który jest w stanie znaleźć w bazie danych po ID. Metoda with ($ data) to moje dodatkowe parametry relacji, więc zwracam $ this, czyli sam obiekt. Na moim kontrolerze mogę go połączyć.

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);
    }
}
JuanBruno
źródło
czy możesz wyjaśnić, co to robi?
ichimaru
jakieś wyjaśnienie, co to robi?
Patrick