Question

I have a class in php that works with the chainning method, but the problem is that I want to chain the methods in some order.

class Chain {
  public function foo () {
    return $this;
  }
  public function bar () {
    return $this;
  }
  public function some () {
    return $this;
  }
}

So, if I use this class, then I can chain this methods in 9 different ways (all the possible combinations of 3 elements)

But what happen if I determine that the method some always must to be chained after foo or bar and not in other way?

$chain = new Chain();
$chain->foo->bar(); //works; i.e: the method some is optional
$chain->foo()->bar()->some(); //works
$chain->bar()->foo()->some(); //works
$chain->some()->bar()->foo(); //throws an exception

I think that I can do this setting boolean values, something like: when the method foo or bar are called, then I set the value to some var to true, and when the developer calls the some function, if that var is false, then throws an exception, otherwise is allowed to continue.

But I need something more elegant, such as pattern or a built-in solution.

There is another way to do it?

Was it helpful?

Solution

The very rough example I imagine will still have some lines of code in each method

<?php
class Chain {

  private $_register = array();

  public function foo () {
    $this->register(__METHOD__);
    return $this;
  }
  public function bar () {
    $this->register(__METHOD__);
    return $this;
  }
  public function some () {;
    $this->verify('foo'); // foo() should be called before some();
    $this->register(__METHOD__);
    echo 'it\'s ok';
    return $this;
  }
  public function verify($method) {
      if(array_key_exists($method, $this->_register) && $this->_register[$method] == true) {
          return true;
      }
      else {
          throw new Exception('Some exception');
      }
  }
  public function register($method) {
      $method = str_replace(__CLASS__.'::', '', $method);
      $this->_register[$method] = true;
  }
}

What do we do here - we have a register() and verify() methods. (they can be helpers, but for the current purpose I added them in the class.

Each method should have before it's returning value a register to itself. Calling $this->register(__METHOD__) from foo() will add in the private array 'foo' => true. The verify() method checks if foo exist as array key and if its value is true. If it is - the script will continue. Otherwise - throws exception.

In this case:

$chain = new Chain();
$chain->bar()->some()->foo(); //throws an exception

Fatal error: Uncaught exception 'Exception' with message 'Some exception' in ...

$chain = new Chain();
$chain->foo()->some()->foo(); // ok

it's ok

The problem here is that we establish a "convention". You need to pass __METHOD__ to the register function so after it replace the classname it will add only the method name in the array. So later, in the function where you need to verify if one or more functions are called before this, you need to use the method name as string i.e. $this->verify('foo');

Ofcourse you can play different scenarios without stripping and testing with strpos() or adding () after the methodname for easier recognition if you are verifying a method or smth else.

But at least it will save you from making for each method, different variable to fill i.e.

function foo() {
    $this->_foo = true;
    return $this;
}
function bar() {
    $this->_bar = true;
    return $this;
}

OTHER TIPS

Forcing the caller to stick to a certain order of calls just as an end to itself is hardly useful at all. Supposedly what you're really interested in is to make sure the state of the object is valid when you call some() and throw an exception if it's not. In that case, yes, you would check certain indicators of your object's state and throw an exception when this state does not fulfil the requirements that some() may be called. As a concrete example:

$api = new SomeAPI;
$api->setUserID($id);
$api->setSecretKey($secret);
$api->call('something');

Here call() would check that the user id and access key has been set, otherwise it can't do its job. Whether these calls are chained or not is irrelevant and just a syntactic detail.

Alternatively, you could return certain objects of other (sub) classes from your methods which physically make it impossible to call certain methods on them if certain conditions haven't been met:

public function bar() {
    if ($this->foo) {
        return new SubFoo($this->foo);
    } else {
        return new SubBar;
    }
}

This may be overly complicated though.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top