1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
<?php
namespace Robo\Task;
use Robo\Result;
/**
* Extend StackBasedTask to create a Robo task that
* runs a sequence of commands.
*
* This is particularly useful for wrapping an existing
* object-oriented API. Doing it this way requires
* less code than manually adding a method for each wrapped
* function in the delegate. Additionally, wrapping the
* external class in a StackBasedTask creates a loosely-coupled
* interface -- i.e. if a new method is added to the delegate
* class, it is not necessary to update your wrapper, as the
* new functionality will be inherited.
*
* For example, you have:
*
* $frobinator = new Frobinator($a, $b, $c)
* ->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|object
*/
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 array $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);
}
}
}