ConsoleShell.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since CakePHP(tm) v 1.2.0.5012
  13. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  14. */
  15. App::uses('AppShell', 'Console/Command');
  16. /**
  17. * Provides a very basic 'interactive' console for CakePHP apps.
  18. *
  19. * @package Cake.Console.Command
  20. */
  21. class ConsoleShell extends AppShell {
  22. /**
  23. * Available binding types
  24. *
  25. * @var array
  26. */
  27. public $associations = array('hasOne', 'hasMany', 'belongsTo', 'hasAndBelongsToMany');
  28. /**
  29. * Chars that describe invalid commands
  30. *
  31. * @var array
  32. */
  33. public $badCommandChars = array('$', ';');
  34. /**
  35. * Available models
  36. *
  37. * @var array
  38. */
  39. public $models = array();
  40. /**
  41. * Override startup of the Shell
  42. *
  43. * @return void
  44. */
  45. public function startup() {
  46. App::uses('Dispatcher', 'Routing');
  47. $this->Dispatcher = new Dispatcher();
  48. $this->models = App::objects('Model');
  49. foreach ($this->models as $model) {
  50. $class = $model;
  51. $this->models[$model] = $class;
  52. App::uses($class, 'Model');
  53. $this->{$class} = new $class();
  54. }
  55. $this->out(__d('cake_console', 'Model classes:'));
  56. $this->hr();
  57. foreach ($this->models as $model) {
  58. $this->out(" - {$model}");
  59. }
  60. if (!$this->_loadRoutes()) {
  61. $message = __d(
  62. 'cake_console',
  63. 'There was an error loading the routes config. Please check that the file exists and contains no errors.'
  64. );
  65. $this->err($message);
  66. }
  67. }
  68. public function getOptionParser() {
  69. $description = array(
  70. 'The interactive console is a tool for testing parts of your',
  71. 'app before you write code.',
  72. '',
  73. 'See below for a list of supported commands.'
  74. );
  75. $epilog = array(
  76. '<info>Model testing</info>',
  77. '',
  78. 'To test model results, use the name of your model without a leading $',
  79. 'e.g. Foo->find("all")',
  80. "",
  81. 'To dynamically set associations, you can do the following:',
  82. '',
  83. "\tModelA bind <association> ModelB",
  84. '',
  85. "where the supported associations are hasOne, hasMany, belongsTo, hasAndBelongsToMany",
  86. "",
  87. 'To dynamically remove associations, you can do the following:',
  88. '',
  89. "\t ModelA unbind <association> ModelB",
  90. '',
  91. "where the supported associations are the same as above",
  92. "",
  93. "To save a new field in a model, you can do the following:",
  94. '',
  95. "\tModelA->save(array('foo' => 'bar', 'baz' => 0))",
  96. '',
  97. "where you are passing a hash of data to be saved in the format",
  98. "of field => value pairs",
  99. "",
  100. "To get column information for a model, use the following:",
  101. '',
  102. "\tModelA columns",
  103. '',
  104. "which returns a list of columns and their type",
  105. "",
  106. '<info>Route testing</info>',
  107. "",
  108. 'To test URLs against your app\'s route configuration, type:',
  109. "",
  110. "\tRoute <url>",
  111. "",
  112. "where url is the path to your your action plus any query parameters,",
  113. "minus the application's base path. For example:",
  114. "",
  115. "\tRoute /posts/view/1",
  116. "",
  117. "will return something like the following:",
  118. "",
  119. "\tarray(",
  120. "\t [...]",
  121. "\t 'controller' => 'posts',",
  122. "\t 'action' => 'view',",
  123. "\t [...]",
  124. "\t)",
  125. "",
  126. 'Alternatively, you can use simple array syntax to test reverse',
  127. 'To reload your routes config (Config/routes.php), do the following:',
  128. "",
  129. "\tRoutes reload",
  130. "",
  131. 'To show all connected routes, do the following:',
  132. '',
  133. "\tRoutes show",
  134. );
  135. return parent::getOptionParser()
  136. ->description($description)
  137. ->epilog($epilog);
  138. }
  139. /**
  140. * Prints the help message
  141. *
  142. * @return void
  143. */
  144. public function help() {
  145. $optionParser = $this->getOptionParser();
  146. $this->out($optionParser->epilog());
  147. }
  148. /**
  149. * Override main() to handle action
  150. *
  151. * @param string $command
  152. * @return void
  153. */
  154. public function main($command = null) {
  155. while (true) {
  156. if (empty($command)) {
  157. $command = trim($this->in(''));
  158. }
  159. switch ($command) {
  160. case 'help':
  161. $this->help();
  162. break;
  163. case 'quit':
  164. case 'exit':
  165. return true;
  166. case 'models':
  167. $this->out(__d('cake_console', 'Model classes:'));
  168. $this->hr();
  169. foreach ($this->models as $model) {
  170. $this->out(" - {$model}");
  171. }
  172. break;
  173. case preg_match("/^(\w+) bind (\w+) (\w+)/", $command, $tmp):
  174. foreach ($tmp as $data) {
  175. $data = strip_tags($data);
  176. $data = str_replace($this->badCommandChars, "", $data);
  177. }
  178. $modelA = $tmp[1];
  179. $association = $tmp[2];
  180. $modelB = $tmp[3];
  181. if ($this->_isValidModel($modelA) && $this->_isValidModel($modelB) && in_array($association, $this->associations)) {
  182. $this->{$modelA}->bindModel(array($association => array($modelB => array('className' => $modelB))), false);
  183. $this->out(__d('cake_console', "Created %s association between %s and %s",
  184. $association, $modelA, $modelB));
  185. } else {
  186. $this->out(__d('cake_console', "Please verify you are using valid models and association types"));
  187. }
  188. break;
  189. case preg_match("/^(\w+) unbind (\w+) (\w+)/", $command, $tmp):
  190. foreach ($tmp as $data) {
  191. $data = strip_tags($data);
  192. $data = str_replace($this->badCommandChars, "", $data);
  193. }
  194. $modelA = $tmp[1];
  195. $association = $tmp[2];
  196. $modelB = $tmp[3];
  197. // Verify that there is actually an association to unbind
  198. $currentAssociations = $this->{$modelA}->getAssociated();
  199. $validCurrentAssociation = false;
  200. foreach ($currentAssociations as $model => $currentAssociation) {
  201. if ($model == $modelB && $association == $currentAssociation) {
  202. $validCurrentAssociation = true;
  203. }
  204. }
  205. if ($this->_isValidModel($modelA) && $this->_isValidModel($modelB) && in_array($association, $this->associations) && $validCurrentAssociation) {
  206. $this->{$modelA}->unbindModel(array($association => array($modelB)));
  207. $this->out(__d('cake_console', "Removed %s association between %s and %s",
  208. $association, $modelA, $modelB));
  209. } else {
  210. $this->out(__d('cake_console', "Please verify you are using valid models, valid current association, and valid association types"));
  211. }
  212. break;
  213. case (strpos($command, "->find") > 0):
  214. // Remove any bad info
  215. $command = strip_tags($command);
  216. $command = str_replace($this->badCommandChars, "", $command);
  217. // Do we have a valid model?
  218. list($modelToCheck, $tmp) = explode('->', $command);
  219. if ($this->_isValidModel($modelToCheck)) {
  220. $findCommand = "\$data = \$this->$command;";
  221. //@codingStandardsIgnoreStart
  222. @eval($findCommand);
  223. //@codingStandardsIgnoreEnd
  224. if (is_array($data)) {
  225. foreach ($data as $idx => $results) {
  226. if (is_numeric($idx)) { // findAll() output
  227. foreach ($results as $modelName => $result) {
  228. $this->out("$modelName");
  229. foreach ($result as $field => $value) {
  230. if (is_array($value)) {
  231. foreach ($value as $field2 => $value2) {
  232. $this->out("\t$field2: $value2");
  233. }
  234. $this->out();
  235. } else {
  236. $this->out("\t$field: $value");
  237. }
  238. }
  239. }
  240. } else { // find() output
  241. $this->out($idx);
  242. foreach ($results as $field => $value) {
  243. if (is_array($value)) {
  244. foreach ($value as $field2 => $value2) {
  245. $this->out("\t$field2: $value2");
  246. }
  247. $this->out();
  248. } else {
  249. $this->out("\t$field: $value");
  250. }
  251. }
  252. }
  253. }
  254. } else {
  255. $this->out();
  256. $this->out(__d('cake_console', "No result set found"));
  257. }
  258. } else {
  259. $this->out(__d('cake_console', "%s is not a valid model", $modelToCheck));
  260. }
  261. break;
  262. case (strpos($command, '->save') > 0):
  263. // Validate the model we're trying to save here
  264. $command = strip_tags($command);
  265. $command = str_replace($this->badCommandChars, "", $command);
  266. list($modelToSave, $tmp) = explode("->", $command);
  267. if ($this->_isValidModel($modelToSave)) {
  268. // Extract the array of data we are trying to build
  269. list(, $data) = explode("->save", $command);
  270. $data = preg_replace('/^\(*(array)?\(*(.+?)\)*$/i', '\\2', $data);
  271. $saveCommand = "\$this->{$modelToSave}->save(array('{$modelToSave}' => array({$data})));";
  272. //@codingStandardsIgnoreStart
  273. @eval($saveCommand);
  274. //@codingStandardsIgnoreEnd
  275. $this->out(__d('cake_console', 'Saved record for %s', $modelToSave));
  276. }
  277. break;
  278. case preg_match("/^(\w+) columns/", $command, $tmp):
  279. $modelToCheck = strip_tags(str_replace($this->badCommandChars, "", $tmp[1]));
  280. if ($this->_isValidModel($modelToCheck)) {
  281. // Get the column info for this model
  282. $fieldsCommand = "\$data = \$this->{$modelToCheck}->getColumnTypes();";
  283. //@codingStandardsIgnoreStart
  284. @eval($fieldsCommand);
  285. //@codingStandardsIgnoreEnd
  286. if (is_array($data)) {
  287. foreach ($data as $field => $type) {
  288. $this->out("\t{$field}: {$type}");
  289. }
  290. }
  291. } else {
  292. $this->out(__d('cake_console', "Please verify that you selected a valid model"));
  293. }
  294. break;
  295. case preg_match("/^routes\s+reload/i", $command, $tmp):
  296. if (!$this->_loadRoutes()) {
  297. $this->err(__d('cake_console', "There was an error loading the routes config. Please check that the file exists and is free of parse errors."));
  298. break;
  299. }
  300. $this->out(__d('cake_console', "Routes configuration reloaded, %d routes connected", count(Router::$routes)));
  301. break;
  302. case preg_match("/^routes\s+show/i", $command, $tmp):
  303. $this->out(print_r(Hash::combine(Router::$routes, '{n}.template', '{n}.defaults'), true));
  304. break;
  305. case (preg_match("/^route\s+(\(.*\))$/i", $command, $tmp) == true):
  306. //@codingStandardsIgnoreStart
  307. if ($url = eval('return array' . $tmp[1] . ';')) {
  308. //@codingStandardsIgnoreEnd
  309. $this->out(Router::url($url));
  310. }
  311. break;
  312. case preg_match("/^route\s+(.*)/i", $command, $tmp):
  313. $this->out(var_export(Router::parse($tmp[1]), true));
  314. break;
  315. default:
  316. $this->out(__d('cake_console', "Invalid command"));
  317. $this->out();
  318. break;
  319. }
  320. $command = '';
  321. }
  322. }
  323. /**
  324. * Tells if the specified model is included in the list of available models
  325. *
  326. * @param string $modelToCheck
  327. * @return boolean true if is an available model, false otherwise
  328. */
  329. protected function _isValidModel($modelToCheck) {
  330. return in_array($modelToCheck, $this->models);
  331. }
  332. /**
  333. * Reloads the routes configuration from app/Config/routes.php, and compiles
  334. * all routes found
  335. *
  336. * @return boolean True if config reload was a success, otherwise false
  337. */
  338. protected function _loadRoutes() {
  339. Router::reload();
  340. extract(Router::getNamedExpressions());
  341. //@codingStandardsIgnoreStart
  342. if (!@include APP . 'Config' . DS . 'routes.php') {
  343. //@codingStandardsIgnoreEnd
  344. return false;
  345. }
  346. CakePlugin::routes();
  347. Router::parse('/');
  348. return true;
  349. }
  350. }