Completed
Pull Request — master (#131)
by Greg
01:46
created

HookManager::addOutputExtractor()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 5
rs 9.4285
cc 1
eloc 3
nc 1
nop 2
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
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
12
use Symfony\Component\EventDispatcher\EventDispatcher;
13
14
use Consolidation\AnnotatedCommand\ExitCodeInterface;
15
use Consolidation\AnnotatedCommand\OutputDataInterface;
16
use Consolidation\AnnotatedCommand\AnnotationData;
17
use Consolidation\AnnotatedCommand\CommandData;
18
use Consolidation\AnnotatedCommand\CommandError;
19
use Consolidation\AnnotatedCommand\Hooks\Dispatchers\CommandEventHookDispatcher;
20
21
/**
22
 * Manage named callback hooks
23
 */
24
class HookManager implements EventSubscriberInterface
25
{
26
    protected $hooks = [];
27
    /** var CommandInfo[] */
28
    protected $hookOptions = [];
29
30
    const REPLACE_COMMAND_HOOK = 'replace-command';
31
    const PRE_COMMAND_EVENT = 'pre-command-event';
32
    const COMMAND_EVENT = 'command-event';
33
    const POST_COMMAND_EVENT = 'post-command-event';
34
    const PRE_OPTION_HOOK = 'pre-option';
35
    const OPTION_HOOK = 'option';
36
    const POST_OPTION_HOOK = 'post-option';
37
    const PRE_INITIALIZE = 'pre-init';
38
    const INITIALIZE = 'init';
39
    const POST_INITIALIZE = 'post-init';
40
    const PRE_INTERACT = 'pre-interact';
41
    const INTERACT = 'interact';
42
    const POST_INTERACT = 'post-interact';
43
    const PRE_ARGUMENT_VALIDATOR = 'pre-validate';
44
    const ARGUMENT_VALIDATOR = 'validate';
45
    const POST_ARGUMENT_VALIDATOR = 'post-validate';
46
    const PRE_COMMAND_HOOK = 'pre-command';
47
    const COMMAND_HOOK = 'command';
48
    const POST_COMMAND_HOOK = 'post-command';
49
    const PRE_PROCESS_RESULT = 'pre-process';
50
    const PROCESS_RESULT = 'process';
51
    const POST_PROCESS_RESULT = 'post-process';
52
    const PRE_ALTER_RESULT = 'pre-alter';
53
    const ALTER_RESULT = 'alter';
54
    const POST_ALTER_RESULT = 'post-alter';
55
    const STATUS_DETERMINER = 'status';
56
    const EXTRACT_OUTPUT = 'extract';
57
    const ON_EVENT = 'on-event';
58
59
    public function __construct()
60
    {
61
    }
62
63
    public function getAllHooks()
64
    {
65
        return $this->hooks;
66
    }
67
68
    /**
69
     * Add a hook
70
     *
71
     * @param mixed $callback The callback function to call
72
     * @param string   $hook     The name of the hook to add
73
     * @param string   $name     The name of the command to hook
74
     *   ('*' for all)
75
     */
76
    public function add(callable $callback, $hook, $name = '*')
77
    {
78
        if (empty($name)) {
79
            $name = static::getClassNameFromCallback($callback);
80
        }
81
        $this->hooks[$name][$hook][] = $callback;
82
        return $this;
83
    }
84
85
    public function recordHookOptions($commandInfo, $name)
86
    {
87
        $this->hookOptions[$name][] = $commandInfo;
88
        return $this;
89
    }
90
91
    public static function getNames($command, $callback)
92
    {
93
        return array_filter(
94
            array_merge(
95
                static::getNamesUsingCommands($command),
96
                [static::getClassNameFromCallback($callback)]
97
            )
98
        );
99
    }
100
101
    protected static function getNamesUsingCommands($command)
102
    {
103
        return array_merge(
104
            [$command->getName()],
105
            $command->getAliases()
106
        );
107
    }
108
109
    /**
110
     * If a command hook does not specify any particular command
111
     * name that it should be attached to, then it will be applied
112
     * to every command that is defined in the same class as the hook.
113
     * This is controlled by using the namespace + class name of
114
     * the implementing class of the callback hook.
115
     */
116
    protected static function getClassNameFromCallback($callback)
117
    {
118
        if (!is_array($callback)) {
119
            return '';
120
        }
121
        $reflectionClass = new \ReflectionClass($callback[0]);
122
        return $reflectionClass->getName();
123
    }
124
125
    /**
126
     * Add a replace command hook
127
     *
128
     * @param type ReplaceCommandHookInterface $provider
129
     * @param type string $command_name The name of the command to replace
130
     */
131
    public function addReplaceCommandHook(ReplaceCommandHookInterface $replaceCommandHook, $name)
132
    {
133
        $this->hooks[$name][self::REPLACE_COMMAND_HOOK][] = $replaceCommandHook;
134
        return $this;
135
    }
136
137
    public function addPreCommandEventDispatcher(EventDispatcherInterface $eventDispatcher)
0 ignored issues
show
Unused Code introduced by
The parameter $eventDispatcher is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
138
    {
139
        $this->hooks[$name][self::PRE_COMMAND_EVENT][] = $replaceCommandHook;
0 ignored issues
show
Bug introduced by
The variable $name does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $replaceCommandHook does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
140
        return $this;
141
    }
142
143
    public function addCommandEventDispatcher(EventDispatcherInterface $eventDispatcher)
0 ignored issues
show
Unused Code introduced by
The parameter $eventDispatcher is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
144
    {
145
        $this->hooks[$name][self::COMMAND_EVENT][] = $replaceCommandHook;
0 ignored issues
show
Bug introduced by
The variable $name does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $replaceCommandHook does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
146
        return $this;
147
    }
148
149
    public function addPostCommandEventDispatcher(EventDispatcherInterface $eventDispatcher)
0 ignored issues
show
Unused Code introduced by
The parameter $eventDispatcher is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
150
    {
151
        $this->hooks[$name][self::POST_COMMAND_EVENT][] = $replaceCommandHook;
0 ignored issues
show
Bug introduced by
The variable $name does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $replaceCommandHook does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
152
        return $this;
153
    }
154
155
    public function addCommandEvent(EventSubscriberInterface $eventSubscriber)
156
    {
157
        // Wrap the event subscriber in a dispatcher and add it
158
        $dispatcher = new EventDispatcher();
159
        $dispatcher->addSubscriber($eventSubscriber);
160
        return $this->addCommandEventDispatcher($dispatcher);
161
    }
162
163
    /**
164
     * Add an configuration provider hook
165
     *
166
     * @param type InitializeHookInterface $provider
167
     * @param type $name The name of the command to hook
168
     *   ('*' for all)
169
     */
170
    public function addInitializeHook(InitializeHookInterface $initializeHook, $name = '*')
171
    {
172
        $this->hooks[$name][self::INITIALIZE][] = $initializeHook;
173
        return $this;
174
    }
175
176
    /**
177
     * Add an option hook
178
     *
179
     * @param type ValidatorInterface $validator
180
     * @param type $name The name of the command to hook
181
     *   ('*' for all)
182
     */
183
    public function addOptionHook(OptionHookInterface $interactor, $name = '*')
184
    {
185
        $this->hooks[$name][self::INTERACT][] = $interactor;
186
        return $this;
187
    }
188
189
    /**
190
     * Add an interact hook
191
     *
192
     * @param type ValidatorInterface $validator
193
     * @param type $name The name of the command to hook
194
     *   ('*' for all)
195
     */
196
    public function addInteractor(InteractorInterface $interactor, $name = '*')
197
    {
198
        $this->hooks[$name][self::INTERACT][] = $interactor;
199
        return $this;
200
    }
201
202
    /**
203
     * Add a pre-validator hook
204
     *
205
     * @param type ValidatorInterface $validator
206
     * @param type $name The name of the command to hook
207
     *   ('*' for all)
208
     */
209
    public function addPreValidator(ValidatorInterface $validator, $name = '*')
210
    {
211
        $this->hooks[$name][self::PRE_ARGUMENT_VALIDATOR][] = $validator;
212
        return $this;
213
    }
214
215
    /**
216
     * Add a validator hook
217
     *
218
     * @param type ValidatorInterface $validator
219
     * @param type $name The name of the command to hook
220
     *   ('*' for all)
221
     */
222
    public function addValidator(ValidatorInterface $validator, $name = '*')
223
    {
224
        $this->hooks[$name][self::ARGUMENT_VALIDATOR][] = $validator;
225
        return $this;
226
    }
227
228
    /**
229
     * Add a pre-command hook.  This is the same as a validator hook, except
230
     * that it will run after all of the post-validator hooks.
231
     *
232
     * @param type ValidatorInterface $preCommand
233
     * @param type $name The name of the command to hook
234
     *   ('*' for all)
235
     */
236
    public function addPreCommandHook(ValidatorInterface $preCommand, $name = '*')
237
    {
238
        $this->hooks[$name][self::PRE_COMMAND_HOOK][] = $preCommand;
239
        return $this;
240
    }
241
242
    /**
243
     * Add a post-command hook.  This is the same as a pre-process hook,
244
     * except that it will run before the first pre-process hook.
245
     *
246
     * @param type ProcessResultInterface $postCommand
247
     * @param type $name The name of the command to hook
248
     *   ('*' for all)
249
     */
250
    public function addPostCommandHook(ProcessResultInterface $postCommand, $name = '*')
251
    {
252
        $this->hooks[$name][self::POST_COMMAND_HOOK][] = $postCommand;
253
        return $this;
254
    }
255
256
    /**
257
     * Add a result processor.
258
     *
259
     * @param type ProcessResultInterface $resultProcessor
260
     * @param type $name The name of the command to hook
261
     *   ('*' for all)
262
     */
263
    public function addResultProcessor(ProcessResultInterface $resultProcessor, $name = '*')
264
    {
265
        $this->hooks[$name][self::PROCESS_RESULT][] = $resultProcessor;
266
        return $this;
267
    }
268
269
    /**
270
     * Add a result alterer. After a result is processed
271
     * by a result processor, an alter hook may be used
272
     * to convert the result from one form to another.
273
     *
274
     * @param type AlterResultInterface $resultAlterer
275
     * @param type $name The name of the command to hook
276
     *   ('*' for all)
277
     */
278
    public function addAlterResult(AlterResultInterface $resultAlterer, $name = '*')
279
    {
280
        $this->hooks[$name][self::ALTER_RESULT][] = $resultAlterer;
281
        return $this;
282
    }
283
284
    /**
285
     * Add a status determiner. Usually, a command should return
286
     * an integer on error, or a result object on success (which
287
     * implies a status code of zero). If a result contains the
288
     * status code in some other field, then a status determiner
289
     * can be used to call the appropriate accessor method to
290
     * determine the status code.  This is usually not necessary,
291
     * though; a command that fails may return a CommandError
292
     * object, which contains a status code and a result message
293
     * to display.
294
     * @see CommandError::getExitCode()
295
     *
296
     * @param type StatusDeterminerInterface $statusDeterminer
297
     * @param type $name The name of the command to hook
298
     *   ('*' for all)
299
     */
300
    public function addStatusDeterminer(StatusDeterminerInterface $statusDeterminer, $name = '*')
301
    {
302
        $this->hooks[$name][self::STATUS_DETERMINER][] = $statusDeterminer;
303
        return $this;
304
    }
305
306
    /**
307
     * Add an output extractor. If a command returns an object
308
     * object, by default it is passed directly to the output
309
     * formatter (if in use) for rendering. If the result object
310
     * contains more information than just the data to render, though,
311
     * then an output extractor can be used to call the appopriate
312
     * accessor method of the result object to get the data to
313
     * rendered.  This is usually not necessary, though; it is preferable
314
     * to have complex result objects implement the OutputDataInterface.
315
     * @see OutputDataInterface::getOutputData()
316
     *
317
     * @param type ExtractOutputInterface $outputExtractor
318
     * @param type $name The name of the command to hook
319
     *   ('*' for all)
320
     */
321
    public function addOutputExtractor(ExtractOutputInterface $outputExtractor, $name = '*')
322
    {
323
        $this->hooks[$name][self::EXTRACT_OUTPUT][] = $outputExtractor;
324
        return $this;
325
    }
326
327
    public function getHookOptionsForCommand($command)
328
    {
329
        $names = $this->addWildcardHooksToNames($command->getNames(), $command->getAnnotationData());
330
        return $this->getHookOptions($names);
331
    }
332
333
    /**
334
     * @return CommandInfo[]
335
     */
336
    public function getHookOptions($names)
337
    {
338
        $result = [];
339
        foreach ($names as $name) {
340
            if (isset($this->hookOptions[$name])) {
341
                $result = array_merge($result, $this->hookOptions[$name]);
342
            }
343
        }
344
        return $result;
345
    }
346
347
    /**
348
     * Get a set of hooks with the provided name(s). Include the
349
     * pre- and post- hooks, and also include the global hooks ('*')
350
     * in addition to the named hooks provided.
351
     *
352
     * @param string|array $names The name of the function being hooked.
353
     * @param string[] $hooks A list of hooks (e.g. [HookManager::ALTER_RESULT])
354
     *
355
     * @return callable[]
356
     */
357
    public function getHooks($names, $hooks, $annotationData = null)
358
    {
359
        return $this->get($this->addWildcardHooksToNames($names, $annotationData), $hooks);
360
    }
361
362
    protected function addWildcardHooksToNames($names, $annotationData = null)
363
    {
364
        $names = array_merge(
365
            (array)$names,
366
            ($annotationData == null) ? [] : array_map(function ($item) {
367
                return "@$item";
368
            }, $annotationData->keys())
369
        );
370
        $names[] = '*';
371
        return array_unique($names);
372
    }
373
374
    /**
375
     * Get a set of hooks with the provided name(s).
376
     *
377
     * @param string|array $names The name of the function being hooked.
378
     * @param string[] $hooks The list of hook names (e.g. [HookManager::ALTER_RESULT])
379
     *
380
     * @return callable[]
381
     */
382
    public function get($names, $hooks)
383
    {
384
        $result = [];
385
        foreach ((array)$hooks as $hook) {
386
            foreach ((array)$names as $name) {
387
                $result = array_merge($result, $this->getHook($name, $hook));
388
            }
389
        }
390
        return $result;
391
    }
392
393
    /**
394
     * Get a single named hook.
395
     *
396
     * @param string $name The name of the hooked method
397
     * @param string $hook The specific hook name (e.g. alter)
398
     *
399
     * @return callable[]
400
     */
401
    public function getHook($name, $hook)
402
    {
403
        if (isset($this->hooks[$name][$hook])) {
404
            return $this->hooks[$name][$hook];
405
        }
406
        return [];
407
    }
408
409
    /**
410
     * Call the command event hooks.
411
     *
412
     * TODO: This should be moved to CommandEventHookDispatcher, which
413
     * should become the class that implements EventSubscriberInterface.
414
     * This change would break all clients, though, so postpone until next
415
     * major release.
416
     *
417
     * @param ConsoleCommandEvent $event
418
     */
419
    public function callCommandEventHooks(ConsoleCommandEvent $event)
420
    {
421
        /* @var Command $command */
422
        $command = $event->getCommand();
423
        $dispatcher = new CommandEventHookDispatcher($this, [$command->getName()]);
424
        $dispatcher->callCommandEventHooks($event);
425
    }
426
427
    /**
428
     * @{@inheritdoc}
429
     */
430
    public static function getSubscribedEvents()
431
    {
432
        return [ConsoleEvents::COMMAND => 'callCommandEventHooks'];
433
    }
434
}
435