Put Your PHP on Port 3000 With Lighttpd

While most of my work these days is in a Ruby and Rails environment, I still get my hands dirty with PHP every now and again.

When working with web projects I practically require the ability to use absolute links for static content. Because of this, I usually have to host a PHP project under Apache and create a virtual name, oh and make a fake host entry in my hosts file. Blegh.

This usually sucks even more when I throw in the fact that my project is under some kind of source control (which is usually always the case). So what is a frustrated developer to do?

Well, the only reason this is annoying is because I don't have to deal with it in my script/server world of Rails development. So I solved my litle PHP problem by borrowing from what I learned with Rails.

Instead of developing PHP apps under Apache, I installed Lighttpd and fire off an instance whenever I need to do work on the app. Very simply I added a lighty config file under APP_ROOT_/config and when I want to work on my PHP app, I just start lighty. Booom. Now I have my app running at localhost:3000. Just like my Rails apps, so my fancy bookmark in my bookmark bar works for both my PHP and Rails projects. Now that DRY!

So, how do you get it going like this? First off, all of these instructions were done in OS X 10.5 Leopard, so be warned.

Roll Your PHP

You need to roll your own PHP in order to get support for php-cgi. Do this:

mkdir src
cd src
wget http://us.php.net/distributions/php-5.2.5.tar.gz
tar xfz php-5.2.5.tar.gz
cd php-5.2.5
./configure --enable-fastcgi --enable-discard-path --enable-force-redirect --with-zlib
 --with-xml --with-mysql=/usr/local/mysql --prefix=/usr/local/php5-fcgi --disable-cli 
 --enable-memory-limit --with-layout=GNU --with-regex=php
 
make
sudo make install

Pretty basic. Download, configure, make, and install PHP.

Next, rename the php binary, use tab complete I think the XXX part is different per install(?)

sudo mv /usr/local/php-cgi/bin/php-cgi.XXX /usr/local/php-cgi/bin/php

Lighttpd

Ok, I'm not a big fan of MacPorts, but I was having so much trouble getting Lighttpd installed on Leopard, that I had macports do it for me. So, go install macports first and:

sudo port -d selfupdate // this errored the first time, did it again and all was well
sudo port install lighttpd

All the Rest

Ok, now you need to fix a small little problem with MySql and your new PHP. The new PHP may not see your MySql stuff correctly, so do this:

sudo mkdir /usr/local/mysql/lib/mysql
sudo cp /usr/local/mysql/lib/libmysqlclient.15.dylib /usr/local/mysql/lib/mysql/libmysqlclient.15.dylib

Set Up Your App

