annotate vendor/consolidation/annotated-command/src/Hooks/HookManager.php @ 19:fa3358dc1485 tip

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