diff --git a/lib/Boris/Boris.php b/lib/Boris/Boris.php index e2319cd..80b1008 100644 --- a/lib/Boris/Boris.php +++ b/lib/Boris/Boris.php @@ -7,168 +7,174 @@ /** * Boris is a tiny REPL for PHP. */ -class Boris { - const VERSION = "1.0.8"; - - private $_prompt; - private $_historyFile; - private $_exports = array(); - private $_startHooks = array(); - private $_failureHooks = array(); - private $_inspector; - - /** - * Create a new REPL, which consists of an evaluation worker and a readline client. - * - * @param string $prompt, optional - * @param string $historyFile, optional - */ - public function __construct($prompt = 'boris> ', $historyFile = null) { - $this->setPrompt($prompt); - $this->_historyFile = $historyFile - ? $historyFile - : sprintf('%s/.boris_history', getenv('HOME')) - ; - $this->_inspector = new ColoredInspector(); - } - - /** - * Add a new hook to run in the context of the REPL when it starts. - * - * @param mixed $hook - * - * The hook is either a string of PHP code to eval(), or a Closure accepting - * the EvalWorker object as its first argument and the array of defined - * local variables in the second argument. - * - * If the hook is a callback and needs to set any local variables in the - * REPL's scope, it should invoke $worker->setLocal($var_name, $value) to - * do so. - * - * Hooks are guaranteed to run in the order they were added and the state - * set by each hook is available to the next hook (either through global - * resources, such as classes and interfaces, or through the 2nd parameter - * of the callback, if any local variables were set. - * - * @example Contrived example where one hook sets the date and another - * prints it in the REPL. - * - * $boris->onStart(function($worker, $vars){ - * $worker->setLocal('date', date('Y-m-d')); - * }); - * - * $boris->onStart('echo "The date is $date\n";'); - */ - public function onStart($hook) { - $this->_startHooks[] = $hook; - } - - /** - * Add a new hook to run in the context of the REPL when a fatal error occurs. - * - * @param mixed $hook - * - * The hook is either a string of PHP code to eval(), or a Closure accepting - * the EvalWorker object as its first argument and the array of defined - * local variables in the second argument. - * - * If the hook is a callback and needs to set any local variables in the - * REPL's scope, it should invoke $worker->setLocal($var_name, $value) to - * do so. - * - * Hooks are guaranteed to run in the order they were added and the state - * set by each hook is available to the next hook (either through global - * resources, such as classes and interfaces, or through the 2nd parameter - * of the callback, if any local variables were set. - * - * @example An example if your project requires some database connection cleanup: - * - * $boris->onFailure(function($worker, $vars){ - * DB::reset(); - * }); - */ - public function onFailure($hook){ - $this->_failureHooks[] = $hook; - } - - /** - * Set a local variable, or many local variables. - * - * @example Setting a single variable - * $boris->setLocal('user', $bob); - * - * @example Setting many variables at once - * $boris->setLocal(array('user' => $bob, 'appContext' => $appContext)); - * - * This method can safely be invoked repeatedly. - * - * @param array|string $local - * @param mixed $value, optional - */ - public function setLocal($local, $value = null) { - if (!is_array($local)) { - $local = array($local => $value); +class Boris +{ + const VERSION = "1.0.8"; + + private $_prompt; + private $_historyFile; + private $_exports = array(); + private $_startHooks = array(); + private $_failureHooks = array(); + private $_inspector; + + /** + * Create a new REPL, which consists of an evaluation worker and a readline client. + * + * @param string $prompt, optional + * @param string $historyFile, optional + */ + public function __construct($prompt = 'boris> ', $historyFile = null) + { + $this->setPrompt($prompt); + $this->_historyFile = $historyFile ? $historyFile : sprintf('%s/.boris_history', getenv('HOME')); + $this->_inspector = new ColoredInspector(); } - - $this->_exports = array_merge($this->_exports, $local); - } - - /** - * Sets the Boris prompt text - * - * @param string $prompt - */ - public function setPrompt($prompt) { - $this->_prompt = $prompt; - } - - /** - * Set an Inspector object for Boris to output return values with. - * - * @param object $inspector any object the responds to inspect($v) - */ - public function setInspector($inspector) { - $this->_inspector = $inspector; - } - - /** - * Start the REPL (display the readline prompt). - * - * This method never returns. - */ - public function start() { - declare(ticks = 1); - pcntl_signal(SIGINT, SIG_IGN, true); - - if (!$pipes = stream_socket_pair( - STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP)) { - throw new \RuntimeException('Failed to create socket pair'); + + /** + * Add a new hook to run in the context of the REPL when it starts. + * + * @param mixed $hook + * + * The hook is either a string of PHP code to eval(), or a Closure accepting + * the EvalWorker object as its first argument and the array of defined + * local variables in the second argument. + * + * If the hook is a callback and needs to set any local variables in the + * REPL's scope, it should invoke $worker->setLocal($var_name, $value) to + * do so. + * + * Hooks are guaranteed to run in the order they were added and the state + * set by each hook is available to the next hook (either through global + * resources, such as classes and interfaces, or through the 2nd parameter + * of the callback, if any local variables were set. + * + * @example Contrived example where one hook sets the date and another + * prints it in the REPL. + * + * $boris->onStart(function($worker, $vars){ + * $worker->setLocal('date', date('Y-m-d')); + * }); + * + * $boris->onStart('echo "The date is $date\n";'); + */ + public function onStart($hook) + { + $this->_startHooks[] = $hook; } - - $pid = pcntl_fork(); - - if ($pid > 0) { - if (function_exists('setproctitle')) { - setproctitle('boris (master)'); - } - - fclose($pipes[0]); - $client = new ReadlineClient($pipes[1]); - $client->start($this->_prompt, $this->_historyFile); - } elseif ($pid < 0) { - throw new \RuntimeException('Failed to fork child process'); - } else { - if (function_exists('setproctitle')) { - setproctitle('boris (worker)'); - } - - fclose($pipes[1]); - $worker = new EvalWorker($pipes[0]); - $worker->setLocal($this->_exports); - $worker->setStartHooks($this->_startHooks); - $worker->setFailureHooks($this->_failureHooks); - $worker->setInspector($this->_inspector); - $worker->start(); + + /** + * Add a new hook to run in the context of the REPL when a fatal error occurs. + * + * @param mixed $hook + * + * The hook is either a string of PHP code to eval(), or a Closure accepting + * the EvalWorker object as its first argument and the array of defined + * local variables in the second argument. + * + * If the hook is a callback and needs to set any local variables in the + * REPL's scope, it should invoke $worker->setLocal($var_name, $value) to + * do so. + * + * Hooks are guaranteed to run in the order they were added and the state + * set by each hook is available to the next hook (either through global + * resources, such as classes and interfaces, or through the 2nd parameter + * of the callback, if any local variables were set. + * + * @example An example if your project requires some database connection cleanup: + * + * $boris->onFailure(function($worker, $vars){ + * DB::reset(); + * }); + */ + public function onFailure($hook) + { + $this->_failureHooks[] = $hook; + } + + /** + * Set a local variable, or many local variables. + * + * @example Setting a single variable + * $boris->setLocal('user', $bob); + * + * @example Setting many variables at once + * $boris->setLocal(array('user' => $bob, 'appContext' => $appContext)); + * + * This method can safely be invoked repeatedly. + * + * @param array|string $local + * @param mixed $value, optional + */ + public function setLocal($local, $value = null) + { + if (!is_array($local)) { + $local = array( + $local => $value + ); + } + + $this->_exports = array_merge($this->_exports, $local); + } + + /** + * Sets the Boris prompt text + * + * @param string $prompt + */ + public function setPrompt($prompt) + { + $this->_prompt = $prompt; + } + + /** + * Set an Inspector object for Boris to output return values with. + * + * @param object $inspector any object the responds to inspect($v) + */ + public function setInspector($inspector) + { + $this->_inspector = $inspector; + } + + /** + * Start the REPL (display the readline prompt). + * + * This method never returns. + */ + public function start() + { + declare (ticks = 1); + pcntl_signal(SIGINT, SIG_IGN, true); + + if (!$pipes = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP)) { + throw new \RuntimeException('Failed to create socket pair'); + } + + $pid = pcntl_fork(); + + if ($pid > 0) { + if (function_exists('setproctitle')) { + setproctitle('boris (master)'); + } + + fclose($pipes[0]); + $client = new ReadlineClient($pipes[1]); + $client->start($this->_prompt, $this->_historyFile); + } elseif ($pid < 0) { + throw new \RuntimeException('Failed to fork child process'); + } else { + if (function_exists('setproctitle')) { + setproctitle('boris (worker)'); + } + + fclose($pipes[1]); + $worker = new EvalWorker($pipes[0]); + $worker->setLocal($this->_exports); + $worker->setStartHooks($this->_startHooks); + $worker->setFailureHooks($this->_failureHooks); + $worker->setInspector($this->_inspector); + $worker->start(); + } } - } }