Security update for Core, with self-updated composer
[yaffs-website] / vendor / consolidation / annotated-command / src / Hooks / HookManager.php
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, $name = '*')
138     {
139         $this->hooks[$name][self::PRE_COMMAND_EVENT][] = $eventDispatcher;
140         return $this;
141     }
142
143     public function addCommandEventDispatcher(EventDispatcherInterface $eventDispatcher, $name = '*')
144     {
145         $this->hooks[$name][self::COMMAND_EVENT][] = $eventDispatcher;
146         return $this;
147     }
148
149     public function addPostCommandEventDispatcher(EventDispatcherInterface $eventDispatcher, $name = '*')
150     {
151         $this->hooks[$name][self::POST_COMMAND_EVENT][] = $eventDispatcher;
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 }