Ok, you're all set up. Now you just need to add a lighttpd config file to your app. Create APP_ROOT/config/lighty.conf with this:

 
  server.port = 3000
 
  # server.errorlog    = CWD + "/log/lighttpd.error.log"
  # accesslog.filename = CWD + "/log/lighttpd.access.log"
 
  server.modules = ( "mod_rewrite",
                     "mod_access",
                     "mod_fastcgi",
                     "mod_userdir",
                     "mod_accesslog" )
 
 
  # If no file is specified, what to look for?
  index-file.names = ( "index.html", "index.php" )
 
  # Run the server under the user-name "www" for security purposes
  # To bind to port 80, the server must be called by root user, but we don't want
  # the server to have free run of the box, so it runs as "www"
 
 
  # Set up the appropriate MIME type mappings
  mimetype.assign             = (
    ".pdf"          =>      "application/pdf",
    ".sig"          =>      "application/pgp-signature",
    ".spl"          =>      "application/futuresplash",
    ".class"        =>      "application/octet-stream",
    ".ps"           =>      "application/postscript",
    ".torrent"      =>      "application/x-bittorrent",
    ".dvi"          =>      "application/x-dvi",
    ".gz"           =>      "application/x-gzip",
    ".pac"          =>      "application/x-ns-proxy-autoconfig",
    ".swf"          =>      "application/x-shockwave-flash",
    ".tar.gz"       =>      "application/x-tgz",
    ".tgz"          =>      "application/x-tgz",
    ".tar"          =>      "application/x-tar",
    ".zip"          =>      "application/zip",
    ".mp3"          =>      "audio/mpeg",
    ".m3u"          =>      "audio/x-mpegurl",
    ".wma"          =>      "audio/x-ms-wma",
    ".wax"          =>      "audio/x-ms-wax",
    ".ogg"          =>      "application/ogg",
    ".wav"          =>      "audio/x-wav",
    ".gif"          =>      "image/gif",
    ".jpg"          =>      "image/jpeg",
    ".jpeg"         =>      "image/jpeg",
    ".png"          =>      "image/png",
    ".xbm"          =>      "image/x-xbitmap",
    ".xpm"          =>      "image/x-xpixmap",
    ".xwd"          =>      "image/x-xwindowdump",
    ".css"          =>      "text/css",
    ".html"         =>      "text/html",
    ".htm"          =>      "text/html",
    ".js"           =>      "text/javascript",
    ".asc"          =>      "text/plain",
    ".c"            =>      "text/plain",
    ".cpp"          =>      "text/plain",
    ".log"          =>      "text/plain",
    ".conf"         =>      "text/plain",
    ".text"         =>      "text/plain",
    ".txt"          =>      "text/plain",
    ".dtd"          =>      "text/xml",
    ".xml"          =>      "text/xml",
    ".mpeg"         =>      "video/mpeg",
    ".mpg"          =>      "video/mpeg",
    ".mov"          =>      "video/quicktime",
    ".qt"           =>      "video/quicktime",
    ".avi"          =>      "video/x-msvideo",
    ".asf"          =>      "video/x-ms-asf",
    ".asx"          =>      "video/x-ms-asf",
    ".wmv"          =>      "video/x-ms-wmv",
    ".bz2"          =>      "application/x-bzip",
    ".tbz"          =>      "application/x-bzip-compressed-tar",
    ".tar.bz2"      =>      "application/x-bzip-compressed-tar"
   )
 
 
  server.document-root = CWD
 
  fastcgi.server = ( ".php" => ("localhost" => (
                       "bin-path" => "/usr/local/php5-fcgi/bin/php", # path to php binary
                       "socket" => "/tmp/php.socket"
                   )))

Now at the root of your app run:

lighttpd -D -f config/lighty.conf

Boom, now you're deving with port 3000. Plus, your app isn't constantly available and just sitting there. When you need to work on it, fire it up, work, and shut'r down.

Popularity: 11% [?]

    Categories: PHP     3 Comments »
    Tags:

BDD’n All Over The Place

I've been working on quite a range of different projects recently, not all of which are Ruby/Rails.

One is in PHP and one is a cross platform widget that will work for the Yahoo Widgets engine as well as OS X's Dashboard.

Of these two projects I've managed to work some BDD into both of them. I thought I'd spread the word on the packages I'm using. For javascript I'm using JSSpec and for PHP I'm using PHPSpec

Neither are documented very much, and both are less than 6 months old. They're limited, but I've been able to get what I need out of them and its been nice to be able to keep the same dev style even if I have to deal with JS and PHP.

Here's a couple pieces of syntax.

PHPSpec

<?php
 
class DescribePage extends PHPSpec_Context{
 
  public function before(){
    $this->p = new Page();
  }
 
  public function itShouldReturnAShortUrl(){
    $this->p->url = "http://www.apple.com/imac/features.html";
    $this->spec($this->p->short_url())->should->be('www.apple.com');
  }
 
 public function itShouldNotBelongToAQueryWhenCreated(){
   $this->spec($this->p->query)->should->beNull();
  }
 
}
 
?>

JSSpec

