PageRenderTime 27ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/libraries/rokcommon/Doctrine/Cli.php

https://bitbucket.org/pastor399/newcastleunifc
PHP | 679 lines | 307 code | 92 blank | 280 comment | 42 complexity | af69590c1ce7315784c066bbaaa2a677 MD5 | raw file
  1. <?php
  2. /*
  3. * $Id: Cli.php 48519 2012-02-03 23:18:52Z btowles $
  4. *
  5. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  6. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  7. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  8. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  9. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  10. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  11. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  12. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  13. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  14. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  15. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  16. *
  17. * This software consists of voluntary contributions made by many individuals
  18. * and is licensed under the LGPL. For more information, see
  19. * <http://www.doctrine-project.org>.
  20. */
  21. /**
  22. * Command line interface class
  23. *
  24. * Interface for easily executing Doctrine_Task classes from a command line interface
  25. *
  26. * @package Doctrine
  27. * @subpackage Cli
  28. * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
  29. * @link www.doctrine-project.org
  30. * @since 1.0
  31. * @version $Revision: 2761 $
  32. * @author Jonathan H. Wage <jwage@mac.com>
  33. */
  34. class Doctrine_Cli
  35. {
  36. /**
  37. * The name of the Doctrine Task base class
  38. *
  39. * @var string
  40. */
  41. const TASK_BASE_CLASS = 'Doctrine_Task';
  42. /**
  43. * @var string
  44. */
  45. protected $_scriptName = null;
  46. /**
  47. * @var array
  48. */
  49. private $_config;
  50. /**
  51. * @var object Doctrine_Cli_Formatter
  52. */
  53. private $_formatter;
  54. /**
  55. * An array, keyed on class name, containing task instances
  56. *
  57. * @var array
  58. */
  59. private $_registeredTask = array();
  60. /**
  61. * @var object Doctrine_Task
  62. */
  63. private $_taskInstance;
  64. /**
  65. * __construct
  66. *
  67. * @param array [$config=array()]
  68. * @param object|null [$formatter=null] Doctrine_Cli_Formatter
  69. */
  70. public function __construct(array $config = array(), Doctrine_Cli_Formatter $formatter = null)
  71. {
  72. $this->setConfig($config);
  73. $this->setFormatter($formatter ? $formatter : new Doctrine_Cli_AnsiColorFormatter());
  74. $this->includeAndRegisterTaskClasses();
  75. }
  76. /**
  77. * @param array $config
  78. */
  79. public function setConfig(array $config)
  80. {
  81. $this->_config = $config;
  82. }
  83. /**
  84. * @return array
  85. */
  86. public function getConfig()
  87. {
  88. return $this->_config;
  89. }
  90. /**
  91. * @param object $formatter Doctrine_Cli_Formatter
  92. */
  93. public function setFormatter(Doctrine_Cli_Formatter $formatter)
  94. {
  95. $this->_formatter = $formatter;
  96. }
  97. /**
  98. * @return object Doctrine_Cli_Formatter
  99. */
  100. public function getFormatter()
  101. {
  102. return $this->_formatter;
  103. }
  104. /**
  105. * Returns the specified value from the config, or the default value, if specified
  106. *
  107. * @param string $name
  108. * @return mixed
  109. * @throws OutOfBoundsException If the element does not exist in the config
  110. */
  111. public function getConfigValue($name/*, $defaultValue*/)
  112. {
  113. if (! isset($this->_config[$name])) {
  114. if (func_num_args() > 1) {
  115. return func_get_arg(1);
  116. }
  117. throw new OutOfBoundsException("The element \"{$name}\" does not exist in the config");
  118. }
  119. return $this->_config[$name];
  120. }
  121. /**
  122. * Returns TRUE if the element in the config has the specified value, or FALSE otherwise
  123. *
  124. * If $value is not passed, this method will return TRUE if the specified element has _any_ value, or FALSE if the
  125. * element is not set
  126. *
  127. * For strict checking, set $strict to TRUE - the default is FALSE
  128. *
  129. * @param string $name
  130. * @param mixed [$value=null]
  131. * @param bool [$strict=false]
  132. * @return bool
  133. */
  134. public function hasConfigValue($name, $value = null, $strict = false)
  135. {
  136. if (isset($this->_config[$name])) {
  137. if (func_num_args() < 2) {
  138. return true;
  139. }
  140. if ($strict) {
  141. return $this->_config[$name] === $value;
  142. }
  143. return $this->_config[$name] == $value;
  144. }
  145. return false;
  146. }
  147. /**
  148. * Sets the array of registered tasks
  149. *
  150. * @param array $registeredTask
  151. */
  152. public function setRegisteredTasks(array $registeredTask)
  153. {
  154. $this->_registeredTask = $registeredTask;
  155. }
  156. /**
  157. * Returns an array containing the registered tasks
  158. *
  159. * @return array
  160. */
  161. public function getRegisteredTasks()
  162. {
  163. return $this->_registeredTask;
  164. }
  165. /**
  166. * Returns TRUE if the specified Task-class is registered, or FALSE otherwise
  167. *
  168. * @param string $className
  169. * @return bool
  170. */
  171. public function taskClassIsRegistered($className)
  172. {
  173. return isset($this->_registeredTask[$className]);
  174. }
  175. /**
  176. * Returns TRUE if a task with the specified name is registered, or FALSE otherwise
  177. *
  178. * If a matching task is found, $className is set with the name of the implementing class
  179. *
  180. * @param string $taskName
  181. * @param string|null [&$className=null]
  182. * @return bool
  183. */
  184. public function taskNameIsRegistered($taskName, &$className = null)
  185. {
  186. foreach ($this->getRegisteredTasks() as $currClassName => $task) {
  187. if ($task->getTaskName() == $taskName) {
  188. $className = $currClassName;
  189. return true;
  190. }
  191. }
  192. return false;
  193. }
  194. /**
  195. * @param object $task Doctrine_Task
  196. */
  197. public function setTaskInstance(Doctrine_Task $task)
  198. {
  199. $this->_taskInstance = $task;
  200. }
  201. /**
  202. * @return object Doctrine_Task
  203. */
  204. public function getTaskInstance()
  205. {
  206. return $this->_taskInstance;
  207. }
  208. /**
  209. * Called by the constructor, this method includes and registers Doctrine core Tasks and then registers all other
  210. * loaded Task classes
  211. *
  212. * The second round of registering will pick-up loaded custom Tasks. Methods are provided that will allow users to
  213. * register Tasks loaded after creating an instance of Doctrine_Cli.
  214. */
  215. protected function includeAndRegisterTaskClasses()
  216. {
  217. $this->includeAndRegisterDoctrineTaskClasses();
  218. //Always autoregister custom tasks _unless_ we've been explicitly asked not to
  219. if ($this->getConfigValue('autoregister_custom_tasks', true)) {
  220. $this->registerIncludedTaskClasses();
  221. }
  222. }
  223. /**
  224. * Includes and registers Doctrine-style tasks from the specified directory / directories
  225. *
  226. * If no directory is given it looks in the default Doctrine/Task folder for the core tasks
  227. *
  228. * @param mixed [$directories=null] Can be a string path or array of paths
  229. */
  230. protected function includeAndRegisterDoctrineTaskClasses($directories = null)
  231. {
  232. if (is_null($directories)) {
  233. $directories = Doctrine_Core::getPath() . DIRECTORY_SEPARATOR . 'Doctrine' . DIRECTORY_SEPARATOR . 'Task';
  234. }
  235. foreach ((array) $directories as $directory) {
  236. foreach ($this->includeDoctrineTaskClasses($directory) as $className) {
  237. $this->registerTaskClass($className);
  238. }
  239. }
  240. }
  241. /**
  242. * Attempts to include Doctrine-style Task-classes from the specified directory - and nothing more besides
  243. *
  244. * Returns an array containing the names of Task classes included
  245. *
  246. * This method effectively makes two assumptions:
  247. * - The directory contains only _Task_ class-files
  248. * - The class files, and the class in each, follow the Doctrine naming conventions
  249. *
  250. * This means that a file called "Foo.php", say, will be expected to contain a Task class called
  251. * "Doctrine_Task_Foo". Hence the method's name, "include*Doctrine*TaskClasses".
  252. *
  253. * @param string $directory
  254. * @return array $taskClassesIncluded
  255. * @throws InvalidArgumentException If the directory does not exist
  256. */
  257. protected function includeDoctrineTaskClasses($directory)
  258. {
  259. if (! is_dir($directory)) {
  260. throw new InvalidArgumentException("The directory \"{$directory}\" does not exist");
  261. }
  262. $taskClassesIncluded = array();
  263. $iterator = new RecursiveIteratorIterator(
  264. new RecursiveDirectoryIterator($directory),
  265. RecursiveIteratorIterator::LEAVES_ONLY
  266. );
  267. foreach ($iterator as $file) {
  268. $baseName = $file->getFileName();
  269. /*
  270. * Class-files must start with an uppercase letter. This additional check will help prevent us
  271. * accidentally running 'executable' scripts that may be mixed-in with the class files.
  272. */
  273. $matched = (bool) preg_match('/^([A-Z].*?)\.php$/', $baseName, $matches);
  274. if ( ! ($matched && (strpos($baseName, '.inc') === false))) {
  275. continue;
  276. }
  277. $expectedClassName = self::TASK_BASE_CLASS . '_' . $matches[1];
  278. if ( ! class_exists($expectedClassName)) {
  279. require_once($file->getPathName());
  280. }
  281. //So was the expected class included, and is it a task? If so, we'll let the calling function know.
  282. if (class_exists($expectedClassName, false) && $this->classIsTask($expectedClassName)) {
  283. $taskClassesIncluded[] = $expectedClassName;
  284. }
  285. }
  286. return $taskClassesIncluded;
  287. }
  288. /**
  289. * Registers the specified _included_ task-class
  290. *
  291. * @param string $className
  292. * @throws InvalidArgumentException If the class does not exist or the task-name is blank
  293. * @throws DomainException If the class is not a Doctrine Task
  294. */
  295. public function registerTaskClass($className)
  296. {
  297. //Simply ignore registered classes
  298. if ($this->taskClassIsRegistered($className)) {
  299. return;
  300. }
  301. if ( ! class_exists($className/*, false*/)) {
  302. throw new InvalidArgumentException("The task class \"{$className}\" does not exist");
  303. }
  304. if ( ! $this->classIsTask($className)) {
  305. throw new DomainException("The class \"{$className}\" is not a Doctrine Task");
  306. }
  307. $this->_registeredTask[$className] = $this->createTaskInstance($className, $this);
  308. }
  309. /**
  310. * Returns TRUE if the specified class is a Task, or FALSE otherwise
  311. *
  312. * @param string $className
  313. * @return bool
  314. */
  315. protected function classIsTask($className)
  316. {
  317. $reflectionClass = new ReflectionClass($className);
  318. return (bool) $reflectionClass->isSubClassOf(self::TASK_BASE_CLASS);
  319. }
  320. /**
  321. * Creates, and returns, a new instance of the specified Task class
  322. *
  323. * Displays a message, and returns FALSE, if there were problems instantiating the class
  324. *
  325. * @param string $className
  326. * @param object $cli Doctrine_Cli
  327. * @return object Doctrine_Task
  328. */
  329. protected function createTaskInstance($className, Doctrine_Cli $cli)
  330. {
  331. return new $className($cli);
  332. }
  333. /**
  334. * Registers all loaded classes - by default - or the specified loaded Task classes
  335. *
  336. * This method will skip registered task classes, so it can be safely called many times over
  337. */
  338. public function registerIncludedTaskClasses()
  339. {
  340. foreach (get_declared_classes() as $className) {
  341. if ($this->classIsTask($className)) {
  342. $this->registerTaskClass($className);
  343. }
  344. }
  345. }
  346. /**
  347. * Notify the formatter of a message
  348. *
  349. * @param string $notification The notification message
  350. * @param string $style Style to format the notification with(INFO, ERROR)
  351. * @return void
  352. */
  353. public function notify($notification = null, $style = 'HEADER')
  354. {
  355. $formatter = $this->getFormatter();
  356. echo(
  357. $formatter->format($this->getTaskInstance()->getTaskName(), 'INFO') . ' - ' .
  358. $formatter->format($notification, $style) . "\n"
  359. );
  360. }
  361. /**
  362. * Formats, and then returns, the message in the specified exception
  363. *
  364. * @param Exception $exception
  365. * @return string
  366. */
  367. protected function formatExceptionMessage(Exception $exception)
  368. {
  369. $message = $exception->getMessage();
  370. if (Doctrine_Core::debug()) {
  371. $message .= "\n" . $exception->getTraceAsString();
  372. }
  373. return $this->getFormatter()->format($message, 'ERROR') . "\n";
  374. }
  375. /**
  376. * Notify the formatter of an exception
  377. *
  378. * N.B. This should really only be called by Doctrine_Cli::run(). Exceptions should be thrown when errors occur:
  379. * it's up to Doctrine_Cli::run() to determine how those exceptions are reported.
  380. *
  381. * @param Exception $exception
  382. * @return void
  383. */
  384. protected function notifyException(Exception $exception)
  385. {
  386. echo $this->formatExceptionMessage($exception);
  387. }
  388. /**
  389. * Public function to run the loaded task with the passed arguments
  390. *
  391. * @param array $args
  392. * @return void
  393. * @throws Doctrine_Cli_Exception
  394. * @todo Should know more about what we're attempting to run so feedback can be improved. Continue refactoring.
  395. */
  396. public function run(array $args)
  397. {
  398. try {
  399. $this->_run($args);
  400. } catch (Exception $exception) {
  401. //Do not rethrow exceptions by default
  402. if ($this->getConfigValue('rethrow_exceptions', false)) {
  403. throw new $exception($this->formatExceptionMessage($exception));
  404. }
  405. $this->notifyException($exception);
  406. //User error
  407. if ($exception instanceof Doctrine_Cli_Exception) {
  408. $this->printTasks();
  409. }
  410. }
  411. }
  412. /**
  413. * Run the actual task execution with the passed arguments
  414. *
  415. * @param array $args Array of arguments for this task being executed
  416. * @return void
  417. * @throws Doctrine_Cli_Exception If the requested task has not been registered or if required arguments are missing
  418. * @todo Continue refactoring for testing
  419. */
  420. protected function _run(array $args)
  421. {
  422. $this->_scriptName = $args[0];
  423. $requestedTaskName = isset($args[1]) ? $args[1] : null;
  424. if ( ! $requestedTaskName || $requestedTaskName == 'help') {
  425. $this->printTasks(null, $requestedTaskName == 'help' ? true : false);
  426. return;
  427. }
  428. if ($requestedTaskName && isset($args[2]) && $args[2] === 'help') {
  429. $this->printTasks($requestedTaskName, true);
  430. return;
  431. }
  432. if (! $this->taskNameIsRegistered($requestedTaskName, $taskClassName)) {
  433. throw new Doctrine_Cli_Exception("The task \"{$requestedTaskName}\" has not been registered");
  434. }
  435. $taskInstance = $this->createTaskInstance($taskClassName, $this);
  436. $this->setTaskInstance($taskInstance);
  437. $this->executeTask($taskInstance, $this->prepareArgs(array_slice($args, 2)));
  438. }
  439. /**
  440. * Executes the task with the specified _prepared_ arguments
  441. *
  442. * @param object $task Doctrine_Task
  443. * @param array $preparedArguments
  444. * @throws Doctrine_Cli_Exception If required arguments are missing
  445. */
  446. protected function executeTask(Doctrine_Task $task, array $preparedArguments)
  447. {
  448. $task->setArguments($preparedArguments);
  449. if (! $task->validate()) {
  450. throw new Doctrine_Cli_Exception('Required arguments missing');
  451. }
  452. $task->execute();
  453. }
  454. /**
  455. * Prepare the raw arguments for execution. Combines with the required and optional argument
  456. * list in order to determine a complete array of arguments for the task
  457. *
  458. * @param array $args Array of raw arguments
  459. * @return array $prepared Array of prepared arguments
  460. * @todo Continue refactoring for testing
  461. */
  462. protected function prepareArgs(array $args)
  463. {
  464. $taskInstance = $this->getTaskInstance();
  465. $args = array_values($args);
  466. // First lets load populate an array with all the possible arguments. required and optional
  467. $prepared = array();
  468. $requiredArguments = $taskInstance->getRequiredArguments();
  469. foreach ($requiredArguments as $key => $arg) {
  470. $prepared[$arg] = null;
  471. }
  472. $optionalArguments = $taskInstance->getOptionalArguments();
  473. foreach ($optionalArguments as $key => $arg) {
  474. $prepared[$arg] = null;
  475. }
  476. // If we have a config array then lets try and fill some of the arguments with the config values
  477. foreach ($this->getConfig() as $key => $value) {
  478. if (array_key_exists($key, $prepared)) {
  479. $prepared[$key] = $value;
  480. }
  481. }
  482. // Now lets fill in the entered arguments to the prepared array
  483. $copy = $args;
  484. foreach ($prepared as $key => $value) {
  485. if ( ! $value && !empty($copy)) {
  486. $prepared[$key] = $copy[0];
  487. unset($copy[0]);
  488. $copy = array_values($copy);
  489. }
  490. }
  491. return $prepared;
  492. }
  493. /**
  494. * Prints an index of all the available tasks in the CLI instance
  495. *
  496. * @param string|null [$taskName=null]
  497. * @param bool [$full=false]
  498. * @todo Continue refactoring for testing
  499. */
  500. public function printTasks($taskName = null, $full = false)
  501. {
  502. $formatter = $this->getFormatter();
  503. $config = $this->getConfig();
  504. $taskIndex = $formatter->format('Doctrine Command Line Interface', 'HEADER') . "\n\n";
  505. foreach ($this->getRegisteredTasks() as $task) {
  506. if ($taskName && (strtolower($taskName) != strtolower($task->getTaskName()))) {
  507. continue;
  508. }
  509. $taskIndex .= $formatter->format($this->_scriptName . ' ' . $task->getTaskName(), 'INFO');
  510. if ($full) {
  511. $taskIndex .= ' - ' . $task->getDescription() . "\n";
  512. $args = '';
  513. $args .= $this->assembleArgumentList($task->getRequiredArgumentsDescriptions(), $config, $formatter);
  514. $args .= $this->assembleArgumentList($task->getOptionalArgumentsDescriptions(), $config, $formatter);
  515. if ($args) {
  516. $taskIndex .= "\n" . $formatter->format('Arguments:', 'HEADER') . "\n" . $args;
  517. }
  518. }
  519. $taskIndex .= "\n";
  520. }
  521. echo $taskIndex;
  522. }
  523. /**
  524. * @param array $argumentsDescriptions
  525. * @param array $config
  526. * @param object $formatter Doctrine_Cli_Formatter
  527. * @return string
  528. */
  529. protected function assembleArgumentList(array $argumentsDescriptions, array $config, Doctrine_Cli_Formatter $formatter)
  530. {
  531. $argumentList = '';
  532. foreach ($argumentsDescriptions as $name => $description) {
  533. $argumentList .= $formatter->format($name, 'ERROR') . ' - ';
  534. if (isset($config[$name])) {
  535. $argumentList .= $formatter->format($config[$name], 'COMMENT');
  536. } else {
  537. $argumentList .= $description;
  538. }
  539. $argumentList .= "\n";
  540. }
  541. return $argumentList;
  542. }
  543. /**
  544. * Used by Doctrine_Cli::loadTasks() and Doctrine_Cli::getLoadedTasks() to re-create their pre-refactoring behaviour
  545. *
  546. * @ignore
  547. * @param array $registeredTask
  548. * @return array
  549. */
  550. private function createOldStyleTaskList(array $registeredTask)
  551. {
  552. $taskNames = array();
  553. foreach ($registeredTask as $className => $task) {
  554. $taskName = $task->getTaskName();
  555. $taskNames[$taskName] = $taskName;
  556. }
  557. return $taskNames;
  558. }
  559. /**
  560. * Old method retained for backwards compatibility
  561. *
  562. * @deprecated
  563. */
  564. public function loadTasks($directory = null)
  565. {
  566. $this->includeAndRegisterDoctrineTaskClasses($directory);
  567. return $this->createOldStyleTaskList($this->getRegisteredTasks());
  568. }
  569. /**
  570. * Old method retained for backwards compatibility
  571. *
  572. * @deprecated
  573. */
  574. protected function _getTaskClassFromArgs(array $args)
  575. {
  576. return self::TASK_BASE_CLASS . '_' . Doctrine_Inflector::classify(str_replace('-', '_', $args[1]));
  577. }
  578. /**
  579. * Old method retained for backwards compatibility
  580. *
  581. * @deprecated
  582. */
  583. public function getLoadedTasks()
  584. {
  585. return $this->createOldStyleTaskList($this->getRegisteredTasks());
  586. }
  587. }