2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
5 (c) 2006 Jan Kneschke <jan@kneschke.de>
7 Permission is hereby granted, free of charge, to any person obtaining a copy of
8 this software and associated documentation files (the "Software"), to deal in
9 the Software without restriction, including without limitation the rights to
10 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
11 of the Software, and to permit persons to whom the Software is furnished to do
12 so, subject to the following conditions:
14 The above copyright notice and this permission notice shall be included in all
15 copies or substantial portions of the Software.
17 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27 * A interactive PHP Shell
29 * The more I work with other languages like python and ruby I like their way how they
30 * work on problems. While PHP is very forgiving on errors, it is weak on the debugging
31 * side. It was missing a simple to use interactive shell for years. Python and Ruby have
32 * their ipython and iruby shell which give you a direct way to interact with the objects.
33 * No need to write a script and execute it afterwards.
37 * The package contains a shell wrapper for windows and unix:
43 * Both are calling the wrapper script <code>php -q php-shell-cmd.php</code>
48 * PHP-Shell - Version 0.2.0, with readline() support
49 * (c) 2006, Jan Kneschke <jan@kneschke.de>
51 * >> use '?' to open the inline help
54 * "inline help for the PHP-shell
59 * get the doccomment for a class, method, property or function
61 * execute a verbose print (if implemented)
69 * - http://david.acz.org/phpa/
70 * - http://www.hping.org/phpinteractive/
71 * - the embedded interactive php-shell: $ php -a
79 * a interactive PHP Shell with tab-completion and history
80 * it can catch FATAL errors before executing the code
82 * Extensions are provided through three side-classes:
84 * - PHP_Shell_Commands
86 * - PHP_Shell_Extensions
91 require_once(dirname(__FILE__)."/Shell/Commands.php");
92 require_once(dirname(__FILE__)."/Shell/Options.php"); /* for the tab-complete */
102 * set if readline support is enabled
105 protected $have_readline;
108 * current version of the class
111 protected $version = '0.3.1';
118 protected $code_buffer;
120 public $has_semicolon=false;
123 * init the shell and change if readline support is available
125 public function __construct() {
130 $this->have_readline = function_exists('readline');
132 if ($this->have_readline) {
133 readline_completion_function('__shell_readline_complete');
136 $this->use_readline = true;
138 $cmd = PHP_Shell_Commands::getInstance();
140 $cmd->registerCommand('#^quit$#', $this, 'cmdQuit', 'quit', 'leaves the shell');
141 $cmd->registerCommand('#^\?$#', $this, 'cmdHelp', '?', 'show this help');
142 $cmd->registerCommand('#^\?\s+license$#', $this, 'cmdLicense', '? license', 'show license of the shell');
149 * we parse before we eval() the code to
150 * - fetch fatal errors before they come up
151 * - know about where we have to wait for closing braces
153 * @return int 0 if a executable statement is in the code-buffer, non-zero otherwise
155 public function parse() {
156 ## remove empty lines
157 if (trim($this->code) == '') return 1;
159 $t = token_get_all('<?php '.$this->code.' ?>');
161 $need_semicolon = 1; /* do we need a semicolon to complete the statement ? */
162 $need_return = 1; /* can we prepend a return to the eval-string ? */
163 $open_comment = 0; /* a open multi-line comment */
164 $eval = ''; /* code to be eval()'ed later */
165 $braces = array(); /* to track if we need more closing braces */
167 $methods = array(); /* to track duplicate methods in a class declaration */
168 $ts = array(); /* tokens without whitespaces */
170 foreach ($t as $ndx => $token) {
171 if (is_array($token)) {
220 case T_OBJECT_OPERATOR:
231 case T_CONSTANT_ENCAPSED_STRING:
232 case T_ENCAPSED_AND_WHITESPACE:
254 case T_IS_GREATER_OR_EQUAL:
255 case T_IS_SMALLER_OR_EQUAL:
283 /* debug unknown tags*/
284 error_log(sprintf("unknown tag: %d (%s): %s".PHP_EOL, $token[0], token_name($token[0]), $token[1]));
289 $eval .= $token[1]." ";
290 $ts[] = array("token" => $token[0], "value" => $token[1]);
293 $ts[] = array("token" => $token, "value" => '');
295 $last = count($ts) - 1;
299 /* walk backwards through the tokens */
302 $ts[$last - 1]['token'] == T_STRING &&
303 $ts[$last - 2]['token'] == T_OBJECT_OPERATOR &&
304 $ts[$last - 3]['token'] == ')' ) {
307 * we can't know what func() is return, so we can't
308 * say if the method() exists or not
311 } else if ($last >= 3 &&
312 $ts[0]['token'] != T_CLASS && /* if we are not in a class definition */
313 $ts[0]['token'] != T_ABSTRACT && /* if we are not in a class definition */
314 $ts[1]['token'] != T_CLASS && /* if we are not in a class definition */
315 $ts[$last - 1]['token'] == T_STRING &&
316 $ts[$last - 2]['token'] == T_OBJECT_OPERATOR &&
317 $ts[$last - 3]['token'] == T_VARIABLE ) {
319 /* $object->method( */
321 /* catch (Exception $e) does not set $e in $GLOBALS[] */
324 foreach ($ts as $v) {
325 if ($v['token'] == T_CATCH) {
331 /* $object has to exist and has to be a object */
332 $objname = $ts[$last - 3]['value'];
334 if (!isset($GLOBALS[ltrim($objname, '$')])) {
335 throw new Exception(sprintf('Variable \'%s\' is not set', $objname));
337 $object = $GLOBALS[ltrim($objname, '$')];
339 if (!is_object($object)) {
340 throw new Exception(sprintf('Variable \'%s\' is not a class', $objname));
343 $method = $ts[$last - 1]['value'];
347 if (!method_exists($object, $method)) {
348 throw new Exception(sprintf("Variable %s (Class '%s') doesn't have a method named '%s'",
349 $objname, get_class($object), $method));
352 } else if ($last >= 3 &&
353 $ts[0]['token'] != T_CLASS && /* if we are not in a class definition */
354 $ts[$last - 1]['token'] == T_VARIABLE &&
355 $ts[$last - 2]['token'] == T_OBJECT_OPERATOR &&
356 $ts[$last - 3]['token'] == T_VARIABLE ) {
358 /* $object->$method( */
360 /* $object has to exist and has to be a object */
361 $objname = $ts[$last - 3]['value'];
363 if (!isset($GLOBALS[ltrim($objname, '$')])) {
364 throw new Exception(sprintf('Variable \'%s\' is not set', $objname));
366 $object = $GLOBALS[ltrim($objname, '$')];
368 if (!is_object($object)) {
369 throw new Exception(sprintf('Variable \'%s\' is not a class', $objname));
372 $methodname = $ts[$last - 1]['value'];
374 if (!isset($GLOBALS[ltrim($methodname, '$')])) {
375 throw new Exception(sprintf('Variable \'%s\' is not set', $methodname));
377 $method = $GLOBALS[ltrim($methodname, '$')];
381 if (!method_exists($object, $method)) {
382 throw new Exception(sprintf("Variable %s (Class '%s') doesn't have a method named '%s'",
383 $objname, get_class($object), $method));
386 } else if ($last >= 6 &&
387 $ts[0]['token'] != T_CLASS && /* if we are not in a class definition */
388 $ts[$last - 1]['token'] == T_STRING &&
389 $ts[$last - 2]['token'] == T_OBJECT_OPERATOR &&
390 $ts[$last - 3]['token'] == ']' &&
391 /* might be anything as index */
392 $ts[$last - 5]['token'] == '[' &&
393 $ts[$last - 6]['token'] == T_VARIABLE ) {
395 /* $object[...]->method( */
397 /* $object has to exist and has to be a object */
398 $objname = $ts[$last - 6]['value'];
400 if (!isset($GLOBALS[ltrim($objname, '$')])) {
401 throw new Exception(sprintf('Variable \'%s\' is not set', $objname));
403 $array = $GLOBALS[ltrim($objname, '$')];
405 if (!is_array($array)) {
406 throw new Exception(sprintf('Variable \'%s\' is not a array', $objname));
409 $andx = $ts[$last - 4]['value'];
411 if (!isset($array[$andx])) {
412 throw new Exception(sprintf('%s[\'%s\'] is not set', $objname, $andx));
415 $object = $array[$andx];
417 if (!is_object($object)) {
418 throw new Exception(sprintf('Variable \'%s\' is not a class', $objname));
421 $method = $ts[$last - 1]['value'];
425 if (!method_exists($object, $method)) {
426 throw new Exception(sprintf("Variable %s (Class '%s') doesn't have a method named '%s'",
427 $objname, get_class($object), $method));
430 } else if ($last >= 3 &&
431 $ts[0]['token'] != T_CLASS && /* if we are not in a class definition */
432 $ts[$last - 1]['token'] == T_STRING &&
433 $ts[$last - 2]['token'] == T_DOUBLE_COLON &&
434 $ts[$last - 3]['token'] == T_STRING ) {
436 /* Class::method() */
438 /* $object has to exist and has to be a object */
439 $classname = $ts[$last - 3]['value'];
441 if (!class_exists($classname)) {
442 throw new Exception(sprintf('Class \'%s\' doesn\'t exist', $classname));
445 $method = $ts[$last - 1]['value'];
447 if (!in_array($method, get_class_methods($classname))) {
448 throw new Exception(sprintf("Class '%s' doesn't have a method named '%s'",
449 $classname, $method));
451 } else if ($last >= 3 &&
452 $ts[0]['token'] != T_CLASS && /* if we are not in a class definition */
453 $ts[$last - 1]['token'] == T_VARIABLE &&
454 $ts[$last - 2]['token'] == T_DOUBLE_COLON &&
455 $ts[$last - 3]['token'] == T_STRING ) {
459 /* $object has to exist and has to be a object */
460 $classname = $ts[$last - 3]['value'];
462 if (!class_exists($classname)) {
463 throw new Exception(sprintf('Class \'%s\' doesn\'t exist', $classname));
466 $methodname = $ts[$last - 1]['value'];
468 if (!isset($GLOBALS[ltrim($methodname, '$')])) {
469 throw new Exception(sprintf('Variable \'%s\' is not set', $methodname));
471 $method = $GLOBALS[ltrim($methodname, '$')];
473 if (!in_array($method, get_class_methods($classname))) {
474 throw new Exception(sprintf("Class '%s' doesn't have a method named '%s'",
475 $classname, $method));
478 } else if ($last >= 2 &&
479 $ts[0]['token'] != T_CLASS && /* if we are not in a class definition */
480 $ts[$last - 1]['token'] == T_STRING &&
481 $ts[$last - 2]['token'] == T_NEW ) {
485 /* don't care about this in a class ... { ... } */
487 $classname = $ts[$last - 1]['value'];
489 if (!class_exists($classname)) {
490 throw new Exception(sprintf('Class \'%s\' doesn\'t exist', $classname));
493 $r = new ReflectionClass($classname);
495 if ($r->isAbstract()) {
496 throw new Exception(sprintf("Can't instantiate abstract Class '%s'", $classname));
499 if (!$r->isInstantiable()) {
500 throw new Exception(sprintf('Class \'%s\' can\'t be instantiated. Is the class abstract ?', $classname));
503 } else if ($last >= 2 &&
504 $ts[0]['token'] != T_CLASS && /* if we are not in a class definition */
505 $ts[$last - 1]['token'] == T_STRING &&
506 $ts[$last - 2]['token'] == T_FUNCTION ) {
508 /* make sure we are not a in class definition */
512 $func = $ts[$last - 1]['value'];
514 if (function_exists($func)) {
515 throw new Exception(sprintf('Function \'%s\' is already defined', $func));
517 } else if ($last >= 4 &&
518 $ts[0]['token'] == T_CLASS &&
519 $ts[1]['token'] == T_STRING &&
520 $ts[$last - 1]['token'] == T_STRING &&
521 $ts[$last - 2]['token'] == T_FUNCTION ) {
523 /* make sure we are not a in class definition */
525 /* class a { .. function a() ... } */
527 $func = $ts[$last - 1]['value'];
528 $classname = $ts[1]['value'];
530 if (isset($methods[$func])) {
531 throw new Exception(sprintf("Can't redeclare method '%s' in Class '%s'", $func, $classname));
536 } else if ($last >= 1 &&
537 $ts[0]['token'] != T_CLASS && /* if we are not in a class definition */
538 $ts[0]['token'] != T_ABSTRACT && /* if we are not in a class definition */
539 $ts[1]['token'] != T_CLASS && /* if we are not in a class definition */
540 $ts[$last - 1]['token'] == T_STRING ) {
542 $funcname = $ts[$last - 1]['value'];
544 if (!function_exists($funcname)) {
545 throw new Exception(sprintf("Function %s() doesn't exist", $funcname));
547 } else if ($last >= 1 &&
548 $ts[0]['token'] != T_CLASS && /* if we are not in a class definition */
549 $ts[$last - 1]['token'] == T_VARIABLE ) {
551 /* $object has to exist and has to be a object */
552 $funcname = $ts[$last - 1]['value'];
554 if (!isset($GLOBALS[ltrim($funcname, '$')])) {
555 throw new Exception(sprintf('Variable \'%s\' is not set', $funcname));
557 $func = $GLOBALS[ltrim($funcname, '$')];
559 if (!function_exists($func)) {
560 throw new Exception(sprintf("Function %s() doesn't exist", $func));
565 array_push($braces, $token);
571 $ts[$last - 1]['token'] == T_STRING &&
572 $ts[$last - 2]['token'] == T_CLASS ) {
576 $classname = $ts[$last - 1]['value'];
578 if (class_exists($classname, false)) {
579 throw new Exception(sprintf("Class '%s' can't be redeclared", $classname));
581 } else if ($last >= 4 &&
582 $ts[$last - 1]['token'] == T_STRING &&
583 $ts[$last - 2]['token'] == T_EXTENDS &&
584 $ts[$last - 3]['token'] == T_STRING &&
585 $ts[$last - 4]['token'] == T_CLASS ) {
587 /* class classname extends classname { */
589 $classname = $ts[$last - 3]['value'];
590 $extendsname = $ts[$last - 1]['value'];
592 if (class_exists($classname, false)) {
593 throw new Exception(sprintf("Class '%s' can't be redeclared",
596 if (!class_exists($extendsname, true)) {
597 throw new Exception(sprintf("Can't extend '%s' ... from not existing Class '%s'",
598 $classname, $extendsname));
600 } else if ($last >= 4 &&
601 $ts[$last - 1]['token'] == T_STRING &&
602 $ts[$last - 2]['token'] == T_IMPLEMENTS &&
603 $ts[$last - 3]['token'] == T_STRING &&
604 $ts[$last - 4]['token'] == T_CLASS ) {
606 /* class name implements interface { */
608 $classname = $ts[$last - 3]['value'];
609 $implements = $ts[$last - 1]['value'];
611 if (class_exists($classname, false)) {
612 throw new Exception(sprintf("Class '%s' can't be redeclared",
615 if (!interface_exists($implements, false)) {
616 throw new Exception(sprintf("Can't implement not existing Interface '%s' for Class '%s'",
617 $implements, $classname));
621 array_push($braces, $token);
629 if ($ts[0]['token'] != T_CLASS && /* if we are not in a class definition */
630 $ts[0]['token'] != T_ABSTRACT && /* if we are not in a class definition */
631 $ts[1]['token'] != T_CLASS && /* if we are not in a class definition */
632 $ts[$last - 1]['token'] == T_VARIABLE) {
633 /* $a[] only works on array and string */
635 /* $object has to exist and has to be a object */
636 $objname = $ts[$last - 1]['value'];
638 if (!isset($GLOBALS[ltrim($objname, '$')])) {
639 throw new Exception(sprintf('Variable \'%s\' is not set', $objname));
641 $obj = $GLOBALS[ltrim($objname, '$')];
643 if (is_object($obj)) {
644 throw new Exception(sprintf('Objects (%s) don\'t support array access operators', $objname));
654 $last = count($ts) - 1;
656 $ts[$last - 0]['token'] == T_STRING &&
657 $ts[$last - 1]['token'] == T_DOUBLE_COLON &&
658 $ts[$last - 2]['token'] == T_STRING ) {
660 /* Class::constant */
662 /* $object has to exist and has to be a object */
663 $classname = $ts[$last - 2]['value'];
665 if (!class_exists($classname)) {
666 throw new Exception(sprintf('Class \'%s\' doesn\'t exist', $classname));
669 $constname = $ts[$last - 0]['value'];
671 $c = new ReflectionClass($classname);
672 if (!$c->hasConstant($constname)) {
673 throw new Exception(sprintf("Class '%s' doesn't have a constant named '%s'",
674 $classname, $constname));
676 } else if ($last == 0 &&
677 $ts[$last - 0]['token'] == T_VARIABLE ) {
681 $varname = $ts[$last - 0]['value'];
683 if (!isset($GLOBALS[ltrim($varname, '$')])) {
684 throw new Exception(sprintf('Variable \'%s\' is not set', $varname));
689 $need_more = (count($braces) > 0) || $open_comment;
691 if ($need_more || ';' === $token) {
696 $eval = "return ".$eval;
699 /* add a traling ; if necessary */
702 $this->has_semicolon = preg_match('/;\s*$/', $eval);
714 * show the prompt and fetch a single line
716 * uses readline() if avaialbe
718 * @return string a input-line
720 public function readline() {
721 if (empty($this->code)) print PHP_EOL;
723 $prompt = (empty($this->code)) ? '>> ' : '.. ';
725 if (count($this->code_buffer) > 0) {
728 $line = array_shift($this->code_buffer);
732 return $line.PHP_EOL;
735 if ($this->have_readline) {
736 $l = readline($prompt);
738 readline_add_history($l);
742 if (is_null($this->stdin)) {
743 if (false === ($this->stdin = fopen("php://stdin", "r"))) {
747 $l = fgets($this->stdin);
753 * get the inline help
755 * @return string the inline help as string
757 public function cmdHelp($l) {
758 $o = 'Inline Help:'.PHP_EOL;
760 $cmds = PHP_Shell_Commands::getInstance()->getCommands();
763 foreach ($cmds as $cmd) {
764 $help[] = sprintf(' >> %s'.PHP_EOL.' %s'.PHP_EOL,
770 return var_export(implode("\n", $help), 1);
774 * get the license string
776 * @return string the inline help as string
778 public function cmdLicense($l) {
780 (c) 2006 Jan Kneschke <jan@kneschke.de>
782 Permission is hereby granted, free of charge, to any person obtaining a copy of
783 this software and associated documentation files (the "Software"), to deal in
784 the Software without restriction, including without limitation the rights to
785 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
786 of the Software, and to permit persons to whom the Software is furnished to do
787 so, subject to the following conditions:
789 The above copyright notice and this permission notice shall be included in all
790 copies or substantial portions of the Software.
792 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
793 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
794 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
795 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
796 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
797 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
801 return var_export($o, 1);
805 * handle the 'quit' command
807 * @return bool false to leave the input() call
810 protected function cmdQuit($l) {
815 * handle the input line
817 * read the input and handle the commands of the shell
819 * @return bool false on 'quit' or EOF, true otherwise
821 public function input() {
822 $l = $this->readline();
825 if (false === $l) return false;
829 if (empty($this->code)) {
832 $cmds = PHP_Shell_Commands::getInstance()->getCommands();
834 foreach ($cmds as $cmd) {
835 if (preg_match($cmd['regex'], $l)) {
837 $func = $cmd['method'];
839 if (false === ($l = $obj->$func($l))) {
845 $this->code_buffer = $l;
853 $this->appendCode($l);
859 * get the code-buffer
861 * @return string the code-buffer
863 public function getCode() {
869 * reset the code-buffer
871 public function resetCode() {
872 $this->has_semicolon=false;
877 * append code to the code-buffer
879 * @param string $code input buffer
881 public function appendCode($code) {
882 if (strlen($code)) $code .= PHP_EOL;
884 $this->code .= $code;
888 * check if readline support is enabled
890 * @return bool true if enabled, false otherwise
892 public function hasReadline() {
893 return $this->have_readline;
897 * get version of the class
899 * @return string version-string
901 public function getVersion() {
902 return $this->version;
907 * a readline completion callback
909 * @param string $str linebuffer
910 * @param integer $pos position in linebuffer
911 * @return array list of possible matches
913 function __shell_readline_complete($str, $pos) {
914 $in = readline_info('line_buffer');
917 * parse the line-buffer backwards to see if we have a
925 if (preg_match('#\$([A-Za-z0-9_]+)->#', $in, $a)) {
926 /* check for $o->... */
929 if (isset($GLOBALS[$name]) && is_object($GLOBALS[$name])) {
930 $c = get_class_methods($GLOBALS[$name]);
935 $c = get_class_vars(get_class($GLOBALS[$name]));
937 foreach ($c as $k => $v) {
943 } else if (preg_match('#\$([A-Za-z0-9_]+)\[([^\]]+)\]->#', $in, $a)) {
944 /* check for $o[...]->... */
947 if (isset($GLOBALS[$name]) &&
948 is_array($GLOBALS[$name]) &&
949 isset($GLOBALS[$name][$a[2]])) {
951 $c = get_class_methods($GLOBALS[$name][$a[2]]);
956 $c = get_class_vars(get_class($GLOBALS[$name][$a[2]]));
958 foreach ($c as $k => $v) {
964 } else if (preg_match('#([A-Za-z0-9_]+)::#', $in, $a)) {
965 /* check for Class:: */
968 if (class_exists($name, false)) {
969 $c = get_class_methods($name);
972 $m[] = sprintf('%s::%s(', $name, $v);
975 $cl = new ReflectionClass($name);
976 $c = $cl->getConstants();
978 foreach ($c as $k => $v) {
979 $m[] = sprintf('%s::%s', $name, $k);
984 } else if (preg_match('#\$([a-zA-Z]?[a-zA-Z0-9_]*)$#', $in)) {
985 $m = array_keys($GLOBALS);
988 } else if (preg_match('#new #', $in)) {
989 $c = get_declared_classes();
996 } else if (preg_match('#^:set #', $in)) {
997 foreach (PHP_Shell_Options::getInstance()->getOptions() as $v) {
1004 $f = get_defined_functions();
1006 foreach ($f['internal'] as $v) {
1010 foreach ($f['user'] as $v) {
1014 $c = get_declared_classes();
1016 foreach ($c as $v) {
1020 $c = get_defined_constants();
1022 foreach ($c as $k => $v) {
1026 /* taken from http://de3.php.net/manual/en/reserved.php */
1045 # $m[] = 'enddeclare';
1057 $m[] = 'implements';
1059 $m[] = 'include_once "';
1070 $m[] = 'require_once "';
1082 $m[] = '__FUNCTION__';
1085 $m[] = '__METHOD__';
1087 # printf("%s ... %s\n", $str, $pos);