2 namespace Consolidation\AnnotatedCommand\Hooks;
4 use Symfony\Component\Console\Command\Command;
5 use Symfony\Component\Console\Input\InputInterface;
6 use Symfony\Component\Console\Output\OutputInterface;
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;
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;
22 * Manage named callback hooks
24 class HookManager implements EventSubscriberInterface
26 protected $hooks = [];
27 /** var CommandInfo[] */
28 protected $hookOptions = [];
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';
59 public function __construct()
63 public function getAllHooks()
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
76 public function add(callable $callback, $hook, $name = '*')
79 $name = static::getClassNameFromCallback($callback);
81 $this->hooks[$name][$hook][] = $callback;
85 public function recordHookOptions($commandInfo, $name)
87 $this->hookOptions[$name][] = $commandInfo;
91 public static function getNames($command, $callback)
95 static::getNamesUsingCommands($command),
96 [static::getClassNameFromCallback($callback)]
101 protected static function getNamesUsingCommands($command)
104 [$command->getName()],
105 $command->getAliases()
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.
116 protected static function getClassNameFromCallback($callback)
118 if (!is_array($callback)) {
121 $reflectionClass = new \ReflectionClass($callback[0]);
122 return $reflectionClass->getName();
126 * Add a replace command hook
128 * @param type ReplaceCommandHookInterface $provider
129 * @param type string $command_name The name of the command to replace
131 public function addReplaceCommandHook(ReplaceCommandHookInterface $replaceCommandHook, $name)
133 $this->hooks[$name][self::REPLACE_COMMAND_HOOK][] = $replaceCommandHook;
137 public function addPreCommandEventDispatcher(EventDispatcherInterface $eventDispatcher, $name = '*')
139 $this->hooks[$name][self::PRE_COMMAND_EVENT][] = $eventDispatcher;
143 public function addCommandEventDispatcher(EventDispatcherInterface $eventDispatcher, $name = '*')
145 $this->hooks[$name][self::COMMAND_EVENT][] = $eventDispatcher;
149 public function addPostCommandEventDispatcher(EventDispatcherInterface $eventDispatcher, $name = '*')
151 $this->hooks[$name][self::POST_COMMAND_EVENT][] = $eventDispatcher;
155 public function addCommandEvent(EventSubscriberInterface $eventSubscriber)
157 // Wrap the event subscriber in a dispatcher and add it
158 $dispatcher = new EventDispatcher();
159 $dispatcher->addSubscriber($eventSubscriber);
160 return $this->addCommandEventDispatcher($dispatcher);
164 * Add an configuration provider hook
166 * @param type InitializeHookInterface $provider
167 * @param type $name The name of the command to hook
170 public function addInitializeHook(InitializeHookInterface $initializeHook, $name = '*')
172 $this->hooks[$name][self::INITIALIZE][] = $initializeHook;
179 * @param type ValidatorInterface $validator
180 * @param type $name The name of the command to hook
183 public function addOptionHook(OptionHookInterface $interactor, $name = '*')
185 $this->hooks[$name][self::INTERACT][] = $interactor;
190 * Add an interact hook
192 * @param type ValidatorInterface $validator
193 * @param type $name The name of the command to hook
196 public function addInteractor(InteractorInterface $interactor, $name = '*')
198 $this->hooks[$name][self::INTERACT][] = $interactor;
203 * Add a pre-validator hook
205 * @param type ValidatorInterface $validator
206 * @param type $name The name of the command to hook
209 public function addPreValidator(ValidatorInterface $validator, $name = '*')
211 $this->hooks[$name][self::PRE_ARGUMENT_VALIDATOR][] = $validator;
216 * Add a validator hook
218 * @param type ValidatorInterface $validator
219 * @param type $name The name of the command to hook
222 public function addValidator(ValidatorInterface $validator, $name = '*')
224 $this->hooks[$name][self::ARGUMENT_VALIDATOR][] = $validator;
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.
232 * @param type ValidatorInterface $preCommand
233 * @param type $name The name of the command to hook
236 public function addPreCommandHook(ValidatorInterface $preCommand, $name = '*')
238 $this->hooks[$name][self::PRE_COMMAND_HOOK][] = $preCommand;
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.
246 * @param type ProcessResultInterface $postCommand
247 * @param type $name The name of the command to hook
250 public function addPostCommandHook(ProcessResultInterface $postCommand, $name = '*')
252 $this->hooks[$name][self::POST_COMMAND_HOOK][] = $postCommand;
257 * Add a result processor.
259 * @param type ProcessResultInterface $resultProcessor
260 * @param type $name The name of the command to hook
263 public function addResultProcessor(ProcessResultInterface $resultProcessor, $name = '*')
265 $this->hooks[$name][self::PROCESS_RESULT][] = $resultProcessor;
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.
274 * @param type AlterResultInterface $resultAlterer
275 * @param type $name The name of the command to hook
278 public function addAlterResult(AlterResultInterface $resultAlterer, $name = '*')
280 $this->hooks[$name][self::ALTER_RESULT][] = $resultAlterer;
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
294 * @see CommandError::getExitCode()
296 * @param type StatusDeterminerInterface $statusDeterminer
297 * @param type $name The name of the command to hook
300 public function addStatusDeterminer(StatusDeterminerInterface $statusDeterminer, $name = '*')
302 $this->hooks[$name][self::STATUS_DETERMINER][] = $statusDeterminer;
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()
317 * @param type ExtractOutputInterface $outputExtractor
318 * @param type $name The name of the command to hook
321 public function addOutputExtractor(ExtractOutputInterface $outputExtractor, $name = '*')
323 $this->hooks[$name][self::EXTRACT_OUTPUT][] = $outputExtractor;
327 public function getHookOptionsForCommand($command)
329 $names = $this->addWildcardHooksToNames($command->getNames(), $command->getAnnotationData());
330 return $this->getHookOptions($names);
334 * @return CommandInfo[]
336 public function getHookOptions($names)
339 foreach ($names as $name) {
340 if (isset($this->hookOptions[$name])) {
341 $result = array_merge($result, $this->hookOptions[$name]);
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.
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])
357 public function getHooks($names, $hooks, $annotationData = null)
359 return $this->get($this->addWildcardHooksToNames($names, $annotationData), $hooks);
362 protected function addWildcardHooksToNames($names, $annotationData = null)
364 $names = array_merge(
366 ($annotationData == null) ? [] : array_map(function ($item) {
368 }, $annotationData->keys())
371 return array_unique($names);
375 * Get a set of hooks with the provided name(s).
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])
382 public function get($names, $hooks)
385 foreach ((array)$hooks as $hook) {
386 foreach ((array)$names as $name) {
387 $result = array_merge($result, $this->getHook($name, $hook));
394 * Get a single named hook.
396 * @param string $name The name of the hooked method
397 * @param string $hook The specific hook name (e.g. alter)
401 public function getHook($name, $hook)
403 if (isset($this->hooks[$name][$hook])) {
404 return $this->hooks[$name][$hook];
410 * Call the command event hooks.
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
417 * @param ConsoleCommandEvent $event
419 public function callCommandEventHooks(ConsoleCommandEvent $event)
421 /* @var Command $command */
422 $command = $event->getCommand();
423 $dispatcher = new CommandEventHookDispatcher($this, [$command->getName()]);
424 $dispatcher->callCommandEventHooks($event);
430 public static function getSubscribedEvents()
432 return [ConsoleEvents::COMMAND => 'callCommandEventHooks'];