Chris@0: hooks; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Add a hook Chris@0: * Chris@0: * @param mixed $callback The callback function to call Chris@0: * @param string $hook The name of the hook to add Chris@0: * @param string $name The name of the command to hook Chris@0: * ('*' for all) Chris@0: */ Chris@0: public function add(callable $callback, $hook, $name = '*') Chris@0: { Chris@0: if (empty($name)) { Chris@0: $name = static::getClassNameFromCallback($callback); Chris@0: } Chris@0: $this->hooks[$name][$hook][] = $callback; Chris@0: return $this; Chris@0: } Chris@0: Chris@0: public function recordHookOptions($commandInfo, $name) Chris@0: { Chris@0: $this->hookOptions[$name][] = $commandInfo; Chris@0: return $this; Chris@0: } Chris@0: Chris@0: public static function getNames($command, $callback) Chris@0: { Chris@0: return array_filter( Chris@0: array_merge( Chris@0: static::getNamesUsingCommands($command), Chris@0: [static::getClassNameFromCallback($callback)] Chris@0: ) Chris@0: ); Chris@0: } Chris@0: Chris@0: protected static function getNamesUsingCommands($command) Chris@0: { Chris@0: return array_merge( Chris@0: [$command->getName()], Chris@0: $command->getAliases() Chris@0: ); Chris@0: } Chris@0: Chris@0: /** Chris@0: * If a command hook does not specify any particular command Chris@0: * name that it should be attached to, then it will be applied Chris@0: * to every command that is defined in the same class as the hook. Chris@0: * This is controlled by using the namespace + class name of Chris@0: * the implementing class of the callback hook. Chris@0: */ Chris@0: protected static function getClassNameFromCallback($callback) Chris@0: { Chris@0: if (!is_array($callback)) { Chris@0: return ''; Chris@0: } Chris@0: $reflectionClass = new \ReflectionClass($callback[0]); Chris@0: return $reflectionClass->getName(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Add a replace command hook Chris@0: * Chris@0: * @param type ReplaceCommandHookInterface $provider Chris@0: * @param type string $command_name The name of the command to replace Chris@0: */ Chris@0: public function addReplaceCommandHook(ReplaceCommandHookInterface $replaceCommandHook, $name) Chris@0: { Chris@0: $this->hooks[$name][self::REPLACE_COMMAND_HOOK][] = $replaceCommandHook; Chris@0: return $this; Chris@0: } Chris@0: Chris@0: public function addPreCommandEventDispatcher(EventDispatcherInterface $eventDispatcher, $name = '*') Chris@0: { Chris@0: $this->hooks[$name][self::PRE_COMMAND_EVENT][] = $eventDispatcher; Chris@0: return $this; Chris@0: } Chris@0: Chris@0: public function addCommandEventDispatcher(EventDispatcherInterface $eventDispatcher, $name = '*') Chris@0: { Chris@0: $this->hooks[$name][self::COMMAND_EVENT][] = $eventDispatcher; Chris@0: return $this; Chris@0: } Chris@0: Chris@0: public function addPostCommandEventDispatcher(EventDispatcherInterface $eventDispatcher, $name = '*') Chris@0: { Chris@0: $this->hooks[$name][self::POST_COMMAND_EVENT][] = $eventDispatcher; Chris@0: return $this; Chris@0: } Chris@0: Chris@0: public function addCommandEvent(EventSubscriberInterface $eventSubscriber) Chris@0: { Chris@0: // Wrap the event subscriber in a dispatcher and add it Chris@0: $dispatcher = new EventDispatcher(); Chris@0: $dispatcher->addSubscriber($eventSubscriber); Chris@0: return $this->addCommandEventDispatcher($dispatcher); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Add an configuration provider hook Chris@0: * Chris@0: * @param type InitializeHookInterface $provider Chris@0: * @param type $name The name of the command to hook Chris@0: * ('*' for all) Chris@0: */ Chris@0: public function addInitializeHook(InitializeHookInterface $initializeHook, $name = '*') Chris@0: { Chris@0: $this->hooks[$name][self::INITIALIZE][] = $initializeHook; Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Add an option hook Chris@0: * Chris@0: * @param type ValidatorInterface $validator Chris@0: * @param type $name The name of the command to hook Chris@0: * ('*' for all) Chris@0: */ Chris@0: public function addOptionHook(OptionHookInterface $interactor, $name = '*') Chris@0: { Chris@0: $this->hooks[$name][self::INTERACT][] = $interactor; Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Add an interact hook Chris@0: * Chris@0: * @param type ValidatorInterface $validator Chris@0: * @param type $name The name of the command to hook Chris@0: * ('*' for all) Chris@0: */ Chris@0: public function addInteractor(InteractorInterface $interactor, $name = '*') Chris@0: { Chris@0: $this->hooks[$name][self::INTERACT][] = $interactor; Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Add a pre-validator hook Chris@0: * Chris@0: * @param type ValidatorInterface $validator Chris@0: * @param type $name The name of the command to hook Chris@0: * ('*' for all) Chris@0: */ Chris@0: public function addPreValidator(ValidatorInterface $validator, $name = '*') Chris@0: { Chris@0: $this->hooks[$name][self::PRE_ARGUMENT_VALIDATOR][] = $validator; Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Add a validator hook Chris@0: * Chris@0: * @param type ValidatorInterface $validator Chris@0: * @param type $name The name of the command to hook Chris@0: * ('*' for all) Chris@0: */ Chris@0: public function addValidator(ValidatorInterface $validator, $name = '*') Chris@0: { Chris@0: $this->hooks[$name][self::ARGUMENT_VALIDATOR][] = $validator; Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Add a pre-command hook. This is the same as a validator hook, except Chris@0: * that it will run after all of the post-validator hooks. Chris@0: * Chris@0: * @param type ValidatorInterface $preCommand Chris@0: * @param type $name The name of the command to hook Chris@0: * ('*' for all) Chris@0: */ Chris@0: public function addPreCommandHook(ValidatorInterface $preCommand, $name = '*') Chris@0: { Chris@0: $this->hooks[$name][self::PRE_COMMAND_HOOK][] = $preCommand; Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Add a post-command hook. This is the same as a pre-process hook, Chris@0: * except that it will run before the first pre-process hook. Chris@0: * Chris@0: * @param type ProcessResultInterface $postCommand Chris@0: * @param type $name The name of the command to hook Chris@0: * ('*' for all) Chris@0: */ Chris@0: public function addPostCommandHook(ProcessResultInterface $postCommand, $name = '*') Chris@0: { Chris@0: $this->hooks[$name][self::POST_COMMAND_HOOK][] = $postCommand; Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Add a result processor. Chris@0: * Chris@0: * @param type ProcessResultInterface $resultProcessor Chris@0: * @param type $name The name of the command to hook Chris@0: * ('*' for all) Chris@0: */ Chris@0: public function addResultProcessor(ProcessResultInterface $resultProcessor, $name = '*') Chris@0: { Chris@0: $this->hooks[$name][self::PROCESS_RESULT][] = $resultProcessor; Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Add a result alterer. After a result is processed Chris@0: * by a result processor, an alter hook may be used Chris@0: * to convert the result from one form to another. Chris@0: * Chris@0: * @param type AlterResultInterface $resultAlterer Chris@0: * @param type $name The name of the command to hook Chris@0: * ('*' for all) Chris@0: */ Chris@0: public function addAlterResult(AlterResultInterface $resultAlterer, $name = '*') Chris@0: { Chris@0: $this->hooks[$name][self::ALTER_RESULT][] = $resultAlterer; Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Add a status determiner. Usually, a command should return Chris@0: * an integer on error, or a result object on success (which Chris@0: * implies a status code of zero). If a result contains the Chris@0: * status code in some other field, then a status determiner Chris@0: * can be used to call the appropriate accessor method to Chris@0: * determine the status code. This is usually not necessary, Chris@0: * though; a command that fails may return a CommandError Chris@0: * object, which contains a status code and a result message Chris@0: * to display. Chris@0: * @see CommandError::getExitCode() Chris@0: * Chris@0: * @param type StatusDeterminerInterface $statusDeterminer Chris@0: * @param type $name The name of the command to hook Chris@0: * ('*' for all) Chris@0: */ Chris@0: public function addStatusDeterminer(StatusDeterminerInterface $statusDeterminer, $name = '*') Chris@0: { Chris@0: $this->hooks[$name][self::STATUS_DETERMINER][] = $statusDeterminer; Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Add an output extractor. If a command returns an object Chris@0: * object, by default it is passed directly to the output Chris@0: * formatter (if in use) for rendering. If the result object Chris@0: * contains more information than just the data to render, though, Chris@0: * then an output extractor can be used to call the appopriate Chris@0: * accessor method of the result object to get the data to Chris@0: * rendered. This is usually not necessary, though; it is preferable Chris@0: * to have complex result objects implement the OutputDataInterface. Chris@0: * @see OutputDataInterface::getOutputData() Chris@0: * Chris@0: * @param type ExtractOutputInterface $outputExtractor Chris@0: * @param type $name The name of the command to hook Chris@0: * ('*' for all) Chris@0: */ Chris@0: public function addOutputExtractor(ExtractOutputInterface $outputExtractor, $name = '*') Chris@0: { Chris@0: $this->hooks[$name][self::EXTRACT_OUTPUT][] = $outputExtractor; Chris@0: return $this; Chris@0: } Chris@0: Chris@0: public function getHookOptionsForCommand($command) Chris@0: { Chris@0: $names = $this->addWildcardHooksToNames($command->getNames(), $command->getAnnotationData()); Chris@0: return $this->getHookOptions($names); Chris@0: } Chris@0: Chris@0: /** Chris@0: * @return CommandInfo[] Chris@0: */ Chris@0: public function getHookOptions($names) Chris@0: { Chris@0: $result = []; Chris@0: foreach ($names as $name) { Chris@0: if (isset($this->hookOptions[$name])) { Chris@0: $result = array_merge($result, $this->hookOptions[$name]); Chris@0: } Chris@0: } Chris@0: return $result; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get a set of hooks with the provided name(s). Include the Chris@0: * pre- and post- hooks, and also include the global hooks ('*') Chris@0: * in addition to the named hooks provided. Chris@0: * Chris@0: * @param string|array $names The name of the function being hooked. Chris@0: * @param string[] $hooks A list of hooks (e.g. [HookManager::ALTER_RESULT]) Chris@0: * Chris@0: * @return callable[] Chris@0: */ Chris@0: public function getHooks($names, $hooks, $annotationData = null) Chris@0: { Chris@0: return $this->get($this->addWildcardHooksToNames($names, $annotationData), $hooks); Chris@0: } Chris@0: Chris@0: protected function addWildcardHooksToNames($names, $annotationData = null) Chris@0: { Chris@0: $names = array_merge( Chris@0: (array)$names, Chris@0: ($annotationData == null) ? [] : array_map(function ($item) { Chris@0: return "@$item"; Chris@0: }, $annotationData->keys()) Chris@0: ); Chris@0: $names[] = '*'; Chris@0: return array_unique($names); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get a set of hooks with the provided name(s). Chris@0: * Chris@0: * @param string|array $names The name of the function being hooked. Chris@0: * @param string[] $hooks The list of hook names (e.g. [HookManager::ALTER_RESULT]) Chris@0: * Chris@0: * @return callable[] Chris@0: */ Chris@0: public function get($names, $hooks) Chris@0: { Chris@0: $result = []; Chris@0: foreach ((array)$hooks as $hook) { Chris@0: foreach ((array)$names as $name) { Chris@0: $result = array_merge($result, $this->getHook($name, $hook)); Chris@0: } Chris@0: } Chris@0: return $result; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get a single named hook. Chris@0: * Chris@0: * @param string $name The name of the hooked method Chris@0: * @param string $hook The specific hook name (e.g. alter) Chris@0: * Chris@0: * @return callable[] Chris@0: */ Chris@0: public function getHook($name, $hook) Chris@0: { Chris@0: if (isset($this->hooks[$name][$hook])) { Chris@0: return $this->hooks[$name][$hook]; Chris@0: } Chris@0: return []; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Call the command event hooks. Chris@0: * Chris@0: * TODO: This should be moved to CommandEventHookDispatcher, which Chris@0: * should become the class that implements EventSubscriberInterface. Chris@0: * This change would break all clients, though, so postpone until next Chris@0: * major release. Chris@0: * Chris@0: * @param ConsoleCommandEvent $event Chris@0: */ Chris@0: public function callCommandEventHooks(ConsoleCommandEvent $event) Chris@0: { Chris@0: /* @var Command $command */ Chris@0: $command = $event->getCommand(); Chris@0: $dispatcher = new CommandEventHookDispatcher($this, [$command->getName()]); Chris@0: $dispatcher->callCommandEventHooks($event); Chris@0: } Chris@0: Chris@0: /** Chris@0: * @{@inheritdoc} Chris@0: */ Chris@0: public static function getSubscribedEvents() Chris@0: { Chris@0: return [ConsoleEvents::COMMAND => 'callCommandEventHooks']; Chris@0: } Chris@0: }