Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
15403 manish.sha 1
<?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
		sort($options);
387
		$default = 1;
388
		foreach ($options as $option) {
389
			if ($option{0} !== '_') {
390
				$choices[$default] = $option;
391
				$default++;
392
			}
393
		}
394
		$choices[$default] = 'none'; // Needed since index starts at 1
395
		$this->_validations = $choices;
396
		return $choices;
397
	}
398
 
399
/**
400
 * Does individual field validation handling.
401
 *
402
 * @param string $fieldName Name of field to be validated.
403
 * @param array $metaData metadata for field
404
 * @param string $primaryKey The primary key field.
405
 * @return array Array of validation for the field.
406
 */
407
	public function fieldValidation($fieldName, $metaData, $primaryKey = 'id') {
408
		$defaultChoice = count($this->_validations);
409
		$validate = $alreadyChosen = array();
410
 
411
		$prompt = __d('cake_console',
412
			"or enter in a valid regex validation string.\nAlternatively [s] skip the rest of the fields.\n"
413
		);
414
		$methods = array_flip($this->_validations);
415
 
416
		$anotherValidator = 'y';
417
		while ($anotherValidator === 'y') {
418
			if ($this->interactive) {
419
				$this->out();
420
				$this->out(__d('cake_console', 'Field: <info>%s</info>', $fieldName));
421
				$this->out(__d('cake_console', 'Type: <info>%s</info>', $metaData['type']));
422
				$this->hr();
423
				$this->out(__d('cake_console', 'Please select one of the following validation options:'));
424
				$this->hr();
425
 
426
				$optionText = '';
427
				for ($i = 1, $m = $defaultChoice / 2; $i <= $m; $i++) {
428
					$line = sprintf("%2d. %s", $i, $this->_validations[$i]);
429
					$optionText .= $line . str_repeat(" ", 31 - strlen($line));
430
					if ($m + $i !== $defaultChoice) {
431
						$optionText .= sprintf("%2d. %s\n", $m + $i, $this->_validations[$m + $i]);
432
					}
433
				}
434
				$this->out($optionText);
435
				$this->out(__d('cake_console', "%s - Do not do any validation on this field.", $defaultChoice));
436
				$this->hr();
437
			}
438
 
439
			$guess = $defaultChoice;
440
			if ($metaData['null'] != 1 && !in_array($fieldName, array($primaryKey, 'created', 'modified', 'updated'))) {
441
				if ($fieldName === 'email') {
442
					$guess = $methods['email'];
443
				} elseif ($metaData['type'] === 'string' && $metaData['length'] == 36) {
444
					$guess = $methods['uuid'];
445
				} elseif ($metaData['type'] === 'string') {
446
					$guess = $methods['notEmpty'];
447
				} elseif ($metaData['type'] === 'text') {
448
					$guess = $methods['notEmpty'];
449
				} elseif ($metaData['type'] === 'integer') {
450
					$guess = $methods['numeric'];
451
				} elseif ($metaData['type'] === 'float') {
452
					$guess = $methods['numeric'];
453
				} elseif ($metaData['type'] === 'boolean') {
454
					$guess = $methods['boolean'];
455
				} elseif ($metaData['type'] === 'date') {
456
					$guess = $methods['date'];
457
				} elseif ($metaData['type'] === 'time') {
458
					$guess = $methods['time'];
459
				} elseif ($metaData['type'] === 'datetime') {
460
					$guess = $methods['datetime'];
461
				} elseif ($metaData['type'] === 'inet') {
462
					$guess = $methods['ip'];
463
				}
464
			}
465
 
466
			if ($this->interactive === true) {
467
				$choice = $this->in($prompt, null, $guess);
468
				if ($choice === 's') {
469
					$validate['_skipFields'] = true;
470
					return $validate;
471
				}
472
				if (in_array($choice, $alreadyChosen)) {
473
					$this->out(__d('cake_console', "You have already chosen that validation rule,\nplease choose again"));
474
					continue;
475
				}
476
				if (!isset($this->_validations[$choice]) && is_numeric($choice)) {
477
					$this->out(__d('cake_console', 'Please make a valid selection.'));
478
					continue;
479
				}
480
				$alreadyChosen[] = $choice;
481
			} else {
482
				$choice = $guess;
483
			}
484
 
485
			if (isset($this->_validations[$choice])) {
486
				$validatorName = $this->_validations[$choice];
487
			} else {
488
				$validatorName = Inflector::slug($choice);
489
			}
490
 
491
			if ($choice != $defaultChoice) {
492
				$validate[$validatorName] = $choice;
493
				if (is_numeric($choice) && isset($this->_validations[$choice])) {
494
					$validate[$validatorName] = $this->_validations[$choice];
495
				}
496
			}
497
			$anotherValidator = 'n';
498
			if ($this->interactive && $choice != $defaultChoice) {
499
				$anotherValidator = $this->in(__d('cake_console', "Would you like to add another validation rule\n" .
500
					"or skip the rest of the fields?"), array('y', 'n', 's'), 'n');
501
				if ($anotherValidator === 's') {
502
					$validate['_skipFields'] = true;
503
					return $validate;
504
				}
505
			}
506
		}
507
		return $validate;
508
	}
