One of my favorite features of (most) interpreted programming languages is the Read-Eval-Print-Loop or REPL.

REPLs allow for much quicker feedback during experimentation, and are invaluable tools for learning a language.

When I started working with PHP more extensively, I quickly became frustrated with the Edit-Save-Refresh workflow I had gotten into, especially when I was just experimenting with some built-in PHP functions.

PHP 5.1 and up has “interactive” mode, but that didn’t work on the version of PHP that ships with Mac OS X and left me to seek out alternatives.

I quickly found Facebook’s Python based phpsh, set it up, and began trying to load WordPress in it.

I’ve put together a tutorial if you wish to setup your own interactive WordPress shell:

What you will need:

  • phpsh – an interactive shell for PHP, by Facebook
  • ctags – builds a tag index for a codebase, allowing tab completion for WordPress core and theme functions
  • a working WordPress install

NOTE: This tutorial was tested on Mac OS X 10.6.5, but should work in most *nix environments

Setting up phpsh

Download the latest version of phpsh from http://www.phpsh.org

Extract the archive

[cc_bash]tar -xzf facebook-phpsh-8438f3f.tar.gz[/cc_bash]

Install readline (this enables tab completion!)

[cc_bash]sudo easy_install readline[/cc_bash]

Enter your password when prompted

Build and Install

Here I’m using /usr/local as my prefix:

[cc_bash]cd facebook-phpsh-8438f3f

python setup.py build

sudo python setup.py install –prefix=/usr/local[/cc_bash]

Setting up ctags

Download the latest version of ctags from http://ctags.sourceforge.net/

Extract the archive

[cc_bash]tar -xzf ctags-5.8.tar.gz[/cc_bash]

Configure, Make and Install

Using my preferred prefix of /usr/local again:

[cc_bash]./configure –prefix=/usr/local && make && sudo make install[/cc_bash]

Loading your WordPress blog in phpsh

Test phpsh

[cc_bash]$ phpsh
Starting php
type ‘h’ or ‘help’ to see instructions & features
php>[/cc_bash]
Let’s try a simple PHP command to make sure it’s all good
[cc_php]php> = ucwords(‘voce communications’)
“Voce Communications”
php>[/cc_php]

As shown above, in order to simple display the return of a function in phpsh, prefix the call with an equals sign.

Loading WordPress

While phpsh is an extremely useful tool on its own, our goal is to make an interactive WordPress playground. Onward!

Navigate to the root of a WordPress project

[cc_bash]cd ~/WordPress/wordpress-trunk[/cc_bash]

Generate the ctags

[cc_bash]ctags -R[/cc_bash]

Create a WordPress shell startup script

Paste the following into a new file, I’ve named mine wpshell:

[cc_bash]
#!/bin/bash
if [ ! -f ./.wp-shell.php ];
then
echo ” > ./.wp-shell.php
fi
echo “Rebuilding ctags…”
ctags -R –languages=PHP
echo “Launching WP Shell…”
phpsh ./.wp-shell.php
[/cc_bash]

What this script does is create a simple PHP startup file that tells WordPress not to load your theme (your functions.php is still loaded), which avoids issues with phpsh and HTTP redirects.

It then rebuilds your ctag index so that each time you load your WP Shell, it has the latest functions from your theme.

Setting WP_USE_THEMES to false avoids any redirects that will muck with phpsh.

Make the wpshell file executable, and move it to a directory in your $PATH. I’ve chosen /usr/local/bin

[cc_bash]
$ chmod +x wpshell
$ sudo mv wpshell /usr/local/bin
[/cc_bash]

Run it!

[cc_bash]$ wpshell
Rebuilding ctags…
Launching WP Shell…
Loading ctags (in background)
Starting php with extra includes: [‘./.wp-shell.php’]

type ‘h’ or ‘help’ to see instructions & features
php>
[/cc_bash]

You may see some PHP notices and warnings here depending on your log level.

Now things get interesting..

What you can do

PHP function documentation

[cc_php]php> d ucwords
# ucwords
(PHP 4, PHP 5)
ucwords — Uppercase the first character of each word in a string
### Description
string ucwords ( string $str )
Returns a string with the first character of each word in str capitalized, if that character is alphabetic.
The definition of a word is any string of characters that is immediately after a whitespace (These are: space, form-feed, newline, carriage return, horizontal tab, and vertical tab).
### Parameters
str
The input string.
### Return Values
Returns the modified string.
###
php>[/cc_php]

WordPress function documentation

[cc_php]php> d get_post_meta

[{‘type’: ‘f’, ‘context’: ‘function get_post_meta($post_id, $key, $single = false) {‘, ‘file’: ‘wp-includes/post.php’}]

/WordPress/wordpress-trunk/wp-includes/post.php, lines 1407-1420:

/**
* Retrieve post meta field for a post.
*
* @since 1.5.0
* @uses $wpdb
* @link http://codex.wordpress.org/Function_Reference/get_post_meta
*
* @param int $post_id Post ID.
* @param string $key The meta key to retrieve.
* @param bool $single Whether to return a single value.
* @return mixed Will be an array if $single is false. Will be value of meta data field if $single
* is true.
*/

php>[/cc_php]

Tab Completion

WordPress builtins:

[cc_bash]php> wp_get_a [TAB][TAB]
wp_get_active_and_valid_plugins wp_get_attachment_image_src wp_get_attachment_thumb_url
wp_get_archives wp_get_attachment_link wp_get_attachment_url
wp_get_associated_nav_menu_items wp_get_attachment_metadata
wp_get_attachment_image wp_get_attachment_thumb_file
php> wp_get_a
[/cc_bash]

Theme functions:

[cc_bash]php> twentyten_ [TAB][TAB]
twentyten_admin_header_style twentyten_posted_in
twentyten_auto_excerpt_more twentyten_posted_on
twentyten_comment twentyten_remove_gallery_css
twentyten_continue_reading_link twentyten_remove_recent_comments_style
twentyten_custom_excerpt_more twentyten_setup
twentyten_excerpt_length twentyten_widgets_init
twentyten_page_menu_args
php> twentyten_
[/cc_bash]

NOTE: The above would require a new scan of the theme directory using ctags each time the theme code changes.

Experimentation

Retrieving the most recent post:

[cc_php]php> = get_posts(array(‘posts_per_page’=>1))
array(
0 => {
ID => 1,
post_author => “1”,
post_date => “2010-12-17 13:52:59”,
post_date_gmt => “2010-12-17 13:52:59”,
post_content => “Welcome to WordPress. This is your first post. Edit or delete it, then start blogging!”,
post_title => “Hello world!”,
post_excerpt => “”,
post_status => “publish”,
comment_status => “open”,
ping_status => “open”,
post_password => “”,
post_name => “hello-world”,
to_ping => “”,
pinged => “”,
post_modified => “2010-12-17 13:52:59”,
post_modified_gmt => “2010-12-17 13:52:59”,
post_content_filtered => “”,
post_parent => 0,
guid => “http://wordpress.trunk/?p=1”,
menu_order => 0,
post_type => “post”,
post_mime_type => “”,
comment_count => “1”,
filter => “raw”,
},
)
php>[/cc_php]

Trying a new function:

[cc_bash]php> function get_posts_with_thumbnails() {
… return get_posts(array(‘meta_key’=>’_thumbnail_id’));
… }
php> $thumbnail_posts = get_posts_with_thumbnails();
php> = count($thumbnail_posts)
1

php>[/cc_bash]

Troubleshooting

No multiline input

This particular issue took me almost an hour to debug, so hopefully it will save you the headache should you run into it yourself.

If all your attempts at multiline input result in phpsh giving you “false” after you’ve entered the first line, check your php.ini file.

You need to make sure that display_errors is set to on!

Internally, phpsh uses PHP from the command line to test each line of your input, looking at the standard error output to determine what to do next (evaluate, wait for valid syntax completion, etc).

WordPress Multisite

Kudos to fellow Vocian Chris Scott for helping get wpshell working on a Multisite install.

Add this to your wp-config.php, above the wp-settings.php include, but after the DOMAIN_CURRENT_SITE constant has been defined (thanks William!):
[cc_php]
if (empty($_SERVER[‘HTTP_HOST’])) {
$_SERVER[‘HTTP_HOST’] = DOMAIN_CURRENT_SITE;
}
if (empty($_SERVER[‘SERVER_PROTOCOL’])) {
$_SERVER[‘SERVER_PROTOCOL’] = ‘HTTP/1.1’;
}
[/cc_php]

Done?

So there you have it. There are a lot of steps involved, but the process is fairly simple.

For most basic new-to-WordPress experimentation, this really is a great tool.

I’m excited to see ways in which this setup may break down when advanced PHP and WordPress techniques are used, so please comment if you find any!