views:

836

answers:

3

I'm looking for something like break for loops.

Here's some example code (using Symfony's lime) where stop() would not let the class continue and I_DONT_WANT_THIS_TO_RUN() would not be executed.

$browser->isStatusCode(200)
  ->isRequestParameter('module', 'home')
  ->isRequestParameter('action', 'index')
  ->click('Register')
  ->stop()
  ->I_DONT_WANT_THIS_TO_RUN();
$browser->thenThisRunsOkay();

Calling $this->__deconstruct(); from within stop() doesn't seem to do the trick. Is there a function I can call within stop() that would make that happen?

+6  A: 

You could use PHP exceptions:

// This function would of course be declared in the class
function stop() {
    throw new Exception('Stopped.');
}

try {
    $browser->isStatusCode(200)
      ->isRequestParameter('module', 'home')
      ->isRequestParameter('action', 'index')
      ->click('Register')
      ->stop()
      ->I_DONT_WANT_THIS_TO_RUN();
} catch (Exception $e) {
    // when stop() throws the exception, control will go on from here.
}

$browser->thenThisRunsOkay();
yjerem
This is an interesting technique, certainly nothing I would have thought of.
Unkwntech
I've learned that lime actually catches any exception and stops it from continuing, just as I was looking for.
thrashr888
+5  A: 

Just return another class which will return $this for every method called.

Example:

class NoMethods {
    public function __call($name, $args)
    {
        echo __METHOD__ . " called $name with " . count($args) . " arguments.\n";
        return $this;
    }
}

class Browser {
    public function runThis()
    {
        echo __METHOD__ . "\n";
        return $this;
    }

    public function stop()
    {
        echo __METHOD__ . "\n";
        return new NoMethods();
    }

    public function dontRunThis()
    {
        echo __METHOD__ . "\n";
        return $this;
    }
}

$browser = new Browser();
echo "with stop\n";
$browser->runThis()->stop()->dontRunThis()->dunno('hey');
echo "without stop\n";
$browser->runThis()->dontRunThis();
echo "the end\n";

Will result in:

with stop
Browser::runThis
Browser::stop
NoMethods::__call called dontRunThis with 0 arguments.
NoMethods::__call called dunno with 1 arguments.
without stop
Browser::runThis
Browser::dontRunThis
the end
OIS
I ended up using this so I can track the methods that don't get run. It's more explicit about what it's doing. Thanks so much!
thrashr888
A: 

OIS's answer is really good, though I could see that it might get confusing if the object suddenly changes to something else. That is, you'd expect that at the end of your chain, you'll end up with the same object. To avoid that problem, I'd add a private variable to tell the class whether or not to actually do anything. If the class has been stopped, then every class just returns $this straight away. This gives you the added benefit of being able to restart the execution.

class MyClass {
    private $halt;

    function __call($func, $args) {
        if ($this->halt) {
            return $this;
        } else {
            return $this->$func($args);
        }
    }

    private function isRequestParameter() {
        // ...
    }
    public function stop() {
        $this->halt = true;
    }
    public function start() {
        $this->halt = false;
    }
}

This could be put into a parent class so you don't have to duplicate this code.

nickf
You'd have to call $browser->start()->method()->method2(); every time. The chainability returning another class/object is only a problem if you where expected to do something with an object returned by one of the methods. But then your method would also fail.
OIS
well obviously you'd initialise $halt to be false. You solution wouldn't be the best if you did something like $a = $b->func1()->func2()->stop()->func3();... since $a would be a NoMethod object.
nickf
Erm, $b is what $a would be, so there is no point, you can just do $a = $b = new Object(); Your argument is flawed.
OIS