509
 
510
/**
511
 * Handles associations
512
 *
513
 * @param Model $model The model object
514
 * @return array Associations
515
 */
516
	public function doAssociations($model) {
517
		if (!$model instanceof Model) {
518
			return false;
519
		}
520
		if ($this->interactive === true) {
521
			$this->out(__d('cake_console', 'One moment while the associations are detected.'));
522
		}
523
 
524
		$fields = $model->schema(true);
525
		if (empty($fields)) {
526
			return array();
527
		}
528
 
529
		if (empty($this->_tables)) {
530
			$this->_tables = (array)$this->getAllTables();
531
		}
532
 
533
		$associations = array(
534
			'belongsTo' => array(),
535
			'hasMany' => array(),
536
			'hasOne' => array(),
537
			'hasAndBelongsToMany' => array()
538
		);
539
 
540
		$associations = $this->findBelongsTo($model, $associations);
541
		$associations = $this->findHasOneAndMany($model, $associations);
542
		$associations = $this->findHasAndBelongsToMany($model, $associations);
543
 
544
		if ($this->interactive !== true) {
545
			unset($associations['hasOne']);
546
		}
547
 
548
		if ($this->interactive === true) {
549
			$this->hr();
550
			if (empty($associations)) {
551
				$this->out(__d('cake_console', 'None found.'));
552
			} else {
553
				$this->out(__d('cake_console', 'Please confirm the following associations:'));
554
				$this->hr();
555
				$associations = $this->confirmAssociations($model, $associations);
556
			}
557
			$associations = $this->doMoreAssociations($model, $associations);
558
		}
559
		return $associations;
560
	}
561
 
562
/**
563
 * Handles behaviors
564
 *
565
 * @param Model $model The model object.
566
 * @return array Behaviors
567
 */
568
	public function doActsAs($model) {
569
		if (!$model instanceof Model) {
570
			return false;
571
		}
572
		$behaviors = array();
573
		$fields = $model->schema(true);
574
		if (empty($fields)) {
575
			return array();
576
		}
577
 
578
		if (isset($fields['lft']) && $fields['lft']['type'] === 'integer' &&
579
			isset($fields['rght']) && $fields['rght']['type'] === 'integer' &&
580
			isset($fields['parent_id'])) {
581
			$behaviors[] = 'Tree';
582
		}
583
		return $behaviors;
584
	}
585
 
586
/**
587
 * Find belongsTo relations and add them to the associations list.
588
 *
589
 * @param Model $model Model instance of model being generated.
590
 * @param array $associations Array of in progress associations
591
 * @return array Associations with belongsTo added in.
592
 */
593
	public function findBelongsTo(Model $model, $associations) {
594
		$fieldNames = array_keys($model->schema(true));
595
		foreach ($fieldNames as $fieldName) {
596
			$offset = substr($fieldName, -3) === '_id';
597
			if ($fieldName != $model->primaryKey && $fieldName !== 'parent_id' && $offset !== false) {
598
				$tmpModelName = $this->_modelNameFromKey($fieldName);
599
				$associations['belongsTo'][] = array(
600
					'alias' => $tmpModelName,
601
					'className' => $tmpModelName,
602
					'foreignKey' => $fieldName,
603
				);
604
			} elseif ($fieldName === 'parent_id') {
605
				$associations['belongsTo'][] = array(
606
					'alias' => 'Parent' . $model->name,
607
					'className' => $model->name,
608
					'foreignKey' => $fieldName,
609
				);
610
			}
611
		}
612
		return $associations;
613
	}
614
 
615
/**
616
 * Find the hasOne and hasMany relations and add them to associations list
617
 *
618
 * @param Model $model Model instance being generated
619
 * @param array $associations Array of in progress associations
620
 * @return array Associations with hasOne and hasMany added in.
621
 */
