Chris@0
|
1 # Consolidation\AnnotatedCommand
|
Chris@0
|
2
|
Chris@0
|
3 Initialize Symfony Console commands from annotated command class methods.
|
Chris@0
|
4
|
Chris@0
|
5 [](https://travis-ci.org/consolidation/annotated-command)
|
Chris@0
|
6 [](https://ci.appveyor.com/project/greg-1-anderson/annotated-command)
|
Chris@0
|
7 [](https://scrutinizer-ci.com/g/consolidation/annotated-command/?branch=master)
|
Chris@17
|
8 [](https://coveralls.io/github/consolidation/annotated-command?branch=master)
|
Chris@0
|
9 [](https://packagist.org/packages/consolidation/annotated-command)
|
Chris@0
|
10
|
Chris@0
|
11 ## Component Status
|
Chris@0
|
12
|
Chris@0
|
13 Currently in use in [Robo](https://github.com/consolidation/Robo) (1.x+), [Drush](https://github.com/drush-ops/drush) (9.x+) and [Terminus](https://github.com/pantheon-systems/terminus) (1.x+).
|
Chris@0
|
14
|
Chris@0
|
15 ## Motivation
|
Chris@0
|
16
|
Chris@0
|
17 Symfony Console provides a set of classes that are widely used to implement command line tools. Increasingly, it is becoming popular to use annotations to describe the characteristics of the command (e.g. its arguments, options and so on) implemented by the annotated method.
|
Chris@0
|
18
|
Chris@0
|
19 Extant commandline tools that utilize this technique include:
|
Chris@0
|
20
|
Chris@0
|
21 - [Robo](https://github.com/consolidation/Robo)
|
Chris@0
|
22 - [wp-cli](https://github.com/wp-cli/wp-cli)
|
Chris@0
|
23 - [Pantheon Terminus](https://github.com/pantheon-systems/terminus)
|
Chris@0
|
24
|
Chris@0
|
25 This library provides routines to produce the Symfony\Component\Console\Command\Command from all public methods defined in the provided class.
|
Chris@0
|
26
|
Chris@17
|
27 **Note** If you are looking for a very fast way to write a Symfony Console-base command-line tool, you should consider using [Robo](https://github.com/consolidation/Robo), which is built on top of this library, and adds additional conveniences to get you going quickly. Use [g1a/starter](https://github.com/g1a/starter) to quickly scaffold a new commandline tool. See [Using Robo as a Framework](http://robo.li/framework/). It is possible to use this project without Robo if desired, of course.
|
Chris@0
|
28
|
Chris@0
|
29 ## Library Usage
|
Chris@0
|
30
|
Chris@0
|
31 This is a library intended to be used in some other project. Require from your composer.json file:
|
Chris@0
|
32 ```
|
Chris@0
|
33 "require": {
|
Chris@17
|
34 "consolidation/annotated-command": "^2"
|
Chris@0
|
35 },
|
Chris@0
|
36 ```
|
Chris@0
|
37
|
Chris@0
|
38 ## Example Annotated Command Class
|
Chris@0
|
39 The public methods of the command class define its commands, and the parameters of each method define its arguments and options. The command options, if any, are declared as the last parameter of the methods. The options will be passed in as an associative array; the default options of the last parameter should list the options recognized by the command.
|
Chris@0
|
40
|
Chris@0
|
41 The rest of the parameters are arguments. Parameters with a default value are optional; those without a default value are required.
|
Chris@0
|
42 ```php
|
Chris@0
|
43 class MyCommandClass
|
Chris@0
|
44 {
|
Chris@0
|
45 /**
|
Chris@17
|
46 * This is the my:echo command
|
Chris@0
|
47 *
|
Chris@0
|
48 * This command will concatenate two parameters. If the --flip flag
|
Chris@0
|
49 * is provided, then the result is the concatenation of two and one.
|
Chris@0
|
50 *
|
Chris@17
|
51 * @command my:echo
|
Chris@0
|
52 * @param integer $one The first parameter.
|
Chris@0
|
53 * @param integer $two The other parameter.
|
Chris@0
|
54 * @option arr An option that takes multiple values.
|
Chris@0
|
55 * @option flip Whether or not the second parameter should come first in the result.
|
Chris@0
|
56 * @aliases c
|
Chris@0
|
57 * @usage bet alpha --flip
|
Chris@0
|
58 * Concatenate "alpha" and "bet".
|
Chris@0
|
59 */
|
Chris@17
|
60 public function myEcho($one, $two, $options = ['flip' => false])
|
Chris@0
|
61 {
|
Chris@0
|
62 if ($options['flip']) {
|
Chris@0
|
63 return "{$two}{$one}";
|
Chris@0
|
64 }
|
Chris@0
|
65 return "{$one}{$two}";
|
Chris@0
|
66 }
|
Chris@0
|
67 }
|
Chris@17
|
68 ```
|
Chris@0
|
69 ## Option Default Values
|
Chris@0
|
70
|
Chris@0
|
71 The `$options` array must be an associative array whose key is the name of the option, and whose value is one of:
|
Chris@0
|
72
|
Chris@0
|
73 - The boolean value `false`, which indicates that the option takes no value.
|
Chris@0
|
74 - A **string** containing the default value for options that may be provided a value, but are not required to.
|
Chris@0
|
75 - The special value InputOption::VALUE_REQUIRED, which indicates that the user must provide a value for the option whenever it is used.
|
Chris@0
|
76 - The special value InputOption::VALUE_OPTIONAL, which produces the following behavior:
|
Chris@0
|
77 - If the option is given a value (e.g. `--foo=bar`), then the value will be a string.
|
Chris@0
|
78 - If the option exists on the commandline, but has no value (e.g. `--foo`), then the value will be `true`.
|
Chris@0
|
79 - If the option does not exist on the commandline at all, then the value will be `null`.
|
Chris@0
|
80 - If the user explicitly sets `--foo=0`, then the value will be converted to `false`.
|
Chris@0
|
81 - LIMITATION: If any Input object other than ArgvInput (or a subclass thereof) is used, then the value will be `null` for both the no-value case (`--foo`) and the no-option case. When using a StringInput, use `--foo=1` instead of `--foo` to avoid this problem.
|
Chris@0
|
82 - The special value `true` produces the following behavior:
|
Chris@0
|
83 - If the option is given a value (e.g. `--foo=bar`), then the value will be a string.
|
Chris@0
|
84 - If the option exists on the commandline, but has no value (e.g. `--foo`), then the value will be `true`.
|
Chris@0
|
85 - If the option does not exist on the commandline at all, then the value will also be `true`.
|
Chris@0
|
86 - If the user explicitly sets `--foo=0`, then the value will be converted to `false`.
|
Chris@0
|
87 - If the user adds `--no-foo` on the commandline, then the value of `foo` will be `false`.
|
Chris@0
|
88 - An empty array, which indicates that the option may appear multiple times on the command line.
|
Chris@0
|
89
|
Chris@0
|
90 No other values should be used for the default value. For example, `$options = ['a' => 1]` is **incorrect**; instead, use `$options = ['a' => '1']`.
|
Chris@0
|
91
|
Chris@0
|
92 Default values for options may also be provided via the `@default` annotation. See hook alter, below.
|
Chris@0
|
93
|
Chris@0
|
94 ## Hooks
|
Chris@0
|
95
|
Chris@0
|
96 Commandfiles may provide hooks in addition to commands. A commandfile method that contains a @hook annotation is registered as a hook instead of a command. The format of the hook annotation is:
|
Chris@0
|
97 ```
|
Chris@0
|
98 @hook type target
|
Chris@0
|
99 ```
|
Chris@0
|
100 The hook **type** determines when during the command lifecycle this hook will be called. The available hook types are described in detail below.
|
Chris@0
|
101
|
Chris@0
|
102 The hook **target** specifies which command or commands the hook will be attached to. There are several different ways to specify the hook target.
|
Chris@0
|
103
|
Chris@0
|
104 - The command's primary name (e.g. `my:command`) or the command's method name (e.g. myCommand) will attach the hook to only that command.
|
Chris@0
|
105 - An annotation (e.g. `@foo`) will attach the hook to any command that is annotated with the given label.
|
Chris@17
|
106 - If the target is specified as `*`, then the hook will be attached to all commands.
|
Chris@0
|
107 - If the target is omitted, then the hook will be attached to every command defined in the same class as the hook implementation.
|
Chris@0
|
108
|
Chris@0
|
109 There are ten types of hooks in the command processing request flow:
|
Chris@0
|
110
|
Chris@0
|
111 - [Command Event](#command-event-hook) (Symfony)
|
Chris@0
|
112 - @pre-command-event
|
Chris@0
|
113 - @command-event
|
Chris@0
|
114 - @post-command-event
|
Chris@0
|
115 - [Option](#option-event-hook)
|
Chris@0
|
116 - @pre-option
|
Chris@0
|
117 - @option
|
Chris@0
|
118 - @post-option
|
Chris@0
|
119 - [Initialize](#initialize-hook) (Symfony)
|
Chris@0
|
120 - @pre-init
|
Chris@0
|
121 - @init
|
Chris@0
|
122 - @post-init
|
Chris@0
|
123 - [Interact](#interact-hook) (Symfony)
|
Chris@0
|
124 - @pre-interact
|
Chris@0
|
125 - @interact
|
Chris@0
|
126 - @post-interact
|
Chris@0
|
127 - [Validate](#validate-hook)
|
Chris@0
|
128 - @pre-validate
|
Chris@0
|
129 - @validate
|
Chris@0
|
130 - @post-validate
|
Chris@0
|
131 - [Command](#command-hook)
|
Chris@0
|
132 - @pre-command
|
Chris@0
|
133 - @command
|
Chris@0
|
134 - @command-init
|
Chris@0
|
135 - [Process](#process-hook)
|
Chris@0
|
136 - @pre-process
|
Chris@0
|
137 - @process
|
Chris@0
|
138 - @post-process
|
Chris@0
|
139 - [Alter](#alter-hook)
|
Chris@0
|
140 - @pre-alter
|
Chris@0
|
141 - @alter
|
Chris@0
|
142 - @post-alter
|
Chris@0
|
143 - [Status](#status-hook)
|
Chris@0
|
144 - @status
|
Chris@0
|
145 - [Extract](#extract-hook)
|
Chris@0
|
146 - @extract
|
Chris@17
|
147
|
Chris@0
|
148 In addition to these, there are two more hooks available:
|
Chris@0
|
149
|
Chris@0
|
150 - [On-event](#on-event-hook)
|
Chris@0
|
151 - @on-event
|
Chris@0
|
152 - [Replace Command](#replace-command-hook)
|
Chris@0
|
153 - @replace-command
|
Chris@0
|
154
|
Chris@0
|
155 The "pre" and "post" varieties of these hooks, where avalable, give more flexibility vis-a-vis hook ordering (and for consistency). Within one type of hook, the running order is undefined and not guaranteed. Note that many validate, process and alter hooks may run, but the first status or extract hook that successfully returns a result will halt processing of further hooks of the same type.
|
Chris@0
|
156
|
Chris@0
|
157 Each hook has an interface that defines its calling conventions; however, any callable may be used when registering a hook, which is convenient if versions of PHP prior to 7.0 (with no anonymous classes) need to be supported.
|
Chris@0
|
158
|
Chris@0
|
159 ### Command Event Hook
|
Chris@0
|
160
|
Chris@0
|
161 The command-event hook is called via the Symfony Console command event notification callback mechanism. This happens prior to event dispatching and command / option validation. Note that Symfony does not allow the $input object to be altered in this hook; any change made here will be reset, as Symfony re-parses the object. Changes to arguments and options should be done in the initialize hook (non-interactive alterations) or the interact hook (which is naturally for interactive alterations).
|
Chris@0
|
162
|
Chris@0
|
163 ### Option Event Hook
|
Chris@0
|
164
|
Chris@0
|
165 The option event hook ([OptionHookInterface](src/Hooks/OptionHookInterface.php)) is called for a specific command, whenever it is executed, or its help command is called. Any additional options for the command may be added here by calling the `addOption` method of the provided `$command` object. Note that the option hook is only necessary for calculating dynamic options. Static options may be added via the @option annotation on any hook that uses them. See the [Alter Hook](https://github.com/consolidation/annotated-command#alter-hook) documentation below for an example.
|
Chris@0
|
166 ```
|
Chris@0
|
167 use Consolidation\AnnotatedCommand\AnnotationData;
|
Chris@0
|
168 use Symfony\Component\Console\Command\Command;
|
Chris@0
|
169
|
Chris@0
|
170 /**
|
Chris@0
|
171 * @hook option some:command
|
Chris@0
|
172 */
|
Chris@0
|
173 public function additionalOption(Command $command, AnnotationData $annotationData)
|
Chris@0
|
174 {
|
Chris@0
|
175 $command->addOption(
|
Chris@0
|
176 'dynamic',
|
Chris@0
|
177 '',
|
Chris@0
|
178 InputOption::VALUE_NONE,
|
Chris@0
|
179 'Option added by @hook option some:command'
|
Chris@0
|
180 );
|
Chris@0
|
181 }
|
Chris@0
|
182 ```
|
Chris@0
|
183
|
Chris@0
|
184 ### Initialize Hook
|
Chris@0
|
185
|
Chris@0
|
186 The initialize hook ([InitializeHookInterface](src/Hooks/InitializeHookInterface.php)) runs prior to the interact hook. It may supply command arguments and options from a configuration file or other sources. It should never do any user interaction.
|
Chris@0
|
187
|
Chris@0
|
188 The [consolidation/config](https://github.com/consolidation/config) project (which is used in [Robo PHP](https://github.com/consolidation/robo)) uses `@hook init` to automatically inject values from `config.yml` configuration files for options that were not provided on the command line.
|
Chris@0
|
189 ```
|
Chris@0
|
190 use Consolidation\AnnotatedCommand\AnnotationData;
|
Chris@0
|
191 use Symfony\Component\Console\Input\InputInterface;
|
Chris@0
|
192
|
Chris@0
|
193 /**
|
Chris@0
|
194 * @hook init some:command
|
Chris@0
|
195 */
|
Chris@0
|
196 public function initSomeCommand(InputInterface $input, AnnotationData $annotationData)
|
Chris@0
|
197 {
|
Chris@0
|
198 $value = $input->getOption('some-option');
|
Chris@0
|
199 if (!$value) {
|
Chris@0
|
200 $input->setOption('some-option', $this->generateRandomOptionValue());
|
Chris@0
|
201 }
|
Chris@0
|
202 }
|
Chris@0
|
203 ```
|
Chris@0
|
204
|
Chris@17
|
205 You may alter the AnnotationData here by using simple array syntax. Below, we
|
Chris@17
|
206 add an additional display field label for a Property List.
|
Chris@17
|
207
|
Chris@17
|
208 ```
|
Chris@17
|
209 use Consolidation\AnnotatedCommand\AnnotationData;
|
Chris@17
|
210 use Symfony\Component\Console\Input\InputInterface;
|
Chris@17
|
211
|
Chris@17
|
212 /**
|
Chris@17
|
213 * @hook init some:command
|
Chris@17
|
214 */
|
Chris@17
|
215 public function initSomeCommand(InputInterface $input, AnnotationData $annotationData)
|
Chris@17
|
216 {
|
Chris@17
|
217 $annotationData['field-labels'] .= "\n" . "new_field: My new field";
|
Chris@17
|
218 }
|
Chris@17
|
219 ```
|
Chris@17
|
220
|
Chris@17
|
221 Alternately, you may use the `set()` or `append()` methods on the AnnotationData
|
Chris@17
|
222 class.
|
Chris@17
|
223
|
Chris@17
|
224 ```
|
Chris@17
|
225 use Consolidation\AnnotatedCommand\AnnotationData;
|
Chris@17
|
226 use Symfony\Component\Console\Input\InputInterface;
|
Chris@17
|
227
|
Chris@17
|
228 /**
|
Chris@17
|
229 * @hook init some:command
|
Chris@17
|
230 */
|
Chris@17
|
231 public function initSomeCommand(InputInterface $input, AnnotationData $annotationData)
|
Chris@17
|
232 {
|
Chris@17
|
233 // Add a line to the field labels.
|
Chris@17
|
234 $annotationData->append('field-labels', "\n" . "new_field: My new field");
|
Chris@17
|
235 // Replace all field labels.
|
Chris@17
|
236 $annotationData->set('field-labels', "one_field: My only field");
|
Chris@17
|
237
|
Chris@17
|
238 }
|
Chris@17
|
239 ```
|
Chris@17
|
240
|
Chris@0
|
241 ### Interact Hook
|
Chris@0
|
242
|
Chris@0
|
243 The interact hook ([InteractorInterface](src/Hooks/InteractorInterface.php)) runs prior to argument and option validation. Required arguments and options not supplied on the command line may be provided during this phase by prompting the user. Note that the interact hook is not called if the --no-interaction flag is supplied, whereas the command-event hook and the init hook are.
|
Chris@0
|
244 ```
|
Chris@0
|
245 use Consolidation\AnnotatedCommand\AnnotationData;
|
Chris@0
|
246 use Symfony\Component\Console\Input\InputInterface;
|
Chris@0
|
247 use Symfony\Component\Console\Output\OutputInterface;
|
Chris@0
|
248 use Symfony\Component\Console\Style\SymfonyStyle;
|
Chris@0
|
249
|
Chris@0
|
250 /**
|
Chris@0
|
251 * @hook interact some:command
|
Chris@0
|
252 */
|
Chris@0
|
253 public function interact(InputInterface $input, OutputInterface $output, AnnotationData $annotationData)
|
Chris@0
|
254 {
|
Chris@0
|
255 $io = new SymfonyStyle($input, $output);
|
Chris@0
|
256
|
Chris@0
|
257 // If the user did not specify a password, then prompt for one.
|
Chris@0
|
258 $password = $input->getOption('password');
|
Chris@0
|
259 if (empty($password)) {
|
Chris@0
|
260 $password = $io->askHidden("Enter a password:", function ($value) { return $value; });
|
Chris@0
|
261 $input->setOption('password', $password);
|
Chris@0
|
262 }
|
Chris@0
|
263 }
|
Chris@0
|
264 ```
|
Chris@0
|
265
|
Chris@0
|
266 ### Validate Hook
|
Chris@0
|
267
|
Chris@0
|
268 The purpose of the validate hook ([ValidatorInterface](src/Hooks/ValidatorInterface.php)) is to ensure the state of the targets of the current command are usabe in the context required by that command. Symfony has already validated the arguments and options prior to this hook. It is possible to alter the values of the arguments and options if necessary, although this is better done in the configure hook. A validation hook may take one of several actions:
|
Chris@0
|
269
|
Chris@0
|
270 - Do nothing. This indicates that validation succeeded.
|
Chris@0
|
271 - Return a CommandError. Validation fails, and execution stops. The CommandError contains a status result code and a message, which is printed.
|
Chris@0
|
272 - Throw an exception. The exception is converted into a CommandError.
|
Chris@0
|
273 - Return false. Message is empty, and status is 1. Deprecated.
|
Chris@0
|
274
|
Chris@0
|
275 The validate hook may change the arguments and options of the command by modifying the Input object in the provided CommandData parameter. Any number of validation hooks may run, but if any fails, then execution of the command stops.
|
Chris@0
|
276 ```
|
Chris@0
|
277 use Consolidation\AnnotatedCommand\CommandData;
|
Chris@0
|
278
|
Chris@0
|
279 /**
|
Chris@0
|
280 * @hook validate some:command
|
Chris@0
|
281 */
|
Chris@0
|
282 public function validatePassword(CommandData $commandData)
|
Chris@0
|
283 {
|
Chris@0
|
284 $input = $commandData->input();
|
Chris@0
|
285 $password = $input->getOption('password');
|
Chris@0
|
286
|
Chris@0
|
287 if (strpbrk($password, '!;$`') === false) {
|
Chris@0
|
288 throw new \Exception("Your password MUST contain at least one of the characters ! ; ` or $, for no rational reason whatsoever.");
|
Chris@0
|
289 }
|
Chris@0
|
290 }
|
Chris@0
|
291 ```
|
Chris@0
|
292
|
Chris@0
|
293 ### Command Hook
|
Chris@0
|
294
|
Chris@0
|
295 The command hook is provided for semantic purposes. The pre-command and command hooks are equivalent to the post-validate hook, and should confirm to the interface ([ValidatorInterface](src/Hooks/ValidatorInterface.php)). All of the post-validate hooks will be called before the first pre-command hook is called. Similarly, the post-command hook is equivalent to the pre-process hook, and should implement the interface ([ProcessResultInterface](src/Hooks/ProcessResultInterface.php)).
|
Chris@0
|
296
|
Chris@0
|
297 The command callback itself (the method annotated @command) is called after the last command hook, and prior to the first post-command hook.
|
Chris@0
|
298 ```
|
Chris@0
|
299 use Consolidation\AnnotatedCommand\CommandData;
|
Chris@0
|
300
|
Chris@0
|
301 /**
|
Chris@0
|
302 * @hook pre-command some:command
|
Chris@0
|
303 */
|
Chris@0
|
304 public function preCommand(CommandData $commandData)
|
Chris@0
|
305 {
|
Chris@0
|
306 // Do something before some:command
|
Chris@0
|
307 }
|
Chris@0
|
308
|
Chris@0
|
309 /**
|
Chris@0
|
310 * @hook post-command some:command
|
Chris@0
|
311 */
|
Chris@0
|
312 public function postCommand($result, CommandData $commandData)
|
Chris@0
|
313 {
|
Chris@0
|
314 // Do something after some:command
|
Chris@0
|
315 }
|
Chris@0
|
316 ```
|
Chris@0
|
317
|
Chris@0
|
318 ### Process Hook
|
Chris@0
|
319
|
Chris@0
|
320 The process hook ([ProcessResultInterface](src/Hooks/ProcessResultInterface.php)) is specifically designed to convert a series of processing instructions into a final result. An example of this is implemented in Robo in the [CollectionProcessHook](https://github.com/consolidation/Robo/blob/master/src/Collection/CollectionProcessHook.php) class; if a Robo command returns a TaskInterface, then a Robo process hook will execute the task and return the result. This allows a pre-process hook to alter the task, e.g. by adding more operations to a task collection.
|
Chris@0
|
321
|
Chris@0
|
322 The process hook should not be used for other purposes.
|
Chris@0
|
323 ```
|
Chris@0
|
324 use Consolidation\AnnotatedCommand\CommandData;
|
Chris@0
|
325
|
Chris@0
|
326 /**
|
Chris@0
|
327 * @hook process some:command
|
Chris@0
|
328 */
|
Chris@0
|
329 public function process($result, CommandData $commandData)
|
Chris@0
|
330 {
|
Chris@0
|
331 if ($result instanceof MyInterimType) {
|
Chris@0
|
332 $result = $this->convertInterimResult($result);
|
Chris@0
|
333 }
|
Chris@0
|
334 }
|
Chris@0
|
335 ```
|
Chris@0
|
336
|
Chris@0
|
337 ### Alter Hook
|
Chris@0
|
338
|
Chris@0
|
339 An alter hook ([AlterResultInterface](src/Hooks/AlterResultInterface.php)) changes the result object. Alter hooks should only operate on result objects of a type they explicitly recognize. They may return an object of the same type, or they may convert the object to some other type.
|
Chris@0
|
340
|
Chris@0
|
341 If something goes wrong, and the alter hooks wishes to force the command to fail, then it may either return a CommandError object, or throw an exception.
|
Chris@0
|
342 ```
|
Chris@0
|
343 use Consolidation\AnnotatedCommand\CommandData;
|
Chris@0
|
344
|
Chris@0
|
345 /**
|
Chris@0
|
346 * Demonstrate an alter hook with an option
|
Chris@0
|
347 *
|
Chris@0
|
348 * @hook alter some:command
|
Chris@0
|
349 * @option $alteration Alter the result of the command in some way.
|
Chris@0
|
350 * @usage some:command --alteration
|
Chris@0
|
351 */
|
Chris@0
|
352 public function alterSomeCommand($result, CommandData $commandData)
|
Chris@0
|
353 {
|
Chris@0
|
354 if ($commandData->input()->getOption('alteration')) {
|
Chris@0
|
355 $result[] = $this->getOneMoreRow();
|
Chris@0
|
356 }
|
Chris@0
|
357
|
Chris@0
|
358 return $result;
|
Chris@0
|
359 }
|
Chris@0
|
360 ```
|
Chris@0
|
361
|
Chris@0
|
362 If an option needs to be provided with a default value, that may be done via the `@default` annotation.
|
Chris@0
|
363
|
Chris@0
|
364 ```
|
Chris@0
|
365 use Consolidation\AnnotatedCommand\CommandData;
|
Chris@0
|
366
|
Chris@0
|
367 /**
|
Chris@0
|
368 * Demonstrate an alter hook with an option that has a default value
|
Chris@0
|
369 *
|
Chris@0
|
370 * @hook alter some:command
|
Chris@0
|
371 * @option $name Give the result a name.
|
Chris@0
|
372 * @default $name George
|
Chris@0
|
373 * @usage some:command --name=George
|
Chris@0
|
374 */
|
Chris@0
|
375 public function nameSomeCommand($result, CommandData $commandData)
|
Chris@0
|
376 {
|
Chris@0
|
377 $result['name'] = $commandData->input()->getOption('name')
|
Chris@0
|
378
|
Chris@0
|
379 return $result;
|
Chris@0
|
380 }
|
Chris@0
|
381 ```
|
Chris@0
|
382
|
Chris@0
|
383 ### Status Hook
|
Chris@0
|
384
|
Chris@17
|
385 **DEPRECATED**
|
Chris@17
|
386
|
Chris@17
|
387 Instead of using a Status Determiner hook, commands should simply return their exit code and result data separately using a CommandResult object.
|
Chris@17
|
388
|
Chris@0
|
389 The status hook ([StatusDeterminerInterface](src/Hooks/StatusDeterminerInterface.php)) is responsible for determing whether a command succeeded (status code 0) or failed (status code > 0). The result object returned by a command may be a compound object that contains multiple bits of information about the command result. If the result object implements [ExitCodeInterface](ExitCodeInterface.php), then the `getExitCode()` method of the result object is called to determine what the status result code for the command should be. If ExitCodeInterface is not implemented, then all of the status hooks attached to this command are executed; the first one that successfully returns a result will stop further execution of status hooks, and the result it returned will be used as the status result code for this operation.
|
Chris@0
|
390
|
Chris@0
|
391 If no status hook returns any result, then success is presumed.
|
Chris@0
|
392
|
Chris@0
|
393 ### Extract Hook
|
Chris@0
|
394
|
Chris@17
|
395 **DEPRECATED**
|
Chris@17
|
396
|
Chris@17
|
397 See [RowsOfFieldsWithMetadata in output-formatters](https://github.com/consolidation/output-formatters/blob/master/src/StructuredData/RowsOfFieldsWithMetadata.php) for an alternative that is more flexible for most use cases.
|
Chris@17
|
398
|
Chris@0
|
399 The extract hook ([ExtractOutputInterface](src/Hooks/ExtractOutputInterface.php)) is responsible for determining what the actual rendered output for the command should be. The result object returned by a command may be a compound object that contains multiple bits of information about the command result. If the result object implements [OutputDataInterface](OutputDataInterface.php), then the `getOutputData()` method of the result object is called to determine what information should be displayed to the user as a result of the command's execution. If OutputDataInterface is not implemented, then all of the extract hooks attached to this command are executed; the first one that successfully returns output data will stop further execution of extract hooks.
|
Chris@0
|
400
|
Chris@0
|
401 If no extract hook returns any data, then the result object itself is printed if it is a string; otherwise, no output is emitted (other than any produced by the command itself).
|
Chris@0
|
402
|
Chris@0
|
403 ### On-Event hook
|
Chris@0
|
404
|
Chris@0
|
405 Commands can define their own custom events; to do so, they need only implement the CustomEventAwareInterface, and use the CustomEventAwareTrait. Event handlers for each custom event can then be defined using the on-event hook.
|
Chris@0
|
406
|
Chris@0
|
407 A handler using an on-event hook looks something like the following:
|
Chris@0
|
408 ```
|
Chris@0
|
409 /**
|
Chris@0
|
410 * @hook on-event custom-event
|
Chris@0
|
411 */
|
Chris@0
|
412 public function handlerForCustomEvent(/* arbitrary parameters, as defined by custom-event */)
|
Chris@0
|
413 {
|
Chris@0
|
414 // do the needful, return what custom-event expects
|
Chris@0
|
415 }
|
Chris@0
|
416 ```
|
Chris@0
|
417 Then, to utilize this in a command:
|
Chris@0
|
418 ```
|
Chris@0
|
419 class MyCommands implements CustomEventAwareInterface
|
Chris@0
|
420 {
|
Chris@0
|
421 use CustomEventAwareTrait;
|
Chris@0
|
422
|
Chris@0
|
423 /**
|
Chris@0
|
424 * @command my-command
|
Chris@0
|
425 */
|
Chris@0
|
426 public myCommand($options = [])
|
Chris@0
|
427 {
|
Chris@0
|
428 $handlers = $this->getCustomEventHandlers('custom-event');
|
Chris@0
|
429 // iterate and call $handlers
|
Chris@0
|
430 }
|
Chris@0
|
431 }
|
Chris@0
|
432 ```
|
Chris@0
|
433 It is up to the command that defines the custom event to declare what the expected parameters for the callback function should be, and what the return value is and how it should be used.
|
Chris@0
|
434
|
Chris@0
|
435 ### Replace Command Hook
|
Chris@0
|
436
|
Chris@0
|
437 The replace-command ([ReplaceCommandHookInterface](src/Hooks/ReplaceCommandHookInterface.php)) hook permits you to replace a command's method with another method of your own.
|
Chris@0
|
438
|
Chris@0
|
439 For instance, if you'd like to replace the `foo:bar` command, you could utilize the following code:
|
Chris@0
|
440
|
Chris@0
|
441 ```php
|
Chris@0
|
442 <?php
|
Chris@0
|
443 class MyReplaceCommandHook {
|
Chris@0
|
444
|
Chris@0
|
445 /**
|
Chris@0
|
446 * @hook replace-command foo:bar
|
Chris@17
|
447 *
|
Chris@17
|
448 * Parameters must match original command method.
|
Chris@0
|
449 */
|
Chris@0
|
450 public function myFooBarReplacement($value) {
|
Chris@0
|
451 print "Hello $value!";
|
Chris@0
|
452 }
|
Chris@0
|
453 }
|
Chris@0
|
454 ```
|
Chris@0
|
455
|
Chris@0
|
456 ## Output
|
Chris@0
|
457
|
Chris@0
|
458 If a command method returns an integer, it is used as the command exit status code. If the command method returns a string, it is printed.
|
Chris@0
|
459
|
Chris@0
|
460 If the [Consolidation/OutputFormatters](https://github.com/consolidation/output-formatters) project is used, then users may specify a --format option to select the formatter to use to transform the output from whatever form the command provides to a string. To make this work, the application must provide a formatter to the AnnotatedCommandFactory. See [API Usage](#api-usage) below.
|
Chris@0
|
461
|
Chris@0
|
462 ## Logging
|
Chris@0
|
463
|
Chris@0
|
464 The Annotated-Command project is completely agnostic to logging. If a command wishes to log progress, then the CommandFile class should implement LoggerAwareInterface, and the Commandline tool should inject a logger for its use via the LoggerAwareTrait `setLogger()` method. Using [Robo](https://github.com/consolidation/robo) is recommended.
|
Chris@0
|
465
|
Chris@0
|
466 ## Access to Symfony Objects
|
Chris@0
|
467
|
Chris@0
|
468 If you want to use annotations, but still want access to the Symfony Command, e.g. to get a reference to the helpers in order to call some legacy code, you may create an ordinary Symfony Command that extends \Consolidation\AnnotatedCommand\AnnotatedCommand, which is a \Symfony\Component\Console\Command\Command. Omit the configure method, and place your annotations on the `execute()` method.
|
Chris@0
|
469
|
Chris@0
|
470 It is also possible to add InputInterface and/or OutputInterface parameters to any annotated method of a command file (the parameters must go before command arguments).
|
Chris@0
|
471
|
Chris@17
|
472 ## Parameter Injection
|
Chris@17
|
473
|
Chris@17
|
474 Just as this library will by default inject $input and/or $output at the head of the parameter list of any command function, it is also possible to add a handler to inject other objects as well.
|
Chris@17
|
475
|
Chris@17
|
476 Given an implementation of SymfonyStyleInjector similar to the example below:
|
Chris@17
|
477 ```
|
Chris@17
|
478 use Consolidation\AnnotatedCommand\ParameterInjector
|
Chris@17
|
479
|
Chris@17
|
480 class SymfonyStyleInjector implements ParameterInjector
|
Chris@17
|
481 {
|
Chris@17
|
482 public function get(CommandData $commandData, $interfaceName)
|
Chris@17
|
483 {
|
Chris@17
|
484 return new MySymfonyStyle($commandData->input(), $commandData->output());
|
Chris@17
|
485 }
|
Chris@17
|
486 }
|
Chris@17
|
487 ```
|
Chris@17
|
488 Then, an instance of 'MySymfonyStyle' will be provided to any command handler method that takes a SymfonyStyle parameter if the SymfonyStyleInjector is registered in your application's initialization code like so:
|
Chris@17
|
489 ```
|
Chris@17
|
490 $commandProcessor->parameterInjection()->register('Symfony\Component\Console\Style\SymfonyStyle', new SymfonyStyleInjector);
|
Chris@17
|
491 ```
|
Chris@17
|
492
|
Chris@17
|
493 ## Handling Standard Input
|
Chris@17
|
494
|
Chris@17
|
495 Any Symfony command may use the provides StdinHandler to imlement commands that read from standard input.
|
Chris@17
|
496
|
Chris@17
|
497 ```php
|
Chris@17
|
498 /**
|
Chris@17
|
499 * @command example
|
Chris@17
|
500 * @option string $file
|
Chris@17
|
501 * @default $file -
|
Chris@17
|
502 */
|
Chris@17
|
503 public function example(InputInterface $input)
|
Chris@17
|
504 {
|
Chris@17
|
505 $data = StdinHandler::selectStream($input, 'file')->contents();
|
Chris@17
|
506 }
|
Chris@17
|
507 ```
|
Chris@17
|
508 This example will read all of the data available from the stdin stream into $data, or, alternately, will read the entire contents of the file specified via the `--file=/path` option.
|
Chris@17
|
509
|
Chris@17
|
510 For more details, including examples of using the StdinHandle with a DI container, see the comments in [StdinHandler.php](src/Input/StdinHandler.php).
|
Chris@17
|
511
|
Chris@0
|
512 ## API Usage
|
Chris@0
|
513
|
Chris@0
|
514 If you would like to use Annotated Commands to build a commandline tool, it is recommended that you use [Robo as a framework](http://robo.li/framework), as it will set up all of the various command classes for you. If you would like to integrate Annotated Commands into some other framework, see the sections below.
|
Chris@0
|
515
|
Chris@0
|
516 ### Set up Command Factory and Instantiate Commands
|
Chris@0
|
517
|
Chris@0
|
518 To use annotated commands in an application, pass an instance of your command class in to AnnotatedCommandFactory::createCommandsFromClass(). The result will be a list of Commands that may be added to your application.
|
Chris@0
|
519 ```php
|
Chris@0
|
520 $myCommandClassInstance = new MyCommandClass();
|
Chris@0
|
521 $commandFactory = new AnnotatedCommandFactory();
|
Chris@0
|
522 $commandFactory->setIncludeAllPublicMethods(true);
|
Chris@0
|
523 $commandFactory->commandProcessor()->setFormatterManager(new FormatterManager());
|
Chris@0
|
524 $commandList = $commandFactory->createCommandsFromClass($myCommandClassInstance);
|
Chris@0
|
525 foreach ($commandList as $command) {
|
Chris@0
|
526 $application->add($command);
|
Chris@0
|
527 }
|
Chris@0
|
528 ```
|
Chris@0
|
529 You may have more than one command class, if you wish. If so, simply call AnnotatedCommandFactory::createCommandsFromClass() multiple times.
|
Chris@0
|
530
|
Chris@0
|
531 If you do not wish every public method in your classes to be added as commands, use `AnnotatedCommandFactory::setIncludeAllPublicMethods(false)`, and only methods annotated with @command will become commands.
|
Chris@0
|
532
|
Chris@0
|
533 Note that the `setFormatterManager()` operation is optional; omit this if not using [Consolidation/OutputFormatters](https://github.com/consolidation/output-formatters).
|
Chris@0
|
534
|
Chris@0
|
535 A CommandInfoAltererInterface can be added via AnnotatedCommandFactory::addCommandInfoAlterer(); it will be given the opportunity to adjust every CommandInfo object parsed from a command file prior to the creation of commands.
|
Chris@0
|
536
|
Chris@0
|
537 ### Command File Discovery
|
Chris@0
|
538
|
Chris@0
|
539 A discovery class, CommandFileDiscovery, is also provided to help find command files on the filesystem. Usage is as follows:
|
Chris@0
|
540 ```php
|
Chris@0
|
541 $discovery = new CommandFileDiscovery();
|
Chris@0
|
542 $myCommandFiles = $discovery->discover($path, '\Drupal');
|
Chris@0
|
543 foreach ($myCommandFiles as $myCommandClass) {
|
Chris@0
|
544 $myCommandClassInstance = new $myCommandClass();
|
Chris@0
|
545 // ... as above
|
Chris@0
|
546 }
|
Chris@0
|
547 ```
|
Chris@0
|
548 For a discussion on command file naming conventions and search locations, see https://github.com/consolidation/annotated-command/issues/12.
|
Chris@0
|
549
|
Chris@0
|
550 If different namespaces are used at different command file paths, change the call to discover as follows:
|
Chris@0
|
551 ```php
|
Chris@0
|
552 $myCommandFiles = $discovery->discover(['\Ns1' => $path1, '\Ns2' => $path2]);
|
Chris@0
|
553 ```
|
Chris@0
|
554 As a shortcut for the above, the method `discoverNamespaced()` will take the last directory name of each path, and append it to the base namespace provided. This matches the conventions used by Drupal modules, for example.
|
Chris@0
|
555
|
Chris@0
|
556 ### Configuring Output Formatts (e.g. to enable wordwrap)
|
Chris@0
|
557
|
Chris@0
|
558 The Output Formatters project supports automatic formatting of tabular output. In order for wordwrapping to work correctly, the terminal width must be passed in to the Output Formatters handlers via `FormatterOptions::setWidth()`.
|
Chris@0
|
559
|
Chris@0
|
560 In the Annotated Commands project, this is done via dependency injection. If a `PrepareFormatter` object is passed to `CommandProcessor::addPrepareFormatter()`, then it will be given an opportunity to set properties on the `FormatterOptions` when it is created.
|
Chris@0
|
561
|
Chris@0
|
562 A `PrepareTerminalWidthOption` class is provided to use the Symfony Application class to fetch the terminal width, and provide it to the FormatterOptions. It is injected as follows:
|
Chris@0
|
563 ```php
|
Chris@0
|
564 $terminalWidthOption = new PrepareTerminalWidthOption();
|
Chris@0
|
565 $terminalWidthOption->setApplication($application);
|
Chris@0
|
566 $commandFactory->commandProcessor()->addPrepareFormatter($terminalWidthOption);
|
Chris@0
|
567 ```
|
Chris@0
|
568 To provide greater control over the width used, create your own `PrepareTerminalWidthOption` subclass, and adjust the width as needed.
|
Chris@0
|
569
|
Chris@0
|
570 ## Other Callbacks
|
Chris@0
|
571
|
Chris@0
|
572 In addition to the hooks provided by the hook manager, there are additional callbacks available to alter the way the annotated command library operates.
|
Chris@0
|
573
|
Chris@0
|
574 ### Factory Listeners
|
Chris@0
|
575
|
Chris@0
|
576 Factory listeners are notified every time a command file instance is used to create annotated commands.
|
Chris@0
|
577 ```
|
Chris@0
|
578 public function AnnotatedCommandFactory::addListener(CommandCreationListenerInterface $listener);
|
Chris@0
|
579 ```
|
Chris@0
|
580 Listeners can be used to construct command file instances as they are provided to the command factory.
|
Chris@0
|
581
|
Chris@0
|
582 ### Option Providers
|
Chris@0
|
583
|
Chris@17
|
584 An option provider is given an opportunity to add options to a command as it is being constructed.
|
Chris@0
|
585 ```
|
Chris@0
|
586 public function AnnotatedCommandFactory::addAutomaticOptionProvider(AutomaticOptionsProviderInterface $listener);
|
Chris@0
|
587 ```
|
Chris@0
|
588 The complete CommandInfo record with all of the annotation data is available, so you can, for example, add an option `--foo` to every command whose method is annotated `@fooable`.
|
Chris@0
|
589
|
Chris@0
|
590 ### CommandInfo Alterers
|
Chris@0
|
591
|
Chris@0
|
592 CommandInfo alterers can adjust information about a command immediately before it is created. Typically, these will be used to supply default values for annotations custom to the command, or take other actions based on the interfaces implemented by the commandfile instance.
|
Chris@0
|
593 ```
|
Chris@0
|
594 public function alterCommandInfo(CommandInfo $commandInfo, $commandFileInstance);
|
Chris@0
|
595 ```
|