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 }
|