622
	public function findHasOneAndMany(Model $model, $associations) {
623
		$foreignKey = $this->_modelKey($model->name);
624
		foreach ($this->_tables as $otherTable) {
625
			$tempOtherModel = $this->_getModelObject($this->_modelName($otherTable), $otherTable);
626
			$tempFieldNames = array_keys($tempOtherModel->schema(true));
627
 
628
			$pattern = '/_' . preg_quote($model->table, '/') . '|' . preg_quote($model->table, '/') . '_/';
629
			$possibleJoinTable = preg_match($pattern, $otherTable);
630
			if ($possibleJoinTable) {
631
				continue;
632
			}
633
			foreach ($tempFieldNames as $fieldName) {
634
				$assoc = false;
635
				if ($fieldName !== $model->primaryKey && $fieldName === $foreignKey) {
636
					$assoc = array(
637
						'alias' => $tempOtherModel->name,
638
						'className' => $tempOtherModel->name,
639
						'foreignKey' => $fieldName
640
					);
641
				} elseif ($otherTable === $model->table && $fieldName === 'parent_id') {
642
					$assoc = array(
643
						'alias' => 'Child' . $model->name,
644
						'className' => $model->name,
645
						'foreignKey' => $fieldName
646
					);
647
				}
648
				if ($assoc) {
649
					$associations['hasOne'][] = $assoc;
650
					$associations['hasMany'][] = $assoc;
651
				}
652
 
653
			}
654
		}
655
		return $associations;
656
	}
657
 
658
/**
659
 * Find the hasAndBelongsToMany relations and add them to associations list
660
 *
661
 * @param Model $model Model instance being generated
662
 * @param array $associations Array of in-progress associations
663
 * @return array Associations with hasAndBelongsToMany added in.
664
 */
665
	public function findHasAndBelongsToMany(Model $model, $associations) {
666
		$foreignKey = $this->_modelKey($model->name);
667
		foreach ($this->_tables as $otherTable) {
668
			$tableName = null;
669
			$offset = strpos($otherTable, $model->table . '_');
670
			$otherOffset = strpos($otherTable, '_' . $model->table);
671
 
672
			if ($offset !== false) {
673
				$tableName = substr($otherTable, strlen($model->table . '_'));
674
			} elseif ($otherOffset !== false) {
675
				$tableName = substr($otherTable, 0, $otherOffset);
676
			}
677
			if ($tableName && in_array($tableName, $this->_tables)) {
678
				$habtmName = $this->_modelName($tableName);
679
				$associations['hasAndBelongsToMany'][] = array(
680
					'alias' => $habtmName,
681
					'className' => $habtmName,
682
					'foreignKey' => $foreignKey,
683
					'associationForeignKey' => $this->_modelKey($habtmName),
684
					'joinTable' => $otherTable
685
				);
686
			}
687
		}
688
		return $associations;
689
	}
690
 
691
/**
692
 * Interact with the user and confirm associations.
693
 *
694
 * @param array $model Temporary Model instance.
695
 * @param array $associations Array of associations to be confirmed.
696
 * @return array Array of confirmed associations
697
 */
698
	public function confirmAssociations(Model $model, $associations) {
699
		foreach ($associations as $type => $settings) {
700
			if (!empty($associations[$type])) {
701
				foreach ($associations[$type] as $i => $assoc) {
702
					$prompt = "{$model->name} {$type} {$assoc['alias']}?";
703
					$response = $this->in($prompt, array('y', 'n'), 'y');
704
 
705
					if (strtolower($response) === 'n') {
706
						unset($associations[$type][$i]);
707
					} elseif ($type === 'hasMany') {
708
						unset($associations['hasOne'][$i]);
709
					}
710
				}
711
				$associations[$type] = array_merge($associations[$type]);
712
			}
713
		}
714
		return $associations;
715
	}
716
 
717
/**
718
 * Interact with the user and generate additional non-conventional associations
719
 *
720
 * @param Model $model Temporary model instance
721
 * @param array $associations Array of associations.
722
 * @return array Array of associations.
723
 */
