ModelTask.php 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914
  1. <?php
  2. /**
  3. * The ModelTask handles creating and updating models files.
  4. *
  5. * PHP 5
  6. *
  7. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  8. * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
  9. *
  10. * Licensed under The MIT License
  11. * Redistributions of files must retain the above copyright notice.
  12. *
  13. * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
  14. * @link http://cakephp.org CakePHP(tm) Project
  15. * @package cake.console.shells.tasks
  16. * @since CakePHP(tm) v 1.2
  17. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  18. */
  19. App::uses('BakeTask', 'Console/Command/Task');
  20. App::uses('ConnectionManager', 'Model');
  21. App::uses('Model', 'Model');
  22. App::uses('Validation', 'Utility');
  23. /**
  24. * Task class for creating and updating model files.
  25. *
  26. * @package cake.console.shells.tasks
  27. */
  28. class ModelTask extends BakeTask {
  29. /**
  30. * path to Model directory
  31. *
  32. * @var string
  33. * @access public
  34. */
  35. public $path = null;
  36. /**
  37. * tasks
  38. *
  39. * @var array
  40. * @access public
  41. */
  42. public $tasks = array('DbConfig', 'Fixture', 'Test', 'Template');
  43. /**
  44. * Tables to skip when running all()
  45. *
  46. * @var array
  47. * @access protected
  48. */
  49. public $skipTables = array('i18n');
  50. /**
  51. * Holds tables found on connection.
  52. *
  53. * @var array
  54. * @access protected
  55. */
  56. protected $_tables = array();
  57. /**
  58. * Holds validation method map.
  59. *
  60. * @var array
  61. * @access protected
  62. */
  63. protected $_validations = array();
  64. /**
  65. * Override initialize
  66. *
  67. */
  68. public function initialize() {
  69. $this->path = current(App::path('Model'));
  70. }
  71. /**
  72. * Execution method always used for tasks
  73. *
  74. */
  75. public function execute() {
  76. parent::execute();
  77. if (empty($this->args)) {
  78. $this->_interactive();
  79. }
  80. if (!empty($this->args[0])) {
  81. $this->interactive = false;
  82. if (!isset($this->connection)) {
  83. $this->connection = 'default';
  84. }
  85. if (strtolower($this->args[0]) == 'all') {
  86. return $this->all();
  87. }
  88. $model = $this->_modelName($this->args[0]);
  89. $object = $this->_getModelObject($model);
  90. if ($this->bake($object, false)) {
  91. if ($this->_checkUnitTest()) {
  92. $this->bakeFixture($model);
  93. $this->bakeTest($model);
  94. }
  95. }
  96. }
  97. }
  98. /**
  99. * Bake all models at once.
  100. *
  101. * @return void
  102. */
  103. public function all() {
  104. $this->listAll($this->connection, false);
  105. $unitTestExists = $this->_checkUnitTest();
  106. foreach ($this->_tables as $table) {
  107. if (in_array($table, $this->skipTables)) {
  108. continue;
  109. }
  110. $modelClass = Inflector::classify($table);
  111. $this->out(__d('cake_console', 'Baking %s', $modelClass));
  112. $object = $this->_getModelObject($modelClass);
  113. if ($this->bake($object, false) && $unitTestExists) {
  114. $this->bakeFixture($modelClass);
  115. $this->bakeTest($modelClass);
  116. }
  117. }
  118. }
  119. /**
  120. * Get a model object for a class name.
  121. *
  122. * @param string $className Name of class you want model to be.
  123. * @return object Model instance
  124. */
  125. protected function &_getModelObject($className, $table = null) {
  126. if (!$table) {
  127. $table = Inflector::tableize($className);
  128. }
  129. $object = new Model(array('name' => $className, 'table' => $table, 'ds' => $this->connection));
  130. return $object;
  131. }
  132. /**
  133. * Generate a key value list of options and a prompt.
  134. *
  135. * @param array $options Array of options to use for the selections. indexes must start at 0
  136. * @param string $prompt Prompt to use for options list.
  137. * @param integer $default The default option for the given prompt.
  138. * @return result of user choice.
  139. */
  140. public function inOptions($options, $prompt = null, $default = null) {
  141. $valid = false;
  142. $max = count($options);
  143. while (!$valid) {
  144. foreach ($options as $i => $option) {
  145. $this->out($i + 1 .'. ' . $option);
  146. }
  147. if (empty($prompt)) {
  148. $prompt = __d('cake_console', 'Make a selection from the choices above');
  149. }
  150. $choice = $this->in($prompt, null, $default);
  151. if (intval($choice) > 0 && intval($choice) <= $max) {
  152. $valid = true;
  153. }
  154. }
  155. return $choice - 1;
  156. }
  157. /**
  158. * Handles interactive baking
  159. *
  160. * @access private
  161. */
  162. protected function _interactive() {
  163. $this->hr();
  164. $this->out(__d('cake_console', "Bake Model\nPath: %s", $this->path));
  165. $this->hr();
  166. $this->interactive = true;
  167. $primaryKey = 'id';
  168. $validate = $associations = array();
  169. if (empty($this->connection)) {
  170. $this->connection = $this->DbConfig->getConfig();
  171. }
  172. $currentModelName = $this->getName();
  173. $useTable = $this->getTable($currentModelName);
  174. $db = ConnectionManager::getDataSource($this->connection);
  175. $fullTableName = $db->fullTableName($useTable);
  176. if (in_array($useTable, $this->_tables)) {
  177. $tempModel = new Model(array('name' => $currentModelName, 'table' => $useTable, 'ds' => $this->connection));
  178. $fields = $tempModel->schema(true);
  179. if (!array_key_exists('id', $fields)) {
  180. $primaryKey = $this->findPrimaryKey($fields);
  181. }
  182. } else {
  183. $this->err(__d('cake_console', 'Table %s does not exist, cannot bake a model without a table.', $useTable));
  184. $this->_stop();
  185. return false;
  186. }
  187. $displayField = $tempModel->hasField(array('name', 'title'));
  188. if (!$displayField) {
  189. $displayField = $this->findDisplayField($tempModel->schema());
  190. }
  191. $prompt = __d('cake_console', "Would you like to supply validation criteria \nfor the fields in your model?");
  192. $wannaDoValidation = $this->in($prompt, array('y','n'), 'y');
  193. if (array_search($useTable, $this->_tables) !== false && strtolower($wannaDoValidation) == 'y') {
  194. $validate = $this->doValidation($tempModel);
  195. }
  196. $prompt = __d('cake_console', "Would you like to define model associations\n(hasMany, hasOne, belongsTo, etc.)?");
  197. $wannaDoAssoc = $this->in($prompt, array('y','n'), 'y');
  198. if (strtolower($wannaDoAssoc) == 'y') {
  199. $associations = $this->doAssociations($tempModel);
  200. }
  201. $this->out();
  202. $this->hr();
  203. $this->out(__d('cake_console', 'The following Model will be created:'));
  204. $this->hr();
  205. $this->out(__d('cake_console', "Name: %s", $currentModelName));
  206. if ($this->connection !== 'default') {
  207. $this->out(__d('cake_console', "DB Config: %s", $this->connection));
  208. }
  209. if ($fullTableName !== Inflector::tableize($currentModelName)) {
  210. $this->out(__d('cake_console', 'DB Table: %s', $fullTableName));
  211. }
  212. if ($primaryKey != 'id') {
  213. $this->out(__d('cake_console', 'Primary Key: %s', $primaryKey));
  214. }
  215. if (!empty($validate)) {
  216. $this->out(__d('cake_console', 'Validation: %s', print_r($validate, true)));
  217. }
  218. if (!empty($associations)) {
  219. $this->out(__d('cake_console', 'Associations:'));
  220. $assocKeys = array('belongsTo', 'hasOne', 'hasMany', 'hasAndBelongsToMany');
  221. foreach ($assocKeys as $assocKey) {
  222. $this->_printAssociation($currentModelName, $assocKey, $associations);
  223. }
  224. }
  225. $this->hr();
  226. $looksGood = $this->in(__d('cake_console', 'Look okay?'), array('y','n'), 'y');
  227. if (strtolower($looksGood) == 'y') {
  228. $vars = compact('associations', 'validate', 'primaryKey', 'useTable', 'displayField');
  229. $vars['useDbConfig'] = $this->connection;
  230. if ($this->bake($currentModelName, $vars)) {
  231. if ($this->_checkUnitTest()) {
  232. $this->bakeFixture($currentModelName, $useTable);
  233. $this->bakeTest($currentModelName, $useTable, $associations);
  234. }
  235. }
  236. } else {
  237. return false;
  238. }
  239. }
  240. /**
  241. * Print out all the associations of a particular type
  242. *
  243. * @param string $modelName Name of the model relations belong to.
  244. * @param string $type Name of association you want to see. i.e. 'belongsTo'
  245. * @param string $associations Collection of associations.
  246. * @return void
  247. */
  248. protected function _printAssociation($modelName, $type, $associations) {
  249. if (!empty($associations[$type])) {
  250. for ($i = 0; $i < count($associations[$type]); $i++) {
  251. $out = "\t" . $modelName . ' ' . $type . ' ' . $associations[$type][$i]['alias'];
  252. $this->out($out);
  253. }
  254. }
  255. }
  256. /**
  257. * Finds a primary Key in a list of fields.
  258. *
  259. * @param array $fields Array of fields that might have a primary key.
  260. * @return string Name of field that is a primary key.
  261. */
  262. public function findPrimaryKey($fields) {
  263. foreach ($fields as $name => $field) {
  264. if (isset($field['key']) && $field['key'] == 'primary') {
  265. break;
  266. }
  267. }
  268. return $this->in(__d('cake_console', 'What is the primaryKey?'), null, $name);
  269. }
  270. /**
  271. * interact with the user to find the displayField value for a model.
  272. *
  273. * @param array $fields Array of fields to look for and choose as a displayField
  274. * @return mixed Name of field to use for displayField or false if the user declines to choose
  275. */
  276. public function findDisplayField($fields) {
  277. $fieldNames = array_keys($fields);
  278. $prompt = __d('cake_console', "A displayField could not be automatically detected\nwould you like to choose one?");
  279. $continue = $this->in($prompt, array('y', 'n'));
  280. if (strtolower($continue) == 'n') {
  281. return false;
  282. }
  283. $prompt = __d('cake_console', 'Choose a field from the options above:');
  284. $choice = $this->inOptions($fieldNames, $prompt);
  285. return $fieldNames[$choice];
  286. }
  287. /**
  288. * Handles Generation and user interaction for creating validation.
  289. *
  290. * @param object $model Model to have validations generated for.
  291. * @return array $validate Array of user selected validations.
  292. */
  293. public function doValidation($model) {
  294. if (!is_object($model)) {
  295. return false;
  296. }
  297. $fields = $model->schema();
  298. if (empty($fields)) {
  299. return false;
  300. }
  301. $validate = array();
  302. $this->initValidations();
  303. foreach ($fields as $fieldName => $field) {
  304. $validation = $this->fieldValidation($fieldName, $field, $model->primaryKey);
  305. if (!empty($validation)) {
  306. $validate[$fieldName] = $validation;
  307. }
  308. }
  309. return $validate;
  310. }
  311. /**
  312. * Populate the _validations array
  313. *
  314. * @return void
  315. */
  316. public function initValidations() {
  317. $options = $choices = array();
  318. if (class_exists('Validation')) {
  319. $options = get_class_methods('Validation');
  320. }
  321. sort($options);
  322. $default = 1;
  323. foreach ($options as $key => $option) {
  324. if ($option{0} != '_') {
  325. $choices[$default] = strtolower($option);
  326. $default++;
  327. }
  328. }
  329. $this->_validations = $choices;
  330. return $choices;
  331. }
  332. /**
  333. * Does individual field validation handling.
  334. *
  335. * @param string $fieldName Name of field to be validated.
  336. * @param array $metaData metadata for field
  337. * @return array Array of validation for the field.
  338. */
  339. public function fieldValidation($fieldName, $metaData, $primaryKey = 'id') {
  340. $defaultChoice = count($this->_validations);
  341. $validate = $alreadyChosen = array();
  342. $anotherValidator = 'y';
  343. while ($anotherValidator == 'y') {
  344. if ($this->interactive) {
  345. $this->out();
  346. $this->out(__d('cake_console', 'Field: %s', $fieldName));
  347. $this->out(__d('cake_console', 'Type: %s', $metaData['type']));
  348. $this->hr();
  349. $this->out(__d('cake_console', 'Please select one of the following validation options:'));
  350. $this->hr();
  351. }
  352. $prompt = '';
  353. for ($i = 1; $i < $defaultChoice; $i++) {
  354. $prompt .= $i . ' - ' . $this->_validations[$i] . "\n";
  355. }
  356. $prompt .= __d('cake_console', "%s - Do not do any validation on this field.\n", $defaultChoice);
  357. $prompt .= __d('cake_console', "... or enter in a valid regex validation string.\n");
  358. $methods = array_flip($this->_validations);
  359. $guess = $defaultChoice;
  360. if ($metaData['null'] != 1 && !in_array($fieldName, array($primaryKey, 'created', 'modified', 'updated'))) {
  361. if ($fieldName == 'email') {
  362. $guess = $methods['email'];
  363. } elseif ($metaData['type'] == 'string') {
  364. $guess = $methods['notempty'];
  365. } elseif ($metaData['type'] == 'integer') {
  366. $guess = $methods['numeric'];
  367. } elseif ($metaData['type'] == 'boolean') {
  368. $guess = $methods['boolean'];
  369. } elseif ($metaData['type'] == 'date') {
  370. $guess = $methods['date'];
  371. } elseif ($metaData['type'] == 'time') {
  372. $guess = $methods['time'];
  373. }
  374. }
  375. if ($this->interactive === true) {
  376. $choice = $this->in($prompt, null, $guess);
  377. if (in_array($choice, $alreadyChosen)) {
  378. $this->out(__d('cake_console', "You have already chosen that validation rule,\nplease choose again"));
  379. continue;
  380. }
  381. if (!isset($this->_validations[$choice]) && is_numeric($choice)) {
  382. $this->out(__d('cake_console', 'Please make a valid selection.'));
  383. continue;
  384. }
  385. $alreadyChosen[] = $choice;
  386. } else {
  387. $choice = $guess;
  388. }
  389. if (isset($this->_validations[$choice])) {
  390. $validatorName = $this->_validations[$choice];
  391. } else {
  392. $validatorName = Inflector::slug($choice);
  393. }
  394. if ($choice != $defaultChoice) {
  395. if (is_numeric($choice) && isset($this->_validations[$choice])) {
  396. $validate[$validatorName] = $this->_validations[$choice];
  397. } else {
  398. $validate[$validatorName] = $choice;
  399. }
  400. }
  401. if ($this->interactive == true && $choice != $defaultChoice) {
  402. $anotherValidator = $this->in(__d('cake_console', 'Would you like to add another validation rule?'), array('y', 'n'), 'n');
  403. } else {
  404. $anotherValidator = 'n';
  405. }
  406. }
  407. return $validate;
  408. }
  409. /**
  410. * Handles associations
  411. *
  412. * @param object $model
  413. * @return array $assocaitons
  414. */
  415. public function doAssociations($model) {
  416. if (!is_object($model)) {
  417. return false;
  418. }
  419. if ($this->interactive === true) {
  420. $this->out(__d('cake_console', 'One moment while the associations are detected.'));
  421. }
  422. $fields = $model->schema(true);
  423. if (empty($fields)) {
  424. return false;
  425. }
  426. if (empty($this->_tables)) {
  427. $this->_tables = $this->getAllTables();
  428. }
  429. $associations = array(
  430. 'belongsTo' => array(), 'hasMany' => array(), 'hasOne'=> array(), 'hasAndBelongsToMany' => array()
  431. );
  432. $associations = $this->findBelongsTo($model, $associations);
  433. $associations = $this->findHasOneAndMany($model, $associations);
  434. $associations = $this->findHasAndBelongsToMany($model, $associations);
  435. if ($this->interactive !== true) {
  436. unset($associations['hasOne']);
  437. }
  438. if ($this->interactive === true) {
  439. $this->hr();
  440. if (empty($associations)) {
  441. $this->out(__d('cake_console', 'None found.'));
  442. } else {
  443. $this->out(__d('cake_console', 'Please confirm the following associations:'));
  444. $this->hr();
  445. $associations = $this->confirmAssociations($model, $associations);
  446. }
  447. $associations = $this->doMoreAssociations($model, $associations);
  448. }
  449. return $associations;
  450. }
  451. /**
  452. * Find belongsTo relations and add them to the associations list.
  453. *
  454. * @param object $model Model instance of model being generated.
  455. * @param array $associations Array of inprogress associations
  456. * @return array $associations with belongsTo added in.
  457. */
  458. public function findBelongsTo($model, $associations) {
  459. $fields = $model->schema(true);
  460. foreach ($fields as $fieldName => $field) {
  461. $offset = strpos($fieldName, '_id');
  462. if ($fieldName != $model->primaryKey && $fieldName != 'parent_id' && $offset !== false) {
  463. $tmpModelName = $this->_modelNameFromKey($fieldName);
  464. $associations['belongsTo'][] = array(
  465. 'alias' => $tmpModelName,
  466. 'className' => $tmpModelName,
  467. 'foreignKey' => $fieldName,
  468. );
  469. } elseif ($fieldName == 'parent_id') {
  470. $associations['belongsTo'][] = array(
  471. 'alias' => 'Parent' . $model->name,
  472. 'className' => $model->name,
  473. 'foreignKey' => $fieldName,
  474. );
  475. }
  476. }
  477. return $associations;
  478. }
  479. /**
  480. * Find the hasOne and HasMany relations and add them to associations list
  481. *
  482. * @param object $model Model instance being generated
  483. * @param array $associations Array of inprogress associations
  484. * @return array $associations with hasOne and hasMany added in.
  485. */
  486. public function findHasOneAndMany($model, $associations) {
  487. $foreignKey = $this->_modelKey($model->name);
  488. foreach ($this->_tables as $otherTable) {
  489. $tempOtherModel = $this->_getModelObject($this->_modelName($otherTable), $otherTable);
  490. $modelFieldsTemp = $tempOtherModel->schema(true);
  491. $pattern = '/_' . preg_quote($model->table, '/') . '|' . preg_quote($model->table, '/') . '_/';
  492. $possibleJoinTable = preg_match($pattern , $otherTable);
  493. if ($possibleJoinTable == true) {
  494. continue;
  495. }
  496. foreach ($modelFieldsTemp as $fieldName => $field) {
  497. $assoc = false;
  498. if ($fieldName != $model->primaryKey && $fieldName == $foreignKey) {
  499. $assoc = array(
  500. 'alias' => $tempOtherModel->name,
  501. 'className' => $tempOtherModel->name,
  502. 'foreignKey' => $fieldName
  503. );
  504. } elseif ($otherTable == $model->table && $fieldName == 'parent_id') {
  505. $assoc = array(
  506. 'alias' => 'Child' . $model->name,
  507. 'className' => $model->name,
  508. 'foreignKey' => $fieldName
  509. );
  510. }
  511. if ($assoc) {
  512. $associations['hasOne'][] = $assoc;
  513. $associations['hasMany'][] = $assoc;
  514. }
  515. }
  516. }
  517. return $associations;
  518. }
  519. /**
  520. * Find the hasAndBelongsToMany relations and add them to associations list
  521. *
  522. * @param object $model Model instance being generated
  523. * @param array $associations Array of in-progress associations
  524. * @return array $associations with hasAndBelongsToMany added in.
  525. */
  526. public function findHasAndBelongsToMany($model, $associations) {
  527. $foreignKey = $this->_modelKey($model->name);
  528. foreach ($this->_tables as $otherTable) {
  529. $tempOtherModel = $this->_getModelObject($this->_modelName($otherTable), $otherTable);
  530. $modelFieldsTemp = $tempOtherModel->schema(true);
  531. $offset = strpos($otherTable, $model->table . '_');
  532. $otherOffset = strpos($otherTable, '_' . $model->table);
  533. if ($offset !== false) {
  534. $offset = strlen($model->table . '_');
  535. $habtmName = $this->_modelName(substr($otherTable, $offset));
  536. $associations['hasAndBelongsToMany'][] = array(
  537. 'alias' => $habtmName,
  538. 'className' => $habtmName,
  539. 'foreignKey' => $foreignKey,
  540. 'associationForeignKey' => $this->_modelKey($habtmName),
  541. 'joinTable' => $otherTable
  542. );
  543. } elseif ($otherOffset !== false) {
  544. $habtmName = $this->_modelName(substr($otherTable, 0, $otherOffset));
  545. $associations['hasAndBelongsToMany'][] = array(
  546. 'alias' => $habtmName,
  547. 'className' => $habtmName,
  548. 'foreignKey' => $foreignKey,
  549. 'associationForeignKey' => $this->_modelKey($habtmName),
  550. 'joinTable' => $otherTable
  551. );
  552. }
  553. }
  554. return $associations;
  555. }
  556. /**
  557. * Interact with the user and confirm associations.
  558. *
  559. * @param array $model Temporary Model instance.
  560. * @param array $associations Array of associations to be confirmed.
  561. * @return array Array of confirmed associations
  562. */
  563. public function confirmAssociations($model, $associations) {
  564. foreach ($associations as $type => $settings) {
  565. if (!empty($associations[$type])) {
  566. foreach ($associations[$type] as $i => $assoc) {
  567. $prompt = "{$model->name} {$type} {$assoc['alias']}?";
  568. $response = $this->in($prompt, array('y','n'), 'y');
  569. if ('n' == strtolower($response)) {
  570. unset($associations[$type][$i]);
  571. } elseif ($type == 'hasMany') {
  572. unset($associations['hasOne'][$i]);
  573. }
  574. }
  575. $associations[$type] = array_merge($associations[$type]);
  576. }
  577. }
  578. return $associations;
  579. }
  580. /**
  581. * Interact with the user and generate additional non-conventional associations
  582. *
  583. * @param object $model Temporary model instance
  584. * @param array $associations Array of associations.
  585. * @return array Array of associations.
  586. */
  587. public function doMoreAssociations($model, $associations) {
  588. $prompt = __d('cake_console', 'Would you like to define some additional model associations?');
  589. $wannaDoMoreAssoc = $this->in($prompt, array('y','n'), 'n');
  590. $possibleKeys = $this->_generatePossibleKeys();
  591. while (strtolower($wannaDoMoreAssoc) == 'y') {
  592. $assocs = array('belongsTo', 'hasOne', 'hasMany', 'hasAndBelongsToMany');
  593. $this->out(__d('cake_console', 'What is the association type?'));
  594. $assocType = intval($this->inOptions($assocs, __d('cake_console', 'Enter a number')));
  595. $this->out(__d('cake_console', "For the following options be very careful to match your setup exactly.\nAny spelling mistakes will cause errors."));
  596. $this->hr();
  597. $alias = $this->in(__d('cake_console', 'What is the alias for this association?'));
  598. $className = $this->in(__d('cake_console', 'What className will %s use?', $alias), null, $alias );
  599. if ($assocType == 0) {
  600. $showKeys = $possibleKeys[$model->table];
  601. $suggestedForeignKey = $this->_modelKey($alias);
  602. } else {
  603. $otherTable = Inflector::tableize($className);
  604. if (in_array($otherTable, $this->_tables)) {
  605. if ($assocType < 3) {
  606. $showKeys = $possibleKeys[$otherTable];
  607. } else {
  608. $showKeys = null;
  609. }
  610. } else {
  611. $otherTable = $this->in(__d('cake_console', 'What is the table for this model?'));
  612. $showKeys = $possibleKeys[$otherTable];
  613. }
  614. $suggestedForeignKey = $this->_modelKey($model->name);
  615. }
  616. if (!empty($showKeys)) {
  617. $this->out(__d('cake_console', 'A helpful List of possible keys'));
  618. $foreignKey = $this->inOptions($showKeys, __d('cake_console', 'What is the foreignKey?'));
  619. $foreignKey = $showKeys[intval($foreignKey)];
  620. }
  621. if (!isset($foreignKey)) {
  622. $foreignKey = $this->in(__d('cake_console', 'What is the foreignKey? Specify your own.'), null, $suggestedForeignKey);
  623. }
  624. if ($assocType == 3) {
  625. $associationForeignKey = $this->in(__d('cake_console', 'What is the associationForeignKey?'), null, $this->_modelKey($model->name));
  626. $joinTable = $this->in(__d('cake_console', 'What is the joinTable?'));
  627. }
  628. $associations[$assocs[$assocType]] = array_values((array)$associations[$assocs[$assocType]]);
  629. $count = count($associations[$assocs[$assocType]]);
  630. $i = ($count > 0) ? $count : 0;
  631. $associations[$assocs[$assocType]][$i]['alias'] = $alias;
  632. $associations[$assocs[$assocType]][$i]['className'] = $className;
  633. $associations[$assocs[$assocType]][$i]['foreignKey'] = $foreignKey;
  634. if ($assocType == 3) {
  635. $associations[$assocs[$assocType]][$i]['associationForeignKey'] = $associationForeignKey;
  636. $associations[$assocs[$assocType]][$i]['joinTable'] = $joinTable;
  637. }
  638. $wannaDoMoreAssoc = $this->in(__d('cake_console', 'Define another association?'), array('y','n'), 'y');
  639. }
  640. return $associations;
  641. }
  642. /**
  643. * Finds all possible keys to use on custom associations.
  644. *
  645. * @return array array of tables and possible keys
  646. */
  647. protected function _generatePossibleKeys() {
  648. $possible = array();
  649. foreach ($this->_tables as $otherTable) {
  650. $tempOtherModel = new Model(array('table' => $otherTable, 'ds' => $this->connection));
  651. $modelFieldsTemp = $tempOtherModel->schema(true);
  652. foreach ($modelFieldsTemp as $fieldName => $field) {
  653. if ($field['type'] == 'integer' || $field['type'] == 'string') {
  654. $possible[$otherTable][] = $fieldName;
  655. }
  656. }
  657. }
  658. return $possible;
  659. }
  660. /**
  661. * Assembles and writes a Model file.
  662. *
  663. * @param mixed $name Model name or object
  664. * @param mixed $data if array and $name is not an object assume bake data, otherwise boolean.
  665. */
  666. public function bake($name, $data = array()) {
  667. if (is_object($name)) {
  668. if ($data == false) {
  669. $data = $associations = array();
  670. $data['associations'] = $this->doAssociations($name, $associations);
  671. $data['validate'] = $this->doValidation($name);
  672. }
  673. $data['primaryKey'] = $name->primaryKey;
  674. $data['useTable'] = $name->table;
  675. $data['useDbConfig'] = $name->useDbConfig;
  676. $data['name'] = $name = $name->name;
  677. } else {
  678. $data['name'] = $name;
  679. }
  680. $defaults = array('associations' => array(), 'validate' => array(), 'primaryKey' => 'id',
  681. 'useTable' => null, 'useDbConfig' => 'default', 'displayField' => null);
  682. $data = array_merge($defaults, $data);
  683. $this->Template->set($data);
  684. $this->Template->set('plugin', Inflector::camelize($this->plugin));
  685. $out = $this->Template->generate('classes', 'model');
  686. $path = $this->getPath();
  687. $filename = $path . $name . '.php';
  688. $this->out("\n" . __d('cake_console', 'Baking model class for %s...', $name), 1, Shell::QUIET);
  689. $this->createFile($filename, $out);
  690. ClassRegistry::flush();
  691. return $out;
  692. }
  693. /**
  694. * Assembles and writes a unit test file
  695. *
  696. * @param string $className Model class name
  697. */
  698. public function bakeTest($className) {
  699. $this->Test->interactive = $this->interactive;
  700. $this->Test->plugin = $this->plugin;
  701. $this->Test->connection = $this->connection;
  702. return $this->Test->bake('Model', $className);
  703. }
  704. /**
  705. * outputs the a list of possible models or controllers from database
  706. *
  707. * @param string $useDbConfig Database configuration name
  708. */
  709. public function listAll($useDbConfig = null) {
  710. $this->_tables = $this->getAllTables($useDbConfig);
  711. if ($this->interactive === true) {
  712. $this->out(__d('cake_console', 'Possible Models based on your current database:'));
  713. $this->_modelNames = array();
  714. $count = count($this->_tables);
  715. for ($i = 0; $i < $count; $i++) {
  716. $this->_modelNames[] = $this->_modelName($this->_tables[$i]);
  717. $this->out($i + 1 . ". " . $this->_modelNames[$i]);
  718. }
  719. }
  720. return $this->_tables;
  721. }
  722. /**
  723. * Interact with the user to determine the table name of a particular model
  724. *
  725. * @param string $modelName Name of the model you want a table for.
  726. * @param string $useDbConfig Name of the database config you want to get tables from.
  727. * @return string Table name
  728. */
  729. public function getTable($modelName, $useDbConfig = null) {
  730. if (!isset($useDbConfig)) {
  731. $useDbConfig = $this->connection;
  732. }
  733. $db = ConnectionManager::getDataSource($useDbConfig);
  734. $useTable = Inflector::tableize($modelName);
  735. $fullTableName = $db->fullTableName($useTable, false);
  736. $tableIsGood = false;
  737. if (array_search($useTable, $this->_tables) === false) {
  738. $this->out();
  739. $this->out(__d('cake_console', "Given your model named '%s',\nCake would expect a database table named '%s'", $modelName, $fullTableName));
  740. $tableIsGood = $this->in(__d('cake_console', 'Do you want to use this table?'), array('y','n'), 'y');
  741. }
  742. if (strtolower($tableIsGood) == 'n') {
  743. $useTable = $this->in(__d('cake_console', 'What is the name of the table?'));
  744. }
  745. return $useTable;
  746. }
  747. /**
  748. * Get an Array of all the tables in the supplied connection
  749. * will halt the script if no tables are found.
  750. *
  751. * @param string $useDbConfig Connection name to scan.
  752. * @return array Array of tables in the database.
  753. */
  754. public function getAllTables($useDbConfig = null) {
  755. if (!isset($useDbConfig)) {
  756. $useDbConfig = $this->connection;
  757. }
  758. $tables = array();
  759. $db = ConnectionManager::getDataSource($useDbConfig);
  760. $db->cacheSources = false;
  761. $usePrefix = empty($db->config['prefix']) ? '' : $db->config['prefix'];
  762. if ($usePrefix) {
  763. foreach ($db->listSources() as $table) {
  764. if (!strncmp($table, $usePrefix, strlen($usePrefix))) {
  765. $tables[] = substr($table, strlen($usePrefix));
  766. }
  767. }
  768. } else {
  769. $tables = $db->listSources();
  770. }
  771. if (empty($tables)) {
  772. $this->err(__d('cake_console', 'Your database does not have any tables.'));
  773. $this->_stop();
  774. }
  775. return $tables;
  776. }
  777. /**
  778. * Forces the user to specify the model he wants to bake, and returns the selected model name.
  779. *
  780. * @return string the model name
  781. */
  782. public function getName($useDbConfig = null) {
  783. $this->listAll($useDbConfig);
  784. $enteredModel = '';
  785. while ($enteredModel == '') {
  786. $enteredModel = $this->in(__d('cake_console', "Enter a number from the list above,\ntype in the name of another model, or 'q' to exit"), null, 'q');
  787. if ($enteredModel === 'q') {
  788. $this->out(__d('cake_console', 'Exit'));
  789. $this->_stop();
  790. }
  791. if ($enteredModel == '' || intval($enteredModel) > count($this->_modelNames)) {
  792. $this->err(__d('cake_console', "The model name you supplied was empty,\nor the number you selected was not an option. Please try again."));
  793. $enteredModel = '';
  794. }
  795. }
  796. if (intval($enteredModel) > 0 && intval($enteredModel) <= count($this->_modelNames)) {
  797. $currentModelName = $this->_modelNames[intval($enteredModel) - 1];
  798. } else {
  799. $currentModelName = $enteredModel;
  800. }
  801. return $currentModelName;
  802. }
  803. /**
  804. * get the option parser.
  805. *
  806. * @return void
  807. */
  808. public function getOptionParser() {
  809. $parser = parent::getOptionParser();
  810. return $parser->description(
  811. __d('cake_console', 'Bake models.')
  812. )->addArgument('name', array(
  813. 'help' => __d('cake_console', 'Name of the model to bake. Can use Plugin.name to bake plugin models.')
  814. ))->addSubcommand('all', array(
  815. 'help' => __d('cake_console', 'Bake all model files with associations and validation.')
  816. ))->addOption('plugin', array(
  817. 'short' => 'p',
  818. 'help' => __d('cake_console', 'Plugin to bake the model into.')
  819. ))->addOption('connection', array(
  820. 'short' => 'c',
  821. 'help' => __d('cake_console', 'The connection the model table is on.')
  822. ))->epilog(__d('cake_console', 'Omitting all arguments and options will enter into an interactive mode.'));
  823. }
  824. /**
  825. * Interact with FixtureTask to automatically bake fixtures when baking models.
  826. *
  827. * @param string $className Name of class to bake fixture for
  828. * @param string $useTable Optional table name for fixture to use.
  829. * @return void
  830. * @see FixtureTask::bake
  831. */
  832. public function bakeFixture($className, $useTable = null) {
  833. $this->Fixture->interactive = $this->interactive;
  834. $this->Fixture->connection = $this->connection;
  835. $this->Fixture->plugin = $this->plugin;
  836. $this->Fixture->bake($className, $useTable);
  837. }
  838. }