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;
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;
20 * Manage named callback hooks
22 class HookManager implements EventSubscriberInterface
24 protected $hooks = [];
25 /** var CommandInfo[] */
26 protected $hookOptions = [];
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';
57 public function __construct()
61 public function getAllHooks()
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
74 public function add(callable $callback, $hook, $name = '*')
77 $name = static::getClassNameFromCallback($callback);
79 $this->hooks[$name][$hook][] = $callback;
83 public function recordHookOptions($commandInfo, $name)
85 $this->hookOptions[$name][] = $commandInfo;
89 public static function getNames($command, $callback)
93 static::getNamesUsingCommands($command),
94 [static::getClassNameFromCallback($callback)]
99 protected static function getNamesUsingCommands($command)
102 [$command->getName()],
103 $command->getAliases()
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.
114 protected static function getClassNameFromCallback($callback)
116 if (!is_array($callback)) {
119 $reflectionClass = new \ReflectionClass($callback[0]);
120 return $reflectionClass->getName();
124 * Add a replace command hook
126 * @param type ReplaceCommandHookInterface $provider
127 * @param type string $command_name The name of the command to replace
129 public function addReplaceCommandHook(ReplaceCommandHookInterface $replaceCommandHook, $name)
131 $this->hooks[$name][self::REPLACE_COMMAND_HOOK][] = $replaceCommandHook;
136 * Add an configuration provider hook
138 * @param type InitializeHookInterface $provider
139 * @param type $name The name of the command to hook
142 public function addInitializeHook(InitializeHookInterface $initializeHook, $name = '*')
144 $this->hooks[$name][self::INITIALIZE][] = $initializeHook;
151 * @param type ValidatorInterface $validator
152 * @param type $name The name of the command to hook
155 public function addOptionHook(OptionHookInterface $interactor, $name = '*')
157 $this->hooks[$name][self::INTERACT][] = $interactor;
162 * Add an interact hook
164 * @param type ValidatorInterface $validator
165 * @param type $name The name of the command to hook
168 public function addInteractor(InteractorInterface $interactor, $name = '*')
170 $this->hooks[$name][self::INTERACT][] = $interactor;
175 * Add a pre-validator hook
177 * @param type ValidatorInterface $validator
178 * @param type $name The name of the command to hook
181 public function addPreValidator(ValidatorInterface $validator, $name = '*')
183 $this->hooks[$name][self::PRE_ARGUMENT_VALIDATOR][] = $validator;
188 * Add a validator hook
190 * @param type ValidatorInterface $validator
191 * @param type $name The name of the command to hook
194 public function addValidator(ValidatorInterface $validator, $name = '*')
196 $this->hooks[$name][self::ARGUMENT_VALIDATOR][] = $validator;
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.
204 * @param type ValidatorInterface $preCommand
205 * @param type $name The name of the command to hook
208 public function addPreCommandHook(ValidatorInterface $preCommand, $name = '*')
210 $this->hooks[$name][self::PRE_COMMAND_HOOK][] = $preCommand;
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.
218 * @param type ProcessResultInterface $postCommand
219 * @param type $name The name of the command to hook
222 public function addPostCommandHook(ProcessResultInterface $postCommand, $name = '*')
224 $this->hooks[$name][self::POST_COMMAND_HOOK][] = $postCommand;
229 * Add a result processor.
231 * @param type ProcessResultInterface $resultProcessor
232 * @param type $name The name of the command to hook
235 public function addResultProcessor(ProcessResultInterface $resultProcessor, $name = '*')
237 $this->hooks[$name][self::PROCESS_RESULT][] = $resultProcessor;
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.
246 * @param type AlterResultInterface $resultAlterer
247 * @param type $name The name of the command to hook
250 public function addAlterResult(AlterResultInterface $resultAlterer, $name = '*')
252 $this->hooks[$name][self::ALTER_RESULT][] = $resultAlterer;
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
266 * @see CommandError::getExitCode()
268 * @param type StatusDeterminerInterface $statusDeterminer
269 * @param type $name The name of the command to hook
272 public function addStatusDeterminer(StatusDeterminerInterface $statusDeterminer, $name = '*')
274 $this->hooks[$name][self::STATUS_DETERMINER][] = $statusDeterminer;
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()
289 * @param type ExtractOutputInterface $outputExtractor
290 * @param type $name The name of the command to hook
293 public function addOutputExtractor(ExtractOutputInterface $outputExtractor, $name = '*')
295 $this->hooks[$name][self::EXTRACT_OUTPUT][] = $outputExtractor;
299 public function getHookOptionsForCommand($command)
301 $names = $this->addWildcardHooksToNames($command->getNames(), $command->getAnnotationData());
302 return $this->getHookOptions($names);
306 * @return CommandInfo[]
308 public function getHookOptions($names)
311 foreach ($names as $name) {
312 if (isset($this->hookOptions[$name])) {
313 $result = array_merge($result, $this->hookOptions[$name]);
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.
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])
329 public function getHooks($names, $hooks, $annotationData = null)
331 return $this->get($this->addWildcardHooksToNames($names, $annotationData), $hooks);
334 protected function addWildcardHooksToNames($names, $annotationData = null)
336 $names = array_merge(
338 ($annotationData == null) ? [] : array_map(function ($item) {
340 }, $annotationData->keys())
343 return array_unique($names);
347 * Get a set of hooks with the provided name(s).
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])
354 public function get($names, $hooks)
357 foreach ((array)$hooks as $hook) {
358 foreach ((array)$names as $name) {
359 $result = array_merge($result, $this->getHook($name, $hook));
366 * Get a single named hook.
368 * @param string $name The name of the hooked method
369 * @param string $hook The specific hook name (e.g. alter)
373 public function getHook($name, $hook)
375 if (isset($this->hooks[$name][$hook])) {
376 return $this->hooks[$name][$hook];
382 * Call the command event hooks.
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
389 * @param ConsoleCommandEvent $event
391 public function callCommandEventHooks(ConsoleCommandEvent $event)
393 /* @var Command $command */
394 $command = $event->getCommand();
395 $dispatcher = new CommandEventHookDispatcher($this, [$command->getName()]);
396 $dispatcher->callCommandEventHooks($event);
402 public static function getSubscribedEvents()
404 return [ConsoleEvents::COMMAND => 'callCommandEventHooks'];