724
	public function doMoreAssociations(Model $model, $associations) {
725
		$prompt = __d('cake_console', 'Would you like to define some additional model associations?');
726
		$wannaDoMoreAssoc = $this->in($prompt, array('y', 'n'), 'n');
727
		$possibleKeys = $this->_generatePossibleKeys();
728
		while (strtolower($wannaDoMoreAssoc) === 'y') {
729
			$assocs = array('belongsTo', 'hasOne', 'hasMany', 'hasAndBelongsToMany');
730
			$this->out(__d('cake_console', 'What is the association type?'));
731
			$assocType = (int)$this->inOptions($assocs, __d('cake_console', 'Enter a number'));
732
 
733
			$this->out(__d('cake_console', "For the following options be very careful to match your setup exactly.\n" .
734
				"Any spelling mistakes will cause errors."));
735
			$this->hr();
736
 
737
			$alias = $this->in(__d('cake_console', 'What is the alias for this association?'));
738
			$className = $this->in(__d('cake_console', 'What className will %s use?', $alias), null, $alias);
739
 
740
			if ($assocType === 0) {
741
				if (!empty($possibleKeys[$model->table])) {
742
					$showKeys = $possibleKeys[$model->table];
743
				} else {
744
					$showKeys = null;
745
				}
746
				$suggestedForeignKey = $this->_modelKey($alias);
747
			} else {
748
				$otherTable = Inflector::tableize($className);
749
				if (in_array($otherTable, $this->_tables)) {
750
					if ($assocType < 3) {
751
						if (!empty($possibleKeys[$otherTable])) {
752
							$showKeys = $possibleKeys[$otherTable];
753
						} else {
754
							$showKeys = null;
755
						}
756
					} else {
757
						$showKeys = null;
758
					}
759
				} else {
760
					$otherTable = $this->in(__d('cake_console', 'What is the table for this model?'));
761
					$showKeys = $possibleKeys[$otherTable];
762
				}
763
				$suggestedForeignKey = $this->_modelKey($model->name);
764
			}
765
			if (!empty($showKeys)) {
766
				$this->out(__d('cake_console', 'A helpful List of possible keys'));
767
				$foreignKey = $this->inOptions($showKeys, __d('cake_console', 'What is the foreignKey?'));
768
				$foreignKey = $showKeys[(int)$foreignKey];
769
			}
770
			if (!isset($foreignKey)) {
771
				$foreignKey = $this->in(__d('cake_console', 'What is the foreignKey? Specify your own.'), null, $suggestedForeignKey);
772
			}
773
			if ($assocType === 3) {
774
				$associationForeignKey = $this->in(__d('cake_console', 'What is the associationForeignKey?'), null, $this->_modelKey($model->name));
775
				$joinTable = $this->in(__d('cake_console', 'What is the joinTable?'));
776
			}
777
			$associations[$assocs[$assocType]] = array_values((array)$associations[$assocs[$assocType]]);
778
			$count = count($associations[$assocs[$assocType]]);
779
			$i = ($count > 0) ? $count : 0;
780
			$associations[$assocs[$assocType]][$i]['alias'] = $alias;
781
			$associations[$assocs[$assocType]][$i]['className'] = $className;
782
			$associations[$assocs[$assocType]][$i]['foreignKey'] = $foreignKey;
783
			if ($assocType === 3) {
784
				$associations[$assocs[$assocType]][$i]['associationForeignKey'] = $associationForeignKey;
785
				$associations[$assocs[$assocType]][$i]['joinTable'] = $joinTable;
786
			}
787
			$wannaDoMoreAssoc = $this->in(__d('cake_console', 'Define another association?'), array('y', 'n'), 'y');
788
		}
789
		return $associations;
790
	}
791
 
792
/**
793
 * Finds all possible keys to use on custom associations.
794
 *
795
 * @return array Array of tables and possible keys
796
 */
797
	protected function _generatePossibleKeys() {
798
		$possible = array();
799
		foreach ($this->_tables as $otherTable) {
800
			$tempOtherModel = new Model(array('table' => $otherTable, 'ds' => $this->connection));
801
			$modelFieldsTemp = $tempOtherModel->schema(true);
802
			foreach ($modelFieldsTemp as $fieldName => $field) {
803
				if ($field['type'] === 'integer' || $field['type'] === 'string') {
804
					$possible[$otherTable][] = $fieldName;
805
				}
806
			}
807
		}
808
		return $possible;
809
	}
810
 
811
/**
812
 * Assembles and writes a Model file.
813
 *
814
 * @param string|object $name Model name or object
815
 * @param array|bool $data if array and $name is not an object assume bake data, otherwise boolean.
816
 * @return string
817
 */
