1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19:
20:
21: App::uses('AppShell', 'Console/Command');
22: App::uses('Folder', 'Utility');
23:
24: 25: 26: 27: 28:
29: class UpgradeShell extends AppShell {
30:
31: 32: 33: 34: 35:
36: protected $_files = array();
37:
38: 39: 40: 41: 42:
43: protected $_paths = array();
44:
45: 46: 47: 48: 49:
50: protected $_map = array(
51: 'Controller' => 'Controller',
52: 'Component' => 'Controller/Component',
53: 'Model' => 'Model',
54: 'Behavior' => 'Model/Behavior',
55: 'Datasource' => 'Model/Datasource',
56: 'Dbo' => 'Model/Datasource/Database',
57: 'View' => 'View',
58: 'Helper' => 'View/Helper',
59: 'Shell' => 'Console/Command',
60: 'Task' => 'Console/Command/Task',
61: 'Case' => 'Test/Case',
62: 'Fixture' => 'Test/Fixture',
63: 'Error' => 'Lib/Error',
64: );
65:
66: 67: 68: 69: 70:
71: public function startup() {
72: parent::startup();
73: if ($this->params['dry-run']) {
74: $this->out(__d('cake_console', '<warning>Dry-run mode enabled!</warning>'), 1, Shell::QUIET);
75: }
76: if ($this->params['git'] && !is_dir('.git')) {
77: $this->out(__d('cake_console', '<warning>No git repository detected!</warning>'), 1, Shell::QUIET);
78: }
79: }
80:
81: 82: 83: 84: 85:
86: public function all() {
87: foreach ($this->OptionParser->subcommands() as $command) {
88: $name = $command->name();
89: if ($name === 'all') {
90: continue;
91: }
92: $this->out(__d('cake_console', 'Running %s', $name));
93: $this->$name();
94: }
95: }
96:
97: 98: 99: 100: 101: 102: 103:
104: public function tests() {
105: $this->_paths = array(APP . 'tests' . DS);
106: if (!empty($this->params['plugin'])) {
107: $this->_paths = array(App::pluginPath($this->params['plugin']) . 'tests' . DS);
108: }
109: $patterns = array(
110: array(
111: '*TestCase extends CakeTestCase to *Test extends CakeTestCase',
112: '/([a-zA-Z]*Test)Case extends CakeTestCase/',
113: '\1 extends CakeTestCase'
114: ),
115: );
116:
117: $this->_filesRegexpUpdate($patterns);
118: }
119:
120: 121: 122: 123: 124: 125: 126: 127: 128:
129: public function locations() {
130: $cwd = getcwd();
131:
132: if (!empty($this->params['plugin'])) {
133: chdir(App::pluginPath($this->params['plugin']));
134: }
135:
136: if (is_dir('plugins')) {
137: $Folder = new Folder('plugins');
138: list($plugins) = $Folder->read();
139: foreach ($plugins as $plugin) {
140: chdir($cwd . DS . 'plugins' . DS . $plugin);
141: $this->out(__d('cake_console', 'Upgrading locations for plugin %s', $plugin));
142: $this->locations();
143: }
144: $this->_files = array();
145: chdir($cwd);
146: $this->out(__d('cake_console', 'Upgrading locations for app directory'));
147: }
148: $moves = array(
149: 'config' => 'Config',
150: 'Config' . DS . 'schema' => 'Config' . DS . 'Schema',
151: 'libs' => 'Lib',
152: 'tests' => 'Test',
153: 'views' => 'View',
154: 'models' => 'Model',
155: 'Model' . DS . 'behaviors' => 'Model' . DS . 'Behavior',
156: 'Model' . DS . 'datasources' => 'Model' . DS . 'Datasource',
157: 'Test' . DS . 'cases' => 'Test' . DS . 'Case',
158: 'Test' . DS . 'fixtures' => 'Test' . DS . 'Fixture',
159: 'vendors' . DS . 'shells' . DS . 'templates' => 'Console' . DS . 'Templates',
160: );
161: foreach ($moves as $old => $new) {
162: if (is_dir($old)) {
163: $this->out(__d('cake_console', 'Moving %s to %s', $old, $new));
164: if (!$this->params['dry-run']) {
165: if ($this->params['git']) {
166: exec('git mv -f ' . escapeshellarg($old) . ' ' . escapeshellarg($old . '__'));
167: exec('git mv -f ' . escapeshellarg($old . '__') . ' ' . escapeshellarg($new));
168: } else {
169: $Folder = new Folder($old);
170: $Folder->move($new);
171: }
172: }
173: }
174: }
175:
176: $this->_moveViewFiles();
177: $this->_moveAppClasses();
178:
179: $sourceDirs = array(
180: '.' => array('recursive' => false),
181: 'Console',
182: 'controllers',
183: 'Controller',
184: 'Lib' => array('checkFolder' => false),
185: 'models',
186: 'Model',
187: 'tests',
188: 'Test' => array('regex' => '@class (\S*Test) extends CakeTestCase@'),
189: 'views',
190: 'View',
191: 'vendors/shells',
192: );
193:
194: $defaultOptions = array(
195: 'recursive' => true,
196: 'checkFolder' => true,
197: 'regex' => '@class (\S*) .*(\s|\v)*{@i'
198: );
199: foreach ($sourceDirs as $dir => $options) {
200: if (is_numeric($dir)) {
201: $dir = $options;
202: $options = array();
203: }
204: $options = array_merge($defaultOptions, $options);
205: $this->_movePhpFiles($dir, $options);
206: }
207: }
208:
209: 210: 211: 212: 213: 214: 215:
216: public function helpers() {
217: $this->_paths = array_diff(App::path('views'), App::core('views'));
218:
219: if (!empty($this->params['plugin'])) {
220: $this->_paths = array(App::pluginPath($this->params['plugin']) . 'views' . DS);
221: }
222:
223: $patterns = array();
224: App::build(array(
225: 'View/Helper' => App::core('View/Helper'),
226: ), App::APPEND);
227: $helpers = App::objects('helper');
228: $plugins = App::objects('plugin');
229: $pluginHelpers = array();
230: foreach ($plugins as $plugin) {
231: CakePlugin::load($plugin);
232: $pluginHelpers = array_merge(
233: $pluginHelpers,
234: App::objects('helper', App::pluginPath($plugin) . DS . 'views' . DS . 'helpers' . DS, false)
235: );
236: }
237: $helpers = array_merge($pluginHelpers, $helpers);
238: foreach ($helpers as $helper) {
239: $helper = preg_replace('/Helper$/', '', $helper);
240: $oldHelper = $helper;
241: $oldHelper{0} = strtolower($oldHelper{0});
242: $patterns[] = array(
243: "\${$oldHelper} to \$this->{$helper}",
244: "/\\\${$oldHelper}->/",
245: "\\\$this->{$helper}->"
246: );
247: }
248:
249: $this->_filesRegexpUpdate($patterns);
250: }
251:
252: 253: 254: 255: 256: 257: 258: 259:
260: public function i18n() {
261: $this->_paths = array(
262: APP
263: );
264: if (!empty($this->params['plugin'])) {
265: $this->_paths = array(App::pluginPath($this->params['plugin']));
266: }
267:
268: $patterns = array(
269: array(
270: '<?php __*(*) to <?php echo __*(*)',
271: '/<\?php\s*(__[a-z]*\(.*?\))/',
272: '<?php echo \1'
273: ),
274: array(
275: '<?php __*(*, true) to <?php echo __*()',
276: '/<\?php\s*(__[a-z]*\(.*?)(,\s*true)(\))/',
277: '<?php echo \1\3'
278: ),
279: array('__*(*, true) to __*(*)', '/(__[a-z]*\(.*?)(,\s*true)(\))/', '\1\3')
280: );
281:
282: $this->_filesRegexpUpdate($patterns);
283: }
284:
285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298:
299: public function basics() {
300: $this->_paths = array(
301: APP
302: );
303: if (!empty($this->params['plugin'])) {
304: $this->_paths = array(App::pluginPath($this->params['plugin']));
305: }
306: $patterns = array(
307: array(
308: 'a(*) -> array(*)',
309: '/\ba\((.*)\)/',
310: 'array(\1)'
311: ),
312: array(
313: 'e(*) -> echo *',
314: '/\be\((.*)\)/',
315: 'echo \1'
316: ),
317: array(
318: 'ife(*, *, *) -> !empty(*) ? * : *',
319: '/ife\((.*), (.*), (.*)\)/',
320: '!empty(\1) ? \2 : \3'
321: ),
322: array(
323: 'r(*, *, *) -> str_replace(*, *, *)',
324: '/\br\(/',
325: 'str_replace('
326: ),
327: array(
328: 'up(*) -> strtoupper(*)',
329: '/\bup\(/',
330: 'strtoupper('
331: ),
332: array(
333: 'low(*) -> strtolower(*)',
334: '/\blow\(/',
335: 'strtolower('
336: ),
337: array(
338: 'getMicrotime() -> microtime(true)',
339: '/getMicrotime\(\)/',
340: 'microtime(true)'
341: ),
342: );
343: $this->_filesRegexpUpdate($patterns);
344: }
345:
346: 347: 348: 349: 350:
351: public function request() {
352: $views = array_diff(App::path('views'), App::core('views'));
353: $controllers = array_diff(App::path('controllers'), App::core('controllers'), array(APP));
354: $components = array_diff(App::path('components'), App::core('components'));
355:
356: $this->_paths = array_merge($views, $controllers, $components);
357:
358: if (!empty($this->params['plugin'])) {
359: $pluginPath = App::pluginPath($this->params['plugin']);
360: $this->_paths = array(
361: $pluginPath . 'controllers' . DS,
362: $pluginPath . 'controllers' . DS . 'components' . DS,
363: $pluginPath . 'views' . DS,
364: );
365: }
366: $patterns = array(
367: array(
368: '$this->data -> $this->request->data',
369: '/(\$this->data\b(?!\())/',
370: '$this->request->data'
371: ),
372: array(
373: '$this->params -> $this->request->params',
374: '/(\$this->params\b(?!\())/',
375: '$this->request->params'
376: ),
377: array(
378: '$this->webroot -> $this->request->webroot',
379: '/(\$this->webroot\b(?!\())/',
380: '$this->request->webroot'
381: ),
382: array(
383: '$this->base -> $this->request->base',
384: '/(\$this->base\b(?!\())/',
385: '$this->request->base'
386: ),
387: array(
388: '$this->here -> $this->request->here',
389: '/(\$this->here\b(?!\())/',
390: '$this->request->here'
391: ),
392: array(
393: '$this->action -> $this->request->action',
394: '/(\$this->action\b(?!\())/',
395: '$this->request->action'
396: ),
397: );
398: $this->_filesRegexpUpdate($patterns);
399: }
400:
401: 402: 403: 404: 405:
406: public function configure() {
407: $this->_paths = array(
408: APP
409: );
410: if (!empty($this->params['plugin'])) {
411: $this->_paths = array(App::pluginPath($this->params['plugin']));
412: }
413: $patterns = array(
414: array(
415: "Configure::read() -> Configure::read('debug')",
416: '/Configure::read\(\)/',
417: 'Configure::read(\'debug\')'
418: ),
419: );
420: $this->_filesRegexpUpdate($patterns);
421: }
422:
423: 424: 425: 426: 427:
428: public function constants() {
429: $this->_paths = array(
430: APP
431: );
432: if (!empty($this->params['plugin'])) {
433: $this->_paths = array(App::pluginPath($this->params['plugin']));
434: }
435: $patterns = array(
436: array(
437: "LIBS -> CAKE",
438: '/\bLIBS\b/',
439: 'CAKE'
440: ),
441: array(
442: "CONFIGS -> APP . 'Config' . DS",
443: '/\bCONFIGS\b/',
444: 'APP . \'Config\' . DS'
445: ),
446: array(
447: "CONTROLLERS -> APP . 'Controller' . DS",
448: '/\bCONTROLLERS\b/',
449: 'APP . \'Controller\' . DS'
450: ),
451: array(
452: "COMPONENTS -> APP . 'Controller' . DS . 'Component' . DS",
453: '/\bCOMPONENTS\b/',
454: 'APP . \'Controller\' . DS . \'Component\''
455: ),
456: array(
457: "MODELS -> APP . 'Model' . DS",
458: '/\bMODELS\b/',
459: 'APP . \'Model\' . DS'
460: ),
461: array(
462: "BEHAVIORS -> APP . 'Model' . DS . 'Behavior' . DS",
463: '/\bBEHAVIORS\b/',
464: 'APP . \'Model\' . DS . \'Behavior\' . DS'
465: ),
466: array(
467: "VIEWS -> APP . 'View' . DS",
468: '/\bVIEWS\b/',
469: 'APP . \'View\' . DS'
470: ),
471: array(
472: "HELPERS -> APP . 'View' . DS . 'Helper' . DS",
473: '/\bHELPERS\b/',
474: 'APP . \'View\' . DS . \'Helper\' . DS'
475: ),
476: array(
477: "LAYOUTS -> APP . 'View' . DS . 'Layouts' . DS",
478: '/\bLAYOUTS\b/',
479: 'APP . \'View\' . DS . \'Layouts\' . DS'
480: ),
481: array(
482: "ELEMENTS -> APP . 'View' . DS . 'Elements' . DS",
483: '/\bELEMENTS\b/',
484: 'APP . \'View\' . DS . \'Elements\' . DS'
485: ),
486: array(
487: "CONSOLE_LIBS -> CAKE . 'Console' . DS",
488: '/\bCONSOLE_LIBS\b/',
489: 'CAKE . \'Console\' . DS'
490: ),
491: array(
492: "CAKE_TESTS_LIB -> CAKE . 'TestSuite' . DS",
493: '/\bCAKE_TESTS_LIB\b/',
494: 'CAKE . \'TestSuite\' . DS'
495: ),
496: array(
497: "CAKE_TESTS -> CAKE . 'Test' . DS",
498: '/\bCAKE_TESTS\b/',
499: 'CAKE . \'Test\' . DS'
500: )
501: );
502: $this->_filesRegexpUpdate($patterns);
503: }
504:
505: 506: 507: 508: 509: 510: 511:
512: public function components() {
513: $this->_paths = App::Path('Controller/Component');
514: if (!empty($this->params['plugin'])) {
515: $this->_paths = App::Path('Controller/Component', $this->params['plugin']);
516: }
517: $patterns = array(
518: array(
519: '*Component extends Object to *Component extends Component',
520: '/([a-zA-Z]*Component extends) Object/',
521: '\1 Component'
522: ),
523: );
524:
525: $this->_filesRegexpUpdate($patterns);
526: }
527:
528: 529: 530: 531: 532:
533: public function exceptions() {
534: $controllers = array_diff(App::path('controllers'), App::core('controllers'), array(APP));
535: $components = array_diff(App::path('components'), App::core('components'));
536:
537: $this->_paths = array_merge($controllers, $components);
538:
539: if (!empty($this->params['plugin'])) {
540: $pluginPath = App::pluginPath($this->params['plugin']);
541: $this->_paths = array(
542: $pluginPath . 'controllers' . DS,
543: $pluginPath . 'controllers' . DS . 'components' . DS,
544: );
545: }
546: $patterns = array(
547: array(
548: '$this->cakeError("error400") -> throw new BadRequestException()',
549: '/(\$this->cakeError\(["\']error400["\']\));/',
550: 'throw new BadRequestException();'
551: ),
552: array(
553: '$this->cakeError("error404") -> throw new NotFoundException()',
554: '/(\$this->cakeError\(["\']error404["\']\));/',
555: 'throw new NotFoundException();'
556: ),
557: array(
558: '$this->cakeError("error500") -> throw new InternalErrorException()',
559: '/(\$this->cakeError\(["\']error500["\']\));/',
560: 'throw new InternalErrorException();'
561: ),
562: );
563: $this->_filesRegexpUpdate($patterns);
564: }
565:
566: 567: 568: 569: 570: 571: 572:
573: protected function _moveViewFiles() {
574: if (!is_dir('View')) {
575: return;
576: }
577:
578: $dirs = scandir('View');
579: foreach ($dirs as $old) {
580: if (!is_dir('View' . DS . $old) || $old === '.' || $old === '..') {
581: continue;
582: }
583:
584: $new = 'View' . DS . Inflector::camelize($old);
585: $old = 'View' . DS . $old;
586: if ($new == $old) {
587: continue;
588: }
589:
590: $this->out(__d('cake_console', 'Moving %s to %s', $old, $new));
591: if (!$this->params['dry-run']) {
592: if ($this->params['git']) {
593: exec('git mv -f ' . escapeshellarg($old) . ' ' . escapeshellarg($old . '__'));
594: exec('git mv -f ' . escapeshellarg($old . '__') . ' ' . escapeshellarg($new));
595: } else {
596: $Folder = new Folder($old);
597: $Folder->move($new);
598: }
599: }
600: }
601: }
602:
603: 604: 605: 606: 607:
608: protected function _moveAppClasses() {
609: $files = array(
610: APP . 'app_controller.php' => APP . 'Controller' . DS . 'AppController.php',
611: APP . 'controllers' . DS . 'app_controller.php' => APP . 'Controller' . DS . 'AppController.php',
612: APP . 'app_model.php' => APP . 'Model' . DS . 'AppModel.php',
613: APP . 'models' . DS . 'app_model.php' => APP . 'Model' . DS . 'AppModel.php',
614: );
615: foreach ($files as $old => $new) {
616: if (file_exists($old)) {
617: $this->out(__d('cake_console', 'Moving %s to %s', $old, $new));
618:
619: if ($this->params['dry-run']) {
620: continue;
621: }
622: if ($this->params['git']) {
623: exec('git mv -f ' . escapeshellarg($old) . ' ' . escapeshellarg($old . '__'));
624: exec('git mv -f ' . escapeshellarg($old . '__') . ' ' . escapeshellarg($new));
625: } else {
626: rename($old, $new);
627: }
628: }
629: }
630: }
631:
632: 633: 634: 635: 636: 637: 638: 639: 640: 641:
642: protected function _movePhpFiles($path, $options) {
643: if (!is_dir($path)) {
644: return;
645: }
646:
647: $paths = $this->_paths;
648:
649: $this->_paths = array($path);
650: $this->_files = array();
651: if ($options['recursive']) {
652: $this->_findFiles('php');
653: } else {
654: $this->_files = scandir($path);
655: foreach ($this->_files as $i => $file) {
656: if (strlen($file) < 5 || substr($file, -4) !== '.php') {
657: unset($this->_files[$i]);
658: }
659: }
660: }
661:
662: $cwd = getcwd();
663: foreach ($this->_files as &$file) {
664: $file = $cwd . DS . $file;
665:
666: $contents = file_get_contents($file);
667: preg_match($options['regex'], $contents, $match);
668: if (!$match) {
669: continue;
670: }
671:
672: $class = $match[1];
673:
674: if (substr($class, 0, 3) === 'Dbo') {
675: $type = 'Dbo';
676: } else {
677: preg_match('@([A-Z][^A-Z]*)$@', $class, $match);
678: if ($match) {
679: $type = $match[1];
680: } else {
681: $type = 'unknown';
682: }
683: }
684:
685: preg_match('@^.*[\\\/]plugins[\\\/](.*?)[\\\/]@', $file, $match);
686: $base = $cwd . DS;
687: $plugin = false;
688: if ($match) {
689: $base = $match[0];
690: $plugin = $match[1];
691: }
692:
693: if ($options['checkFolder'] && !empty($this->_map[$type])) {
694: $folder = str_replace('/', DS, $this->_map[$type]);
695: $new = $base . $folder . DS . $class . '.php';
696: } else {
697: $new = dirname($file) . DS . $class . '.php';
698: }
699:
700: if ($file === $new) {
701: continue;
702: }
703:
704: $dir = dirname($new);
705: if (!is_dir($dir)) {
706: new Folder($dir, true);
707: }
708:
709: $this->out(__d('cake_console', 'Moving %s to %s', $file, $new), 1, Shell::VERBOSE);
710: if (!$this->params['dry-run']) {
711: if ($this->params['git']) {
712: exec('git mv -f ' . escapeshellarg($file) . ' ' . escapeshellarg($file . '__'));
713: exec('git mv -f ' . escapeshellarg($file . '__') . ' ' . escapeshellarg($new));
714: } else {
715: rename($file, $new);
716: }
717: }
718: }
719:
720: $this->_paths = $paths;
721: }
722:
723: 724: 725: 726: 727: 728:
729: protected function _filesRegexpUpdate($patterns) {
730: $this->_findFiles($this->params['ext']);
731: foreach ($this->_files as $file) {
732: $this->out(__d('cake_console', 'Updating %s...', $file), 1, Shell::VERBOSE);
733: $this->_updateFile($file, $patterns);
734: }
735: }
736:
737: 738: 739: 740: 741: 742:
743: protected function _findFiles($extensions = '') {
744: $this->_files = array();
745: foreach ($this->_paths as $path) {
746: if (!is_dir($path)) {
747: continue;
748: }
749: $Iterator = new RegexIterator(
750: new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path)),
751: '/^.+\.(' . $extensions . ')$/i',
752: RegexIterator::MATCH
753: );
754: foreach ($Iterator as $file) {
755: if ($file->isFile()) {
756: $this->_files[] = $file->getPathname();
757: }
758: }
759: }
760: }
761:
762: 763: 764: 765: 766: 767: 768:
769: protected function _updateFile($file, $patterns) {
770: $contents = file_get_contents($file);
771:
772: foreach ($patterns as $pattern) {
773: $this->out(__d('cake_console', ' * Updating %s', $pattern[0]), 1, Shell::VERBOSE);
774: $contents = preg_replace($pattern[1], $pattern[2], $contents);
775: }
776:
777: $this->out(__d('cake_console', 'Done updating %s', $file), 1);
778: if (!$this->params['dry-run']) {
779: file_put_contents($file, $contents);
780: }
781: }
782:
783: 784: 785: 786: 787:
788: public function getOptionParser() {
789: $subcommandParser = array(
790: 'options' => array(
791: 'plugin' => array(
792: 'short' => 'p',
793: 'help' => __d('cake_console', 'The plugin to update. Only the specified plugin will be updated.')
794: ),
795: 'ext' => array(
796: 'short' => 'e',
797: 'help' => __d('cake_console', 'The extension(s) to search. A pipe delimited list, or a preg_match compatible subpattern'),
798: 'default' => 'php|ctp|thtml|inc|tpl'
799: ),
800: 'git' => array(
801: 'short' => 'g',
802: 'help' => __d('cake_console', 'Use git command for moving files around.'),
803: 'boolean' => true
804: ),
805: 'dry-run' => array(
806: 'short' => 'd',
807: 'help' => __d('cake_console', 'Dry run the update, no files will actually be modified.'),
808: 'boolean' => true
809: )
810: )
811: );
812:
813: return parent::getOptionParser()
814: ->description(__d('cake_console', "A shell to help automate upgrading from CakePHP 1.3 to 2.0. \n" .
815: "Be sure to have a backup of your application before running these commands."))
816: ->addSubcommand('all', array(
817: 'help' => __d('cake_console', 'Run all upgrade commands.'),
818: 'parser' => $subcommandParser
819: ))
820: ->addSubcommand('tests', array(
821: 'help' => __d('cake_console', 'Update tests class names to FooTest rather than FooTestCase.'),
822: 'parser' => $subcommandParser
823: ))
824: ->addSubcommand('locations', array(
825: 'help' => __d('cake_console', 'Move files and folders to their new homes.'),
826: 'parser' => $subcommandParser
827: ))
828: ->addSubcommand('i18n', array(
829: 'help' => __d('cake_console', 'Update the i18n translation method calls.'),
830: 'parser' => $subcommandParser
831: ))
832: ->addSubcommand('helpers', array(
833: 'help' => __d('cake_console', 'Update calls to helpers.'),
834: 'parser' => $subcommandParser
835: ))
836: ->addSubcommand('basics', array(
837: 'help' => __d('cake_console', 'Update removed basics functions to PHP native functions.'),
838: 'parser' => $subcommandParser
839: ))
840: ->addSubcommand('request', array(
841: 'help' => __d('cake_console', 'Update removed request access, and replace with $this->request.'),
842: 'parser' => $subcommandParser
843: ))
844: ->addSubcommand('configure', array(
845: 'help' => __d('cake_console', "Update Configure::read() to Configure::read('debug')"),
846: 'parser' => $subcommandParser
847: ))
848: ->addSubcommand('constants', array(
849: 'help' => __d('cake_console', "Replace Obsolete constants"),
850: 'parser' => $subcommandParser
851: ))
852: ->addSubcommand('components', array(
853: 'help' => __d('cake_console', 'Update components to extend Component class.'),
854: 'parser' => $subcommandParser
855: ))
856: ->addSubcommand('exceptions', array(
857: 'help' => __d('cake_console', 'Replace use of cakeError with exceptions.'),
858: 'parser' => $subcommandParser
859: ));
860: }
861:
862: }
863: