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