friz() * ->fraz() * ->frob(); * * We presume that the existing library throws an exception on error. * * You want: * * $result = $this->taskFrobinator($a, $b, $c) * ->friz() * ->fraz() * ->frob() * ->run(); * * Execution is deferred until run(), and a Robo\Result instance is * returned. Additionally, using Robo will covert Exceptions * into RoboResult objects. * * To create a new Robo task: * * - Make a new class that extends StackBasedTask * - Give it a constructor that creates a new Frobinator * - Override getDelegate(), and return the Frobinator instance * * Finally, add your new class to loadTasks.php as usual, * and you are all done. * * If you need to add any methods to your task that should run * immediately (e.g. to set parameters used at run() time), just * implement them in your derived class. * * If you need additional methods that should run deferred, just * define them as 'protected function _foo()'. Then, users may * call $this->taskFrobinator()->foo() to get deferred execution * of _foo(). */ abstract class StackBasedTask extends BaseTask { /** * @var array */ protected $stack = []; /** * @var bool */ protected $stopOnFail = true; /** * @param bool $stop * * @return $this */ public function stopOnFail($stop = true) { $this->stopOnFail = $stop; return $this; } /** * Derived classes should override the getDelegate() method, and * return an instance of the API class being wrapped. When this * is done, any method of the delegate is available as a method of * this class. Calling one of the delegate's methods will defer * execution until the run() method is called. * * @return null */ protected function getDelegate() { return null; } /** * Derived classes that have more than one delegate may override * getCommandList to add as many delegate commands as desired to * the list of potential functions that __call() tried to find. * * @param string $function * * @return array */ protected function getDelegateCommandList($function) { return [[$this, "_$function"], [$this->getDelegate(), $function]]; } /** * Print progress about the commands being executed * * @param string $command * @param string $action */ protected function printTaskProgress($command, $action) { $this->printTaskInfo('{command} {action}', ['command' => "{$command[1]}", 'action' => json_encode($action, JSON_UNESCAPED_SLASHES)]); } /** * Derived classes can override processResult to add more * logic to result handling from functions. By default, it * is assumed that if a function returns in int, then * 0 == success, and any other value is the error code. * * @param int|\Robo\Result $function_result * * @return \Robo\Result */ protected function processResult($function_result) { if (is_int($function_result)) { if ($function_result) { return Result::error($this, $function_result); } } return Result::success($this); } /** * Record a function to call later. * * @param string $command * @param array $args * * @return $this */ protected function addToCommandStack($command, $args) { $this->stack[] = array_merge([$command], $args); return $this; } /** * Any API function provided by the delegate that executes immediately * may be handled by __call automatically. These operations will all * be deferred until this task's run() method is called. * * @param string $function * @param array $args * * @return $this */ public function __call($function, $args) { foreach ($this->getDelegateCommandList($function) as $command) { if (method_exists($command[0], $command[1])) { // Otherwise, we'll defer calling this function // until run(), and return $this. $this->addToCommandStack($command, $args); return $this; } } $message = "Method $function does not exist.\n"; throw new \BadMethodCallException($message); } /** * @return int */ public function progressIndicatorSteps() { // run() will call advanceProgressIndicator() once for each // file, one after calling stopBuffering, and again after compression. return count($this->stack); } /** * Run all of the queued objects on the stack * * @return \Robo\Result */ public function run() { $this->startProgressIndicator(); $result = Result::success($this); foreach ($this->stack as $action) { $command = array_shift($action); $this->printTaskProgress($command, $action); $this->advanceProgressIndicator(); // TODO: merge data from the result on this call // with data from the result on the previous call? // For now, the result always comes from the last function. $result = $this->callTaskMethod($command, $action); if ($this->stopOnFail && $result && !$result->wasSuccessful()) { break; } } $this->stopProgressIndicator(); // todo: add timing information to the result return $result; } /** * Execute one task method * * @param string $command * @param string $action * * @return \Robo\Result */ protected function callTaskMethod($command, $action) { try { $function_result = call_user_func_array($command, $action); return $this->processResult($function_result); } catch (\Exception $e) { $this->printTaskError($e->getMessage()); return Result::fromException($this, $e); } } }