Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

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