describe('Get Song From XML', {
  before_each : function() {
    podcast = new Podcast()
    podcast.xmlUrl = 'podcast_xml_fixture.xml';
  },
    'should parse song title from xml': function() {
      topTune.readFeed();
      song = podcast.parseTune();
      expect(song[0]['title'].strip()).should_be("Willoughby - Frankenstein");
  },
      'should parse song url from xml': function() {
        podcast.readFeed();
        song = podcast.parseSong();
        expect(song[0]['link'].strip()).should_be("http://www.domain.com/featured/willoughby_-_franken");
  },
      'should create song': function() {
        podcast.getSong();
        expect(podcast.song).should_not_be(null);
    }
})

Popularity: 15% [?]

    Categories: Javascript, PHP     1 Comment »

Use Capistrano to disable_web On Your PHP Apps

Just like using Capistrano to deploy my Rails apps made my PHP apps jealous, being able to disable the entire app with a single command did too.

Using a simple cap disable_web command allows my Rails apps to be completely disabled while I perform maintenance on the site without having to worry about users accessing it. Being able to suspend the site with a single command is wonderful, trust me.

Well I want to do this with my PHP apps too. Here's how I did it.

First create a generic page that you want your users to see when your site is disabled and name it maintenance.html. Then throw it into your /config directory.

Cap Tasks

Next, write a disable_web task for Capistrano.

desc "This will disable the application and show a warning screen"
task :disable_web do
  run "cp #{current_path}/config/maintenance.html #{current_path}/maintenance.html"
end

This simple one liner will copy the maintenance.html file into the root of your application. Now lets write a task to re-enable the application.

desc "This will enable the application and remove the warning screen"
task :enable_web do
  run "rm #{current_path}/maintenance.html"
end

This will simply delete the maintenance file.

Ok, so, basically, when you run cap disable_web, Capistrano will move your maintenance file into the root of your app, and enable_web will delete it. So far this does nothing to disable your PHP application.

mod_rewrite Magic

The trick to this is using mod_rewrite to look for your maintenance file. If it's at the root of your application, then all requests will be directed to it. Otherwise the application works as normal.

RewriteEngine On

# Check for maintenance file and redirect all requests
RewriteCond %{DOCUMENT_ROOT}/maintenance.html -f
RewriteCond %{SCRIPT_FILENAME} !maintenance.html
RewriteRule ^.*(\.html|\.php)$ /maintenance.html [L]

This will look for any requests of .html and .php files. All requests that fit that condition will pointed to the maintenance.html page if it exists at the root of the application.

Huzzah! You've now got a "we're fixin' stuff" page. And you can go from this:

To this:

Popularity: 11% [?]

    Categories: Deployment, PHP     1 Comment »

Automated PHP Deployment With Capistrano

Sure, I'd like to spend my days and nights writing Ruby and cap deploy ing it to success, but I have to face the facts.

I Still Have PHP Apps To Maintain.

I have no one to blame but myself. I wrote them. And I still work where they were written. There's no escape. After using Capistrano more than one time, you'll be completely spoiled.

But Capistrano is only for crazy Rails apps right? Nope. Its simply a platform for automation. It just so happens that deployment usually requires quite a bit of steps. Hmmm...lots of steps...automation... a match made in heaven. So sure, Capistrano was thought up with Rails in mind, but created openly so you're able to use it any way you'd like.

Capistrano's power is in its remoting features. You tell it what to do on that other server, and it does it. Beauty. So, if your PHP deployment process requires an svn export combined with a few other repetitive steps you have to do every time, Capistrano is here to help you too.

Without going into all the details of Capistrano (following the link will give you a nice overview), it uses a deploy script (recipe) to understand your deployment environment and required tasks.

Ok ok ok, Lets Get On With It

So, we have a PHP app that we need to deploy to server X. It needs to be sucked out of its repository, its version symlinked to a doc_root, and then a shared directory symlinked into the site. This is a pretty common deployment scenario, so lets go with this.

First, we need a Capistrano recipe. Currently, it can only be auto generated for a Rails app, so psst, here it is. Download this and create a directory at the root of your PHP app and name it config.


mkdir config
cd config
wget http://simplisticcomplexity.com/assets/2006/9/21/deploy.rb
 

