Interactive command-line REPL for CakePHP
REPL: A read–eval–print loop. It allows developers to test, write and run code interactively from the command-line.
One constant annoyance I have with PHP is it’s lack of a decent, built-in REPL. PHP does have a REPL, but it:
- doesn’t automatically print the output of each command
- auto-exits on fatal errors, causing you to lose work
- doesn’t have pretty output by default :(
All I want to do is the following:
Console/shell
cakephp> App::uses('ClassRegistry', 'Utility');
cakephp> App::uses('Post', 'Model');
cakephp> $posts = ClassRegistry::init('Post');
cakephp> $posts->find('first');
→ array(
0 => array(
'Post' => array(
'id' => '1',
'title' => 'The title',
'body' => 'This is the post body.',
'created' => '2013-12-03 04:47:58',
'modified' => NULL
)
)
)
Luckily, there are a few options for CakePHP:
Console Shell
There is a Console Shell in CakePHP. It allows you do the following:
Thats cool. A few issues though:
- Very limited. Basically only useful for Model finds and routes. Doesn’t do much else. For example,
echo "hi";
returnsInvalid command
- Not native PHP output. Try copy-pasting the output into an editor. Then spend the rest of your life reformating it.
- Deprecated as of 2.4, and will be removed in 3.x
Boris
Boris is a relative newcomer to PHP repl land. It’s a pure-php REPL with some interesting code behind the implementation. We’ll first need to install it:
cd path/to/app/Vendor
git clone git@github.com:d11wtq/boris.git
Next we’ll need something to bootstrap the CakePHP codebase. We will create a file called app/Console/boris
. You’ll need to set proper permissions on it:
chmod +x app/Console/boris
Next, we need to bootstrap some constants. The CakePHP ShellDispatcher
class does this, though we’ll need to make a few exceptions to it. Lets ensure we can include it (the code snippets here will all be in the same file!):
#!/usr/bin/php -q
<?php
$ds = DIRECTORY_SEPARATOR;
$dispatcher = 'Cake' . $ds . 'Console' . $ds . 'ShellDispatcher.php';
if (function_exists('ini_set')) {
$root = dirname(dirname(dirname(__FILE__)));
// the following line differs from its sibling
// /lib/Cake/Console/Templates/skel/Console/cake.php
ini_set('include_path', $root . $ds . 'lib' . PATH_SEPARATOR . ini_get('include_path'));
}
if (!include $dispatcher) {
trigger_error('Could not locate CakePHP core files.', E_USER_ERROR);
}
unset($paths, $path, $dispatcher, $root, $ds);
?>
Next, we’ll create a wrapper BorisShellDispatcher
which will contain our customizations:
<?php
class BorisShellDispatcher extends ShellDispatcher {
public function __construct($args = array(), $bootstrap = true) {
set_time_limit(0);
$this->parseParams($args);
if ($bootstrap) {
$this->_initConstants();
$this->_bootstrap();
}
}
public static function run($argv) {
$dispatcher = new BorisShellDispatcher($argv);
}
}
?>
The customizations are enforced because we don’t want to run a CakePHP shell, we simply want to borrow the initialization code for constants etc.
Finally, lets run our custom dispatcher and start the boris runner:
<?php
BorisShellDispatcher::run($argv);
if (!include (ROOT . DS . 'app' . DS . 'vendor' . DS . 'boris' . DS . 'lib' . DS . 'autoload.php')) {
trigger_error("Unable to load boris autoload.", E_USER_ERROR);
exit(1);
}
$boris = new \Boris\Boris('cakephp> ');
$config = new \Boris\Config();
$config->apply($boris);
$options = new \Boris\CLIOptionsHandler();
$options->handle($boris);
$boris->start();
?>
Lets run it!
Pretty slick, but a few (minor) quirks:
- Doesn’t seem like you can call
App::uses()
before$boris->start()
and have the loaded files persist. - Output is sometimes verbose. If you just do
$posts = ClassRegistry::init('Post')
, it outputs the$posts
object - Doesn’t work on Windows. Using Vagrant would solve this!
Interactive shell for CakePHP
This is something from @nodesagency. It is a CakePHP shell, similar to the Console
shell, but in plugin format. We’ll need to install it first:
git clone git://github.com/nodesagency/cake-interactive-shell.git app/Plugin/Interactive
We’ll also need to enable it:
<?php
// in app/Config/bootstrap.php
CakePlugin::load('Interactive');
?>
And now install it:
Console/cake Interactive.Install
Lets try our commands:
Well that didn’t work. Guess there are a few bugs, or we need to make our own database connection?
One other (small) issue. This shell appears to require phpsh
from Facebook. That project has been unmaintained for 3 years, and requires readline
, ncurses
, and emacs
to build properly. I know because I had to go down a thirty-minute rabbit hole to figure that out. Annoying, but if you ever get it working, the above information is important if you ever want to actually install phpsh
.
The plugin didn’t load any required files without phpsh
, so it appears to be unmaintained. Boo.
The straight skinny
I think your current best bet is to use Boris
. It’s quite easy to install, and other than not having an official CakePHP integration, is the best of the group atm.
Going forward, I expect to see CakePHP get a more officially-sanctioned integration with REPLs such as Boris - and I expect there will be more like them - so we’ll see where 3.0 brings us!