Chris Bornhoft
Chris Bornhoft

Reputation: 4291

PHP Method Chaining and Strings

This is an interesting quirk I found while method chaining and I'm having difficulty bypassing it. I'm sure there's a solution or another way. It's tough to explain but I'll do my best.

Example:

You have three functions which are part of a class, as well as 2 protected properties as follows.

class Chain {

  protected $_str  = '';
  protected $_part = 0;

  public function __toString() {
    return implode(' ', $this->_str);
  }

  public function AAA () {
    $this->_str[$this->_part] = 'AAA';
    $this->_part++;
    return $this;
  }

  public function BBB () {
    $this->_str[$this->_part] = 'BBB';
    $this->_part++;
    return $this;
  }

  public function wrap ($str) {
    $part = $this->_part - 1;
    $this->_str[$part] = "({$str})";
    return $this;
  }

}

Now when chaining these methods and specifically using the wrap method, the strings from previous chains are unintentionally appended. Example:

$chain = new Chain();
$chain->AAA()->BBB()->wrap($chain->AAA());
echo $chain;

What you would expect the string to look like is AAA BBB (AAA).

However, what actually returns is AAA BBB (AAA BBB AAA).

Why is it that wrap() takes all the previous methods called within the chain instead of only the method that's actually wrapped by it? What is the best way around this assuming there is one?

Upvotes: 1

Views: 883

Answers (3)

Nir Alfasi
Nir Alfasi

Reputation: 53525

$chain->AAA()->BBB() is doing the first two 'AAA' and 'BBB' - obvious.
Then the $chain->AAA() which comes inside wrap($chain->AAA()) does the 3rd 'AAA'.
and last, the wrap method takes all the three and wraps them with () and concatenated to the first 'AAA' and 'BBB' using this line: $this->_str[$part] = "({$str})";
which resolves to: AAA BBB (AAA BBB AAA).

UPDATE:
I believe that what you're trying to do, is to avoid the side-effect of returning this from methods AAA() and BBB() - will be achieved with the following changes:

<?php
class Chain {

  protected $_str  = '';
  protected $_part = 0;

  public function __toString() {
    return implode(' ', $this->_str);
  }

  public function AAA () {
    $this->_str[$this->_part] = 'AAA';
    $this->_part++;
    return "AAA";
  }

  public function BBB () {
    $this->_str[$this->_part] = 'BBB';
    $this->_part++;
    return "BBB";
  }

  public function wrap ($str) {
    $part = $this->_part - 1;
    $this->_str[$part] = "({$str})";
    return $str;
  }

}

$chain = new Chain();
$chain->AAA();
$chain->BBB();
$chain->wrap($chain->AAA());
echo $chain->__toString();

?>

Upvotes: 2

Elbek
Elbek

Reputation: 3484

The calling queue is:

1. $chain->AAA() //this is first method not in wrap() method, you have now AAA
2. $chain->BBB() //then next method, you have now AAA BBB
3. $chain->AAA() //again AAA() method inside wrap method, so you have AAA BBB AAA

inside the wrap() you are putting chain(which is AAA BBB AAA after 3rd step) string in () so you have AAA BBB AAA string at the end of _part array.

Upvotes: 0

PhilMasteG
PhilMasteG

Reputation: 3185

I'd call it kind of a 'race condition'. It seems PHP first interprets the $chain->AAA()->BBB().

Then $chain->_part is 2 and $chain->_str is 'AAA BBB'.

Then, to be able to call wrap, the argument, so $chain->AAA() is run.

Then $chain->_part is 3 and $chain->_str is 'AAA BBB AAA'.

Finally wrap is called, warpping up the 'AAA BBB AAA'. I would seperate the wrap()-call and reset $chain->_part back to zero in between.

Upvotes: 1

Related Questions