Capistrano wants its recipe to be in the config directory, so do as it likes. Now lets open it up and make it work with us. Once again, a complete description of the Capistrano recipe file is bit out of scope for this little post, so please read up.

We'll edit all the normal items

set :application, "php_app"
set :repository, "http://code.phpdevelopers.com/#{application}/trunk"

We only deploy to one server, so edit...

role :web, "phpsite.com"

Now you need to add the directory you want to deploy to. Add..

set :deploy_to, "/home/www/#{application}"

Capistrano defaults to checkout when pulling from subversion. I prefer an export, if you do too, then add..

set :checkout, "export"

Ok, so if you're familiar with Capistrano, you know exactly what we did. If you're not, the most important thing to know is that Capistrano is going to suck your code from the repository defined, do everything it's told to do in /home/www/php_app on your deployment server, phpsite.com.

Set'r Up

Believe it or not, we're half way done. Next, lets let Capistrano get the directory we just defined ready for its magic.


cap setup

If you look on your deployment server, you should see 2 directories that were just created.


drwxrwxr-x    2 user  group      4096 Aug 16 01:48 releases
drwxr-sr-x    4 user  group      4096 Aug 16 01:48 shared

WOW! We're already saving time! releases is where your app will be sucked down to. Capistrano exports your PHP app into a directory named after the current date/time. Each version you deploy will be kept in this directory. Shared is where you can keep files like uploaded assets or images that aren't kept in your repository, but need to be around every time you export from subversion. If your users upload files all day long, and you redeploy your PHP app from subversion, all your uploaded files will be gone. So we keep those in here.

Here's the deployment rub. Every time you deploy, you have to go symlink these directories from your /shared directory, into your current version of your PHP app. And a small bit of math can calculate the complexity when you start adding 2,3, or 6 asset folders. Yuck.

That's where Capistrano helps ya out.

Capistrano, Take Me Away

Ok, so our deploy directory is set up and ready to get our files. Now we need to tell Capistrano exactly what to do when we deploy. Get it? Recipe...

Define a new task called deploy

desc "This will deploy the app"
task :deploy do
 
end

Inside the block we'll just tell Capistrano exactly what it needs to do in order to deploy our app.

desc "This will deploy the app"
task :deploy do
  run "svn --quiet #{checkout} #{repository} #{release_path}"
  run "ln -nfs #{release_path} #{current_path}"
  run "ln -nfs #{shared_path}/photos #{current_path}/photos"
end

Ok, lets walk through exactly what this does. First, Capistrano uses subversion to suck your app out of the repository and into the releases directory. That releases_path is just a nice convenient variable for /home/www/php_site/releases/200608162012. Next, it will create a current symlink to that version of your app in the releases directory. Then we told it to symlink the directory photos from shared into the current version of the application.

And since you're hosting a PHP app with Apache (a 99% guess), there's no need to restart any servers. You're done. You're new version will be deployed safely and linked. The IMPORTANT thing to note, is that your new doc_root for your site will be /home/www/php_app/current.

Deploy!

Now, all ya gotta do to redeploy your site is...


cap deploy

Capistrano will go on to do everything you told it to do, on your deployment server. When its done, your directory should look like this...


lrwxrwxrwx   1 user group   42 2006-08-16 01:32 current -> /home/www/php_site_/releases/200608162012
drwxrwxr-x   4 user group 4096 2006-08-16 01:31 releases
drwxr-xr-x   4 user group 4096 2006-06-24 02:08 shared

And there you have it. Automated PHP deployment, with one simple command. Peering inside the current directory, you'll see where the photos directory has been symlinked into it.

The moral is, Capistrano isn't just for Rails deployment. Its powerful remote execution can be exploited to do anything you want. While our Rails apps get a lot for free when it comes to recipes, creating your own isn't complicated. Especially if its replacing repetitive tasks you've done over and over and over and over and over again.

You'll see the payoff the minute you get this set up. Especially on a Friday night at 5:30p when you're walking out the door. And its not just for Rails!

Popularity: 70% [?]

    Categories: Deployment, PHP     24 Comments »