source: pro-violet-viettel/sourcecode/application/libraries/Doctrine/Symfony/Component/Console/Application.php @ 345

Last change on this file since 345 was 345, checked in by quyenla, 11 years ago

collaborator page

File size: 24.8 KB
Line 
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
12namespace Symfony\Component\Console;
13
14use Symfony\Component\Console\Input\InputInterface;
15use Symfony\Component\Console\Input\ArgvInput;
16use Symfony\Component\Console\Input\ArrayInput;
17use Symfony\Component\Console\Input\InputDefinition;
18use Symfony\Component\Console\Input\InputOption;
19use Symfony\Component\Console\Input\InputArgument;
20use Symfony\Component\Console\Output\OutputInterface;
21use Symfony\Component\Console\Output\Output;
22use Symfony\Component\Console\Output\ConsoleOutput;
23use Symfony\Component\Console\Command\Command;
24use Symfony\Component\Console\Command\HelpCommand;
25use Symfony\Component\Console\Command\ListCommand;
26use Symfony\Component\Console\Helper\HelperSet;
27use Symfony\Component\Console\Helper\FormatterHelper;
28use Symfony\Component\Console\Helper\DialogHelper;
29
30/**
31 * An Application is the container for a collection of commands.
32 *
33 * It is the main entry point of a Console application.
34 *
35 * This class is optimized for a standard CLI environment.
36 *
37 * Usage:
38 *
39 *     $app = new Application('myapp', '1.0 (stable)');
40 *     $app->add(new SimpleCommand());
41 *     $app->run();
42 *
43 * @author Fabien Potencier <fabien@symfony.com>
44 *
45 * @api
46 */
47class Application
48{
49    private $commands;
50    private $wantHelps = false;
51    private $runningCommand;
52    private $name;
53    private $version;
54    private $catchExceptions;
55    private $autoExit;
56    private $definition;
57    private $helperSet;
58
59    /**
60     * Constructor.
61     *
62     * @param string  $name    The name of the application
63     * @param string  $version The version of the application
64     *
65     * @api
66     */
67    public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN')
68    {
69        $this->name = $name;
70        $this->version = $version;
71        $this->catchExceptions = true;
72        $this->autoExit = true;
73        $this->commands = array();
74        $this->helperSet = new HelperSet(array(
75            new FormatterHelper(),
76            new DialogHelper(),
77        ));
78
79        $this->add(new HelpCommand());
80        $this->add(new ListCommand());
81
82        $this->definition = new InputDefinition(array(
83            new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
84
85            new InputOption('--help',           '-h', InputOption::VALUE_NONE, 'Display this help message.'),
86            new InputOption('--quiet',          '-q', InputOption::VALUE_NONE, 'Do not output any message.'),
87            new InputOption('--verbose',        '-v', InputOption::VALUE_NONE, 'Increase verbosity of messages.'),
88            new InputOption('--version',        '-V', InputOption::VALUE_NONE, 'Display this program version.'),
89            new InputOption('--ansi',           '',   InputOption::VALUE_NONE, 'Force ANSI output.'),
90            new InputOption('--no-ansi',        '',   InputOption::VALUE_NONE, 'Disable ANSI output.'),
91            new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question.'),
92        ));
93    }
94
95    /**
96     * Runs the current application.
97     *
98     * @param InputInterface  $input  An Input instance
99     * @param OutputInterface $output An Output instance
100     *
101     * @return integer 0 if everything went fine, or an error code
102     *
103     * @throws \Exception When doRun returns Exception
104     *
105     * @api
106     */
107    public function run(InputInterface $input = null, OutputInterface $output = null)
108    {
109        if (null === $input) {
110            $input = new ArgvInput();
111        }
112
113        if (null === $output) {
114            $output = new ConsoleOutput();
115        }
116
117        try {
118            $statusCode = $this->doRun($input, $output);
119        } catch (\Exception $e) {
120            if (!$this->catchExceptions) {
121                throw $e;
122            }
123
124            $this->renderException($e, $output);
125            $statusCode = $e->getCode();
126
127            $statusCode = is_numeric($statusCode) && $statusCode ? $statusCode : 1;
128        }
129
130        if ($this->autoExit) {
131            if ($statusCode > 255) {
132                $statusCode = 255;
133            }
134            // @codeCoverageIgnoreStart
135            exit($statusCode);
136            // @codeCoverageIgnoreEnd
137        }
138
139        return $statusCode;
140    }
141
142    /**
143     * Runs the current application.
144     *
145     * @param InputInterface  $input  An Input instance
146     * @param OutputInterface $output An Output instance
147     *
148     * @return integer 0 if everything went fine, or an error code
149     */
150    public function doRun(InputInterface $input, OutputInterface $output)
151    {
152        $name = $this->getCommandName($input);
153
154        if (true === $input->hasParameterOption(array('--ansi'))) {
155            $output->setDecorated(true);
156        } elseif (true === $input->hasParameterOption(array('--no-ansi'))) {
157            $output->setDecorated(false);
158        }
159
160        if (true === $input->hasParameterOption(array('--help', '-h'))) {
161            if (!$name) {
162                $name = 'help';
163                $input = new ArrayInput(array('command' => 'help'));
164            } else {
165                $this->wantHelps = true;
166            }
167        }
168
169        if (true === $input->hasParameterOption(array('--no-interaction', '-n'))) {
170            $input->setInteractive(false);
171        }
172
173        if (true === $input->hasParameterOption(array('--quiet', '-q'))) {
174            $output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
175        } elseif (true === $input->hasParameterOption(array('--verbose', '-v'))) {
176            $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
177        }
178
179        if (true === $input->hasParameterOption(array('--version', '-V'))) {
180            $output->writeln($this->getLongVersion());
181
182            return 0;
183        }
184
185        if (!$name) {
186            $name = 'list';
187            $input = new ArrayInput(array('command' => 'list'));
188        }
189
190        // the command name MUST be the first element of the input
191        $command = $this->find($name);
192
193        $this->runningCommand = $command;
194        $statusCode = $command->run($input, $output);
195        $this->runningCommand = null;
196
197        return is_numeric($statusCode) ? $statusCode : 0;
198    }
199
200    /**
201     * Set a helper set to be used with the command.
202     *
203     * @param HelperSet $helperSet The helper set
204     *
205     * @api
206     */
207    public function setHelperSet(HelperSet $helperSet)
208    {
209        $this->helperSet = $helperSet;
210    }
211
212    /**
213     * Get the helper set associated with the command.
214     *
215     * @return HelperSet The HelperSet instance associated with this command
216     *
217     * @api
218     */
219    public function getHelperSet()
220    {
221        return $this->helperSet;
222    }
223
224    /**
225     * Gets the InputDefinition related to this Application.
226     *
227     * @return InputDefinition The InputDefinition instance
228     */
229    public function getDefinition()
230    {
231        return $this->definition;
232    }
233
234    /**
235     * Gets the help message.
236     *
237     * @return string A help message.
238     */
239    public function getHelp()
240    {
241        $messages = array(
242            $this->getLongVersion(),
243            '',
244            '<comment>Usage:</comment>',
245            sprintf("  [options] command [arguments]\n"),
246            '<comment>Options:</comment>',
247        );
248
249        foreach ($this->getDefinition()->getOptions() as $option) {
250            $messages[] = sprintf('  %-29s %s %s',
251                '<info>--'.$option->getName().'</info>',
252                $option->getShortcut() ? '<info>-'.$option->getShortcut().'</info>' : '  ',
253                $option->getDescription()
254            );
255        }
256
257        return implode("\n", $messages);
258    }
259
260    /**
261     * Sets whether to catch exceptions or not during commands execution.
262     *
263     * @param Boolean $boolean Whether to catch exceptions or not during commands execution
264     *
265     * @api
266     */
267    public function setCatchExceptions($boolean)
268    {
269        $this->catchExceptions = (Boolean) $boolean;
270    }
271
272    /**
273     * Sets whether to automatically exit after a command execution or not.
274     *
275     * @param Boolean $boolean Whether to automatically exit after a command execution or not
276     *
277     * @api
278     */
279    public function setAutoExit($boolean)
280    {
281        $this->autoExit = (Boolean) $boolean;
282    }
283
284    /**
285     * Gets the name of the application.
286     *
287     * @return string The application name
288     *
289     * @api
290     */
291    public function getName()
292    {
293        return $this->name;
294    }
295
296    /**
297     * Sets the application name.
298     *
299     * @param string $name The application name
300     *
301     * @api
302     */
303    public function setName($name)
304    {
305        $this->name = $name;
306    }
307
308    /**
309     * Gets the application version.
310     *
311     * @return string The application version
312     *
313     * @api
314     */
315    public function getVersion()
316    {
317        return $this->version;
318    }
319
320    /**
321     * Sets the application version.
322     *
323     * @param string $version The application version
324     *
325     * @api
326     */
327    public function setVersion($version)
328    {
329        $this->version = $version;
330    }
331
332    /**
333     * Returns the long version of the application.
334     *
335     * @return string The long application version
336     *
337     * @api
338     */
339    public function getLongVersion()
340    {
341        if ('UNKNOWN' !== $this->getName() && 'UNKNOWN' !== $this->getVersion()) {
342            return sprintf('<info>%s</info> version <comment>%s</comment>', $this->getName(), $this->getVersion());
343        }
344
345        return '<info>Console Tool</info>';
346    }
347
348    /**
349     * Registers a new command.
350     *
351     * @param string $name The command name
352     *
353     * @return Command The newly created command
354     *
355     * @api
356     */
357    public function register($name)
358    {
359        return $this->add(new Command($name));
360    }
361
362    /**
363     * Adds an array of command objects.
364     *
365     * @param Command[] $commands An array of commands
366     *
367     * @api
368     */
369    public function addCommands(array $commands)
370    {
371        foreach ($commands as $command) {
372            $this->add($command);
373        }
374    }
375
376    /**
377     * Adds a command object.
378     *
379     * If a command with the same name already exists, it will be overridden.
380     *
381     * @param Command $command A Command object
382     *
383     * @return Command The registered command
384     *
385     * @api
386     */
387    public function add(Command $command)
388    {
389        $command->setApplication($this);
390
391        $this->commands[$command->getName()] = $command;
392
393        foreach ($command->getAliases() as $alias) {
394            $this->commands[$alias] = $command;
395        }
396
397        return $command;
398    }
399
400    /**
401     * Returns a registered command by name or alias.
402     *
403     * @param string $name The command name or alias
404     *
405     * @return Command A Command object
406     *
407     * @throws \InvalidArgumentException When command name given does not exist
408     *
409     * @api
410     */
411    public function get($name)
412    {
413        if (!isset($this->commands[$name])) {
414            throw new \InvalidArgumentException(sprintf('The command "%s" does not exist.', $name));
415        }
416
417        $command = $this->commands[$name];
418
419        if ($this->wantHelps) {
420            $this->wantHelps = false;
421
422            $helpCommand = $this->get('help');
423            $helpCommand->setCommand($command);
424
425            return $helpCommand;
426        }
427
428        return $command;
429    }
430
431    /**
432     * Returns true if the command exists, false otherwise.
433     *
434     * @param string $name The command name or alias
435     *
436     * @return Boolean true if the command exists, false otherwise
437     *
438     * @api
439     */
440    public function has($name)
441    {
442        return isset($this->commands[$name]);
443    }
444
445    /**
446     * Returns an array of all unique namespaces used by currently registered commands.
447     *
448     * It does not returns the global namespace which always exists.
449     *
450     * @return array An array of namespaces
451     */
452    public function getNamespaces()
453    {
454        $namespaces = array();
455        foreach ($this->commands as $command) {
456            $namespaces[] = $this->extractNamespace($command->getName());
457
458            foreach ($command->getAliases() as $alias) {
459                $namespaces[] = $this->extractNamespace($alias);
460            }
461        }
462
463        return array_values(array_unique(array_filter($namespaces)));
464    }
465
466    /**
467     * Finds a registered namespace by a name or an abbreviation.
468     *
469     * @param string $namespace A namespace or abbreviation to search for
470     *
471     * @return string A registered namespace
472     *
473     * @throws \InvalidArgumentException When namespace is incorrect or ambiguous
474     */
475    public function findNamespace($namespace)
476    {
477        $allNamespaces = array();
478        foreach ($this->getNamespaces() as $n) {
479            $allNamespaces[$n] = explode(':', $n);
480        }
481
482        $found = array();
483        foreach (explode(':', $namespace) as $i => $part) {
484            $abbrevs = static::getAbbreviations(array_unique(array_values(array_filter(array_map(function ($p) use ($i) { return isset($p[$i]) ? $p[$i] : ''; }, $allNamespaces)))));
485
486            if (!isset($abbrevs[$part])) {
487                throw new \InvalidArgumentException(sprintf('There are no commands defined in the "%s" namespace.', $namespace));
488            }
489
490            if (count($abbrevs[$part]) > 1) {
491                throw new \InvalidArgumentException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions($abbrevs[$namespace])));
492            }
493
494            $found[] = $abbrevs[$part][0];
495        }
496
497        return implode(':', $found);
498    }
499
500    /**
501     * Finds a command by name or alias.
502     *
503     * Contrary to get, this command tries to find the best
504     * match if you give it an abbreviation of a name or alias.
505     *
506     * @param  string $name A command name or a command alias
507     *
508     * @return Command A Command instance
509     *
510     * @throws \InvalidArgumentException When command name is incorrect or ambiguous
511     *
512     * @api
513     */
514    public function find($name)
515    {
516        // namespace
517        $namespace = '';
518        $searchName = $name;
519        if (false !== $pos = strrpos($name, ':')) {
520            $namespace = $this->findNamespace(substr($name, 0, $pos));
521            $searchName = $namespace.substr($name, $pos);
522        }
523
524        // name
525        $commands = array();
526        foreach ($this->commands as $command) {
527            if ($this->extractNamespace($command->getName()) == $namespace) {
528                $commands[] = $command->getName();
529            }
530        }
531
532        $abbrevs = static::getAbbreviations(array_unique($commands));
533        if (isset($abbrevs[$searchName]) && 1 == count($abbrevs[$searchName])) {
534            return $this->get($abbrevs[$searchName][0]);
535        }
536
537        if (isset($abbrevs[$searchName]) && count($abbrevs[$searchName]) > 1) {
538            $suggestions = $this->getAbbreviationSuggestions($abbrevs[$searchName]);
539
540            throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions));
541        }
542
543        // aliases
544        $aliases = array();
545        foreach ($this->commands as $command) {
546            foreach ($command->getAliases() as $alias) {
547                if ($this->extractNamespace($alias) == $namespace) {
548                    $aliases[] = $alias;
549                }
550            }
551        }
552
553        $abbrevs = static::getAbbreviations(array_unique($aliases));
554        if (!isset($abbrevs[$searchName])) {
555            throw new \InvalidArgumentException(sprintf('Command "%s" is not defined.', $name));
556        }
557
558        if (count($abbrevs[$searchName]) > 1) {
559            throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $this->getAbbreviationSuggestions($abbrevs[$searchName])));
560        }
561
562        return $this->get($abbrevs[$searchName][0]);
563    }
564
565    /**
566     * Gets the commands (registered in the given namespace if provided).
567     *
568     * The array keys are the full names and the values the command instances.
569     *
570     * @param  string  $namespace A namespace name
571     *
572     * @return array An array of Command instances
573     *
574     * @api
575     */
576    public function all($namespace = null)
577    {
578        if (null === $namespace) {
579            return $this->commands;
580        }
581
582        $commands = array();
583        foreach ($this->commands as $name => $command) {
584            if ($namespace === $this->extractNamespace($name)) {
585                $commands[$name] = $command;
586            }
587        }
588
589        return $commands;
590    }
591
592    /**
593     * Returns an array of possible abbreviations given a set of names.
594     *
595     * @param array $names An array of names
596     *
597     * @return array An array of abbreviations
598     */
599    static public function getAbbreviations($names)
600    {
601        $abbrevs = array();
602        foreach ($names as $name) {
603            for ($len = strlen($name) - 1; $len > 0; --$len) {
604                $abbrev = substr($name, 0, $len);
605                if (!isset($abbrevs[$abbrev])) {
606                    $abbrevs[$abbrev] = array($name);
607                } else {
608                    $abbrevs[$abbrev][] = $name;
609                }
610            }
611        }
612
613        // Non-abbreviations always get entered, even if they aren't unique
614        foreach ($names as $name) {
615            $abbrevs[$name] = array($name);
616        }
617
618        return $abbrevs;
619    }
620
621    /**
622     * Returns a text representation of the Application.
623     *
624     * @param string $namespace An optional namespace name
625     *
626     * @return string A string representing the Application
627     */
628    public function asText($namespace = null)
629    {
630        $commands = $namespace ? $this->all($this->findNamespace($namespace)) : $this->commands;
631
632        $messages = array($this->getHelp(), '');
633        if ($namespace) {
634            $messages[] = sprintf("<comment>Available commands for the \"%s\" namespace:</comment>", $namespace);
635        } else {
636            $messages[] = '<comment>Available commands:</comment>';
637        }
638
639        $width = 0;
640        foreach ($commands as $command) {
641            $width = strlen($command->getName()) > $width ? strlen($command->getName()) : $width;
642        }
643        $width += 2;
644
645        // add commands by namespace
646        foreach ($this->sortCommands($commands) as $space => $commands) {
647            if (!$namespace && '_global' !== $space) {
648                $messages[] = '<comment>'.$space.'</comment>';
649            }
650
651            foreach ($commands as $name => $command) {
652                $messages[] = sprintf("  <info>%-${width}s</info> %s", $name, $command->getDescription());
653            }
654        }
655
656        return implode("\n", $messages);
657    }
658
659    /**
660     * Returns an XML representation of the Application.
661     *
662     * @param string  $namespace An optional namespace name
663     * @param Boolean $asDom     Whether to return a DOM or an XML string
664     *
665     * @return string|DOMDocument An XML string representing the Application
666     */
667    public function asXml($namespace = null, $asDom = false)
668    {
669        $commands = $namespace ? $this->all($this->findNamespace($namespace)) : $this->commands;
670
671        $dom = new \DOMDocument('1.0', 'UTF-8');
672        $dom->formatOutput = true;
673        $dom->appendChild($xml = $dom->createElement('symfony'));
674
675        $xml->appendChild($commandsXML = $dom->createElement('commands'));
676
677        if ($namespace) {
678            $commandsXML->setAttribute('namespace', $namespace);
679        } else {
680            $namespacesXML = $dom->createElement('namespaces');
681            $xml->appendChild($namespacesXML);
682        }
683
684        // add commands by namespace
685        foreach ($this->sortCommands($commands) as $space => $commands) {
686            if (!$namespace) {
687                $namespaceArrayXML = $dom->createElement('namespace');
688                $namespacesXML->appendChild($namespaceArrayXML);
689                $namespaceArrayXML->setAttribute('id', $space);
690            }
691
692            foreach ($commands as $name => $command) {
693                if ($name !== $command->getName()) {
694                    continue;
695                }
696
697                if (!$namespace) {
698                    $commandXML = $dom->createElement('command');
699                    $namespaceArrayXML->appendChild($commandXML);
700                    $commandXML->appendChild($dom->createTextNode($name));
701                }
702
703                $node = $command->asXml(true)->getElementsByTagName('command')->item(0);
704                $node = $dom->importNode($node, true);
705
706                $commandsXML->appendChild($node);
707            }
708        }
709
710        return $asDom ? $dom : $dom->saveXml();
711    }
712
713    /**
714     * Renders a catched exception.
715     *
716     * @param Exception       $e      An exception instance
717     * @param OutputInterface $output An OutputInterface instance
718     */
719    public function renderException($e, $output)
720    {
721        $strlen = function ($string)
722        {
723            return function_exists('mb_strlen') ? mb_strlen($string, mb_detect_encoding($string)) : strlen($string);
724        };
725
726        do {
727            $title = sprintf('  [%s]  ', get_class($e));
728            $len = $strlen($title);
729            $lines = array();
730            foreach (explode("\n", $e->getMessage()) as $line) {
731                $lines[] = sprintf('  %s  ', $line);
732                $len = max($strlen($line) + 4, $len);
733            }
734
735            $messages = array(str_repeat(' ', $len), $title.str_repeat(' ', $len - $strlen($title)));
736
737            foreach ($lines as $line) {
738                $messages[] = $line.str_repeat(' ', $len - $strlen($line));
739            }
740
741            $messages[] = str_repeat(' ', $len);
742
743            $output->writeln("\n");
744            foreach ($messages as $message) {
745                $output->writeln('<error>'.$message.'</error>');
746            }
747            $output->writeln("\n");
748
749            if (OutputInterface::VERBOSITY_VERBOSE === $output->getVerbosity()) {
750                $output->writeln('<comment>Exception trace:</comment>');
751
752                // exception related properties
753                $trace = $e->getTrace();
754                array_unshift($trace, array(
755                    'function' => '',
756                    'file'     => $e->getFile() != null ? $e->getFile() : 'n/a',
757                    'line'     => $e->getLine() != null ? $e->getLine() : 'n/a',
758                    'args'     => array(),
759                ));
760
761                for ($i = 0, $count = count($trace); $i < $count; $i++) {
762                    $class = isset($trace[$i]['class']) ? $trace[$i]['class'] : '';
763                    $type = isset($trace[$i]['type']) ? $trace[$i]['type'] : '';
764                    $function = $trace[$i]['function'];
765                    $file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a';
766                    $line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a';
767
768                    $output->writeln(sprintf(' %s%s%s() at <info>%s:%s</info>', $class, $type, $function, $file, $line));
769                }
770
771                $output->writeln("\n");
772            }
773        } while ($e = $e->getPrevious());
774
775        if (null !== $this->runningCommand) {
776            $output->writeln(sprintf('<info>%s</info>', sprintf($this->runningCommand->getSynopsis(), $this->getName())));
777            $output->writeln("\n");
778        }
779    }
780
781    /**
782     * Gets the name of the command based on input.
783     *
784     * @param InputInterface $input The input interface
785     *
786     * @return string The command name
787     */
788    protected function getCommandName(InputInterface $input)
789    {
790        return $input->getFirstArgument('command');
791    }
792
793    /**
794     * Sorts commands in alphabetical order.
795     *
796     * @param array $commands An associative array of commands to sort
797     *
798     * @return array A sorted array of commands
799     */
800    private function sortCommands($commands)
801    {
802        $namespacedCommands = array();
803        foreach ($commands as $name => $command) {
804            $key = $this->extractNamespace($name, 1);
805            if (!$key) {
806                $key = '_global';
807            }
808
809            $namespacedCommands[$key][$name] = $command;
810        }
811        ksort($namespacedCommands);
812
813        foreach ($namespacedCommands as &$commands) {
814            ksort($commands);
815        }
816
817        return $namespacedCommands;
818    }
819
820    /**
821     * Returns abbreviated suggestions in string format.
822     *
823     * @param array $abbrevs Abbreviated suggestions to convert
824     *
825     * @return string A formatted string of abbreviated suggestions
826     */
827    private function getAbbreviationSuggestions($abbrevs)
828    {
829        return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : '');
830    }
831
832    private function extractNamespace($name, $limit = null)
833    {
834        $parts = explode(':', $name);
835        array_pop($parts);
836
837        return implode(':', null === $limit ? $parts : array_slice($parts, 0, $limit));
838    }
839}
Note: See TracBrowser for help on using the repository browser.