Development

/branches/1.2/lib/command/sfCommandApplication.class.php

You must first sign up to be able to contribute.

root/branches/1.2/lib/command/sfCommandApplication.class.php

Revision 19217, 15.0 kB (checked in by fabien, 6 years ago)

[1.1, 1.2, 1.3] fixed class name case

  • Property svn:mime-type set to text/x-php
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1 <?php
2
3 /*
4  * This file is part of the symfony package.
5  * (c) 2004-2006 Fabien Potencier <fabien.potencier@symfony-project.com>
6  *
7  * For the full copyright and license information, please view the LICENSE
8  * file that was distributed with this source code.
9  */
10
11 /**
12  * sfCommandApplication manages the lifecycle of a CLI application.
13  *
14  * @package    symfony
15  * @subpackage command
16  * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
17  * @version    SVN: $Id$
18  */
19 abstract class sfCommandApplication
20 {
21   protected
22     $commandManager = null,
23     $trace          = false,
24     $verbose        = true,
25     $dryrun         = false,
26     $nowrite        = false,
27     $name           = 'UNKNOWN',
28     $version        = 'UNKNOWN',
29     $tasks          = array(),
30     $currentTask    = null,
31     $dispatcher     = null,
32     $options        = array(),
33     $formatter      = null;
34
35   /**
36    * Constructor.
37    *
38    * @param sfEventDispatcher $dispatcher A sfEventDispatcher instance
39    * @param sfFormatter       $formatter  A sfFormatter instance
40    * @param array             $options    An array of options
41    */
42   public function __construct(sfEventDispatcher $dispatcher, sfFormatter $formatter, $options = array())
43   {
44     $this->dispatcher = $dispatcher;
45     $this->formatter  = $formatter;
46     $this->options    = $options;
47
48     $this->fixCgi();
49
50     $this->configure();
51
52     $this->registerTasks();
53   }
54
55   /**
56    * Configures the current command application.
57    */
58   abstract public function configure();
59
60   /**
61    * Returns the value of a given option.
62    *
63    * @param string $name The option name
64    *
65    * @return mixed The option value
66    */
67   public function getOption($name)
68   {
69     return isset($this->options[$name]) ? $this->options[$name] : null;
70   }
71
72   /**
73    * Returns the formatter instance.
74    *
75    * @return object The formatter instance
76    */
77   public function getFormatter()
78   {
79     return $this->formatter;
80   }
81
82   /**
83    * Registers an array of task objects.
84    *
85    * If you pass null, this method will register all available tasks.
86    *
87    * @param array $tasks An array of tasks
88    */
89   public function registerTasks($tasks = null)
90   {
91     if (is_null($tasks))
92     {
93       $tasks = array();
94       foreach (get_declared_classes() as $class)
95       {
96         $r = new ReflectionClass($class);
97
98         if ($r->isSubclassOf('sfTask') && !$r->isAbstract())
99         {
100           $tasks[] = new $class($this->dispatcher, $this->formatter);
101         }
102       }
103     }
104
105     foreach ($tasks as $task)
106     {
107       $this->registerTask($task);
108     }
109   }
110
111   /**
112    * Registers a task object.
113    *
114    * @param sfTask $task An sfTask object
115    */
116   public function registerTask(sfTask $task)
117   {
118     if (isset($this->tasks[$task->getFullName()]))
119     {
120       throw new sfCommandException(sprintf('The task named "%s" in "%s" task is already registered by the "%s" task.', $task->getFullName(), get_class($task), get_class($this->tasks[$task->getFullName()])));
121     }
122
123     $this->tasks[$task->getFullName()] = $task;
124
125     foreach ($task->getAliases() as $alias)
126     {
127       if (isset($this->tasks[$alias]))
128       {
129         throw new sfCommandException(sprintf('A task named "%s" is already registered.', $alias));
130       }
131
132       $this->tasks[$alias] = $task;
133     }
134   }
135
136   /**
137    * Returns all registered tasks.
138    *
139    * @return array An array of sfTask objects
140    */
141   public function getTasks()
142   {
143     return $this->tasks;
144   }
145
146   /**
147    * Returns a registered task by name or alias.
148    *
149    * @param string $name The task name or alias
150    *
151    * @return sfTask An sfTask object
152    */
153   public function getTask($name)
154   {
155     if (!isset($this->tasks[$name]))
156     {
157       throw new sfCommandException(sprintf('The task "%s" does not exist.', $name));
158     }
159
160     return $this->tasks[$name];
161   }
162
163   /**
164    * Runs the current application.
165    *
166    * @param mixed $options The command line options
167    *
168    * @return integer 0 if everything went fine, or an error code
169    */
170   public function run($options = null)
171   {
172     $this->handleOptions($options);
173     $arguments = $this->commandManager->getArgumentValues();
174
175     $this->currentTask = $this->getTaskToExecute($arguments['task']);
176
177     $ret = $this->currentTask->runFromCLI($this->commandManager, $this->commandOptions);
178
179     $this->currentTask = null;
180
181     return $ret;
182   }
183
184   /**
185    * Gets the name of the application.
186    *
187    * @return string The application name
188    */
189   public function getName()
190   {
191     return $this->name;
192   }
193
194   /**
195    * Sets the application name.
196    *
197    * @param string $name The application name
198    */
199   public function setName($name)
200   {
201     $this->name = $name;
202   }
203
204   /**
205    * Gets the application version.
206    *
207    * @return string The application version
208    */
209   public function getVersion()
210   {
211     return $this->version;
212   }
213
214   /**
215    * Sets the application version.
216    *
217    * @param string $version The application version
218    */
219   public function setVersion($version)
220   {
221     $this->version = $version;
222   }
223
224   /**
225    * Returns the long version of the application.
226    *
227    * @return string The long application version
228    */
229   public function getLongVersion()
230   {
231     return sprintf('%s version %s', $this->getName(), $this->formatter->format($this->getVersion(), 'INFO'))."\n";
232   }
233
234   /**
235    * Returns whether the application must be verbose.
236    *
237    * @return Boolean true if the application must be verbose, false otherwise
238    */
239   public function isVerbose()
240   {
241     return $this->verbose;
242   }
243
244   /**
245    * Returns whether the application must activate the trace.
246    *
247    * @return Boolean true if the application must activate the trace, false otherwise
248    */
249   public function withTrace()
250   {
251     return $this->trace;
252   }
253
254   /*
255    * Returns whether the application must run in dry mode.
256    *
257    * @return Boolean true if the application must run in dry mode, false otherwise
258    */
259   public function isDryrun()
260   {
261     return $this->dryrun;
262   }
263
264   /**
265    * Outputs a help message for the current application.
266    */
267   public function help()
268   {
269     $messages = array(
270       $this->formatter->format('Usage:', 'COMMENT'),
271       sprintf("  %s [options] task_name [arguments]\n", $this->getName()),
272       $this->formatter->format('Options:', 'COMMENT'),
273     );
274
275     foreach ($this->commandManager->getOptionSet()->getOptions() as $option)
276     {
277       $messages[] = sprintf('  %-24s %s  %s', $this->formatter->format('--'.$option->getName(), 'INFO'), $this->formatter->format('-'.$option->getShortcut(), 'INFO'), $option->getHelp());
278     }
279
280     $this->dispatcher->notify(new sfEvent($this, 'command.log', $messages));
281   }
282
283   /**
284    * Parses and handles command line options.
285    *
286    * @param mixed $options The command line options
287    */
288   protected function handleOptions($options = null)
289   {
290     $argumentSet = new sfCommandArgumentSet(array(
291       new sfCommandArgument('task', sfCommandArgument::REQUIRED, 'The task to execute'),
292     ));
293     $optionSet = new sfCommandOptionSet(array(
294       new sfCommandOption('--dry-run', '-n', sfCommandOption::PARAMETER_NONE, 'Do a dry run without executing actions.'),
295       new sfCommandOption('--help',    '-H', sfCommandOption::PARAMETER_NONE, 'Display this help message.'),
296       new sfCommandOption('--quiet',   '-q', sfCommandOption::PARAMETER_NONE, 'Do not log messages to standard output.'),
297       new sfCommandOption('--trace',   '-t', sfCommandOption::PARAMETER_NONE, 'Turn on invoke/execute tracing, enable full backtrace.'),
298       new sfCommandOption('--version', '-V', sfCommandOption::PARAMETER_NONE, 'Display the program version.'),
299     ));
300     $this->commandManager = new sfCommandManager($argumentSet, $optionSet);
301     $this->commandManager->process($options);
302     foreach ($this->commandManager->getOptionValues() as $opt => $value)
303     {
304       if (false === $value)
305       {
306         continue;
307       }
308
309       switch ($opt)
310       {
311         case 'dry-run':
312           $this->verbose = true;
313           $this->nowrite = true;
314           $this->dryrun = true;
315           $this->trace = true;
316           break;
317         case 'help':
318           $this->help();
319           exit();
320         case 'quiet':
321           $this->verbose = false;
322           break;
323         case 'trace':
324           $this->trace = true;
325           $this->verbose = true;
326           break;
327         case 'version':
328           echo $this->getLongVersion();
329           exit(0);
330       }
331     }
332
333     $this->commandOptions = $options;
334   }
335
336   /**
337    * Renders an exception.
338    *
339    * @param Exception $e An exception object
340    */
341   public function renderException($e)
342   {
343     $title = sprintf('  [%s]  ', get_class($e));
344     $len = $this->strlen($title);
345     $lines = array();
346     foreach (explode("\n", $e->getMessage()) as $line)
347     {
348       $lines[] = sprintf('  %s  ', $line);
349       $len = max($this->strlen($line) + 4, $len);
350     }
351
352     $messages = array(str_repeat(' ', $len));
353
354     if ($this->trace)
355     {
356       $messages[] = $title.str_repeat(' ', $len - $this->strlen($title));
357     }
358
359     foreach ($lines as $line)
360     {
361       $messages[] = $line.str_repeat(' ', $len - $this->strlen($line));
362     }
363
364     $messages[] = str_repeat(' ', $len);
365
366     fwrite(STDERR, "\n");
367     foreach ($messages as $message)
368     {
369       fwrite(STDERR, $this->formatter->format($message, 'ERROR', STDERR)."\n");
370     }
371     fwrite(STDERR, "\n");
372
373     if (!is_null($this->currentTask) && $e instanceof sfCommandArgumentsException)
374     {
375       fwrite(STDERR, $this->formatter->format(sprintf($this->currentTask->getSynopsis(), $this->getName()), 'INFO', STDERR)."\n");
376       fwrite(STDERR, "\n");
377     }
378
379     if ($this->trace)
380     {
381       fwrite(STDERR, $this->formatter->format("Exception trace:\n", 'COMMENT'));
382
383       // exception related properties
384       $trace = $e->getTrace();
385       array_unshift($trace, array(
386         'function' => '',
387         'file'     => $e->getFile() != null ? $e->getFile() : 'n/a',
388         'line'     => $e->getLine() != null ? $e->getLine() : 'n/a',
389         'args'     => array(),
390       ));
391
392       for ($i = 0, $count = count($trace); $i < $count; $i++)
393       {
394         $class = isset($trace[$i]['class']) ? $trace[$i]['class'] : '';
395         $type = isset($trace[$i]['type']) ? $trace[$i]['type'] : '';
396         $function = $trace[$i]['function'];
397         $file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a';
398         $line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a';
399
400         fwrite(STDERR, sprintf(" %s%s%s at %s:%s\n", $class, $type, $function, $this->formatter->format($file, 'INFO', STDERR), $this->formatter->format($line, 'INFO', STDERR)));
401       }
402
403       fwrite(STDERR, "\n");
404     }
405   }
406
407   /**
408    * Gets a task from a task name or a shortcut.
409    *
410    * @param string $name The task name or a task shortcut
411    *
412    * @return sfTask A sfTask object
413    */
414   protected function getTaskToExecute($name)
415   {
416     // namespace
417     if (false !== $pos = strpos($name, ':'))
418     {
419       $namespace = substr($name, 0, $pos);
420       $name = substr($name, $pos + 1);
421
422       $namespaces = array();
423       foreach ($this->tasks as $task)
424       {
425         if ($task->getNamespace() && !in_array($task->getNamespace(), $namespaces))
426         {
427           $namespaces[] = $task->getNamespace();
428         }
429       }
430       $abbrev = $this->getAbbreviations($namespaces);
431
432       if (!isset($abbrev[$namespace]))
433       {
434         throw new sfCommandException(sprintf('There are no tasks defined in the "%s" namespace.', $namespace));
435       }
436       else if (count($abbrev[$namespace]) > 1)
437       {
438         throw new sfCommandException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, implode(', ', $abbrev[$namespace])));
439       }
440       else
441       {
442         $namespace = $abbrev[$namespace][0];
443       }
444     }
445     else
446     {
447       $namespace = '';
448     }
449
450     // name
451     $tasks = array();
452     foreach ($this->tasks as $taskName => $task)
453     {
454       if ($taskName == $task->getFullName() && $task->getNamespace() == $namespace)
455       {
456         $tasks[] = $task->getName();
457       }
458     }
459
460     $abbrev = $this->getAbbreviations($tasks);
461     if (isset($abbrev[$name]) && count($abbrev[$name]) == 1)
462     {
463       return $this->getTask($namespace ? $namespace.':'.$abbrev[$name][0] : $abbrev[$name][0]);
464     }
465
466     // aliases
467     $aliases = array();
468     foreach ($this->tasks as $taskName => $task)
469     {
470       if ($taskName == $task->getFullName())
471       {
472         foreach ($task->getAliases() as $alias)
473         {
474           $aliases[] = $alias;
475         }
476       }
477     }
478
479     $abbrev = $this->getAbbreviations($aliases);
480     $fullName = $namespace ? $namespace.':'.$name : $name;
481     if (!isset($abbrev[$fullName]))
482     {
483       throw new sfCommandException(sprintf('Task "%s" is not defined.', $fullName));
484     }
485     else if (count($abbrev[$fullName]) > 1)
486     {
487       throw new sfCommandException(sprintf('Task "%s" is ambiguous (%s).', $fullName, implode(', ', $abbrev[$fullName])));
488     }
489     else
490     {
491       return $this->getTask($abbrev[$fullName][0]);
492     }
493   }
494
495   protected function strlen($string)
496   {
497     return function_exists('mb_strlen') ? mb_strlen($string) : strlen($string);
498   }
499
500   /**
501    * Fixes php behavior if using cgi php.
502    *
503    * @see http://www.sitepoint.com/article/php-command-line-1/3
504    */
505   protected function fixCgi()
506   {
507     // handle output buffering
508     @ob_end_flush();
509     ob_implicit_flush(true);
510
511     // PHP ini settings
512     set_time_limit(0);
513     ini_set('track_errors', true);
514     ini_set('html_errors', false);
515     ini_set('magic_quotes_runtime', false);
516
517     if (false === strpos(PHP_SAPI, 'cgi'))
518     {
519       return;
520     }
521
522     // define stream constants
523     define('STDIN'fopen('php://stdin''r'));
524     define('STDOUT', fopen('php://stdout', 'w'));
525     define('STDERR', fopen('php://stderr', 'w'));
526
527     // change directory
528     if (isset($_SERVER['PWD']))
529     {
530       chdir($_SERVER['PWD']);
531     }
532
533     // close the streams on script termination
534     register_shutdown_function(create_function('', 'fclose(STDIN); fclose(STDOUT); fclose(STDERR); return true;'));
535   }
536
537   /**
538    * Returns an array of possible abbreviations given a set of names.
539    *
540    * @see Text::Abbrev perl module for the algorithm
541    */
542   protected function getAbbreviations($names)
543   {
544     $abbrevs = array();
545     $table   = array();
546
547     foreach ($names as $name)
548     {
549       for ($len = strlen($name) - 1; $len > 0; --$len)
550       {
551         $abbrev = substr($name, 0, $len);
552         if (!array_key_exists($abbrev, $table))
553         {
554           $table[$abbrev] = 1;
555         }
556         else
557         {
558           ++$table[$abbrev];
559         }
560
561         $seen = $table[$abbrev];
562         if ($seen == 1)
563         {
564           // We're the first word so far to have this abbreviation.
565           $abbrevs[$abbrev] = array($name);
566         }
567         else if ($seen == 2)
568         {
569           // We're the second word to have this abbreviation, so we can't use it.
570           // unset($abbrevs[$abbrev]);
571           $abbrevs[$abbrev][] = $name;
572         }
573         else
574         {
575           // We're the third word to have this abbreviation, so skip to the next word.
576           continue;
577         }
578       }
579     }
580
581     // Non-abbreviations always get entered, even if they aren't unique
582     foreach ($names as $name)
583     {
584       $abbrevs[$name] = array($name);
585     }
586
587     return $abbrevs;
588   }
589 }
590
Note: See TracBrowser for help on using the browser.