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