123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174 |
- <?php
- /* vim: set shiftwidth=2 expandtab softtabstop=2: */
- namespace Boris;
- /**
- * 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);
- }
- $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();
- }
- }
- }
|