818
	public function bake($name, $data = array()) {
819
		if ($name instanceof Model) {
820
			if (!$data) {
821
				$data = array();
822
				$data['associations'] = $this->doAssociations($name);
823
				$data['validate'] = $this->doValidation($name);
824
				$data['actsAs'] = $this->doActsAs($name);
825
			}
826
			$data['primaryKey'] = $name->primaryKey;
827
			$data['useTable'] = $name->table;
828
			$data['useDbConfig'] = $name->useDbConfig;
829
			$data['name'] = $name = $name->name;
830
		} else {
831
			$data['name'] = $name;
832
		}
833
 
834
		$defaults = array(
835
			'associations' => array(),
836
			'actsAs' => array(),
837
			'validate' => array(),
838
			'primaryKey' => 'id',
839
			'useTable' => null,
840
			'useDbConfig' => 'default',
841
			'displayField' => null
842
		);
843
		$data = array_merge($defaults, $data);
844
 
845
		$pluginPath = '';
846
		if ($this->plugin) {
847
			$pluginPath = $this->plugin . '.';
848
		}
849
 
850
		$this->Template->set($data);
851
		$this->Template->set(array(
852
			'plugin' => $this->plugin,
853
			'pluginPath' => $pluginPath
854
		));
855
		$out = $this->Template->generate('classes', 'model');
856
 
857
		$path = $this->getPath();
858
		$filename = $path . $name . '.php';
859
		$this->out("\n" . __d('cake_console', 'Baking model class for %s...', $name), 1, Shell::QUIET);
860
		$this->createFile($filename, $out);
861
		ClassRegistry::flush();
862
		return $out;
863
	}
864
 
865
/**
866
 * Assembles and writes a unit test file
867
 *
868
 * @param string $className Model class name
869
 * @return string
870
 */
871
	public function bakeTest($className) {
872
		$this->Test->interactive = $this->interactive;
873
		$this->Test->plugin = $this->plugin;
874
		$this->Test->connection = $this->connection;
875
		return $this->Test->bake('Model', $className);
876
	}
877
 
878
/**
879
 * outputs the a list of possible models or controllers from database
880
 *
881
 * @param string $useDbConfig Database configuration name
882
 * @return array
883
 */
884
	public function listAll($useDbConfig = null) {
885
		$this->_tables = $this->getAllTables($useDbConfig);
886
 
887
		$this->_modelNames = array();
888
		$count = count($this->_tables);
889
		for ($i = 0; $i < $count; $i++) {
890
			$this->_modelNames[] = $this->_modelName($this->_tables[$i]);
891
		}
892
		if ($this->interactive === true) {
893
			$this->out(__d('cake_console', 'Possible Models based on your current database:'));
894
			$len = strlen($count + 1);
895
			for ($i = 0; $i < $count; $i++) {
896
				$this->out(sprintf("%${len}d. %s", $i + 1, $this->_modelNames[$i]));
897
			}
898
		}
899
		return $this->_tables;
900
	}
901
 
902
/**
903
 * Interact with the user to determine the table name of a particular model
904
 *
905
 * @param string $modelName Name of the model you want a table for.
906
 * @param string $useDbConfig Name of the database config you want to get tables from.
907
 * @return string Table name
908
 */
909
	public function getTable($modelName, $useDbConfig = null) {
910
		$useTable = Inflector::tableize($modelName);
911
		if (in_array($modelName, $this->_modelNames)) {
912
			$modelNames = array_flip($this->_modelNames);
913
			$useTable = $this->_tables[$modelNames[$modelName]];
914
		}
915
 
916
		if ($this->interactive === true) {
917
			if (!isset($useDbConfig)) {
918
				$useDbConfig = $this->connection;
919
			}
920
			$db = ConnectionManager::getDataSource($useDbConfig);
921
			$fullTableName = $db->fullTableName($useTable, false);
922
			$tableIsGood = false;
923
			if (array_search($useTable, $this->_tables) === false) {
924
				$this->out();
925
				$this->out(__d('cake_console', "Given your model named '%s',\nCake would expect a database table named '%s'", $modelName, $fullTableName));
926
				$tableIsGood = $this->in(__d('cake_console', 'Do you want to use this table?'), array('y', 'n'), 'y');
927
			}
928
			if (strtolower($tableIsGood) === 'n') {
929
				$useTable = $this->in(__d('cake_console', 'What is the name of the table (without prefix)?'));
930
			}
931
		}
932
		return $useTable;
933
	}
934
 
