7 * Extend StackBasedTask to create a Robo task that
8 * runs a sequence of commands.
10 * This is particularly useful for wrapping an existing
11 * object-oriented API. Doing it this way requires
12 * less code than manually adding a method for each wrapped
13 * function in the delegate. Additionally, wrapping the
14 * external class in a StackBasedTask creates a loosely-coupled
15 * interface -- i.e. if a new method is added to the delegate
16 * class, it is not necessary to update your wrapper, as the
17 * new functionality will be inherited.
19 * For example, you have:
21 * $frobinator = new Frobinator($a, $b, $c)
26 * We presume that the existing library throws an exception on error.
30 * $result = $this->taskFrobinator($a, $b, $c)
36 * Execution is deferred until run(), and a Robo\Result instance is
37 * returned. Additionally, using Robo will covert Exceptions
38 * into RoboResult objects.
40 * To create a new Robo task:
42 * - Make a new class that extends StackBasedTask
43 * - Give it a constructor that creates a new Frobinator
44 * - Override getDelegate(), and return the Frobinator instance
46 * Finally, add your new class to loadTasks.php as usual,
47 * and you are all done.
49 * If you need to add any methods to your task that should run
50 * immediately (e.g. to set parameters used at run() time), just
51 * implement them in your derived class.
53 * If you need additional methods that should run deferred, just
54 * define them as 'protected function _foo()'. Then, users may
55 * call $this->taskFrobinator()->foo() to get deferred execution
58 abstract class StackBasedTask extends BaseTask
63 protected $stack = [];
68 protected $stopOnFail = true;
75 public function stopOnFail($stop = true)
77 $this->stopOnFail = $stop;
82 * Derived classes should override the getDelegate() method, and
83 * return an instance of the API class being wrapped. When this
84 * is done, any method of the delegate is available as a method of
85 * this class. Calling one of the delegate's methods will defer
86 * execution until the run() method is called.
90 protected function getDelegate()
96 * Derived classes that have more than one delegate may override
97 * getCommandList to add as many delegate commands as desired to
98 * the list of potential functions that __call() tried to find.
100 * @param string $function
104 protected function getDelegateCommandList($function)
106 return [[$this, "_$function"], [$this->getDelegate(), $function]];
110 * Print progress about the commands being executed
112 * @param string $command
113 * @param string $action
115 protected function printTaskProgress($command, $action)
117 $this->printTaskInfo('{command} {action}', ['command' => "{$command[1]}", 'action' => json_encode($action, JSON_UNESCAPED_SLASHES)]);
121 * Derived classes can override processResult to add more
122 * logic to result handling from functions. By default, it
123 * is assumed that if a function returns in int, then
124 * 0 == success, and any other value is the error code.
126 * @param int|\Robo\Result $function_result
128 * @return \Robo\Result
130 protected function processResult($function_result)
132 if (is_int($function_result)) {
133 if ($function_result) {
134 return Result::error($this, $function_result);
137 return Result::success($this);
141 * Record a function to call later.
143 * @param string $command
148 protected function addToCommandStack($command, $args)
150 $this->stack[] = array_merge([$command], $args);
155 * Any API function provided by the delegate that executes immediately
156 * may be handled by __call automatically. These operations will all
157 * be deferred until this task's run() method is called.
159 * @param string $function
164 public function __call($function, $args)
166 foreach ($this->getDelegateCommandList($function) as $command) {
167 if (method_exists($command[0], $command[1])) {
168 // Otherwise, we'll defer calling this function
169 // until run(), and return $this.
170 $this->addToCommandStack($command, $args);
175 $message = "Method $function does not exist.\n";
176 throw new \BadMethodCallException($message);
182 public function progressIndicatorSteps()
184 // run() will call advanceProgressIndicator() once for each
185 // file, one after calling stopBuffering, and again after compression.
186 return count($this->stack);
190 * Run all of the queued objects on the stack
192 * @return \Robo\Result
194 public function run()
196 $this->startProgressIndicator();
197 $result = Result::success($this);
199 foreach ($this->stack as $action) {
200 $command = array_shift($action);
201 $this->printTaskProgress($command, $action);
202 $this->advanceProgressIndicator();
203 // TODO: merge data from the result on this call
204 // with data from the result on the previous call?
205 // For now, the result always comes from the last function.
206 $result = $this->callTaskMethod($command, $action);
207 if ($this->stopOnFail && $result && !$result->wasSuccessful()) {
212 $this->stopProgressIndicator();
214 // todo: add timing information to the result
219 * Execute one task method
221 * @param string $command
222 * @param string $action
224 * @return \Robo\Result
226 protected function callTaskMethod($command, $action)
229 $function_result = call_user_func_array($command, $action);
230 return $this->processResult($function_result);
231 } catch (\Exception $e) {
232 $this->printTaskError($e->getMessage());
233 return Result::fromException($this, $e);