Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
16591 anikendra 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
		$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
}