|
1 <?php |
|
2 |
|
3 /* |
|
4 * This file is part of the Symfony package. |
|
5 * |
|
6 * (c) Fabien Potencier <fabien@symfony.com> |
|
7 * |
|
8 * For the full copyright and license information, please view the LICENSE |
|
9 * file that was distributed with this source code. |
|
10 */ |
|
11 |
|
12 namespace Symfony\Component\Console\Command; |
|
13 |
|
14 use Symfony\Component\Console\Input\InputDefinition; |
|
15 use Symfony\Component\Console\Input\InputOption; |
|
16 use Symfony\Component\Console\Input\InputArgument; |
|
17 use Symfony\Component\Console\Input\InputInterface; |
|
18 use Symfony\Component\Console\Output\OutputInterface; |
|
19 use Symfony\Component\Console\Application; |
|
20 use Symfony\Component\Console\Helper\HelperSet; |
|
21 |
|
22 /** |
|
23 * Base class for all commands. |
|
24 * |
|
25 * @author Fabien Potencier <fabien@symfony.com> |
|
26 * |
|
27 * @api |
|
28 */ |
|
29 class Command |
|
30 { |
|
31 private $application; |
|
32 private $name; |
|
33 private $aliases; |
|
34 private $definition; |
|
35 private $help; |
|
36 private $description; |
|
37 private $ignoreValidationErrors; |
|
38 private $applicationDefinitionMerged; |
|
39 private $code; |
|
40 private $synopsis; |
|
41 private $helperSet; |
|
42 |
|
43 /** |
|
44 * Constructor. |
|
45 * |
|
46 * @param string $name The name of the command |
|
47 * |
|
48 * @throws \LogicException When the command name is empty |
|
49 * |
|
50 * @api |
|
51 */ |
|
52 public function __construct($name = null) |
|
53 { |
|
54 $this->definition = new InputDefinition(); |
|
55 $this->ignoreValidationErrors = false; |
|
56 $this->applicationDefinitionMerged = false; |
|
57 $this->aliases = array(); |
|
58 |
|
59 if (null !== $name) { |
|
60 $this->setName($name); |
|
61 } |
|
62 |
|
63 $this->configure(); |
|
64 |
|
65 if (!$this->name) { |
|
66 throw new \LogicException('The command name cannot be empty.'); |
|
67 } |
|
68 } |
|
69 |
|
70 /** |
|
71 * Sets the application instance for this command. |
|
72 * |
|
73 * @param Application $application An Application instance |
|
74 * |
|
75 * @api |
|
76 */ |
|
77 public function setApplication(Application $application = null) |
|
78 { |
|
79 $this->application = $application; |
|
80 if ($application) { |
|
81 $this->setHelperSet($application->getHelperSet()); |
|
82 } else { |
|
83 $this->helperSet = null; |
|
84 } |
|
85 } |
|
86 |
|
87 /** |
|
88 * Sets the helper set. |
|
89 * |
|
90 * @param HelperSet $helperSet A HelperSet instance |
|
91 */ |
|
92 public function setHelperSet(HelperSet $helperSet) |
|
93 { |
|
94 $this->helperSet = $helperSet; |
|
95 } |
|
96 |
|
97 /** |
|
98 * Gets the helper set. |
|
99 * |
|
100 * @return HelperSet A HelperSet instance |
|
101 */ |
|
102 public function getHelperSet() |
|
103 { |
|
104 return $this->helperSet; |
|
105 } |
|
106 |
|
107 /** |
|
108 * Gets the application instance for this command. |
|
109 * |
|
110 * @return Application An Application instance |
|
111 * |
|
112 * @api |
|
113 */ |
|
114 public function getApplication() |
|
115 { |
|
116 return $this->application; |
|
117 } |
|
118 |
|
119 /** |
|
120 * Configures the current command. |
|
121 */ |
|
122 protected function configure() |
|
123 { |
|
124 } |
|
125 |
|
126 /** |
|
127 * Executes the current command. |
|
128 * |
|
129 * This method is not abstract because you can use this class |
|
130 * as a concrete class. In this case, instead of defining the |
|
131 * execute() method, you set the code to execute by passing |
|
132 * a Closure to the setCode() method. |
|
133 * |
|
134 * @param InputInterface $input An InputInterface instance |
|
135 * @param OutputInterface $output An OutputInterface instance |
|
136 * |
|
137 * @return integer 0 if everything went fine, or an error code |
|
138 * |
|
139 * @throws \LogicException When this abstract method is not implemented |
|
140 * @see setCode() |
|
141 */ |
|
142 protected function execute(InputInterface $input, OutputInterface $output) |
|
143 { |
|
144 throw new \LogicException('You must override the execute() method in the concrete command class.'); |
|
145 } |
|
146 |
|
147 /** |
|
148 * Interacts with the user. |
|
149 * |
|
150 * @param InputInterface $input An InputInterface instance |
|
151 * @param OutputInterface $output An OutputInterface instance |
|
152 */ |
|
153 protected function interact(InputInterface $input, OutputInterface $output) |
|
154 { |
|
155 } |
|
156 |
|
157 /** |
|
158 * Initializes the command just after the input has been validated. |
|
159 * |
|
160 * This is mainly useful when a lot of commands extends one main command |
|
161 * where some things need to be initialized based on the input arguments and options. |
|
162 * |
|
163 * @param InputInterface $input An InputInterface instance |
|
164 * @param OutputInterface $output An OutputInterface instance |
|
165 */ |
|
166 protected function initialize(InputInterface $input, OutputInterface $output) |
|
167 { |
|
168 } |
|
169 |
|
170 /** |
|
171 * Runs the command. |
|
172 * |
|
173 * The code to execute is either defined directly with the |
|
174 * setCode() method or by overriding the execute() method |
|
175 * in a sub-class. |
|
176 * |
|
177 * @param InputInterface $input An InputInterface instance |
|
178 * @param OutputInterface $output An OutputInterface instance |
|
179 * |
|
180 * @see setCode() |
|
181 * @see execute() |
|
182 * |
|
183 * @api |
|
184 */ |
|
185 public function run(InputInterface $input, OutputInterface $output) |
|
186 { |
|
187 // force the creation of the synopsis before the merge with the app definition |
|
188 $this->getSynopsis(); |
|
189 |
|
190 // add the application arguments and options |
|
191 $this->mergeApplicationDefinition(); |
|
192 |
|
193 // bind the input against the command specific arguments/options |
|
194 try { |
|
195 $input->bind($this->definition); |
|
196 } catch (\Exception $e) { |
|
197 if (!$this->ignoreValidationErrors) { |
|
198 throw $e; |
|
199 } |
|
200 } |
|
201 |
|
202 $this->initialize($input, $output); |
|
203 |
|
204 if ($input->isInteractive()) { |
|
205 $this->interact($input, $output); |
|
206 } |
|
207 |
|
208 $input->validate(); |
|
209 |
|
210 if ($this->code) { |
|
211 return call_user_func($this->code, $input, $output); |
|
212 } |
|
213 |
|
214 return $this->execute($input, $output); |
|
215 } |
|
216 |
|
217 /** |
|
218 * Sets the code to execute when running this command. |
|
219 * |
|
220 * If this method is used, it overrides the code defined |
|
221 * in the execute() method. |
|
222 * |
|
223 * @param \Closure $code A \Closure |
|
224 * |
|
225 * @return Command The current instance |
|
226 * |
|
227 * @see execute() |
|
228 * |
|
229 * @api |
|
230 */ |
|
231 public function setCode(\Closure $code) |
|
232 { |
|
233 $this->code = $code; |
|
234 |
|
235 return $this; |
|
236 } |
|
237 |
|
238 /** |
|
239 * Merges the application definition with the command definition. |
|
240 */ |
|
241 private function mergeApplicationDefinition() |
|
242 { |
|
243 if (null === $this->application || true === $this->applicationDefinitionMerged) { |
|
244 return; |
|
245 } |
|
246 |
|
247 $this->definition->setArguments(array_merge( |
|
248 $this->application->getDefinition()->getArguments(), |
|
249 $this->definition->getArguments() |
|
250 )); |
|
251 |
|
252 $this->definition->addOptions($this->application->getDefinition()->getOptions()); |
|
253 |
|
254 $this->applicationDefinitionMerged = true; |
|
255 } |
|
256 |
|
257 /** |
|
258 * Sets an array of argument and option instances. |
|
259 * |
|
260 * @param array|Definition $definition An array of argument and option instances or a definition instance |
|
261 * |
|
262 * @return Command The current instance |
|
263 * |
|
264 * @api |
|
265 */ |
|
266 public function setDefinition($definition) |
|
267 { |
|
268 if ($definition instanceof InputDefinition) { |
|
269 $this->definition = $definition; |
|
270 } else { |
|
271 $this->definition->setDefinition($definition); |
|
272 } |
|
273 |
|
274 $this->applicationDefinitionMerged = false; |
|
275 |
|
276 return $this; |
|
277 } |
|
278 |
|
279 /** |
|
280 * Gets the InputDefinition attached to this Command. |
|
281 * |
|
282 * @return InputDefinition An InputDefinition instance |
|
283 * |
|
284 * @api |
|
285 */ |
|
286 public function getDefinition() |
|
287 { |
|
288 return $this->definition; |
|
289 } |
|
290 |
|
291 /** |
|
292 * Adds an argument. |
|
293 * |
|
294 * @param string $name The argument name |
|
295 * @param integer $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL |
|
296 * @param string $description A description text |
|
297 * @param mixed $default The default value (for InputArgument::OPTIONAL mode only) |
|
298 * |
|
299 * @return Command The current instance |
|
300 * |
|
301 * @api |
|
302 */ |
|
303 public function addArgument($name, $mode = null, $description = '', $default = null) |
|
304 { |
|
305 $this->definition->addArgument(new InputArgument($name, $mode, $description, $default)); |
|
306 |
|
307 return $this; |
|
308 } |
|
309 |
|
310 /** |
|
311 * Adds an option. |
|
312 * |
|
313 * @param string $name The option name |
|
314 * @param string $shortcut The shortcut (can be null) |
|
315 * @param integer $mode The option mode: One of the InputOption::VALUE_* constants |
|
316 * @param string $description A description text |
|
317 * @param mixed $default The default value (must be null for InputOption::VALUE_REQUIRED or self::VALUE_NONE) |
|
318 * |
|
319 * @return Command The current instance |
|
320 * |
|
321 * @api |
|
322 */ |
|
323 public function addOption($name, $shortcut = null, $mode = null, $description = '', $default = null) |
|
324 { |
|
325 $this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default)); |
|
326 |
|
327 return $this; |
|
328 } |
|
329 |
|
330 /** |
|
331 * Sets the name of the command. |
|
332 * |
|
333 * This method can set both the namespace and the name if |
|
334 * you separate them by a colon (:) |
|
335 * |
|
336 * $command->setName('foo:bar'); |
|
337 * |
|
338 * @param string $name The command name |
|
339 * |
|
340 * @return Command The current instance |
|
341 * |
|
342 * @throws \InvalidArgumentException When command name given is empty |
|
343 * |
|
344 * @api |
|
345 */ |
|
346 public function setName($name) |
|
347 { |
|
348 $this->validateName($name); |
|
349 |
|
350 $this->name = $name; |
|
351 |
|
352 return $this; |
|
353 } |
|
354 |
|
355 /** |
|
356 * Returns the command name. |
|
357 * |
|
358 * @return string The command name |
|
359 * |
|
360 * @api |
|
361 */ |
|
362 public function getName() |
|
363 { |
|
364 return $this->name; |
|
365 } |
|
366 |
|
367 /** |
|
368 * Sets the description for the command. |
|
369 * |
|
370 * @param string $description The description for the command |
|
371 * |
|
372 * @return Command The current instance |
|
373 * |
|
374 * @api |
|
375 */ |
|
376 public function setDescription($description) |
|
377 { |
|
378 $this->description = $description; |
|
379 |
|
380 return $this; |
|
381 } |
|
382 |
|
383 /** |
|
384 * Returns the description for the command. |
|
385 * |
|
386 * @return string The description for the command |
|
387 * |
|
388 * @api |
|
389 */ |
|
390 public function getDescription() |
|
391 { |
|
392 return $this->description; |
|
393 } |
|
394 |
|
395 /** |
|
396 * Sets the help for the command. |
|
397 * |
|
398 * @param string $help The help for the command |
|
399 * |
|
400 * @return Command The current instance |
|
401 * |
|
402 * @api |
|
403 */ |
|
404 public function setHelp($help) |
|
405 { |
|
406 $this->help = $help; |
|
407 |
|
408 return $this; |
|
409 } |
|
410 |
|
411 /** |
|
412 * Returns the help for the command. |
|
413 * |
|
414 * @return string The help for the command |
|
415 * |
|
416 * @api |
|
417 */ |
|
418 public function getHelp() |
|
419 { |
|
420 return $this->help; |
|
421 } |
|
422 |
|
423 /** |
|
424 * Returns the processed help for the command replacing the %command.name% and |
|
425 * %command.full_name% patterns with the real values dynamically. |
|
426 * |
|
427 * @return string The processed help for the command |
|
428 */ |
|
429 public function getProcessedHelp() |
|
430 { |
|
431 $name = $this->name; |
|
432 |
|
433 $placeholders = array( |
|
434 '%command.name%', |
|
435 '%command.full_name%' |
|
436 ); |
|
437 $replacements = array( |
|
438 $name, |
|
439 $_SERVER['PHP_SELF'].' '.$name |
|
440 ); |
|
441 |
|
442 return str_replace($placeholders, $replacements, $this->getHelp()); |
|
443 } |
|
444 |
|
445 /** |
|
446 * Sets the aliases for the command. |
|
447 * |
|
448 * @param array $aliases An array of aliases for the command |
|
449 * |
|
450 * @return Command The current instance |
|
451 * |
|
452 * @api |
|
453 */ |
|
454 public function setAliases($aliases) |
|
455 { |
|
456 foreach ($aliases as $alias) { |
|
457 $this->validateName($alias); |
|
458 } |
|
459 |
|
460 $this->aliases = $aliases; |
|
461 |
|
462 return $this; |
|
463 } |
|
464 |
|
465 /** |
|
466 * Returns the aliases for the command. |
|
467 * |
|
468 * @return array An array of aliases for the command |
|
469 * |
|
470 * @api |
|
471 */ |
|
472 public function getAliases() |
|
473 { |
|
474 return $this->aliases; |
|
475 } |
|
476 |
|
477 /** |
|
478 * Returns the synopsis for the command. |
|
479 * |
|
480 * @return string The synopsis |
|
481 */ |
|
482 public function getSynopsis() |
|
483 { |
|
484 if (null === $this->synopsis) { |
|
485 $this->synopsis = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis())); |
|
486 } |
|
487 |
|
488 return $this->synopsis; |
|
489 } |
|
490 |
|
491 /** |
|
492 * Gets a helper instance by name. |
|
493 * |
|
494 * @param string $name The helper name |
|
495 * |
|
496 * @return mixed The helper value |
|
497 * |
|
498 * @throws \InvalidArgumentException if the helper is not defined |
|
499 * |
|
500 * @api |
|
501 */ |
|
502 public function getHelper($name) |
|
503 { |
|
504 return $this->helperSet->get($name); |
|
505 } |
|
506 |
|
507 /** |
|
508 * Returns a text representation of the command. |
|
509 * |
|
510 * @return string A string representing the command |
|
511 */ |
|
512 public function asText() |
|
513 { |
|
514 $messages = array( |
|
515 '<comment>Usage:</comment>', |
|
516 ' '.$this->getSynopsis(), |
|
517 '', |
|
518 ); |
|
519 |
|
520 if ($this->getAliases()) { |
|
521 $messages[] = '<comment>Aliases:</comment> <info>'.implode(', ', $this->getAliases()).'</info>'; |
|
522 } |
|
523 |
|
524 $messages[] = $this->definition->asText(); |
|
525 |
|
526 if ($help = $this->getProcessedHelp()) { |
|
527 $messages[] = '<comment>Help:</comment>'; |
|
528 $messages[] = ' '.implode("\n ", explode("\n", $help))."\n"; |
|
529 } |
|
530 |
|
531 return implode("\n", $messages); |
|
532 } |
|
533 |
|
534 /** |
|
535 * Returns an XML representation of the command. |
|
536 * |
|
537 * @param Boolean $asDom Whether to return a DOM or an XML string |
|
538 * |
|
539 * @return string|DOMDocument An XML string representing the command |
|
540 */ |
|
541 public function asXml($asDom = false) |
|
542 { |
|
543 $dom = new \DOMDocument('1.0', 'UTF-8'); |
|
544 $dom->formatOutput = true; |
|
545 $dom->appendChild($commandXML = $dom->createElement('command')); |
|
546 $commandXML->setAttribute('id', $this->name); |
|
547 $commandXML->setAttribute('name', $this->name); |
|
548 |
|
549 $commandXML->appendChild($usageXML = $dom->createElement('usage')); |
|
550 $usageXML->appendChild($dom->createTextNode(sprintf($this->getSynopsis(), ''))); |
|
551 |
|
552 $commandXML->appendChild($descriptionXML = $dom->createElement('description')); |
|
553 $descriptionXML->appendChild($dom->createTextNode(implode("\n ", explode("\n", $this->getDescription())))); |
|
554 |
|
555 $commandXML->appendChild($helpXML = $dom->createElement('help')); |
|
556 $help = $this->help; |
|
557 $helpXML->appendChild($dom->createTextNode(implode("\n ", explode("\n", $help)))); |
|
558 |
|
559 $commandXML->appendChild($aliasesXML = $dom->createElement('aliases')); |
|
560 foreach ($this->getAliases() as $alias) { |
|
561 $aliasesXML->appendChild($aliasXML = $dom->createElement('alias')); |
|
562 $aliasXML->appendChild($dom->createTextNode($alias)); |
|
563 } |
|
564 |
|
565 $definition = $this->definition->asXml(true); |
|
566 $commandXML->appendChild($dom->importNode($definition->getElementsByTagName('arguments')->item(0), true)); |
|
567 $commandXML->appendChild($dom->importNode($definition->getElementsByTagName('options')->item(0), true)); |
|
568 |
|
569 return $asDom ? $dom : $dom->saveXml(); |
|
570 } |
|
571 |
|
572 private function validateName($name) |
|
573 { |
|
574 if (!preg_match('/^[^\:]+(\:[^\:]+)*$/', $name)) { |
|
575 throw new \InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name)); |
|
576 } |
|
577 } |
|
578 } |