91659f33a6bffeb43545a7e531b3c6f8b5519e8c
[yaffs-website] / vendor / consolidation / robo / src / Task / StackBasedTask.php
1 <?php
2 namespace Robo\Task;
3
4 use Robo\Result;
5
6 /**
7  * Extend StackBasedTask to create a Robo task that
8  * runs a sequence of commands.
9  *
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.
18  *
19  * For example, you have:
20  *
21  *   $frobinator = new Frobinator($a, $b, $c)
22  *      ->friz()
23  *      ->fraz()
24  *      ->frob();
25  *
26  * We presume that the existing library throws an exception on error.
27  *
28  * You want:
29  *
30  *   $result = $this->taskFrobinator($a, $b, $c)
31  *      ->friz()
32  *      ->fraz()
33  *      ->frob()
34  *      ->run();
35  *
36  * Execution is deferred until run(), and a Robo\Result instance is
37  * returned. Additionally, using Robo will covert Exceptions
38  * into RoboResult objects.
39  *
40  * To create a new Robo task:
41  *
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
45  *
46  * Finally, add your new class to loadTasks.php as usual,
47  * and you are all done.
48  *
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.
52  *
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
56  * of _foo().
57  */
58 abstract class StackBasedTask extends BaseTask
59 {
60     /**
61      * @var array
62      */
63     protected $stack = [];
64
65     /**
66      * @var bool
67      */
68     protected $stopOnFail = true;
69
70     /**
71      * @param bool $stop
72      *
73      * @return $this
74      */
75     public function stopOnFail($stop = true)
76     {
77         $this->stopOnFail = $stop;
78         return $this;
79     }
80
81     /**
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.
87      *
88      * @return null
89      */
90     protected function getDelegate()
91     {
92         return null;
93     }
94
95     /**
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.
99      *
100      * @param string $function
101      *
102      * @return array
103      */
104     protected function getDelegateCommandList($function)
105     {
106         return [[$this, "_$function"], [$this->getDelegate(), $function]];
107     }
108
109     /**
110      * Print progress about the commands being executed
111      *
112      * @param string $command
113      * @param string $action
114      */
115     protected function printTaskProgress($command, $action)
116     {
117         $this->printTaskInfo('{command} {action}', ['command' => "{$command[1]}", 'action' => json_encode($action, JSON_UNESCAPED_SLASHES)]);
118     }
119
120     /**
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.
125      *
126      * @param int|\Robo\Result $function_result
127      *
128      * @return \Robo\Result
129      */
130     protected function processResult($function_result)
131     {
132         if (is_int($function_result)) {
133             if ($function_result) {
134                 return Result::error($this, $function_result);
135             }
136         }
137         return Result::success($this);
138     }
139
140     /**
141      * Record a function to call later.
142      *
143      * @param string $command
144      * @param array $args
145      *
146      * @return $this
147      */
148     protected function addToCommandStack($command, $args)
149     {
150         $this->stack[] = array_merge([$command], $args);
151         return $this;
152     }
153
154     /**
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.
158      *
159      * @param string $function
160      * @param array $args
161      *
162      * @return $this
163      */
164     public function __call($function, $args)
165     {
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);
171                 return $this;
172             }
173         }
174
175         $message = "Method $function does not exist.\n";
176         throw new \BadMethodCallException($message);
177     }
178
179     /**
180      * @return int
181      */
182     public function progressIndicatorSteps()
183     {
184         // run() will call advanceProgressIndicator() once for each
185         // file, one after calling stopBuffering, and again after compression.
186         return count($this->stack);
187     }
188
189     /**
190      * Run all of the queued objects on the stack
191      *
192      * @return \Robo\Result
193      */
194     public function run()
195     {
196         $this->startProgressIndicator();
197         $result = Result::success($this);
198
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()) {
208                 break;
209             }
210         }
211
212         $this->stopProgressIndicator();
213
214         // todo: add timing information to the result
215         return $result;
216     }
217
218     /**
219      * Execute one task method
220      *
221      * @param string $command
222      * @param string $action
223      *
224      * @return \Robo\Result
225      */
226     protected function callTaskMethod($command, $action)
227     {
228         try {
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);
234         }
235     }
236 }