Subversion-Projekte lars-tiefland.cakephp

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
/* SVN FILE: $Id: model.php 7945 2008-12-19 02:16:01Z gwoo $ */
3
/**
4
 * The ModelTask handles creating and updating models files.
5
 *
6
 * Long description for file
7
 *
8
 * PHP versions 4 and 5
9
 *
10
 * CakePHP(tm) :  Rapid Development Framework (http://www.cakephp.org)
11
 * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
12
 *
13
 * Licensed under The MIT License
14
 * Redistributions of files must retain the above copyright notice.
15
 *
16
 * @filesource
17
 * @copyright     Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
18
 * @link          http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
19
 * @package       cake
20
 * @subpackage    cake.cake.console.libs.tasks
21
 * @since         CakePHP(tm) v 1.2
22
 * @version       $Revision: 7945 $
23
 * @modifiedby    $LastChangedBy: gwoo $
24
 * @lastmodified  $Date: 2008-12-18 18:16:01 -0800 (Thu, 18 Dec 2008) $
25
 * @license       http://www.opensource.org/licenses/mit-license.php The MIT License
26
 */
27
App::import('Model', 'ConnectionManager');
28
/**
29
 * Task class for creating and updating model files.
30
 *
31
 * @package       cake
32
 * @subpackage    cake.cake.console.libs.tasks
33
 */
