Completed
Push — master ( d10d37...6376dc )
by Greg
02:35
created

HookManager::getValidators()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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