935
/**
936
 * Get an Array of all the tables in the supplied connection
937
 * will halt the script if no tables are found.
938
 *
939
 * @param string $useDbConfig Connection name to scan.
940
 * @return array Array of tables in the database.
941
 */
942
	public function getAllTables($useDbConfig = null) {
943
		if (!isset($useDbConfig)) {
944
			$useDbConfig = $this->connection;
945
		}
946
 
947
		$tables = array();
948
		$db = ConnectionManager::getDataSource($useDbConfig);
949
		$db->cacheSources = false;
950
		$usePrefix = empty($db->config['prefix']) ? '' : $db->config['prefix'];
951
		if ($usePrefix) {
952
			foreach ($db->listSources() as $table) {
953
				if (!strncmp($table, $usePrefix, strlen($usePrefix))) {
954
					$tables[] = substr($table, strlen($usePrefix));
955
				}
956
			}
957
		} else {
958
			$tables = $db->listSources();
959
		}
960
		if (empty($tables)) {
961
			$this->err(__d('cake_console', 'Your database does not have any tables.'));
962
			return $this->_stop();
963
		}
964
		sort($tables);
965
		return $tables;
966
	}
967
 
968
/**
969
 * Forces the user to specify the model he wants to bake, and returns the selected model name.
970
 *
971
 * @param string $useDbConfig Database config name
972
 * @return string The model name
973
 */
974
	public function getName($useDbConfig = null) {
975
		$this->listAll($useDbConfig);
976
 
977
		$enteredModel = '';
978
 
979
		while (!$enteredModel) {
980
			$enteredModel = $this->in(__d('cake_console', "Enter a number from the list above,\n" .
981
				"type in the name of another model, or 'q' to exit"), null, 'q');
982
 
983
			if ($enteredModel === 'q') {
984
				$this->out(__d('cake_console', 'Exit'));
985
				return $this->_stop();
986
			}
987
 
988
			if (!$enteredModel || (int)$enteredModel > count($this->_modelNames)) {
989
				$this->err(__d('cake_console', "The model name you supplied was empty,\n" .
990
					"or the number you selected was not an option. Please try again."));
991
				$enteredModel = '';
992
			}
993
		}
994
		if ((int)$enteredModel > 0 && (int)$enteredModel <= count($this->_modelNames)) {
995
			return $this->_modelNames[(int)$enteredModel - 1];
996
		}
997
 
998
		return $enteredModel;
999
	}
1000
 
1001
/**
1002
 * Gets the option parser instance and configures it.
1003
 *
1004
 * @return ConsoleOptionParser
1005
 */
1006
	public function getOptionParser() {
1007
		$parser = parent::getOptionParser();
1008
 
1009
		$parser->description(
1010
			__d('cake_console', 'Bake models.')
1011
		)->addArgument('name', array(
1012
			'help' => __d('cake_console', 'Name of the model to bake. Can use Plugin.name to bake plugin models.')
1013
		))->addSubcommand('all', array(
1014
			'help' => __d('cake_console', 'Bake all model files with associations and validation.')
1015
		))->addOption('plugin', array(
1016
			'short' => 'p',
1017
			'help' => __d('cake_console', 'Plugin to bake the model into.')
1018
		))->addOption('theme', array(
1019
			'short' => 't',
1020
			'help' => __d('cake_console', 'Theme to use when baking code.')
1021
		))->addOption('connection', array(
1022
			'short' => 'c',
1023
			'help' => __d('cake_console', 'The connection the model table is on.')
1024
		))->addOption('force', array(
1025
			'short' => 'f',
1026
			'help' => __d('cake_console', 'Force overwriting existing files without prompting.')
1027
		))->epilog(
1028
			__d('cake_console', 'Omitting all arguments and options will enter into an interactive mode.')
1029
		);
1030
 
1031
		return $parser;
1032
	}
1033
 
1034
/**
1035
 * Interact with FixtureTask to automatically bake fixtures when baking models.
1036
 *
1037
 * @param string $className Name of class to bake fixture for
1038
 * @param string $useTable Optional table name for fixture to use.
1039
 * @return void
1040
 * @see FixtureTask::bake
1041
 */
1042
	public function bakeFixture($className, $useTable = null) {
1043
		$this->Fixture->interactive = $this->interactive;
1044
		$this->Fixture->connection = $this->connection;
1045
		$this->Fixture->plugin = $this->plugin;
1046
		$this->Fixture->bake($className, $useTable);
1047
	}
1048
 
1049
}