Source for file shell.php
Documentation is available at shell.php
* @copyright (c) 2005 John Peterson
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
(c) 2006 Jan Kneschke <jan@kneschke.de>
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* A interactive PHP Shell
* The more I work with other languages like python and ruby I like their way how they
* work on problems. While PHP is very forgiving on errors, it is weak on the debugging
* side. It was missing a simple to use interactive shell for years. Python and Ruby have
* their ipython and iruby shell which give you a direct way to interact with the objects.
* No need to write a script and execute it afterwards.
* If you have a php-cli at hand you can open the shell by defining 'SHELL'
* and opening the PHP_Shell class file.
* PHP 5.1.4 (cli) (built: May 7 2006 20:52:45)
* Copyright (c) 1997-2006 The PHP Group
* Zend Engine v2.1.0, Copyright (c) 1998-2006 Zend Technologies
* $ php -r "define('SHELL', 1); require 'PHP/Shell.php';"
* If you only have php-cgi write a php-script:
* error_reporting(E_ALL);
* ## in case your terminal support colours:
* define("SHELL_HAS_COLOUR", 1);
* require "PHP/Shell.php";
* PHP-Shell - Version 0.2.0, with readline() support
* (c) 2006, Jan Kneschke <jan@kneschke.de>
* released under the terms of the PHP License 2.0
* >> use '?' to open the inline help
* "inline help for the PHP-shell
* get the doccomment for a class, method, property or function
* execute a verbose print (if implemented)
* - http://david.acz.org/phpa/
* - http://www.hping.org/phpinteractive/
* - the embedded interactive php-shell: $ php -a
* a interactive PHP Shell with tab-completion and history
* it can catch FATAL errors before executing the code
* to customize the operation of the shell you can either
* extend the PHP_Shell class or declare a external autoload
* or error-handler. If you want to use your own print-out
* functions declare __shell_print_vars().
* - __shell_error_handler()
* To keep the namespace clashing between shell and your program
* as small as possible all public variables and functions from
* the shell are prefixed with __shell:
* - $__shell is the object of the shell
* can be read, this is the shell object itself, don't touch it
* - $__shell_retval is the return value of the eval() before
* can't be read, but overwrites existing vars with this name
* - $__shell_exception is the catched Exception on Warnings, Notices, ..
* can't be read, but overwrites existing vars with this name
require_once("php_shell/shell_prototypes.php");
* set if 'p ...' is executed
* set if readline support is enabled
* current version of the class
* does the use want to use the internal autoload ?
* @see registerColourScheme
const C_RESET = "\033[0m";
const C_BLACK = "\033[0;30m";
const C_RED = "\033[0;31m";
const C_GREEN = "\033[0;32m";
const C_BROWN = "\033[0;33m";
const C_BLUE = "\033[0;34m";
const C_PURPLE = "\033[0;35m";
const C_CYAN = "\033[0;36m";
const C_LIGHT_GRAY = "\033[0;37m";
const C_GRAY = "\033[1;30m";
const C_LIGHT_RED = "\033[1;31m";
const C_LIGHT_GREEN = "\033[1;32m";
const C_YELLOW = "\033[1;33m";
const C_LIGHT_BLUE = "\033[1;34m";
const C_LIGHT_PURPLE = "\033[1;35m";
const C_LIGHT_CYAN = "\033[1;36m";
const C_WHITE = "\033[1;37m";
* init the shell and change if readline support is available
readline_completion_function('__shell_readline_complete');
$this->use_readline = true;
$this->registerCommand('#^\? #', 'cmdHelp', '? <var>', 'show the DocComment a Class, Method or Function'. PHP_EOL. ' e.g.: ? fopen(), ? PHP_Shell, ? $__shell');
$this->registerCommand('#^p #', 'cmdPrint', 'p <var>', 'print the variable verbosly');
$this->registerCommand('#^:set #', 'cmdSet', ':set <var>', 'set a shell variable');
"default" => "", "value" => "",
"exception" => "", "reset" => ""));
"default" => PHP_SHELL::C_YELLOW,
"value" => PHP_SHELL::C_WHITE,
"exception" => PHP_SHELL::C_PURPLE));
"default" => PHP_SHELL::C_BLACK,
"value" => PHP_SHELL::C_BLUE,
"exception" => PHP_SHELL::C_RED));
* register your own command for the shell
* @param string $regex a regex to match against the input line
* @param string $callback a method in this class to call of the regex matches
* @param string $cmd the command string for the help
* @param string $help the full help description for this command
* we parse before we eval() the code to
* - fetch fatal errors before they come up
* - know about where we have to wait for closing braces
* @return int 0 if a executable statement is in the code-buffer, non-zero otherwise
public function parse() {
if ($this->code == '') return 1;
$need_semicolon = 1; /* do we need a semicolon to complete the statement ? */
$need_return = 1; /* can we prepend a return to the eval-string ? */
$eval = ''; /* code to be eval()'ed later */
$braces = array(); /* to track if we need more closing braces */
$methods = array(); /* to track duplicate methods in a class declaration */
$ts = array(); /* tokens without whitespaces */
foreach ($t as $ndx => $token) {
case T_CONSTANT_ENCAPSED_STRING:
case T_ENCAPSED_AND_WHITESPACE:
case T_IS_GREATER_OR_EQUAL:
case T_IS_SMALLER_OR_EQUAL:
$ts[] = array("token" => $token[0], "value" => $token[1]);
$ts[] = array("token" => $token, "value" => '');
/* walk backwards through the tokens */
$ts[$last - 1]['token'] == T_STRING &&
$ts[$last - 2]['token'] == T_OBJECT_OPERATOR &&
$ts[$last - 3]['token'] == T_VARIABLE ) {
/* $object has to exist and has to be a object */
$objname = $ts[$last - 3]['value'];
if (!isset ($GLOBALS[ltrim($objname, '$')])) {
throw new Exception(sprintf('Variable \'%s\' is not set', $objname));
$object = $GLOBALS[ltrim($objname, '$')];
throw new Exception(sprintf('Variable \'%s\' is not a class', $objname));
$method = $ts[$last - 1]['value'];
throw new Exception(sprintf("Variable %s (Class '%s') doesn't have a method named '%s'",
$ts[$last - 1]['token'] == T_VARIABLE &&
$ts[$last - 2]['token'] == T_OBJECT_OPERATOR &&
$ts[$last - 3]['token'] == T_VARIABLE ) {
/* $object has to exist and has to be a object */
$objname = $ts[$last - 3]['value'];
if (!isset ($GLOBALS[ltrim($objname, '$')])) {
throw new Exception(sprintf('Variable \'%s\' is not set', $objname));
$object = $GLOBALS[ltrim($objname, '$')];
throw new Exception(sprintf('Variable \'%s\' is not a class', $objname));
$methodname = $ts[$last - 1]['value'];
if (!isset ($GLOBALS[ltrim($methodname, '$')])) {
throw new Exception(sprintf('Variable \'%s\' is not set', $methodname));
$method = $GLOBALS[ltrim($methodname, '$')];
throw new Exception(sprintf("Variable %s (Class '%s') doesn't have a method named '%s'",
$ts[$last - 1]['token'] == T_STRING &&
$ts[$last - 2]['token'] == T_OBJECT_OPERATOR &&
$ts[$last - 3]['token'] == ']' &&
/* might be anything as index */
$ts[$last - 5]['token'] == '[' &&
$ts[$last - 6]['token'] == T_VARIABLE ) {
/* $object[...]->method( */
/* $object has to exist and has to be a object */
$objname = $ts[$last - 6]['value'];
if (!isset ($GLOBALS[ltrim($objname, '$')])) {
throw new Exception(sprintf('Variable \'%s\' is not set', $objname));
$array = $GLOBALS[ltrim($objname, '$')];
throw new Exception(sprintf('Variable \'%s\' is not a array', $objname));
$andx = $ts[$last - 4]['value'];
if (!isset ($array[$andx])) {
throw new Exception(sprintf('%s[\'%s\'] is not set', $objname, $andx));
throw new Exception(sprintf('Variable \'%s\' is not a class', $objname));
$method = $ts[$last - 1]['value'];
throw new Exception(sprintf("Variable %s (Class '%s') doesn't have a method named '%s'",
$ts[$last - 1]['token'] == T_STRING &&
$ts[$last - 2]['token'] == T_DOUBLE_COLON &&
$ts[$last - 3]['token'] == T_STRING ) {
/* $object has to exist and has to be a object */
$classname = $ts[$last - 3]['value'];
throw new Exception(sprintf('Class \'%s\' doesn\'t exist', $classname));
$method = $ts[$last - 1]['value'];
throw new Exception(sprintf("Class '%s' doesn't have a method named '%s'",
$ts[$last - 1]['token'] == T_VARIABLE &&
$ts[$last - 2]['token'] == T_DOUBLE_COLON &&
$ts[$last - 3]['token'] == T_STRING ) {
/* $object has to exist and has to be a object */
$classname = $ts[$last - 3]['value'];
throw new Exception(sprintf('Class \'%s\' doesn\'t exist', $classname));
$methodname = $ts[$last - 1]['value'];
if (!isset ($GLOBALS[ltrim($methodname, '$')])) {
throw new Exception(sprintf('Variable \'%s\' is not set', $methodname));
$method = $GLOBALS[ltrim($methodname, '$')];
throw new Exception(sprintf("Class '%s' doesn't have a method named '%s'",
$ts[$last - 1]['token'] == T_STRING &&
$ts[$last - 2]['token'] == T_NEW ) {
$classname = $ts[$last - 1]['value'];
throw new Exception(sprintf('Class \'%s\' doesn\'t exist', $classname));
$r = new ReflectionClass($classname);
throw new Exception(sprintf("Can't instantiate abstract Class '%s'", $classname));
if (!$r->isInstantiable()) {
throw new Exception(sprintf('Class \'%s\' can\'t be instantiated. Is the class abstract ?', $classname));
$ts[0]['token'] != T_CLASS &&
$ts[$last - 1]['token'] == T_STRING &&
$ts[$last - 2]['token'] == T_FUNCTION ) {
/* make sure we are not a in class definition */
$func = $ts[$last - 1]['value'];
throw new Exception(sprintf('Function \'%s\' is already defined', $func));
$ts[0]['token'] == T_CLASS &&
$ts[1]['token'] == T_STRING &&
$ts[$last - 1]['token'] == T_STRING &&
$ts[$last - 2]['token'] == T_FUNCTION ) {
/* make sure we are not a in class definition */
/* class a { .. function a() ... } */
$func = $ts[$last - 1]['value'];
$classname = $ts[1]['value'];
if (isset ($methods[$func])) {
throw new Exception(sprintf("Can't redeclare method '%s' in Class '%s'", $func, $classname));
$ts[$last - 1]['token'] == T_STRING ) {
$funcname = $ts[$last - 1]['value'];
throw new Exception(sprintf("Function %s() doesn't exist", $funcname));
$ts[$last - 1]['token'] == T_VARIABLE ) {
/* $object has to exist and has to be a object */
$funcname = $ts[$last - 1]['value'];
if (!isset ($GLOBALS[ltrim($funcname, '$')])) {
throw new Exception(sprintf('Variable \'%s\' is not set', $funcname));
$func = $GLOBALS[ltrim($funcname, '$')];
throw new Exception(sprintf("Function %s() doesn't exist", $func));
$ts[$last - 1]['token'] == T_STRING &&
$ts[$last - 2]['token'] == T_CLASS ) {
$classname = $ts[$last - 1]['value'];
throw new Exception(sprintf("Class '%s' can't be redeclared", $classname));
$ts[$last - 1]['token'] == T_STRING &&
$ts[$last - 2]['token'] == T_EXTENDS &&
$ts[$last - 3]['token'] == T_STRING &&
$ts[$last - 4]['token'] == T_CLASS ) {
/* class classname extends classname { */
$classname = $ts[$last - 3]['value'];
$extendsname = $ts[$last - 1]['value'];
throw new Exception(sprintf("Class '%s' can't be redeclared",
throw new Exception(sprintf("Can't extend '%s' from not existing Class '%s'",
$classname, $extendsname));
$ts[$last - 1]['token'] == T_STRING &&
$ts[$last - 2]['token'] == T_IMPLEMENTS &&
$ts[$last - 3]['token'] == T_STRING &&
$ts[$last - 4]['token'] == T_CLASS ) {
/* class name implements interface { */
$classname = $ts[$last - 3]['value'];
$implements = $ts[$last - 1]['value'];
throw new Exception(sprintf("Class '%s' can't be redeclared",
throw new Exception(sprintf("Can't implement not existing Interface '%s' for Class '%s'",
$implements, $classname));
$ts[$last - 0]['token'] == T_STRING &&
$ts[$last - 1]['token'] == T_DOUBLE_COLON &&
$ts[$last - 2]['token'] == T_STRING ) {
/* $object has to exist and has to be a object */
$classname = $ts[$last - 2]['value'];
throw new Exception(sprintf('Class \'%s\' doesn\'t exist', $classname));
$constname = $ts[$last - 0]['value'];
$c = new ReflectionClass($classname);
if (!$c->hasConstant($constname)) {
throw new Exception(sprintf("Class '%s' doesn't have a constant named '%s'",
$classname, $constname));
$ts[$last - 0]['token'] == T_VARIABLE ) {
$varname = $ts[$last - 0]['value'];
if (!isset ($GLOBALS[ltrim($varname, '$')])) {
throw new Exception(sprintf('Variable \'%s\' is not set', $varname));
$need_more = count($braces);
if ($need_more || ';' === $token) {
/* add a traling ; if necessary */
if ($need_semicolon) $eval .= ';';
* show the prompt and fetch a single line
* uses readline() if avaialbe
* @return string a input-line
if (empty($this->code)) print PHP_EOL;
$prompt = (empty($this->code)) ? '>> ' : '.. ';
readline_add_history($l);
if (false === ($this->stdin = fopen("php://stdin", "r"))) {
$l = fgets($this->stdin);
* @return string the inline help as string
$o = 'Inline Help:'. PHP_EOL;
$o .= sprintf(' >> %s'. PHP_EOL. ' %s'. PHP_EOL,
* handle the 'quit' command
* @return bool false to leave the input() call
* handle the 'p ' command
* @return string the pure command-string without the 'p ' command
* handle the '?' commands
* With the help of the Reflection Class we extract the DocComments and display them
* For internal Functions we extract the prototype from the php source.
* The license of the PHP_Shell class
* @return string the help text
if (preg_match('#^([A-Za-z0-9_]+)::([a-zA-Z0-9_]+)\(\s*\)\s*#', $str, $a)) {
$cmd = sprintf("/**\n* %s\n\n* @params %s\n* @return %s\n*/\n",
$c = new ReflectionClass($class);
if ($c->hasMethod($method)) {
$cmd = $c->getMethod($method)->getDocComment();
} else if (preg_match('#^\$([A-Za-z0-9_]+)->([a-zA-Z0-9_]+)\(\s*\)\s*#', $str, $a)) {
if (isset ($GLOBALS[$a[1]]) && is_object($GLOBALS[$a[1]])) {
$c = new ReflectionClass($class);
if ($c->hasMethod($method)) {
$cmd = $c->getMethod($method)->getDocComment();
} else if (preg_match('#^([A-Za-z0-9_]+)::([a-zA-Z0-9_]+)\s*$#', $str, $a)) {
$c = new ReflectionClass($class);
if ($c->hasProperty($property)) {
$cmd = $c->getProperty($property)->getDocComment();
} else if (preg_match('#^\$([A-Za-z0-9_]+)->([a-zA-Z0-9_]+)\s*$#', $str, $a)) {
if (isset ($GLOBALS[$a[1]]) && is_object($GLOBALS[$a[1]])) {
$c = new ReflectionClass($class);
if ($c->hasProperty($property)) {
$cmd = $c->getProperty($property)->getDocComment();
} else if (preg_match('#^([A-Za-z0-9_]+)$#', $str, $a)) {
$c = new ReflectionClass($a[1]);
$cmd = $c->getDocComment();
} else if ($a[1] == 'license') {
( c) 2006 Jan Kneschke < jan@ kneschke. de>
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files ( the " Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/ or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED " AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
} else if (preg_match('#^\$([A-Za-z0-9_]+)$#', $str, $a)) {
if (isset ($GLOBALS[$obj]) && is_object($GLOBALS[$obj])) {
$c = new ReflectionClass($class);
$cmd = $c->getDocComment();
} else if (preg_match('#^([A-Za-z0-9_]+)\(\s*\)$#', $str, $a)) {
$cmd = sprintf("/**\n* %s\n*\n* @params %s\n* @return %s\n*/\n",
$c = new ReflectionFunction($func);
$cmd = $c->getDocComment();
* :set al to enable autoload
* :set bg=dark to enable highlighting with a dark backgroud
if (!preg_match('#:set\s+([a-z]+)\s*(?:=\s*([a-z0-9]+)\s*)?$#i', $l, $a)) {
print ('unknown :set syntax');
print (':set '. $key. ' failed: a value is required, example: :set '. $key. '=dark');
print ('setting colourscheme failed: colourscheme '. $a[2]. ' is unknown');
print ('can\'t enabled autoload as a external __autoload() function is already defined');
print ('autload is already enabled');
print (':set '. $key. ' failed: unknown key');
* read the input and handle the commands of the shell
* @return bool false on 'quit' or EOF, true otherwise
public function input() {
if (false === $l) return false;
if (empty($this->code)) {
if (false === ($l = $this->$func($l))) {
* @return string the code-buffer
* append code to the code-buffer
* @param string $code input buffer
* check if we have a verbose print-out
* @return bool 1 if verbose, 0 otherwise
* check if readline support is enabled
* @return bool true if enabled, false otherwise
* get version of the class
* @return string version-string
* get a colour for the shell
* @param string $type one of (value|exception|reset|default)
* @return string a colour string or a empty string
return isset ($this->colour[$type]) ? $this->colour[$type] : '';
* apply a colour scheme to the current shell
* @param string $scheme name of the scheme
* @return false if colourscheme is not known, otherwise true
* registers a colour scheme
* @param string $scheme name of the colour scheme
* @param array a array of colours
/* set a reset colour if it is not supplied from the outside */
if (!isset ($colours["reset"])) $colours["reset"] = PHP_SHELL::C_RESET;
* a readline completion callback
* @param string $str linebuffer
* @param integer $pos position in linebuffer
* @return array list of possible matches
$in = readline_info('line_buffer');
* parse the line-buffer backwards to see if we have a
if (preg_match('#\$([A-Za-z0-9_]+)->#', $in, $a)) {
if (isset ($GLOBALS[$name]) && is_object($GLOBALS[$name])) {
foreach ($c as $k => $v) {
} else if (preg_match('#\$([A-Za-z0-9_]+)\[([^\]]+)\]->#', $in, $a)) {
/* check for $o[...]->... */
if (isset ($GLOBALS[$name]) &&
isset ($GLOBALS[$name][$a[2]])) {
foreach ($c as $k => $v) {
} else if (preg_match('#([A-Za-z0-9_]+)::#', $in, $a)) {
$m[] = sprintf('%s::%s(', $name, $v);
$cl = new ReflectionClass($name);
$c = $cl->getConstants();
foreach ($c as $k => $v) {
$m[] = sprintf('%s::%s', $name, $k);
} else if (preg_match('#\$([a-zA-Z]?[a-zA-Z0-9_]*)$#', $in)) {
foreach ($f['internal'] as $v) {
foreach ($f['user'] as $v) {
foreach ($c as $k => $v) {
# printf("%s ... %s\n", $str, $pos);
|