Completed
Push — master ( 23d58c...27ec94 )
by Greg
02:24
created

HookManager::addOptionHook()   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
12
use Consolidation\AnnotatedCommand\ExitCodeInterface;
13
use Consolidation\AnnotatedCommand\OutputDataInterface;
14
use Consolidation\AnnotatedCommand\AnnotationData;
15
use Consolidation\AnnotatedCommand\CommandData;
16
use Consolidation\AnnotatedCommand\CommandError;
17
use Consolidation\AnnotatedCommand\Hooks\Dispatchers\CommandEventHookDispatcher;
18
19
/**
20
 * Manage named callback hooks
21
 */
22
class HookManager implements EventSubscriberInterface
23
{
24
    protected $hooks = [];
25
    /** var CommandInfo[] */
26
    protected $hookOptions = [];
27
28
    const PRE_COMMAND_EVENT = 'pre-command-event';
29
    const COMMAND_EVENT = 'command-event';
30
    const POST_COMMAND_EVENT = 'post-command-event';
31
    const PRE_OPTION_HOOK = 'pre-option';
32
    const OPTION_HOOK = 'option';
33
    const POST_OPTION_HOOK = 'post-option';
34
    const PRE_INITIALIZE = 'pre-init';
35
    const INITIALIZE = 'init';
36
    const POST_INITIALIZE = 'post-init';
37
    const PRE_INTERACT = 'pre-interact';
38
    const INTERACT = 'interact';
39
    const POST_INTERACT = 'post-interact';
40
    const PRE_ARGUMENT_VALIDATOR = 'pre-validate';
41
    const ARGUMENT_VALIDATOR = 'validate';
42
    const POST_ARGUMENT_VALIDATOR = 'post-validate';
43
    const PRE_COMMAND_HOOK = 'pre-command';
44
    const COMMAND_HOOK = 'command';
45
    const POST_COMMAND_HOOK = 'post-command';
46
    const PRE_PROCESS_RESULT = 'pre-process';
47
    const PROCESS_RESULT = 'process';
48
    const POST_PROCESS_RESULT = 'post-process';
49
    const PRE_ALTER_RESULT = 'pre-alter';
50
    const ALTER_RESULT = 'alter';
51
    const POST_ALTER_RESULT = 'post-alter';
52
    const STATUS_DETERMINER = 'status';
53
    const EXTRACT_OUTPUT = 'extract';
54
    const ON_EVENT = 'on-event';
55
56
    public function __construct()
57
    {
58
    }
59
60
    public function getAllHooks()
61
    {
62
        return $this->hooks;
63
    }
64
65
    /**
66
     * Add a hook
67
     *
68
     * @param mixed $callback The callback function to call
69
     * @param string   $hook     The name of the hook to add
70
     * @param string   $name     The name of the command to hook
71
     *   ('*' for all)
72
     */
73
    public function add(callable $callback, $hook, $name = '*')
74
    {
75
        if (empty($name)) {
76
            $name = static::getClassNameFromCallback($callback);
77
        }
78
        $this->hooks[$name][$hook][] = $callback;
79
        return $this;
80
    }
81
82
    public function recordHookOptions($commandInfo, $name)
83
    {
84
        $this->hookOptions[$name][] = $commandInfo;
85
        return $this;
86
    }
87
88
    public static function getNames($command, $callback)
89
    {
90
        return array_filter(
91
            array_merge(
92
                static::getNamesUsingCommands($command),
93
                [static::getClassNameFromCallback($callback)]
94
            )
95
        );
96
    }
97
98
    protected static function getNamesUsingCommands($command)
99
    {
100
        return array_merge(
101
            [$command->getName()],
102
            $command->getAliases()
103
        );
104
    }
105
106
    /**
107
     * If a command hook does not specify any particular command
108
     * name that it should be attached to, then it will be applied
109
     * to every command that is defined in the same class as the hook.
110
     * This is controlled by using the namespace + class name of
111
     * the implementing class of the callback hook.
112
     */
113
    protected static function getClassNameFromCallback($callback)
114
    {
115
        if (!is_array($callback)) {
116
            return '';
117
        }
118
        $reflectionClass = new \ReflectionClass($callback[0]);
119
        return $reflectionClass->getName();
120
    }
121
122
    /**
123
     * Add an configuration provider hook
124
     *
125
     * @param type InitializeHookInterface $provider
126
     * @param type $name The name of the command to hook
127
     *   ('*' for all)
128
     */
129
    public function addInitializeHook(InitializeHookInterface $initializeHook, $name = '*')
130
    {
131
        $this->hooks[$name][self::INITIALIZE][] = $initializeHook;
132
        return $this;
133
    }
134
135
    /**
136
     * Add an option hook
137
     *
138
     * @param type ValidatorInterface $validator
139
     * @param type $name The name of the command to hook
140
     *   ('*' for all)
141
     */
142
    public function addOptionHook(OptionHookInterface $interactor, $name = '*')
143
    {
144
        $this->hooks[$name][self::INTERACT][] = $interactor;
145
        return $this;
146
    }
147
148
    /**
149
     * Add an interact hook
150
     *
151
     * @param type ValidatorInterface $validator
152
     * @param type $name The name of the command to hook
153
     *   ('*' for all)
154
     */
155
    public function addInteractor(InteractorInterface $interactor, $name = '*')
156
    {
157
        $this->hooks[$name][self::INTERACT][] = $interactor;
158
        return $this;
159
    }
160
161
    /**
162
     * Add a pre-validator hook
163
     *
164
     * @param type ValidatorInterface $validator
165
     * @param type $name The name of the command to hook
166
     *   ('*' for all)
167
     */
168
    public function addPreValidator(ValidatorInterface $validator, $name = '*')
169
    {
170
        $this->hooks[$name][self::PRE_ARGUMENT_VALIDATOR][] = $validator;
171
        return $this;
172
    }
173
174
    /**
175
     * Add a validator hook
176
     *
177
     * @param type ValidatorInterface $validator
178
     * @param type $name The name of the command to hook
179
     *   ('*' for all)
180
     */
181
    public function addValidator(ValidatorInterface $validator, $name = '*')
182
    {
183
        $this->hooks[$name][self::ARGUMENT_VALIDATOR][] = $validator;
184
        return $this;
185
    }
186
187
    /**
188
     * Add a pre-command hook.  This is the same as a validator hook, except
189
     * that it will run after all of the post-validator hooks.
190
     *
191
     * @param type ValidatorInterface $preCommand
192
     * @param type $name The name of the command to hook
193
     *   ('*' for all)
194
     */
195
    public function addPreCommandHook(ValidatorInterface $preCommand, $name = '*')
196
    {
197
        $this->hooks[$name][self::PRE_COMMAND_HOOK][] = $preCommand;
198
        return $this;
199
    }
200
201
    /**
202
     * Add a post-command hook.  This is the same as a pre-process hook,
203
     * except that it will run before the first pre-process hook.
204
     *
205
     * @param type ProcessResultInterface $postCommand
206
     * @param type $name The name of the command to hook
207
     *   ('*' for all)
208
     */
209
    public function addPostCommandHook(ProcessResultInterface $postCommand, $name = '*')
210
    {
211
        $this->hooks[$name][self::POST_COMMAND_HOOK][] = $postCommand;
212
        return $this;
213
    }
214
215
    /**
216
     * Add a result processor.
217
     *
218
     * @param type ProcessResultInterface $resultProcessor
219
     * @param type $name The name of the command to hook
220
     *   ('*' for all)
221
     */
222
    public function addResultProcessor(ProcessResultInterface $resultProcessor, $name = '*')
223
    {
224
        $this->hooks[$name][self::PROCESS_RESULT][] = $resultProcessor;
225
        return $this;
226
    }
227
228
    /**
229
     * Add a result alterer. After a result is processed
230
     * by a result processor, an alter hook may be used
231
     * to convert the result from one form to another.
232
     *
233
     * @param type AlterResultInterface $resultAlterer
234
     * @param type $name The name of the command to hook
235
     *   ('*' for all)
236
     */
237
    public function addAlterResult(AlterResultInterface $resultAlterer, $name = '*')
238
    {
239
        $this->hooks[$name][self::ALTER_RESULT][] = $resultAlterer;
240
        return $this;
241
    }
242
243
    /**
244
     * Add a status determiner. Usually, a command should return
245
     * an integer on error, or a result object on success (which
246
     * implies a status code of zero). If a result contains the
247
     * status code in some other field, then a status determiner
248
     * can be used to call the appropriate accessor method to
249
     * determine the status code.  This is usually not necessary,
250
     * though; a command that fails may return a CommandError
251
     * object, which contains a status code and a result message
252
     * to display.
253
     * @see CommandError::getExitCode()
254
     *
255
     * @param type StatusDeterminerInterface $statusDeterminer
256
     * @param type $name The name of the command to hook
257
     *   ('*' for all)
258
     */
259
    public function addStatusDeterminer(StatusDeterminerInterface $statusDeterminer, $name = '*')
260
    {
261
        $this->hooks[$name][self::STATUS_DETERMINER][] = $statusDeterminer;
262
        return $this;
263
    }
264
265
    /**
266
     * Add an output extractor. If a command returns an object
267
     * object, by default it is passed directly to the output
268
     * formatter (if in use) for rendering. If the result object
269
     * contains more information than just the data to render, though,
270
     * then an output extractor can be used to call the appopriate
271
     * accessor method of the result object to get the data to
272
     * rendered.  This is usually not necessary, though; it is preferable
273
     * to have complex result objects implement the OutputDataInterface.
274
     * @see OutputDataInterface::getOutputData()
275
     *
276
     * @param type ExtractOutputInterface $outputExtractor
277
     * @param type $name The name of the command to hook
278
     *   ('*' for all)
279
     */
280
    public function addOutputExtractor(ExtractOutputInterface $outputExtractor, $name = '*')
281
    {
282
        $this->hooks[$name][self::EXTRACT_OUTPUT][] = $outputExtractor;
283
        return $this;
284
    }
285
286
    public function getHookOptionsForCommand($command)
287
    {
288
        $names = $this->addWildcardHooksToNames($command->getNames(), $command->getAnnotationData());
289
        return $this->getHookOptions($names);
290
    }
291
292
    /**
293
     * @return CommandInfo[]
294
     */
295
    public function getHookOptions($names)
296
    {
297
        $result = [];
298
        foreach ($names as $name) {
299
            if (isset($this->hookOptions[$name])) {
300
                $result = array_merge($result, $this->hookOptions[$name]);
301
            }
302
        }
303
        return $result;
304
    }
305
306
    /**
307
     * Get a set of hooks with the provided name(s). Include the
308
     * pre- and post- hooks, and also include the global hooks ('*')
309
     * in addition to the named hooks provided.
310
     *
311
     * @param string|array $names The name of the function being hooked.
312
     * @param string[] $hooks A list of hooks (e.g. [HookManager::ALTER_RESULT])
313
     *
314
     * @return callable[]
315
     */
316
    public function getHooks($names, $hooks, $annotationData = null)
317
    {
318
        return $this->get($this->addWildcardHooksToNames($names, $annotationData), $hooks);
319
    }
320
321
    protected function addWildcardHooksToNames($names, $annotationData = null)
322
    {
323
        $names = array_merge(
324
            (array)$names,
325
            ($annotationData == null) ? [] : array_map(function ($item) {
326
                return "@$item";
327
            }, $annotationData->keys())
328
        );
329
        $names[] = '*';
330
        return array_unique($names);
331
    }
332
333
    /**
334
     * Get a set of hooks with the provided name(s).
335
     *
336
     * @param string|array $names The name of the function being hooked.
337
     * @param string[] $hooks The list of hook names (e.g. [HookManager::ALTER_RESULT])
338
     *
339
     * @return callable[]
340
     */
341
    public function get($names, $hooks)
342
    {
343
        $result = [];
344
        foreach ((array)$hooks as $hook) {
345
            foreach ((array)$names as $name) {
346
                $result = array_merge($result, $this->getHook($name, $hook));
347
            }
348
        }
349
        return $result;
350
    }
351
352
    /**
353
     * Get a single named hook.
354
     *
355
     * @param string $name The name of the hooked method
356
     * @param string $hook The specific hook name (e.g. alter)
357
     *
358
     * @return callable[]
359
     */
360
    public function getHook($name, $hook)
361
    {
362
        if (isset($this->hooks[$name][$hook])) {
363
            return $this->hooks[$name][$hook];
364
        }
365
        return [];
366
    }
367
368
    /**
369
     * Call the command event hooks.
370
     *
371
     * TODO: This should be moved to CommandEventHookDispatcher, which
372
     * should become the class that implements EventSubscriberInterface.
373
     * This change would break all clients, though, so postpone until next
374
     * major release.
375
     *
376
     * @param ConsoleCommandEvent $event
377
     */
378
    public function callCommandEventHooks(ConsoleCommandEvent $event)
379
    {
380
        /* @var Command $command */
381
        $command = $event->getCommand();
382
        $dispatcher = new CommandEventHookDispatcher($this, [$command->getName()]);
383
        $dispatcher->callCommandEventHooks($event);
384
    }
385
386
    /**
387
     * @{@inheritdoc}
388
     */
389
    public static function getSubscribedEvents()
390
    {
391
        return [ConsoleEvents::COMMAND => 'callCommandEventHooks'];
392
    }
393
}
394