Development

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

You must first sign up to be able to contribute.

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

Revision 19217, 14.7 kB (checked in by fabien, 5 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       sprintf("%s [options] task_name [arguments]\n", $this->getName()),
271       "\nAvailable options:\n",
272     );
273
274     foreach ($this->commandManager->getOptionSet()->getOptions() as $option)
275     {
276       $messages[] = sprintf("  %-10s (%s) %s\n", $option->getName(), $option->getShortcut(), $option->getHelp());
277     }
278
279     $this->dispatcher->notify(new sfEvent($this, 'command.log', $messages));
280   }
281
282   /**
283    * Parses and handles command line options.
284    *
285    * @param mixed $options The command line options
286    */
287   protected function handleOptions($options = null)
288   {
289     $argumentSet = new sfCommandArgumentSet(array(
290       new sfCommandArgument('task', sfCommandArgument::REQUIRED, 'The task to execute'),
291     ));
292     $optionSet = new sfCommandOptionSet(array(
293       new sfCommandOption('--dry-run', '-n', sfCommandOption::PARAMETER_NONE, 'Do a dry run without executing actions.'),
294       new sfCommandOption('--help',    '-H', sfCommandOption::PARAMETER_NONE, 'Display this help message.'),
295       new sfCommandOption('--quiet',   '-q', sfCommandOption::PARAMETER_NONE, 'Do not log messages to standard output.'),
296       new sfCommandOption('--trace',   '-t', sfCommandOption::PARAMETER_NONE, 'Turn on invoke/execute tracing, enable full backtrace.'),
297       new sfCommandOption('--version', '-V', sfCommandOption::PARAMETER_NONE, 'Display the program version.'),
298     ));
299     $this->commandManager = new sfCommandManager($argumentSet, $optionSet);
300     $this->commandManager->process($options);
301     foreach ($this->commandManager->getOptionValues() as $opt => $value)
302     {
303       if (false === $value)
304       {
305         continue;
306       }
307
308       switch ($opt)
309       {
310         case 'dry-run':
311           $this->verbose = true;
312           $this->nowrite = true;
313           $this->dryrun = true;
314           $this->trace = true;
315           break;
316         case 'help':
317           $this->help();
318           exit();
319         case 'quiet':
320           $this->verbose = false;
321           break;
322         case 'trace':
323           $this->trace = true;
324           $this->verbose = true;
325           break;
326         case 'version':
327           echo $this->getLongVersion();
328           exit(0);
329       }
330     }
331
332     $this->commandOptions = $options;
333   }
334
335   /**
336    * Renders an exception.
337    *
338    * @param Exception $e An exception object
339    */
340   public function renderException($e)
341   {
342     $title = sprintf('  [%s]  ', get_class($e));
343     $len = $this->strlen($title);
344     $lines = array();
345     foreach (explode("\n", $e->getMessage()) as $line)
346     {
347       $lines[] = sprintf('  %s  ', $line);
348       $len = max($this->strlen($line) + 4, $len);
349     }
350
351     $messages = array(str_repeat(' ', $len));
352
353     if ($this->trace)
354     {
355       $messages[] = $title.str_repeat(' ', $len - $this->strlen($title));
356     }
357
358     foreach ($lines as $line)
359     {
360       $messages[] = $line.str_repeat(' ', $len - $this->strlen($line));
361     }
362
363     $messages[] = str_repeat(' ', $len);
364
365     fwrite(STDERR, "\n");
366     foreach ($messages as $message)
367     {
368       fwrite(STDERR, $this->formatter->format($message, 'ERROR', STDERR)."\n");
369     }
370     fwrite(STDERR, "\n");
371
372     if (!is_null($this->currentTask) && $e instanceof sfCommandArgumentsException)
373     {
374       fwrite(STDERR, $this->formatter->format(sprintf($this->currentTask->getSynopsis(), $this->getName()), 'INFO', STDERR)."\n");
375       fwrite(STDERR, "\n");
376     }
377
378     if ($this->trace)
379     {
380       fwrite(STDERR, $this->formatter->format("Exception trace:\n", 'COMMENT'));
381
382       // exception related properties
383       $trace = $e->getTrace();
384       array_unshift($trace, array(
385         'function' => '',
386         'file'     => $e->getFile() != null ? $e->getFile() : 'n/a',
387         'line'     => $e->getLine() != null ? $e->getLine() : 'n/a',
388         'args'     => array(),
389       ));
390
391       for ($i = 0, $count = count($trace); $i < $count; $i++)
392       {
393         $class = isset($trace[$i]['class']) ? $trace[$i]['class'] : '';
394         $type = isset($trace[$i]['type']) ? $trace[$i]['type'] : '';
395         $function = $trace[$i]['function'];
396         $file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a';
397         $line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a';
398
399         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)));
400       }
401
402       fwrite(STDERR, "\n");
403     }
404   }
405
406   /**
407    * Gets a task from a task name or a shortcut.
408    *
409    * @param  string  $name  The task name or a task shortcut
410    *
411    * @return sfTask A sfTask object
412    */
413   protected function getTaskToExecute($name)
414   {
415     // namespace
416     if (false !== $pos = strpos($name, ':'))
417     {
418       $namespace = substr($name, 0, $pos);
419       $name = substr($name, $pos + 1);
420
421       $namespaces = array();
422       foreach ($this->tasks as $task)
423       {
424         if ($task->getNamespace() && !in_array($task->getNamespace(), $namespaces))
425         {
426           $namespaces[] = $task->getNamespace();
427         }
428       }
429       $abbrev = $this->getAbbreviations($namespaces);
430
431       if (!isset($abbrev[$namespace]))
432       {
433         throw new sfCommandException(sprintf('There are no tasks defined in the "%s" namespace.', $namespace));
434       }
435       else if (count($abbrev[$namespace]) > 1)
436       {
437         throw new sfCommandException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, implode(', ', $abbrev[$namespace])));
438       }
439       else
440       {
441         $namespace = $abbrev[$namespace][0];
442       }
443     }
444     else
445     {
446       $namespace = '';
447     }
448
449     // name
450     $tasks = array();
451     foreach ($this->tasks as $taskName => $task)
452     {
453       if ($taskName == $task->getFullName() && $task->getNamespace() == $namespace)
454       {
455         $tasks[] = $task->getName();
456       }
457     }
458
459     $abbrev = $this->getAbbreviations($tasks);
460     if (isset($abbrev[$name]) && count($abbrev[$name]) == 1)
461     {
462       return $this->getTask($namespace ? $namespace.':'.$abbrev[$name][0] : $abbrev[$name][0]);
463     }
464
465     // aliases
466     $aliases = array();
467     foreach ($this->tasks as $taskName => $task)
468     {
469       if ($taskName == $task->getFullName())
470       {
471         foreach ($task->getAliases() as $alias)
472         {
473           $aliases[] = $alias;
474         }
475       }
476     }
477
478     $abbrev = $this->getAbbreviations($aliases);
479     if (!isset($abbrev[$name]))
480     {
481       throw new sfCommandException(sprintf('Task "%s" is not defined.', $name));
482     }
483     else if (count($abbrev[$name]) > 1)
484     {
485       throw new sfCommandException(sprintf('Task "%s" is ambiguous (%s).', $name, implode(', ', $abbrev[$name])));
486     }
487     else
488     {
489       return $this->getTask($abbrev[$name][0]);
490     }
491   }
492
493   protected function strlen($string)
494   {
495     return function_exists('mb_strlen') ? mb_strlen($string) : strlen($string);
496   }
497
498   /**
499    * Fixes php behavior if using cgi php.
500    *
501    * @see http://www.sitepoint.com/article/php-command-line-1/3
502    */
503   protected function fixCgi()
504   {
505     // handle output buffering
506     @ob_end_flush();
507     ob_implicit_flush(true);
508
509     // PHP ini settings
510     set_time_limit(0);
511     ini_set('track_errors', true);
512     ini_set('html_errors', false);
513     ini_set('magic_quotes_runtime', false);
514
515     if (false === strpos(PHP_SAPI, 'cgi'))
516     {
517       return;
518     }
519
520     // define stream constants
521     define('STDIN'fopen('php://stdin''r'));
522     define('STDOUT', fopen('php://stdout', 'w'));
523     define('STDERR', fopen('php://stderr', 'w'));
524
525     // change directory
526     if (isset($_SERVER['PWD']))
527     {
528       chdir($_SERVER['PWD']);
529     }
530
531     // close the streams on script termination
532     register_shutdown_function(create_function('', 'fclose(STDIN); fclose(STDOUT); fclose(STDERR); return true;'));
533   }
534
535   /**
536    * Returns an array of possible abbreviations given a set of names.
537    *
538    * @see Text::Abbrev perl module for the algorithm
539    */
540   protected function getAbbreviations($names)
541   {
542     $abbrevs = array();
543     $table   = array();
544
545     foreach ($names as $name)
546     {
547       for ($len = strlen($name) - 1; $len > 0; --$len)
548       {
549         $abbrev = substr($name, 0, $len);
550         if (!array_key_exists($abbrev, $table))
551         {
552           $table[$abbrev] = 1;
553         }
554         else
555         {
556           ++$table[$abbrev];
557         }
558
559         $seen = $table[$abbrev];
560         if ($seen == 1)
561         {
562           // We're the first word so far to have this abbreviation.
563           $abbrevs[$abbrev] = array($name);
564         }
565         else if ($seen == 2)
566         {
567           // We're the second word to have this abbreviation, so we can't use it.
568           // unset($abbrevs[$abbrev]);
569           $abbrevs[$abbrev][] = $name;
570         }
571         else
572         {
573           // We're the third word to have this abbreviation, so skip to the next word.
574           continue;
575         }
576       }
577     }
578
579     // Non-abbreviations always get entered, even if they aren't unique
580     foreach ($names as $name)
581     {
582       $abbrevs[$name] = array($name);
583     }
584
585     return $abbrevs;
586   }
587 }
588
Note: See TracBrowser for help on using the browser.