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