Completed
Push — master ( c65c33...20bdd9 )
by Greg
02:18
created

HookManager::checkValidStage()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 7
rs 9.4285
cc 2
eloc 4
nc 2
nop 1
1
<?php
2
namespace Consolidation\AnnotatedCommand\Hooks;
3
4
use Symfony\Component\Console\Command\Command;
5
use Symfony\Component\Console\Input\InputInterface;
6
use Symfony\Component\Console\Output\OutputInterface;
7
8
use Consolidation\AnnotatedCommand\ExitCodeInterface;
9
use Consolidation\AnnotatedCommand\OutputDataInterface;
10
11
/**
12
 * Manage named callback hooks
13
 */
14
class HookManager
15
{
16
    protected $hooks = [];
17
18
    const PRE_ARGUMENT_VALIDATOR = 'pre-validate';
19
    const ARGUMENT_VALIDATOR = 'validate';
20
    const POST_ARGUMENT_VALIDATOR = 'post-validate';
21
    const PRE_PROCESS_RESULT = 'pre-process';
22
    const PROCESS_RESULT = 'process';
23
    const POST_PROCESS_RESULT = 'post-process';
24
    const PRE_ALTER_RESULT = 'pre-alter';
25
    const ALTER_RESULT = 'alter';
26
    const POST_ALTER_RESULT = 'post-alter';
27
    const STATUS_DETERMINER = 'status';
28
    const EXTRACT_OUTPUT = 'extract';
29
30
    public function __construct()
31
    {
32
    }
33
34
    /**
35
     * Add a hook
36
     *
37
     * @param string   $name     The name of the command to hook
38
     *   ('*' for all)
39
     * @param string   $hook     The name of the hook to add
40
     * @param mixed $callback The callback function to call
41
     */
42
    public function add($name, $hook, callable $callback)
43
    {
44
        $this->hooks[$name][$hook][] = $callback;
45
    }
46
47
    /**
48
     * Add a validator hook
49
     *
50
     * @param type ValidatorInterface $validator
51
     * @param type $name The name of the command to hook
52
     *   ('*' for all)
53
     */
54
    public function addValidator(ValidatorInterface $validator, $name = '*')
55
    {
56
        $this->hooks[$name][self::ARGUMENT_VALIDATOR][] = $validator;
57
    }
58
59
    /**
60
     * Add a result processor.
61
     *
62
     * @param type ProcessResultInterface $resultProcessor
63
     * @param type $name The name of the command to hook
64
     *   ('*' for all)
65
     */
66
    public function addResultProcessor(ProcessResultInterface $resultProcessor, $name = '*')
67
    {
68
        $this->hooks[$name][self::PROCESS_RESULT][] = $resultProcessor;
69
    }
70
71
    /**
72
     * Add a result alterer. After a result is processed
73
     * by a result processor, an alter hook may be used
74
     * to convert the result from one form to another.
75
     *
76
     * @param type AlterResultInterface $resultAlterer
77
     * @param type $name The name of the command to hook
78
     *   ('*' for all)
79
     */
80
    public function addAlterResult(AlterResultInterface $resultAlterer, $name = '*')
81
    {
82
        $this->hooks[$name][self::ALTER_RESULT][] = $resultAlterer;
83
    }
84
85
    /**
86
     * Add a status determiner. Usually, a command should return
87
     * an integer on error, or a result object on success (which
88
     * implies a status code of zero). If a result contains the
89
     * status code in some other field, then a status determiner
90
     * can be used to call the appropriate accessor method to
91
     * determine the status code.  This is usually not necessary,
92
     * though; a command that fails may return a CommandError
93
     * object, which contains a status code and a result message
94
     * to display.
95
     * @see CommandError::getExitCode()
96
     *
97
     * @param type StatusDeterminerInterface $statusDeterminer
98
     * @param type $name The name of the command to hook
99
     *   ('*' for all)
100
     */
101
    public function addStatusDeterminer(StatusDeterminerInterface $statusDeterminer, $name = '*')
102
    {
103
        $this->hooks[$name][self::STATUS_DETERMINER][] = $statusDeterminer;
104
    }
105
106
    /**
107
     * Add an output extractor. If a command returns an object
108
     * object, by default it is passed directly to the output
109
     * formatter (if in use) for rendering. If the result object
110
     * contains more information than just the data to render, though,
111
     * then an output extractor can be used to call the appopriate
112
     * accessor method of the result object to get the data to
113
     * rendered.  This is usually not necessary, though; it is preferable
114
     * to have complex result objects implement the OutputDataInterface.
115
     * @see OutputDataInterface::getOutputData()
116
     *
117
     * @param type ExtractOutputInterface $outputExtractor
118
     * @param type $name The name of the command to hook
119
     *   ('*' for all)
120
     */
121
    public function addOutputExtractor(ExtractOutputInterface $outputExtractor, $name = '*')
122
    {
123
        $this->hooks[$name][self::EXTRACT_OUTPUT][] = $outputExtractor;
124
    }
125
126
    /**
127
     * Get a set of hooks with the provided name(s).
128
     *
129
     * @param string|array $names The name of the function being hooked.
130
     * @param string $hook The specific hook name (e.g. alter)
131
     *
132
     * @return callable[]
133
     */
134
    public function get($names, $hook)
135
    {
136
        $hooks = [];
137
        foreach ((array)$names as $name) {
138
            $hooks = array_merge($hooks, $this->getHook($name, $hook));
139
        }
140
        return $hooks;
141
    }
142
143
    public function validateArguments($names, $args)
144
    {
145
        $validators = $this->getValidators($names);
146
        foreach ($validators as $validator) {
147
            $validated = $this->callValidator($validator, $args);
148
            if (is_object($validated)) {
149
                return $validated;
150
            }
151
            if (is_array($validated)) {
152
                $args = $validated;
153
            }
154
        }
155
        return $args;
156
    }
157
158
    /**
159
     * Process result and decide what to do with it.
160
     * Allow client to add transformation / interpretation
161
     * callbacks.
162
     */
163
    public function alterResult($names, $result, $args)
164
    {
165
        $processors = $this->getProcessResultHooks($names);
166
        foreach ($processors as $processor) {
167
            $result = $this->callProcessor($processor, $result, $args);
168
        }
169
        $alterers = $this->getAlterResultHooks($names);
170
        foreach ($alterers as $alterer) {
171
            $result = $this->callProcessor($alterer, $result, $args);
172
        }
173
174
        return $result;
175
    }
176
177
    /**
178
     * Call all status determiners, and see if any of them
179
     * know how to convert to a status code.
180
     */
181
    public function determineStatusCode($names, $result)
182
    {
183
        // If the result (post-processing) is an object that
184
        // implements ExitCodeInterface, then we will ask it
185
        // to give us the status code.
186
        if ($result instanceof ExitCodeInterface) {
187
            return $result->getExitCode();
188
        }
189
190
        // If the result does not implement ExitCodeInterface,
191
        // then we'll see if there is a determiner that can
192
        // extract a status code from the result.
193
        $determiners = $this->getStatusDeterminers($names);
194
        foreach ($determiners as $determiner) {
195
            $status = $this->callDeterminer($determiner, $result);
196
            if (isset($status)) {
197
                return $status;
198
            }
199
        }
200
    }
201
202
    /**
203
     * Convert the result object to printable output in
204
     * structured form.
205
     */
206
    public function extractOutput($names, $result)
207
    {
208
        if ($result instanceof OutputDataInterface) {
209
            return $result->getOutputData();
210
        }
211
212
        $extractors = $this->getOutputExtractors($names);
213
        foreach ($extractors as $extractor) {
214
            $structuredOutput = $this->callExtractor($extractor, $result);
215
            if (isset($structuredOutput)) {
216
                return $structuredOutput;
217
            }
218
        }
219
220
        return $result;
221
    }
222
223
    protected function getValidators($names)
224
    {
225
        return $this->getHooks($names, self::ARGUMENT_VALIDATOR);
226
    }
227
228
    protected function getStatusDeterminers($names)
229
    {
230
        return $this->getHooks($names, self::STATUS_DETERMINER);
231
    }
232
233
    protected function getProcessResultHooks($names)
234
    {
235
        return $this->getHooks($names, self::PROCESS_RESULT);
236
    }
237
238
    protected function getAlterResultHooks($names)
239
    {
240
        return $this->getHooks($names, self::ALTER_RESULT);
241
    }
242
243
    protected function getOutputExtractors($names)
244
    {
245
        return $this->getHooks($names, self::EXTRACT_OUTPUT);
246
    }
247
248
    /**
249
     * Get a set of hooks with the provided name(s). Include the
250
     * pre- and post- hooks, and also include the global hooks ('*')
251
     * in addition to the named hooks provided.
252
     *
253
     * @param string|array $names The name of the function being hooked.
254
     * @param string $hook The specific hook name (e.g. alter)
255
     *
256
     * @return callable[]
257
     */
258
    protected function getHooks($names, $hook)
259
    {
260
        $names = (array)$names;
261
        $names[] = '*';
262
        return array_merge(
263
            $this->get($names, "pre-$hook"),
264
            $this->get($names, $hook),
265
            $this->get($names, "post-$hook")
266
        );
267
    }
268
269
    /**
270
     * Get a single named hook.
271
     *
272
     * @param string $name The name of the hooked method
273
     * @param string $hook The specific hook name (e.g. alter)
274
     *
275
     * @return callable[]
276
     */
277
    protected function getHook($name, $hook)
278
    {
279
        if (isset($this->hooks[$name][$hook])) {
280
            return $this->hooks[$name][$hook];
281
        }
282
        return [];
283
    }
284
285
    protected function callValidator($validator, $args)
286
    {
287
        if ($validator instanceof ValidatorInterface) {
288
            return $validator->validate($args);
289
        }
290
        if (is_callable($validator)) {
291
            return $validator($args);
292
        }
293
    }
294
295
    protected function callProcessor($processor, $result, $args)
296
    {
297
        $processed = null;
298
        if ($processor instanceof ProcessResultInterface) {
299
            $processed = $processor->process($result, $args);
300
        }
301
        if (is_callable($processor)) {
302
            $processed = $processor($result, $args);
303
        }
304
        if (isset($processed)) {
305
            return $processed;
306
        }
307
        return $result;
308
    }
309
310
    protected function callDeterminer($determiner, $result)
311
    {
312
        if ($determiner instanceof StatusDeterminerInterface) {
313
            return $determiner->determineStatusCode($result);
314
        }
315
        if (is_callable($determiner)) {
316
            return $determiner($result);
317
        }
318
    }
319
320
    protected function callExtractor($extractor, $result)
321
    {
322
        if ($extractor instanceof ExtractOutputInterface) {
323
            return $extractor->extractOutput($result);
324
        }
325
        if (is_callable($extractor)) {
326
            return $extractor($result);
327
        }
328
    }
329
}
330