PageRenderTime 70ms CodeModel.GetById 36ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/Cake/Console/Command/Task/ModelTask.php

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