1. sfTask.class.php
  2. /** * Abstract class for all tasks. * * @package symfony * @subpackage task * @author Fabien Potencier * @version SVN: $Id: sfTask.class.php 23437 2009-10-29 16:12:53Z fabien $ */
  3. abstract class sfTask
  4. {
  5. protected
  6. $namespace = '',
  7. $name = null,
  8. $aliases = array(),
  9. $briefDescription = '',
  10. $detailedDescription = '',
  11. $arguments = array(),
  12. $options = array(),
  13. $dispatcher = null,
  14. $formatter = null;
  15. /**
  16. * Constructor.
  17. *
  18. * @param sfEventDispatcher $dispatcher An sfEventDispatcher instance
  19. * @param sfFormatter $formatter An sfFormatter instance
  20. */
  21. public function __construct(sfEventDispatcher $dispatcher, sfFormatter $formatter)
  22. {
  23. $this->initialize($dispatcher, $formatter);
  24. $this->configure();
  25. }
  26. /**
  27. * Initializes the sfTask instance.
  28. *
  29. * @param sfEventDispatcher $dispatcher A sfEventDispatcher instance
  30. * @param sfFormatter $formatter A sfFormatter instance
  31. */
  32. public function initialize(sfEventDispatcher $dispatcher, sfFormatter $formatter)
  33. {
  34. $this->dispatcher = $dispatcher;
  35. $this->formatter = $formatter;
  36. }
  37. /**
  38. * Configures the current task.
  39. */
  40. protected function configure()
  41. {
  42. }
  43. /**
  44. * Returns the formatter instance.
  45. *
  46. * @return sfFormatter The formatter instance
  47. */
  48. public function getFormatter()
  49. {
  50. return $this->formatter;
  51. }
  52. /**
  53. * Sets the formatter instance.
  54. *
  55. * @param sfFormatter The formatter instance
  56. */
  57. public function setFormatter(sfFormatter $formatter)
  58. {
  59. $this->formatter = $formatter;
  60. }
  61. /**
  62. * Runs the task from the CLI.
  63. *
  64. * @param sfCommandManager $commandManager An sfCommandManager instance
  65. * @param mixed $options The command line options
  66. *
  67. * @return integer 0 if everything went fine, or an error code
  68. */
  69. public function runFromCLI(sfCommandManager $commandManager, $options = null)
  70. {
  71. $commandManager->getArgumentSet()->addArguments($this->getArguments());
  72. $commandManager->getOptionSet()->addOptions($this->getOptions());
  73. return $this->doRun($commandManager, $options);
  74. }
  75. /**
  76. * Runs the task.
  77. *
  78. * @param array|string $arguments An array of arguments or a string representing the CLI arguments and options
  79. * @param array $options An array of options
  80. *
  81. * @return integer 0 if everything went fine, or an error code
  82. */
  83. public function run($arguments = array(), $options = array())
  84. {
  85. $commandManager = new sfCommandManager(new sfCommandArgumentSet($this->getArguments()), new sfCommandOptionSet($this->getOptions()));
  86. if (is_array($arguments) && is_string(key($arguments)))
  87. {
  88. // index arguments by name for ordering and reference
  89. $indexArguments = array();
  90. foreach ($this->arguments as $argument)
  91. {
  92. $indexArguments[$argument->getName()] = $argument;
  93. }
  94. foreach ($arguments as $name => $value)
  95. {
  96. if (false !== $pos = array_search($name, array_keys($indexArguments)))
  97. {
  98. if ($indexArguments[$name]->isArray())
  99. {
  100. $value = join(' ', (array) $value);
  101. $arguments[$pos] = isset($arguments[$pos]) ? $arguments[$pos].' '.$value : $value;
  102. }
  103. else
  104. {
  105. $arguments[$pos] = $value;
  106. }
  107. unset($arguments[$name]);
  108. }
  109. }
  110. ksort($arguments);
  111. }
  112. // index options by name for reference
  113. $indexedOptions = array();
  114. foreach ($this->options as $option)
  115. {
  116. $indexedOptions[$option->getName()] = $option;
  117. }
  118. foreach ($options as $name => $value)
  119. {
  120. if (is_string($name))
  121. {
  122. if (false === $value || null === $value || (isset($indexedOptions[$name]) && $indexedOptions[$name]->isArray() && !$value))
  123. {
  124. unset($options[$name]);
  125. continue;
  126. }
  127. // convert associative array
  128. $value = true === $value ? $name : sprintf('%s=%s', $name, isset($indexedOptions[$name]) && $indexedOptions[$name]->isArray() ? join(' --'.$name.'=', (array) $value) : $value);
  129. }
  130. // add -- before each option if needed
  131. if (0 !== strpos($value, '--'))
  132. {
  133. $value = '--'.$value;
  134. }
  135. $options[] = $value;
  136. unset($options[$name]);
  137. }
  138. return $this->doRun($commandManager, is_string($arguments) ? $arguments : implode(' ', array_merge($arguments, $options)));
  139. }
  140. /**
  141. * Returns the argument objects.
  142. *
  143. * @return sfCommandArgument An array of sfCommandArgument objects.
  144. */
  145. public function getArguments()
  146. {
  147. return $this->arguments;
  148. }
  149. /**
  150. * Adds an array of argument objects.
  151. *
  152. * @param array $arguments An array of arguments
  153. */
  154. public function addArguments($arguments)
  155. {
  156. $this->arguments = array_merge($this->arguments, $arguments);
  157. }
  158. /**
  159. * Add an argument.
  160. *
  161. * This method always use the sfCommandArgument class to create an option.
  162. *
  163. * @see sfCommandArgument::__construct()
  164. */
  165. public function addArgument($name, $mode = null, $help = '', $default = null)
  166. {
  167. $this->arguments[] = new sfCommandArgument($name, $mode, $help, $default);
  168. }
  169. /**
  170. * Returns the options objects.
  171. *
  172. * @return sfCommandOption An array of sfCommandOption objects.
  173. */
  174. public function getOptions()
  175. {
  176. return $this->options;
  177. }
  178. /**
  179. * Adds an array of option objects.
  180. *
  181. * @param array $options An array of options
  182. */
  183. public function addOptions($options)
  184. {
  185. $this->options = array_merge($this->options, $options);
  186. }
  187. /**
  188. * Add an option.
  189. *
  190. * This method always use the sfCommandOption class to create an option.
  191. *
  192. * @see sfCommandOption::__construct()
  193. */
  194. public function addOption($name, $shortcut = null, $mode = null, $help = '', $default = null)
  195. {
  196. $this->options[] = new sfCommandOption($name, $shortcut, $mode, $help, $default);
  197. }
  198. /**
  199. * Returns the task namespace.
  200. *
  201. * @return string The task namespace
  202. */
  203. public function getNamespace()
  204. {
  205. return $this->namespace;
  206. }
  207. /**
  208. * Returns the task name
  209. *
  210. * @return string The task name
  211. */
  212. public function getName()
  213. {
  214. if ($this->name)
  215. {
  216. return $this->name;
  217. }
  218. $name = get_class($this);
  219. if ('sf' == substr($name, 0, 2))
  220. {
  221. $name = substr($name, 2);
  222. }
  223. if ('Task' == substr($name, -4))
  224. {
  225. $name = substr($name, 0, -4);
  226. }
  227. return str_replace('_', '-', sfInflector::underscore($name));
  228. }
  229. /**
  230. * Returns the fully qualified task name.
  231. *
  232. * @return string The fully qualified task name
  233. */
  234. final function getFullName()
  235. {
  236. return $this->getNamespace() ? $this->getNamespace().':'.$this->getName() : $this->getName();
  237. }
  238. /**
  239. * Returns the brief description for the task.
  240. *
  241. * @return string The brief description for the task
  242. */
  243. public function getBriefDescription()
  244. {
  245. return $this->briefDescription;
  246. }
  247. /**
  248. * Returns the detailed description for the task.
  249. *
  250. * It also formats special string like [...|COMMENT]
  251. * depending on the current formatter.
  252. *
  253. * @return string The detailed description for the task
  254. */
  255. public function getDetailedDescription()
  256. {
  257. return preg_replace('/\[(.+?)\|(\w+)\]/se', '$this->formatter->format("$1", "$2")', $this->detailedDescription);
  258. }
  259. /**
  260. * Returns the aliases for the task.
  261. *
  262. * @return array An array of aliases for the task
  263. */
  264. public function getAliases()
  265. {
  266. return $this->aliases;
  267. }
  268. /**
  269. * Returns the synopsis for the task.
  270. *
  271. * @return string The synopsis
  272. */
  273. public function getSynopsis()
  274. {
  275. $options = array();
  276. foreach ($this->getOptions() as $option)
  277. {
  278. $shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : '';
  279. $options[] = sprintf('['.($option->isParameterRequired() ? '%s--%s="..."' : ($option->isParameterOptional() ? '%s--%s[="..."]' : '%s--%s')).']', $shortcut, $option->getName());
  280. }
  281. $arguments = array();
  282. foreach ($this->getArguments() as $argument)
  283. {
  284. $arguments[] = sprintf($argument->isRequired() ? '%s' : '[%s]', $argument->getName().($argument->isArray() ? '1' : ''));
  285. if ($argument->isArray())
  286. {
  287. $arguments[] = sprintf('... [%sN]', $argument->getName());
  288. }
  289. }
  290. return sprintf('%%s %s %s %s', $this->getFullName(), implode(' ', $options), implode(' ', $arguments));
  291. }
  292. protected function process(sfCommandManager $commandManager, $options)
  293. {
  294. $commandManager->process($options);
  295. if (!$commandManager->isValid())
  296. {
  297. throw new sfCommandArgumentsException(sprintf("The execution of task \"%s\" failed.\n- %s", $this->getFullName(), implode("\n- ", $commandManager->getErrors())));
  298. }
  299. }
  300. protected function doRun(sfCommandManager $commandManager, $options)
  301. {
  302. $this->dispatcher->filter(new sfEvent($this, 'command.filter_options', array('command_manager' => $commandManager)), $options);
  303. $this->process($commandManager, $options);
  304. $event = new sfEvent($this, 'command.pre_command', array('arguments' => $commandManager->getArgumentValues(), 'options' => $commandManager->getOptionValues()));
  305. $this->dispatcher->notifyUntil($event);
  306. if ($event->isProcessed())
  307. {
  308. return $event->getReturnValue();
  309. }
  310. $ret = $this->execute($commandManager->getArgumentValues(), $commandManager->getOptionValues());
  311. $this->dispatcher->notify(new sfEvent($this, 'command.post_command'));
  312. return $ret;
  313. }
  314. /**
  315. * Logs a message.
  316. *
  317. * @param mixed $messages The message as an array of lines of a single string
  318. */
  319. public function log($messages)
  320. {
  321. if (!is_array($messages))
  322. {
  323. $messages = array($messages);
  324. }
  325. $this->dispatcher->notify(new sfEvent($this, 'command.log', $messages));
  326. }
  327. /**
  328. * Logs a message in a section.
  329. *
  330. * @param string $section The section name
  331. * @param string $message The message
  332. * @param int $size The maximum size of a line
  333. * @param string $style The color scheme to apply to the section string (INFO, ERROR, or COMMAND)
  334. */
  335. public function logSection($section, $message, $size = null, $style = 'INFO')
  336. {
  337. $this->dispatcher->notify(new sfEvent($this, 'command.log', array($this->formatter->formatSection($section, $message, $size, $style))));
  338. }
  339. /**
  340. * Logs a message as a block of text.
  341. *
  342. * @param string|array $messages The message to display in the block
  343. * @param string $style The style to use
  344. */
  345. public function logBlock($messages, $style)
  346. {
  347. if (!is_array($messages))
  348. {
  349. $messages = array($messages);
  350. }
  351. $style = str_replace('_LARGE', '', $style, $count);
  352. $large = (Boolean) $count;
  353. $len = 0;
  354. $lines = array();
  355. foreach ($messages as $message)
  356. {
  357. $lines[] = sprintf($large ? ' %s ' : ' %s ', $message);
  358. $len = max($this->strlen($message) + ($large ? 4 : 2), $len);
  359. }
  360. $messages = $large ? array(str_repeat(' ', $len)) : array();
  361. foreach ($lines as $line)
  362. {
  363. $messages[] = $line.str_repeat(' ', $len - $this->strlen($line));
  364. }
  365. if ($large)
  366. {
  367. $messages[] = str_repeat(' ', $len);
  368. }
  369. foreach ($messages as $message)
  370. {
  371. $this->log($this->formatter->format($message, $style));
  372. }
  373. }
  374. /**
  375. * Asks a question to the user.
  376. *
  377. * @param string|array $question The question to ask
  378. * @param string $style The style to use (QUESTION by default)
  379. * @param string $default The default answer if none is given by the user
  380. *
  381. * @param string The user answer
  382. */
  383. public function ask($question, $style = 'QUESTION', $default = null)
  384. {
  385. if (false === $style)
  386. {
  387. $this->log($question);
  388. }
  389. else
  390. {
  391. $this->logBlock($question, null === $style ? 'QUESTION' : $style);
  392. }
  393. $ret = trim(fgets(STDIN));
  394. return $ret ? $ret : $default;
  395. }
  396. /**
  397. * Asks a confirmation to the user.
  398. *
  399. * The question will be asked until the user answer by nothing, yes, or no.
  400. *
  401. * @param string|array $question The question to ask
  402. * @param string $style The style to use (QUESTION by default)
  403. * @param Boolean $default The default answer if the user enters nothing
  404. *
  405. * @param Boolean true if the user has confirmed, false otherwise
  406. */
  407. public function askConfirmation($question, $style = 'QUESTION', $default = true)
  408. {
  409. $answer = 'z';
  410. while ($answer && !in_array(strtolower($answer[0]), array('y', 'n')))
  411. {
  412. $answer = $this->ask($question, $style);
  413. }
  414. if (false === $default)
  415. {
  416. return $answer && 'y' == strtolower($answer[0]);
  417. }
  418. else
  419. {
  420. return !$answer || 'y' == strtolower($answer[0]);
  421. }
  422. }
  423. /**
  424. * Asks for a value and validates the response.
  425. *
  426. * Available options:
  427. *
  428. * * value: A value to try against the validator before asking the user
  429. * * attempts: Max number of times to ask before giving up (false by default, which means infinite)
  430. * * style: Style for question output (QUESTION by default)
  431. *
  432. * @param string|array $question
  433. * @param sfValidatorBase $validator
  434. * @param array $options
  435. *
  436. * @return mixed
  437. */
  438. public function askAndValidate($question, sfValidatorBase $validator, array $options = array())
  439. {
  440. if (!is_array($question))
  441. {
  442. $question = array($question);
  443. }
  444. $options = array_merge(array(
  445. 'value' => null,
  446. 'attempts' => false,
  447. 'style' => 'QUESTION',
  448. ), $options);
  449. // does the provided value passes the validator?
  450. if ($options['value'])
  451. {
  452. try
  453. {
  454. return $validator->clean($options['value']);
  455. }
  456. catch (sfValidatorError $error)
  457. {
  458. }
  459. }
  460. // no, ask the user for a valid user
  461. $error = null;
  462. while (false === $options['attempts'] || $options['attempts']--)
  463. {
  464. if (null !== $error)
  465. {
  466. $this->logBlock($error->getMessage(), 'ERROR');
  467. }
  468. $value = $this->ask($question, $options['style'], null);
  469. try
  470. {
  471. return $validator->clean($value);
  472. }
  473. catch (sfValidatorError $error)
  474. {
  475. }
  476. }
  477. throw $error;
  478. }
  479. /**
  480. * Returns an XML representation of a task.
  481. *
  482. * @return string An XML string representing the task
  483. */
  484. public function asXml()
  485. {
  486. $dom = new DOMDocument('1.0', 'UTF-8');
  487. $dom->formatOutput = true;
  488. $dom->appendChild($taskXML = $dom->createElement('task'));
  489. $taskXML->setAttribute('id', $this->getFullName());
  490. $taskXML->setAttribute('namespace', $this->getNamespace() ? $this->getNamespace() : '_global');
  491. $taskXML->setAttribute('name', $this->getName());
  492. $taskXML->appendChild($usageXML = $dom->createElement('usage'));
  493. $usageXML->appendChild($dom->createTextNode(sprintf($this->getSynopsis(), '')));
  494. $taskXML->appendChild($descriptionXML = $dom->createElement('description'));
  495. $descriptionXML->appendChild($dom->createTextNode(implode("\n ", explode("\n", $this->getBriefDescription()))));
  496. $taskXML->appendChild($helpXML = $dom->createElement('help'));
  497. $help = $this->detailedDescription;
  498. $help = str_replace(array('|COMMENT', '|INFO'), array('|strong', '|em'), $help);
  499. $help = preg_replace('/\[(.+?)\|(\w+)\]/s', '<$2>$1</$2>', $help);
  500. $helpXML->appendChild($dom->createTextNode(implode("\n ", explode("\n", $help))));
  501. $taskXML->appendChild($aliasesXML = $dom->createElement('aliases'));
  502. foreach ($this->getAliases() as $alias)
  503. {
  504. $aliasesXML->appendChild($aliasXML = $dom->createElement('alias'));
  505. $aliasXML->appendChild($dom->createTextNode($alias));
  506. }
  507. $taskXML->appendChild($argumentsXML = $dom->createElement('arguments'));
  508. foreach ($this->getArguments() as $argument)
  509. {
  510. $argumentsXML->appendChild($argumentXML = $dom->createElement('argument'));
  511. $argumentXML->setAttribute('name', $argument->getName());
  512. $argumentXML->setAttribute('is_required', $argument->isRequired() ? 1 : 0);
  513. $argumentXML->setAttribute('is_array', $argument->isArray() ? 1 : 0);
  514. $argumentXML->appendChild($helpXML = $dom->createElement('description'));
  515. $helpXML->appendChild($dom->createTextNode($argument->getHelp()));
  516. $argumentXML->appendChild($defaultsXML = $dom->createElement('defaults'));
  517. $defaults = is_array($argument->getDefault()) ? $argument->getDefault() : ($argument->getDefault() ? array($argument->getDefault()) : array());
  518. foreach ($defaults as $default)
  519. {
  520. $defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
  521. $defaultXML->appendChild($dom->createTextNode($default));
  522. }
  523. }
  524. $taskXML->appendChild($optionsXML = $dom->createElement('options'));
  525. foreach ($this->getOptions() as $option)
  526. {
  527. $optionsXML->appendChild($optionXML = $dom->createElement('option'));
  528. $optionXML->setAttribute('name', '--'.$option->getName());
  529. $optionXML->setAttribute('shortcut', $option->getShortcut() ? '-'.$option->getShortcut() : '');
  530. $optionXML->setAttribute('accept_parameter', $option->acceptParameter() ? 1 : 0);
  531. $optionXML->setAttribute('is_parameter_required', $option->isParameterRequired() ? 1 : 0);
  532. $optionXML->setAttribute('is_multiple', $option->isArray() ? 1 : 0);
  533. $optionXML->appendChild($helpXML = $dom->createElement('description'));
  534. $helpXML->appendChild($dom->createTextNode($option->getHelp()));
  535. if ($option->acceptParameter())
  536. {
  537. $optionXML->appendChild($defaultsXML = $dom->createElement('defaults'));
  538. $defaults = is_array($option->getDefault()) ? $option->getDefault() : ($option->getDefault() ? array($option->getDefault()) : array());
  539. foreach ($defaults as $default)
  540. {
  541. $defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
  542. $defaultXML->appendChild($dom->createTextNode($default));
  543. }
  544. }
  545. }
  546. return $dom->saveXml();
  547. }
  548. /**
  549. * Executes the current task.
  550. *
  551. * @param array $arguments An array of arguments
  552. * @param array $options An array of options
  553. *
  554. * @return integer 0 if everything went fine, or an error code
  555. */
  556. abstract protected function execute($arguments = array(), $options = array());
  557. protected function strlen($string)
  558. {
  559. return function_exists('mb_strlen') ? mb_strlen($string) : strlen($string);
  560. }
  561. }

Debug toolbar