34
class ModelTask extends Shell {
35
/**
36
 * Name of plugin
37
 *
38
 * @var string
39
 * @access public
40
 */
41
	var $plugin = null;
42
/**
43
 * path to MODELS directory
44
 *
45
 * @var string
46
 * @access public
47
 */
48
	var $path = MODELS;
49
/**
50
 * tasks
51
 *
52
 * @var array
53
 * @access public
54
 */
55
	var $tasks = array('DbConfig');
56
/**
57
 * Execution method always used for tasks
58
 *
59
 * @access public
60
 */
61
	function execute() {
62
		if (empty($this->args)) {
63
			$this->__interactive();
64
		}
65
 
66
		if (!empty($this->args[0])) {
67
			$model = Inflector::camelize($this->args[0]);
68
			if ($this->bake($model)) {
69
				if ($this->_checkUnitTest()) {
70
					$this->bakeTest($model);
71
				}
72
			}
73
		}
74
	}
75
/**
76
 * Handles interactive baking
77
 *
78
 * @access private
79
 */
80
	function __interactive() {
81
		$this->hr();
82
		$this->out(sprintf("Bake Model\nPath: %s", $this->path));
83
		$this->hr();
84
		$this->interactive = true;
85
 
86
		$useTable = null;
87
		$primaryKey = 'id';
88
		$validate = array();
89
		$associations = array('belongsTo'=> array(), 'hasOne'=> array(), 'hasMany' => array(), 'hasAndBelongsToMany'=> array());
90
 
91
		$useDbConfig = 'default';
92
		$configs = get_class_vars('DATABASE_CONFIG');
93
 
94
		if (!is_array($configs)) {
95
			return $this->DbConfig->execute();
96
		}
97
 
98
		$connections = array_keys($configs);
99
		if (count($connections) > 1) {
100
			$useDbConfig = $this->in(__('Use Database Config', true) .':', $connections, 'default');
101
		}
102
 
103
		$currentModelName = $this->getName($useDbConfig);
104
		$db =& ConnectionManager::getDataSource($useDbConfig);
105
		$useTable = Inflector::tableize($currentModelName);
106
		$fullTableName = $db->fullTableName($useTable, false);
107
		$tableIsGood = false;
108
 
109
		if (array_search($useTable, $this->__tables) === false) {
110
			$this->out('');
111
			$this->out(sprintf(__("Given your model named '%s', Cake would expect a database table named %s", true), $currentModelName, $fullTableName));
112
			$tableIsGood = $this->in(__('Do you want to use this table?', true), array('y','n'), 'y');
113
		}
114
 
115
		if (low($tableIsGood) == 'n' || low($tableIsGood) == 'no') {
116
			$useTable = $this->in(__('What is the name of the table (enter "null" to use NO table)?', true));
117
		}
118
 
119
		while ($tableIsGood == false && low($useTable) != 'null') {
120
			if (is_array($this->__tables) && !in_array($useTable, $this->__tables)) {
121
				$fullTableName = $db->fullTableName($useTable, false);
122
				$this->out($fullTableName . ' does not exist.');
123
				$useTable = $this->in(__('What is the name of the table (enter "null" to use NO table)?', true));
124
				$tableIsGood = false;
125
			} else {
126
				$tableIsGood = true;
127
			}
128
		}
129
 
130
		$wannaDoValidation = $this->in(__('Would you like to supply validation criteria for the fields in your model?', true), array('y','n'), 'y');
131
 
132
		if (in_array($useTable, $this->__tables)) {
133
			App::import('Model');
134
			$tempModel = new Model(array('name' => $currentModelName, 'table' => $useTable, 'ds' => $useDbConfig));
135
 
136
			$fields = $tempModel->schema();
137
			if (!array_key_exists('id', $fields)) {
138
				foreach ($fields as $name => $field) {
139
					if (isset($field['key']) && $field['key'] == 'primary') {
140
						break;
141
					}
142
				}
143
				$primaryKey = $this->in(__('What is the primaryKey?', true), null, $name);
144
			}
145
		}
146
 
147
		if (array_search($useTable, $this->__tables) !== false && (low($wannaDoValidation) == 'y' || low($wannaDoValidation) == 'yes')) {
148
			$validate = $this->doValidation($tempModel);
149
		}
150
 
151
		$wannaDoAssoc = $this->in(__('Would you like to define model associations (hasMany, hasOne, belongsTo, etc.)?', true), array('y','n'), 'y');
152
		if ((low($wannaDoAssoc) == 'y' || low($wannaDoAssoc) == 'yes')) {
153
			$associations = $this->doAssociations($tempModel);
154
		}
155
 
156
		$this->out('');
157
		$this->hr();
158
		$this->out(__('The following Model will be created:', true));
159
		$this->hr();
160
		$this->out("Name:       " . $currentModelName);
161
 
162
		if ($useDbConfig !== 'default') {
163
			$this->out("DB Config:  " . $useDbConfig);
164
		}
165
		if ($fullTableName !== Inflector::tableize($currentModelName)) {
166
			$this->out("DB Table:   " . $fullTableName);
167
		}
168
		if ($primaryKey != 'id') {
169
			$this->out("Primary Key: " . $primaryKey);
170
		}
171
		if (!empty($validate)) {
172
			$this->out("Validation: " . print_r($validate, true));
173
		}
174
		if (!empty($associations)) {
175
			$this->out("Associations:");
176
 
177
			if (!empty($associations['belongsTo'])) {
178
				for ($i = 0; $i < count($associations['belongsTo']); $i++) {
179
					$this->out("			$currentModelName belongsTo {$associations['belongsTo'][$i]['alias']}");
180
				}
181
			}
182
 
183
			if (!empty($associations['hasOne'])) {
184
				for ($i = 0; $i < count($associations['hasOne']); $i++) {
185
					$this->out("			$currentModelName hasOne	{$associations['hasOne'][$i]['alias']}");
186
				}
187
			}
188
 
189
			if (!empty($associations['hasMany'])) {
190
				for ($i = 0; $i < count($associations['hasMany']); $i++) {
191
					$this->out("			$currentModelName hasMany	{$associations['hasMany'][$i]['alias']}");
192
				}
193
			}
194
 
195
			if (!empty($associations['hasAndBelongsToMany'])) {
196
				for ($i = 0; $i < count($associations['hasAndBelongsToMany']); $i++) {
197
					$this->out("			$currentModelName hasAndBelongsToMany {$associations['hasAndBelongsToMany'][$i]['alias']}");
198
				}
199
			}
200
		}
201
		$this->hr();
202
		$looksGood = $this->in(__('Look okay?', true), array('y','n'), 'y');
203
 
204
		if (low($looksGood) == 'y' || low($looksGood) == 'yes') {
205
			if ($this->bake($currentModelName, $associations, $validate, $primaryKey, $useTable, $useDbConfig)) {
206
				if ($this->_checkUnitTest()) {
207
					$this->bakeTest($currentModelName, $useTable, $associations);
208
				}
209
			}
210
		} else {
211
			return false;
212
		}
213
	}
214
/**
215
 * Handles associations
216
 *
217
 * @param object $model
218
 * @param boolean $interactive
219
 * @return array $validate
220
 * @access public
221
 */
222
	function doValidation(&$model, $interactive = true) {
223
		if (!is_object($model)) {
224
			return false;
225
		}
226
		$fields = $model->schema();
227
 
228
		if (empty($fields)) {
229
			return false;
230
		}
231
 
232
		$validate = array();
233
 
234
		$options = array();
235
 
236
		if (class_exists('Validation')) {
237
			$parent = get_class_methods(get_parent_class('Validation'));
238
			$options = array_diff(get_class_methods('Validation'), $parent);
239
		}
240
 
241
		foreach ($fields as $fieldName => $field) {
242
			$prompt = 'Field: ' . $fieldName . "\n";
243
			$prompt .= 'Type: ' . $field['type'] . "\n";
244
			$prompt .= '---------------------------------------------------------------'."\n";
245
			$prompt .= 'Please select one of the following validation options:'."\n";
246
			$prompt .= '---------------------------------------------------------------'."\n";
247
 
248
			sort($options);
249
 
250
			$skip = 1;
251
			foreach ($options as $key => $option) {
252
				if ($option{0} != '_' && strtolower($option) != 'getinstance') {
253
					$prompt .= "{$skip} - {$option}\n";
254
					$choices[$skip] = strtolower($option);
255
					$skip++;
256
				}
257
			}
258
 
259
			$methods = array_flip($choices);
260
 
261
			$prompt .=  "{$skip} - Do not do any validation on this field.\n";
262
			$prompt .= "... or enter in a valid regex validation string.\n";
263
 
264
			$guess = $skip;
265
			if ($field['null'] != 1 && $fieldName != $model->primaryKey && !in_array($fieldName, array('created', 'modified', 'updated'))) {
266
				if ($fieldName == 'email') {
267
					$guess = $methods['email'];
268
				} elseif ($field['type'] == 'string') {
269
					$guess = $methods['notempty'];
270
				} elseif ($field['type'] == 'integer') {
271
					$guess = $methods['numeric'];
272
				} elseif ($field['type'] == 'boolean') {
273
					$guess = $methods['numeric'];
274
				} elseif ($field['type'] == 'datetime') {
275
					$guess = $methods['date'];
276
				}
277
			}
278
 
279
			if ($interactive === true) {
280
				$this->out('');
281
				$choice = $this->in($prompt, null, $guess);
282
			} else {
283
				$choice = $guess;
284
			}
285
			if ($choice != $skip) {
286
				if (is_numeric($choice) && isset($choices[$choice])) {
287
					$validate[$fieldName] = $choices[$choice];
288
				} else {
289
					$validate[$fieldName] = $choice;
290
				}
291
			}
292
		}
293
		return $validate;
294
	}
295
 
296
/**
297
 * Handles associations
298
 *
299
 * @param object $model
300
 * @param boolean $interactive
301
 * @return array $assocaitons
302
 * @access public
303
 */
304
	function doAssociations(&$model, $interactive = true) {
305
 
306
		if (!is_object($model)) {
307
			return false;
308
		}
309
		$this->out(__('One moment while the associations are detected.', true));
310
 
311
		$fields = $model->schema();
312
 
313
		if (empty($fields)) {
314
			return false;
315
		}
316
 
317
		$primaryKey = $model->primaryKey;
318
		$foreignKey = $this->_modelKey($model->name);
319
 
320
		$associations = array('belongsTo' => array(), 'hasMany' => array(), 'hasOne'=> array(), 'hasAndBelongsToMany' => array());
321
		$possibleKeys = array();
322
 
323
		//Look for belongsTo
324
		$i = 0;
325
		foreach ($fields as $fieldName => $field) {
326
			$offset = strpos($fieldName, '_id');
327
			if ($fieldName != $model->primaryKey && $offset !== false) {
328
				$tmpModelName = $this->_modelNameFromKey($fieldName);
329
				$associations['belongsTo'][$i]['alias'] = $tmpModelName;
330
				$associations['belongsTo'][$i]['className'] = $tmpModelName;
331
				$associations['belongsTo'][$i]['foreignKey'] = $fieldName;
332
				$i++;
333
			}
334
		}
335
		//Look for hasOne and hasMany and hasAndBelongsToMany
336
		$i = $j = 0;
337
 
338
		foreach ($this->__tables as $otherTable) {
339
			App::import('Model');
340
			$tmpModelName = $this->_modelName($otherTable);
341
			$tempOtherModel = & new Model(array('name' => $tmpModelName, 'table' => $otherTable, 'ds' => $model->useDbConfig));
342
			$modelFieldsTemp = $tempOtherModel->schema();
343
 
344
			$offset = strpos($otherTable, $model->table . '_');
345
			$otherOffset = strpos($otherTable, '_' . $model->table);
346
 
347
			foreach ($modelFieldsTemp as $fieldName => $field) {
348
				if ($field['type'] == 'integer' || $field['type'] == 'string') {
349
					$possibleKeys[$otherTable][] = $fieldName;
350
				}
351
				if ($fieldName != $model->primaryKey && $fieldName == $foreignKey && $offset === false && $otherOffset === false) {
352
					$associations['hasOne'][$j]['alias'] = $tempOtherModel->name;
353
					$associations['hasOne'][$j]['className'] = $tempOtherModel->name;
354
					$associations['hasOne'][$j]['foreignKey'] = $fieldName;
355
 
356
					$associations['hasMany'][$j]['alias'] = $tempOtherModel->name;
357
					$associations['hasMany'][$j]['className'] = $tempOtherModel->name;
358
					$associations['hasMany'][$j]['foreignKey'] = $fieldName;
359
					$j++;
360
				}
361
			}
362
 
363
			if ($offset !== false) {
364
				$offset = strlen($model->table . '_');
365
				$tmpModelName = $this->_modelName(substr($otherTable, $offset));
366
				$associations['hasAndBelongsToMany'][$i]['alias'] = $tmpModelName;
367
				$associations['hasAndBelongsToMany'][$i]['className'] = $tmpModelName;
368
				$associations['hasAndBelongsToMany'][$i]['foreignKey'] = $foreignKey;
369
				$associations['hasAndBelongsToMany'][$i]['associationForeignKey'] = $this->_modelKey($tmpModelName);
370
				$associations['hasAndBelongsToMany'][$i]['joinTable'] = $otherTable;
371
				$i++;
372
			}
373
 
374
			if ($otherOffset !== false) {
375
				$tmpModelName = $this->_modelName(substr($otherTable, 0, $otherOffset));
376
				$associations['hasAndBelongsToMany'][$i]['alias'] = $tmpModelName;
377
				$associations['hasAndBelongsToMany'][$i]['className'] = $tmpModelName;
378
				$associations['hasAndBelongsToMany'][$i]['foreignKey'] = $foreignKey;
379
				$associations['hasAndBelongsToMany'][$i]['associationForeignKey'] = $this->_modelKey($tmpModelName);
380
				$associations['hasAndBelongsToMany'][$i]['joinTable'] = $otherTable;
381
				$i++;
382
			}
383
		}
384
 
385
		if ($interactive !== true) {
386
			unset($associations['hasOne']);
387
		}
388
 
389
		if ($interactive === true) {
390
			$this->hr();
391
			if (empty($associations)) {
392
				$this->out(__('None found.', true));
393
			} else {
394
				$this->out(__('Please confirm the following associations:', true));
395
				$this->hr();
396
				foreach ($associations as $type => $settings) {
397
					if (!empty($associations[$type])) {
398
						$count = count($associations[$type]);
399
						$response = 'y';
400
						for ($i = 0; $i < $count; $i++) {
401
							$prompt = "{$model->name} {$type} {$associations[$type][$i]['alias']}";
402
							$response = $this->in("{$prompt}?", array('y','n'), 'y');
403
 
404
							if ('n' == low($response) || 'no' == low($response)) {
405
								unset($associations[$type][$i]);
406
							} else {
407
								if ($model->name === $associations[$type][$i]['alias']) {
408
									if ($type === 'belongsTo') {
409
										$alias = 'Parent' . $associations[$type][$i]['alias'];
410
									}
411
									if ($type === 'hasOne' || $type === 'hasMany') {
412
										$alias = 'Child' . $associations[$type][$i]['alias'];
413
									}
414
 
415
									$alternateAlias = $this->in(sprintf(__('This is a self join. Use %s as the alias', true), $alias), array('y', 'n'), 'y');
416
 
417
									if ('n' == low($alternateAlias) || 'no' == low($alternateAlias)) {
418
										$associations[$type][$i]['alias'] = $this->in(__('Specify an alternate alias.', true));
419
									} else {
420
										$associations[$type][$i]['alias'] = $alias;
421
									}
422
								}
423
							}
424
						}
425
						$associations[$type] = array_merge($associations[$type]);
426
					}
427
				}
428
			}
429
 
430
			$wannaDoMoreAssoc = $this->in(__('Would you like to define some additional model associations?', true), array('y','n'), 'n');
431
 
432
			while ((low($wannaDoMoreAssoc) == 'y' || low($wannaDoMoreAssoc) == 'yes')) {
433
				$assocs = array(1 => 'belongsTo', 2 => 'hasOne', 3 => 'hasMany', 4 => 'hasAndBelongsToMany');
434
				$bad = true;
435
				while ($bad) {
436
					$this->out(__('What is the association type?', true));
437
					$prompt = "1. belongsTo\n";
438
					$prompt .= "2. hasOne\n";
439
					$prompt .= "3. hasMany\n";
440
					$prompt .= "4. hasAndBelongsToMany\n";
441
					$assocType = intval($this->in($prompt, null, __("Enter a number", true)));
442
 
443
					if (intval($assocType) < 1 || intval($assocType) > 4) {
444
						$this->out(__('The selection you entered was invalid. Please enter a number between 1 and 4.', true));
445
					} else {
446
						$bad = false;
447
					}
448
				}
449
				$this->out(__('For the following options be very careful to match your setup exactly. Any spelling mistakes will cause errors.', true));
450
				$this->hr();
451
				$alias = $this->in(__('What is the alias for this association?', true));
452
				$className = $this->in(sprintf(__('What className will %s use?', true), $alias), null, $alias );
453
				$suggestedForeignKey = null;
454
				if ($assocType == '1') {
455
					$showKeys = $possibleKeys[$model->table];
456
					$suggestedForeignKey = $this->_modelKey($alias);
457
				} else {
458
					$otherTable = Inflector::tableize($className);
459
					if (in_array($otherTable, $this->__tables)) {
460
						if ($assocType < '4') {
461
							$showKeys = $possibleKeys[$otherTable];
462
						} else {
463
							$showKeys = null;
464
						}
465
					} else {
466
						$otherTable = $this->in(__('What is the table for this model?', true));
467
						$showKeys = $possibleKeys[$otherTable];
468
					}
469
					$suggestedForeignKey = $this->_modelKey($model->name);
470
				}
471
				if (!empty($showKeys)) {
472
					$this->out(__('A helpful List of possible keys', true));
473
					for ($i = 0; $i < count($showKeys); $i++) {
474
						$this->out($i + 1 . ". " . $showKeys[$i]);
475
					}
476
					$foreignKey = $this->in(__('What is the foreignKey?', true), null, __("Enter a number", true));
477
					if (intval($foreignKey) > 0 && intval($foreignKey) <= $i ) {
478
						$foreignKey = $showKeys[intval($foreignKey) - 1];
479
					}
480
				}
481
				if (!isset($foreignKey)) {
482
					$foreignKey = $this->in(__('What is the foreignKey? Specify your own.', true), null, $suggestedForeignKey);
483
				}
484
				if ($assocType == '4') {
485
					$associationForeignKey = $this->in(__('What is the associationForeignKey?', true), null, $this->_modelKey($model->name));
486
					$joinTable = $this->in(__('What is the joinTable?', true));
487
				}
488
				$associations[$assocs[$assocType]] = array_values((array)$associations[$assocs[$assocType]]);
489
				$count = count($associations[$assocs[$assocType]]);
490
				$i = ($count > 0) ? $count : 0;
491
				$associations[$assocs[$assocType]][$i]['alias'] = $alias;
492
				$associations[$assocs[$assocType]][$i]['className'] = $className;
493
				$associations[$assocs[$assocType]][$i]['foreignKey'] = $foreignKey;
494
				if ($assocType == '4') {
495
					$associations[$assocs[$assocType]][$i]['associationForeignKey'] = $associationForeignKey;
496
					$associations[$assocs[$assocType]][$i]['joinTable'] = $joinTable;
497
				}
498
				$wannaDoMoreAssoc = $this->in(__('Define another association?', true), array('y','n'), 'y');
499
			}
500
		}
501
		return $associations;
502
	}
503
/**
504
 * Assembles and writes a Model file.
505
 *
506
 * @param mixed $name Model name or object
507
 * @param mixed $associations if array and $name is not an object assume Model associations array otherwise boolean interactive
508
 * @param array $validate Validation rules
509
 * @param string $primaryKey Primary key to use
510
 * @param string $useTable Table to use
511
 * @param string $useDbConfig Database configuration setting to use
512
 * @access private
513
 */
514
	function bake($name, $associations = array(),  $validate = array(), $primaryKey = 'id', $useTable = null, $useDbConfig = 'default') {
515
 
516
		if (is_object($name)) {
517
			if (!is_array($associations)) {
518
				$associations = $this->doAssociations($name, $associations);
519
				$validate = $this->doValidation($name, $associations);
520
			}
521
			$primaryKey = $name->primaryKey;
522
			$useTable = $name->table;
523
			$useDbConfig = $name->useDbConfig;
524
			$name = $name->name;
525
		}
526
 
527
		$out = "<?php\n";
528
		$out .= "class {$name} extends {$this->plugin}AppModel {\n\n";
529
		$out .= "\tvar \$name = '{$name}';\n";
530
 
531
		if ($useDbConfig !== 'default') {
532
			$out .= "\tvar \$useDbConfig = '$useDbConfig';\n";
533
		}
534
 
535
		if (($useTable && $useTable !== Inflector::tableize($name)) || $useTable === false) {
536
			$table = "'$useTable'";
537
			if (!$useTable) {
538
				$table = 'false';
539
			}
540
			$out .= "\tvar \$useTable = $table;\n";
541
		}
542
 
543
		if ($primaryKey !== 'id') {
544
			$out .= "\tvar \$primaryKey = '$primaryKey';\n";
545
		}
546
 
547
		$validateCount = count($validate);
548
		if (is_array($validate) && $validateCount > 0) {
549
			$out .= "\tvar \$validate = array(\n";
550
			$keys = array_keys($validate);
551
			for ($i = 0; $i < $validateCount; $i++) {
552
				$val = "'" . $validate[$keys[$i]] . "'";
553
				$out .= "\t\t'" . $keys[$i] . "' => array({$val})";
554
				if ($i + 1 < $validateCount) {
555
					$out .= ",";
556
				}
557
				$out .= "\n";
558
			}
559
			$out .= "\t);\n";
560
		}
561
		$out .= "\n";
562
 
563
		if (!empty($associations)) {
564
			if (!empty($associations['belongsTo']) || !empty($associations['hasOne']) || !empty($associations['hasMany']) || !empty($associations['hasAndBelongsToMany'])) {
565
				$out.= "\t//The Associations below have been created with all possible keys, those that are not needed can be removed\n";
566
			}
567
 
568
			if (!empty($associations['belongsTo'])) {
569
				$out .= "\tvar \$belongsTo = array(\n";
570
				$belongsToCount = count($associations['belongsTo']);
571
 
572
				for ($i = 0; $i < $belongsToCount; $i++) {
573
					$out .= "\t\t\t'{$associations['belongsTo'][$i]['alias']}' => ";
574
					$out .= "array('className' => '{$associations['belongsTo'][$i]['className']}',\n";
575
					$out .= "\t\t\t\t\t\t\t\t'foreignKey' => '{$associations['belongsTo'][$i]['foreignKey']}',\n";
576
					$out .= "\t\t\t\t\t\t\t\t'conditions' => '',\n";
577
					$out .= "\t\t\t\t\t\t\t\t'fields' => '',\n";
578
					$out .= "\t\t\t\t\t\t\t\t'order' => ''\n";
579
					$out .= "\t\t\t)";
580
					if ($i + 1 < $belongsToCount) {
581
						$out .= ",";
582
					}
583
					$out .= "\n";
584
 
585
				}
586
				$out .= "\t);\n\n";
587
			}
588
 
589
			if (!empty($associations['hasOne'])) {
590
				$out .= "\tvar \$hasOne = array(\n";
591
				$hasOneCount = count($associations['hasOne']);
592
 
593
				for ($i = 0; $i < $hasOneCount; $i++) {
594
					$out .= "\t\t\t'{$associations['hasOne'][$i]['alias']}' => ";
595
					$out .= "array('className' => '{$associations['hasOne'][$i]['className']}',\n";
596
					$out .= "\t\t\t\t\t\t\t\t'foreignKey' => '{$associations['hasOne'][$i]['foreignKey']}',\n";
597
					$out .= "\t\t\t\t\t\t\t\t'dependent' => false,\n";
598
					$out .= "\t\t\t\t\t\t\t\t'conditions' => '',\n";
599
					$out .= "\t\t\t\t\t\t\t\t'fields' => '',\n";
600
					$out .= "\t\t\t\t\t\t\t\t'order' => ''\n";
601
					$out .= "\t\t\t)";
602
					if ($i + 1 < $hasOneCount) {
603
						$out .= ",";
604
					}
605
					$out .= "\n";
606
 
607
				}
608
				$out .= "\t);\n\n";
609
			}
610
 
611
			if (!empty($associations['hasMany'])) {
612
				$out .= "\tvar \$hasMany = array(\n";
613
				$hasManyCount = count($associations['hasMany']);
614
 
615
				for ($i = 0; $i < $hasManyCount; $i++) {
616
					$out .= "\t\t\t'{$associations['hasMany'][$i]['alias']}' => ";
617
					$out .= "array('className' => '{$associations['hasMany'][$i]['className']}',\n";
618
					$out .= "\t\t\t\t\t\t\t\t'foreignKey' => '{$associations['hasMany'][$i]['foreignKey']}',\n";
619
					$out .= "\t\t\t\t\t\t\t\t'dependent' => false,\n";
620
					$out .= "\t\t\t\t\t\t\t\t'conditions' => '',\n";
621
					$out .= "\t\t\t\t\t\t\t\t'fields' => '',\n";
622
					$out .= "\t\t\t\t\t\t\t\t'order' => '',\n";
623
					$out .= "\t\t\t\t\t\t\t\t'limit' => '',\n";
624
					$out .= "\t\t\t\t\t\t\t\t'offset' => '',\n";
625
					$out .= "\t\t\t\t\t\t\t\t'exclusive' => '',\n";
626
					$out .= "\t\t\t\t\t\t\t\t'finderQuery' => '',\n";
627
					$out .= "\t\t\t\t\t\t\t\t'counterQuery' => ''\n";
628
					$out .= "\t\t\t)";
629
					if ($i + 1 < $hasManyCount) {
630
						$out .= ",";
631
					}
632
					$out .= "\n";
633
				}
634
				$out .= "\t);\n\n";
635
			}
636
 
637
			if (!empty($associations['hasAndBelongsToMany'])) {
638
				$out .= "\tvar \$hasAndBelongsToMany = array(\n";
639
				$hasAndBelongsToManyCount = count($associations['hasAndBelongsToMany']);
640
 
641
				for ($i = 0; $i < $hasAndBelongsToManyCount; $i++) {
642
					$out .= "\t\t\t'{$associations['hasAndBelongsToMany'][$i]['alias']}' => ";
643
					$out .= "array('className' => '{$associations['hasAndBelongsToMany'][$i]['className']}',\n";
644
					$out .= "\t\t\t\t\t\t'joinTable' => '{$associations['hasAndBelongsToMany'][$i]['joinTable']}',\n";
645
					$out .= "\t\t\t\t\t\t'foreignKey' => '{$associations['hasAndBelongsToMany'][$i]['foreignKey']}',\n";
646
					$out .= "\t\t\t\t\t\t'associationForeignKey' => '{$associations['hasAndBelongsToMany'][$i]['associationForeignKey']}',\n";
647
					$out .= "\t\t\t\t\t\t'unique' => true,\n";
648
					$out .= "\t\t\t\t\t\t'conditions' => '',\n";
649
					$out .= "\t\t\t\t\t\t'fields' => '',\n";
650
					$out .= "\t\t\t\t\t\t'order' => '',\n";
651
					$out .= "\t\t\t\t\t\t'limit' => '',\n";
652
					$out .= "\t\t\t\t\t\t'offset' => '',\n";
653
					$out .= "\t\t\t\t\t\t'finderQuery' => '',\n";
654
					$out .= "\t\t\t\t\t\t'deleteQuery' => '',\n";
655
					$out .= "\t\t\t\t\t\t'insertQuery' => ''\n";
656
					$out .= "\t\t\t)";
657
					if ($i + 1 < $hasAndBelongsToManyCount) {
658
						$out .= ",";
659
					}
660
					$out .= "\n";
661
				}
662
				$out .= "\t);\n\n";
663
			}
664
		}
665
		$out .= "}\n";
666
		$out .= "?>";
667
		$filename = $this->path . Inflector::underscore($name) . '.php';
668
		$this->out("\nBaking model class for $name...");
669
		return $this->createFile($filename, $out);
670
	}
671
 
672
/**
673
 * Assembles and writes a unit test file
674
 *
675
 * @param string $className Model class name
676
 * @access private
677
 */
678
	function bakeTest($className, $useTable = null, $associations = array()) {
679
		$results = $this->fixture($className, $useTable);
680
 
681
		if ($results) {
682
			$fixtureInc = 'app';
683
			if ($this->plugin) {
684
				$fixtureInc = 'plugin.'.Inflector::underscore($this->plugin);
685
			}
686
 
687
			$fixture[] = "'{$fixtureInc}." . Inflector::underscore($className) ."'";
688
 
689
			if (!empty($associations)) {
690
				$assoc[] = Set::extract($associations, 'belongsTo.{n}.className');
691
				$assoc[] = Set::extract($associations, 'hasOne.{n}.className');
692
				$assoc[] = Set::extract($associations, 'hasMany.{n}.className');
693
				foreach ($assoc as $key => $value) {
694
					if (is_array($value)) {
695
						foreach ($value as $class) {
696
							$fixture[] = "'{$fixtureInc}." . Inflector::underscore($class) ."'";
697
						}
698
					}
699
				}
700
			}
701
			$fixture = join(", ", $fixture);
702
 
703
			$import = $className;
704
			if (isset($this->plugin)) {
705
				$import = $this->plugin . '.' . $className;
706
			}
707
 
708
			$out = "App::import('Model', '$import');\n\n";
709
			$out .= "class {$className}TestCase extends CakeTestCase {\n";
710
			$out .= "\tvar \${$className} = null;\n";
711
			$out .= "\tvar \$fixtures = array($fixture);\n\n";
712
			$out .= "\tfunction startTest() {\n";
713
			$out .= "\t\t\$this->{$className} =& ClassRegistry::init('{$className}');\n";
714
			$out .= "\t}\n\n";
715
			$out .= "\tfunction test{$className}Instance() {\n";
716
			$out .= "\t\t\$this->assertTrue(is_a(\$this->{$className}, '{$className}'));\n";
717
			$out .= "\t}\n\n";
718
			$out .= "\tfunction test{$className}Find() {\n";
719
			$out .= "\t\t\$this->{$className}->recursive = -1;\n";
720
			$out .= "\t\t\$results = \$this->{$className}->find('first');\n\t\t\$this->assertTrue(!empty(\$results));\n\n";
721
			$out .= "\t\t\$expected = array('$className' => array(\n$results\n\t\t\t));\n";
722
			$out .= "\t\t\$this->assertEqual(\$results, \$expected);\n";
723
			$out .= "\t}\n";
724
			$out .= "}\n";
725
 
726
			$path = MODEL_TESTS;
727
			if (isset($this->plugin)) {
728
				$pluginPath = 'plugins' . DS . Inflector::underscore($this->plugin) . DS;
729
				$path = APP . $pluginPath . 'tests' . DS . 'cases' . DS . 'models' . DS;
730
			}
731
 
732
			$filename = Inflector::underscore($className).'.test.php';
733
			$this->out("\nBaking unit test for $className...");
734
 
735
			$header = '$Id';
736
			$content = "<?php \n/* SVN FILE: $header$ */\n/* ". $className ." Test cases generated on: " . date('Y-m-d H:m:s') . " : ". time() . "*/\n{$out}?>";
737
			return $this->createFile($path . $filename, $content);
738
		}
739
		return false;
740
	}
741
/**
742
 * outputs the a list of possible models or controllers from database
743
 *
744
 * @param string $useDbConfig Database configuration name
745
 * @access public
746
 */
747
	function listAll($useDbConfig = 'default', $interactive = true) {
748
		$db =& ConnectionManager::getDataSource($useDbConfig);
749
		$usePrefix = empty($db->config['prefix']) ? '' : $db->config['prefix'];
750
		if ($usePrefix) {
751
			$tables = array();
752
			foreach ($db->listSources() as $table) {
753
				if (!strncmp($table, $usePrefix, strlen($usePrefix))) {
754
					$tables[] = substr($table, strlen($usePrefix));
755
				}
756
			}
757
		} else {
758
			$tables = $db->listSources();
759
		}
760
		if (empty($tables)) {
761
			$this->err(__('Your database does not have any tables.', true));
762
			$this->_stop();
763
		}
764
 
765
		$this->__tables = $tables;
766
 
767
		if ($interactive === true) {
768
			$this->out(__('Possible Models based on your current database:', true));
769
			$this->_modelNames = array();
770
			$count = count($tables);
771
			for ($i = 0; $i < $count; $i++) {
772
				$this->_modelNames[] = $this->_modelName($tables[$i]);
773
				$this->out($i + 1 . ". " . $this->_modelNames[$i]);
774
			}
775
		}
776
	}
777
/**
778
 * Forces the user to specify the model he wants to bake, and returns the selected model name.
779
 *
780
 * @return string the model name
781
 * @access public
782
 */
783
	function getName($useDbConfig) {
784
		$this->listAll($useDbConfig);
785
 
786
		$enteredModel = '';
787
 
788
		while ($enteredModel == '') {
789
			$enteredModel = $this->in(__("Enter a number from the list above, type in the name of another model, or 'q' to exit", true), null, 'q');
790
 
791
			if ($enteredModel === 'q') {
792
				$this->out(__("Exit", true));
793
				$this->_stop();
794
			}
795
 
796
			if ($enteredModel == '' || intval($enteredModel) > count($this->_modelNames)) {
797
				$this->err(__("The model name you supplied was empty, or the number you selected was not an option. Please try again.", true));
798
				$enteredModel = '';
799
			}
800
		}
801
 
802
		if (intval($enteredModel) > 0 && intval($enteredModel) <= count($this->_modelNames)) {
803
			$currentModelName = $this->_modelNames[intval($enteredModel) - 1];
804
		} else {
805
			$currentModelName = $enteredModel;
806
		}
807
 
808
		return $currentModelName;
809
	}
810
/**
811
 * Displays help contents
812
 *
813
 * @access public
814
 */
815
	function help() {
816
		$this->hr();
817
		$this->out("Usage: cake bake model <arg1>");
818
		$this->hr();
819
		$this->out('Commands:');
820
		$this->out("\n\tmodel\n\t\tbakes model in interactive mode.");
821
		$this->out("\n\tmodel <name>\n\t\tbakes model file with no associations or validation");
822
		$this->out("");
823
		$this->_stop();
824
	}
825
/**
826
 * Builds the tests fixtures for the model and create the file
827
 *
828
 * @param string $model the name of the model
829
 * @param string $useTable table name
830
 * @return array $records, used in ModelTask::bakeTest() to create $expected
831
 * @todo move this to a task
832
 */
833
	function fixture($model, $useTable = null) {
834
		if (!class_exists('CakeSchema')) {
835
			App::import('Model', 'Schema');
836
		}
837
		$out = "\nclass {$model}Fixture extends CakeTestFixture {\n";
838
		$out .= "\tvar \$name = '$model';\n";
839
 
840
		if (!$useTable) {
841
			$useTable = Inflector::tableize($model);
842
		} else {
843
			$out .= "\tvar \$table = '$useTable';\n";
844
		}
845
		$schema = new CakeSchema();
846
		$data = $schema->read(array('models' => false));
847
 
848
		if (!isset($data['tables'][$useTable])) {
849
			return false;
850
		}
851
		$tables[$model] = $data['tables'][$useTable];
852
 
853
		foreach ($tables as $table => $fields) {
854
			if (!is_numeric($table) && $table !== 'missing') {
855
				$out .= "\tvar \$fields = array(\n";
856
				$records = array();
857
				if (is_array($fields)) {
858
					$cols = array();
859
					foreach ($fields as $field => $value) {
860
						if ($field != 'indexes') {
861
							if (is_string($value)) {
862
								$type = $value;
863
								$value = array('type'=> $type);
864
							}
865
							$col = "\t\t\t'{$field}' => array('type'=>'" . $value['type'] . "', ";
866
 
867
							switch ($value['type']) {
868
								case 'integer':
869
									$insert = 1;
870
								break;
871
								case 'string';
872
									$insert = "Lorem ipsum dolor sit amet";
873
									if (!empty($value['length'])) {
874
										$insert = substr($insert, 0, (int)$value['length'] - 2);
875
									}
876
									$insert = "'$insert'";
877
								break;
878
								case 'datetime':
879
									$ts = date('Y-m-d H:i:s');
880
									$insert = "'$ts'";
881
								break;
882
								case 'date':
883
									$ts = date('Y-m-d');
884
									$insert = "'$ts'";
885
								break;
886
								case 'time':
887
									$ts = date('H:i:s');
888
									$insert = "'$ts'";
889
								break;
890
								case 'boolean':
891
									$insert = 1;
892
								break;
893
								case 'text':
894
									$insert =
895
									"'Lorem ipsum dolor sit amet, aliquet feugiat. Convallis morbi fringilla gravida,";
896
									$insert .= "phasellus feugiat dapibus velit nunc, pulvinar eget sollicitudin venenatis cum nullam,";
897
									$insert .= "vivamus ut a sed, mollitia lectus. Nulla vestibulum massa neque ut et, id hendrerit sit,";
898
									$insert .= "feugiat in taciti enim proin nibh, tempor dignissim, rhoncus duis vestibulum nunc mattis convallis.'";
899
								break;
900
							}
901
							$records[] = "\t\t\t'$field'  => $insert";
902
							unset($value['type']);
903
							$col .= join(', ',  $schema->__values($value));
904
						} else {
905
							$col = "\t\t\t'indexes' => array(";
906
							$props = array();
907
							foreach ((array)$value as $key => $index) {
908
								$props[] = "'{$key}' => array(".join(', ',  $schema->__values($index)).")";
909
							}
910
							$col .= join(', ', $props);
911
						}
912
						$col .= ")";
913
						$cols[] = $col;
914
					}
915
					$out .= join(",\n", $cols);
916
				}
917
				$out .= "\n\t\t\t);\n";
918
			}
919
		}
920
		$records = join(",\n", $records);
921
		$out .= "\tvar \$records = array(array(\n$records\n\t\t\t));\n";
922
		$out .= "}\n";
923
		$path = TESTS . DS . 'fixtures' . DS;
924
		if (isset($this->plugin)) {
925
			$pluginPath = 'plugins' . DS . Inflector::underscore($this->plugin) . DS;
926
			$path = APP . $pluginPath . 'tests' . DS . 'fixtures' . DS;
927
		}
928
		$filename = Inflector::underscore($model).'_fixture.php';
929
		$header = '$Id';
930
		$content = "<?php \n/* SVN FILE: $header$ */\n/* ". $model ." Fixture generated on: " . date('Y-m-d H:m:s') . " : ". time() . "*/\n{$out}?>";
931
		$this->out("\nBaking test fixture for $model...");
932
		if ($this->createFile($path . $filename, $content)) {
933
			return $records;
934
		}
935
		return false;
936
	}
937
}
938
?>