1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13:
14:
15: App::uses('AppShell', 'Console/Command');
16:
17: 18: 19: 20: 21:
22: class ConsoleShell extends AppShell {
23:
24: 25: 26: 27: 28:
29: public $associations = array('hasOne', 'hasMany', 'belongsTo', 'hasAndBelongsToMany');
30:
31: 32: 33: 34: 35:
36: public $badCommandChars = array('$', ';');
37:
38: 39: 40: 41: 42:
43: public $models = array();
44:
45: 46: 47: 48: 49:
50: public function startup() {
51: App::uses('Dispatcher', 'Routing');
52: $this->Dispatcher = new Dispatcher();
53: $this->models = App::objects('Model');
54:
55: foreach ($this->models as $model) {
56: $class = $model;
57: $this->models[$model] = $class;
58: App::uses($class, 'Model');
59: $this->{$class} = new $class();
60: }
61: $this->out(__d('cake_console', 'Model classes:'));
62: $this->hr();
63:
64: foreach ($this->models as $model) {
65: $this->out(" - {$model}");
66: }
67: $this->_loadRoutes();
68: }
69:
70: 71: 72: 73: 74:
75: public function help() {
76: $out = 'Console help:';
77: $out .= '-------------';
78: $out .= 'The interactive console is a tool for testing parts of your app before you';
79: $out .= 'write code.';
80: $out .= "\n";
81: $out .= 'Model testing:';
82: $out .= 'To test model results, use the name of your model without a leading $';
83: $out .= 'e.g. Foo->find("all")';
84: $out .= "\n";
85: $out .= 'To dynamically set associations, you can do the following:';
86: $out .= "\tModelA bind <association> ModelB";
87: $out .= "where the supported associations are hasOne, hasMany, belongsTo, hasAndBelongsToMany";
88: $out .= "\n";
89: $out .= 'To dynamically remove associations, you can do the following:';
90: $out .= "\t ModelA unbind <association> ModelB";
91: $out .= "where the supported associations are the same as above";
92: $out .= "\n";
93: $out .= "To save a new field in a model, you can do the following:";
94: $out .= "\tModelA->save(array('foo' => 'bar', 'baz' => 0))";
95: $out .= "where you are passing a hash of data to be saved in the format";
96: $out .= "of field => value pairs";
97: $out .= "\n";
98: $out .= "To get column information for a model, use the following:";
99: $out .= "\tModelA columns";
100: $out .= "which returns a list of columns and their type";
101: $out .= "\n";
102: $out .= "\n";
103: $out .= 'Route testing:';
104: $out .= "\n";
105: $out .= 'To test URLs against your app\'s route configuration, type:';
106: $out .= "\n";
107: $out .= "\tRoute <url>";
108: $out .= "\n";
109: $out .= "where url is the path to your your action plus any query parameters,";
110: $out .= "minus the application's base path. For example:";
111: $out .= "\n";
112: $out .= "\tRoute /posts/view/1";
113: $out .= "\n";
114: $out .= "will return something like the following:";
115: $out .= "\n";
116: $out .= "\tarray(";
117: $out .= "\t [...]";
118: $out .= "\t 'controller' => 'posts',";
119: $out .= "\t 'action' => 'view',";
120: $out .= "\t [...]";
121: $out .= "\t)";
122: $out .= "\n";
123: $out .= 'Alternatively, you can use simple array syntax to test reverse';
124: $out .= 'To reload your routes config (Config/routes.php), do the following:';
125: $out .= "\n";
126: $out .= "\tRoutes reload";
127: $out .= "\n";
128: $out .= 'To show all connected routes, do the following:';
129: $out .= "\tRoutes show";
130: $this->out($out);
131: }
132:
133: 134: 135: 136: 137: 138:
139: public function main($command = null) {
140: while (true) {
141: if (empty($command)) {
142: $command = trim($this->in(''));
143: }
144:
145: switch ($command) {
146: case 'help':
147: $this->help();
148: break;
149: case 'quit':
150: case 'exit':
151: return true;
152: break;
153: case 'models':
154: $this->out(__d('cake_console', 'Model classes:'));
155: $this->hr();
156: foreach ($this->models as $model) {
157: $this->out(" - {$model}");
158: }
159: break;
160: case (preg_match("/^(\w+) bind (\w+) (\w+)/", $command, $tmp) == true):
161: foreach ($tmp as $data) {
162: $data = strip_tags($data);
163: $data = str_replace($this->badCommandChars, "", $data);
164: }
165:
166: $modelA = $tmp[1];
167: $association = $tmp[2];
168: $modelB = $tmp[3];
169:
170: if ($this->_isValidModel($modelA) && $this->_isValidModel($modelB) && in_array($association, $this->associations)) {
171: $this->{$modelA}->bindModel(array($association => array($modelB => array('className' => $modelB))), false);
172: $this->out(__d('cake_console', "Created %s association between %s and %s",
173: $association, $modelA, $modelB));
174: } else {
175: $this->out(__d('cake_console', "Please verify you are using valid models and association types"));
176: }
177: break;
178: case (preg_match("/^(\w+) unbind (\w+) (\w+)/", $command, $tmp) == true):
179: foreach ($tmp as $data) {
180: $data = strip_tags($data);
181: $data = str_replace($this->badCommandChars, "", $data);
182: }
183:
184: $modelA = $tmp[1];
185: $association = $tmp[2];
186: $modelB = $tmp[3];
187:
188:
189: $currentAssociations = $this->{$modelA}->getAssociated();
190: $validCurrentAssociation = false;
191:
192: foreach ($currentAssociations as $model => $currentAssociation) {
193: if ($model == $modelB && $association == $currentAssociation) {
194: $validCurrentAssociation = true;
195: }
196: }
197:
198: if ($this->_isValidModel($modelA) && $this->_isValidModel($modelB) && in_array($association, $this->associations) && $validCurrentAssociation) {
199: $this->{$modelA}->unbindModel(array($association => array($modelB)));
200: $this->out(__d('cake_console', "Removed %s association between %s and %s",
201: $association, $modelA, $modelB));
202: } else {
203: $this->out(__d('cake_console', "Please verify you are using valid models, valid current association, and valid association types"));
204: }
205: break;
206: case (strpos($command, "->find") > 0):
207:
208: $command = strip_tags($command);
209: $command = str_replace($this->badCommandChars, "", $command);
210:
211:
212: list($modelToCheck, $tmp) = explode('->', $command);
213:
214: if ($this->_isValidModel($modelToCheck)) {
215: $findCommand = "\$data = \$this->$command;";
216: @eval($findCommand);
217:
218: if (is_array($data)) {
219: foreach ($data as $idx => $results) {
220: if (is_numeric($idx)) {
221: foreach ($results as $modelName => $result) {
222: $this->out("$modelName");
223:
224: foreach ($result as $field => $value) {
225: if (is_array($value)) {
226: foreach ($value as $field2 => $value2) {
227: $this->out("\t$field2: $value2");
228: }
229:
230: $this->out();
231: } else {
232: $this->out("\t$field: $value");
233: }
234: }
235: }
236: } else {
237: $this->out($idx);
238:
239: foreach ($results as $field => $value) {
240: if (is_array($value)) {
241: foreach ($value as $field2 => $value2) {
242: $this->out("\t$field2: $value2");
243: }
244:
245: $this->out();
246: } else {
247: $this->out("\t$field: $value");
248: }
249: }
250: }
251: }
252: } else {
253: $this->out();
254: $this->out(__d('cake_console', "No result set found"));
255: }
256: } else {
257: $this->out(__d('cake_console', "%s is not a valid model", $modelToCheck));
258: }
259:
260: break;
261: case (strpos($command, '->save') > 0):
262:
263: $command = strip_tags($command);
264: $command = str_replace($this->badCommandChars, "", $command);
265: list($modelToSave, $tmp) = explode("->", $command);
266:
267: if ($this->_isValidModel($modelToSave)) {
268:
269: list($foo, $data) = explode("->save", $command);
270: $data = preg_replace('/^\(*(array)?\(*(.+?)\)*$/i', '\\2', $data);
271: $saveCommand = "\$this->{$modelToSave}->save(array('{$modelToSave}' => array({$data})));";
272: @eval($saveCommand);
273: $this->out(__d('cake_console', 'Saved record for %s', $modelToSave));
274: }
275: break;
276: case (preg_match("/^(\w+) columns/", $command, $tmp) == true):
277: $modelToCheck = strip_tags(str_replace($this->badCommandChars, "", $tmp[1]));
278:
279: if ($this->_isValidModel($modelToCheck)) {
280:
281: $fieldsCommand = "\$data = \$this->{$modelToCheck}->getColumnTypes();";
282: @eval($fieldsCommand);
283:
284: if (is_array($data)) {
285: foreach ($data as $field => $type) {
286: $this->out("\t{$field}: {$type}");
287: }
288: }
289: } else {
290: $this->out(__d('cake_console', "Please verify that you selected a valid model"));
291: }
292: break;
293: case (preg_match("/^routes\s+reload/i", $command, $tmp) == true):
294: $router = Router::getInstance();
295: if (!$this->_loadRoutes()) {
296: $this->out(__d('cake_console', "There was an error loading the routes config. Please check that the file exists and is free of parse errors."));
297: break;
298: }
299: $this->out(__d('cake_console', "Routes configuration reloaded, %d routes connected", count($router->routes)));
300: break;
301: case (preg_match("/^routes\s+show/i", $command, $tmp) == true):
302: $router = Router::getInstance();
303: $this->out(implode("\n", Set::extract($router->routes, '{n}.0')));
304: break;
305: case (preg_match("/^route\s+(\(.*\))$/i", $command, $tmp) == true):
306: if ($url = eval('return array' . $tmp[1] . ';')) {
307: $this->out(Router::url($url));
308: }
309: break;
310: case (preg_match("/^route\s+(.*)/i", $command, $tmp) == true):
311: $this->out(var_export(Router::parse($tmp[1]), true));
312: break;
313: default:
314: $this->out(__d('cake_console', "Invalid command"));
315: $this->out();
316: break;
317: }
318: $command = '';
319: }
320: }
321:
322: 323: 324: 325: 326: 327:
328: protected function _isValidModel($modelToCheck) {
329: return in_array($modelToCheck, $this->models);
330: }
331:
332: 333: 334: 335: 336: 337:
338: protected function _loadRoutes() {
339: Router::reload();
340: extract(Router::getNamedExpressions());
341:
342: if (!@include APP . 'Config' . DS . 'routes.php') {
343: return false;
344: }
345: CakePlugin::routes();
346:
347: Router::parse('/');
348:
349: foreach (array_keys(Router::getNamedExpressions()) as $var) {
350: unset(${$var});
351: }
352:
353: foreach (Router::$routes as $route) {
354: $route->compile();
355: }
356: return true;
357: }
358:
359: }
360: