Rewriting URLs for static files using PHP’s built-in webserver

I don’t particularly like coding in PHP, but I do think WordPress works well for building websites for small organizations in certain use cases. PHPs built-in webserver, which was added in recent versions of PHP helps make PHP web development feel closer to my flow using other languages and frameworks. In particular, it removes the overhead and context switch for having to configure instances of a webserver like Apache or Nginx for local development.

One feature of the the built-in webserver is that you can define a “router” script to segment out serving of static assets or to direct certain paths to a CMS’ main PHP file.

There are lots of examples of making a router script that will work for one’s particular environment. I used this one for WordPress, because it’s just what came up first in my Google search.

However, I ran into trouble when I was trying to develop locally on a multi-site WordPress instance that used path prefixes rather than subdomains to identify certain blogs. For instance, /blog-1/ would go to one blog while /blog-2/ would go to another. I needed to replicate the functionality of these Apache rewrite rules that would remove the blog prefix from the path:

RewriteRule  ^([_0-9a-zA-Z-]+/)?(wp-.*) $2 [L]
RewriteRule  ^([_0-9a-zA-Z-]+/)?(.*.php)$ $2 [L]

The first rule caused the most problems since I needed to return a static file at a path different than the one reflected in the request URL. I found the answer in this example from the built-in server docs of handling unsupported file types.

To rewrite the path of a static file, you need to:

  • Use a regex to update the path.
  • Figure out the mime type of the file and set the appropriate header.
  • Read the contents of the file and return them.

My finished router.php looks like this:

$root = $_SERVER['DOCUMENT_ROOT'];
chdir($root);
$path = '/'.ltrim(parse_url($_SERVER['REQUEST_URI'])['path'],'/');

// Do some URL rewriting
if (preg_match('/\/([_0-9a-zA-Z-]+\/)?(wp-.*)/', $path, $matches)) {
  $path = '/' . $matches[2];
  if (file_exists($root . $path) && !strpos($path, ".php")) {
    // The rewritten path is to a non-PHP file.  It's probably a static asset
    // or theme asset.  Load the file and return it.
    header("Content-Type: " . mime_content_type($path));
    return readfile($root . $path);
  }
}

if (preg_match('/\/([_0-9a-zA-Z-]+\/)?(.*\.php)$/', $path, $matches)) {
  // The path is to some PHP file.  Remove the leading blog prefix.
  // Logic below will load this PHP file.
  $path = '/' . $matches[2];
}

set_include_path(get_include_path().':'.__DIR__);
if (file_exists($root.$path)) {
  if (is_dir($root.$path) && substr($path,strlen($path) - 1, 1) !== '/')
    $path = rtrim($path,'/').'/index.php';
  if (strpos($path,'.php') === false)
    return false;
  else {
    chdir(dirname($root.$path));
    require_once $root.$path;
  }
} else include_once 'index.php';

Setting up a local Drupal development environment on Ubuntu 12.10

I’ve recently started developing with the Drupal content management framework.  I did a lot of work with Drupal in the past, but haven’t done much work with the platform since Drupal 6.  While it still feels a bit strange coming from MVC frameworks and more expressive languages, it seems like Drupal and PHP have come a long way in offering a fluid development experience in the last few years.  This is how I set up my development environment in Ubuntu 12.10, though it probably works for newer releases of Ubuntu or other Debian-based Linuxes.

# Add the PHP5 PPA
sudo add-apt-repository ppa:ondrej/php5 
sudo apt-get update

# Install PHP5.  This will also install a recent version of Apache
sudo apt-get install php5

# Install PEAR, this will make it easier to install drush
sudo apt-get install php-pear

# Install drush via PEAR
pear channel-discover pear.drush.org
sudo pear channel-discover pear.drush.org
sudo pear install drush/drush

# HACK: The first time I ran drush it needed to install some dependencies via PEAR, 
# but I ran drush as my user so I didn't have permissions to write to the global PEAR
# directory.  Work around this by running drush with sudo and then chmoding your
# ~/.drush directory back to your own user.  You can probably just install the
# dependencies using PEAR.
sudo drush
sudo chown yourusername:yourgroupname -R ~/.drush

# For local development, I like using SQLite
sudo apt-get install php5-sqlite

# drush dl/en needs crul
sudo apt-get install php5-curl

# Drupal needs GD
sudo apt-get install php5-gd

Drupal and content purgatory

Let’s jut say you’re migrating content from a static site to a Drupal-based site.  Let’s say that you don’t want to migrate the content all at once and want to roll out the new Drupal-based site ASAP and migrate content as you’re able to.  One thing that you could do is to make your old site static.mysite.com and your new Drupal-based site mysite.com.  Then use the Custom Error module and this snippet to automatically redirect to content on the old static site.  If the content isn’t in either place, they’ll get a 404 from the webserver of the static site.

PHP cheatsheet

This post is for PHP stuff that I needed to use, but couldn’t easily find on the web

‘pass’ in PHP

Maybe it’s bad to start using conventions from other languages, but I find that sometimes the code is more readable if you write an if statement so that nothing happens if the conditon is true and then put the code that will get run often in the else block.  In python, if you don’t want anything to happen in a block, you use the pass statement.  In PHP, you can just leave the block empty:


if ( $condition == true) {
// do nothing. note that other than the comment
// there are no statements in this block
}
else {
// do stuff

}

community blog hacking

Spent a good amount of time fruitlessly looking for how to get future posts to show up in RSS feed (to allow syndication of events). The place to start looking is in the main method of the WP class (wp-inst/wp-includes/classes.php). Just follow the sub-methods and the relevent code is in there somewhere.

Update: look for the ‘//BOOKMARK’ comment to see where I was looking last.