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