Tiger Heron
Tiger Heron
Tiger Heron

A Tale of Two Patterns

No votes yet

Originally, I planned to write about how to configure the Apache server to run both PHP4 and PHP5. With the push toward PHP5 (see Go PHP5), there are now hundreds of articles covering that topic. Instead, I will talk about two IT patterns I use for PHP development on Windows. The patterns are ancient and familiar to most Unix developers, but little used in the Windows world.

Since the patterns existed long before the term "design patterns" came into vogue, they don't have any names that I am aware of. In this article, I will call them the Bait-and Switch and the Shell Game patterns.

Junction points

On Unix, the Bait-and-Switch pattern is implemented using soft links. On Windows, the equivalent feature (at least for folders) is a junction point. Since this is an obscure feature for many Windows users, let me start by describing what junction points do and how to manage them.

Junction points can be used to assign the address of one directory to another. Once created, file operations performed on the junction point are actually forwarded to the linked directory. Essentially, you are reaching the same files through alternate file paths.

The concept is similar to a Windows "shortcut", but shortcuts are limited in the number of file operations they support. Junction points are much better at "fooling" the operating system.

How do you create a junction point? Microsoft must feel that this feature is too confusing for most users as they omitted creating a user interface for it. I've been using a great open source tool: NTFS Link, written originally by Michael Elsdoerfer and now maintained on SourceForge.

I won't go into the details of installing and configuring NTFS Link since there is plenty of information on Michael's site. The only tip I'll give is to take advantage of the icon overlays feature in the configuration dialog so that junction points are easy to see in an Explorer window. You won't have any trouble spotting the junction points in my screen capture below.

I don't believe NTFS Link will work on Vista, since Microsoft has introduced a replacement for junction points called NTFS symbolic links. These can point to files as well as folders, which is somewhat closer to the Unix soft link command. You can use the "mklink" to create these links, but that's quite a step down from the beautiful interface that NTFS Link provides.

One caveat, specially for Unix users on Windows: if you delete a junction point, you will also delete the files in the folder pointed to by the junction point. On Unix, deleting a soft link only removes the link. This is one reason why it's important to ensure that junction points are easy to spot. The good news is that the NTFS Link package intercepts folder deletions and implements the Unix behavior. This works only for the junction points it creates.

Folder copies can also pose problems, but NTFS Link helps us there as well. When you attempt to copy a junction point, it will ask whether you want to copy just the junction point or to copy it as they it were a real directory.

The Bait-and-Switch pattern

To use the Bait-and-Switch pattern, start by installing multiple versions of a software package. Each version is usually placed in a directory with the package and version as part of the name; for example, php-5.2.5-Win32. Then create a junction point to the version you want to use. Finally, have the rest of the system access the package through the junction point. Now, changing the package version is as easy as redirecting the junction point.

Junction points can be cascaded. For example, you could use "php" to point to the latest version of PHP. Then use "php4" and "php5" to point to the latest version of PHP 4 or 5. So "php" would point to "php5" which would point to the latest version of php5. When PHP 6 is released, "php" would change to point to "php6", but "php5" would continue to point to the latest PHP 5 version.

The Shell Game pattern

The Shell Game pattern uses a different way of fooling the operating system. The idea is to run a program by first running a script that configures the program environment and then starts the program. If set up properly, the command you type to start the script can be the same as what you would normally type to start the program (e.g. typing "php" to start the CLI PHP program). On Unix, the script is usually a sh (shell) script and thus my name for this pattern.

This pattern is not as useful on Windows as Bait-and-Switch because so many programs store their environment in the registry. Unix programs rely more on environment variables and command-line options. The registry is global and any change affects all versions of the running program. Environment variables and command-line options, however, can be set per-application, so that we can simultaneously run multiple copies of a program, each tailored to behave different from the others.

PHP uses environment variables and command-line options for its behavior, so the Shell Game pattern works well for it.

On Windows, you can write the script using any of a number of tools. A batch file should be sufficient, but I use Perl. Perl is fast and also provides a solution for properly starting a windowed application based on PHP-GTK. You can get a version of Perl for Windows from ActiveState. I use IndigoPerl, but it hasn't been updated since early 2005.

Some Windows scripting tools are very slow, so be careful with your choice.

Applying Bait-and-Switch to a PHP installation

Here's a screen shot of my PHP installation.

Explorer window screen shot showing my PHP installation

I've installed PHP in C:\Program Files\php instead of C:\php. A few things to note:

  • The files with the large green arrows are junction points.
  • There are multiple PHP 4 and 5 installations as well as PHP-GTK.
  • The junction points point to one of the PHP 4, PHP 5 or PHP-GTK installations (typically, the latest, but it can be any version).
  • The "bin" directory is the only directory in the execution path.
  • There is one "pear" directory shared by all PHP versions. When I add a new PEAR package, all PHP versions have access to it.
  • There is one "php5-ext" directory where I place extensions that are independent of the PHP 5 version (currently, I have XCache and XDebug).
  • All the files needed to run PHP are stored in C:\Program Files\php. This includes the php.ini file. Each PHP version has one php.ini file, although I could also have had one copy shared by all versions.

Why did I configure PHP this way?

  • I was able to place PHP where I wanted it.
  • I was able to keep the php.ini file with the rest of the PHP installation.
  • I can easily switch from one version of PHP to another.
  • I can quickly install a new version of PHP without deleting my current version.
  • I need to maintain only one version of PEAR.
  • I can simultaneously run CLI versions of PHP 4, PHP 5 and PHP-GTK without any conflicts.

There may be other and better ways to achieve these goals, but this approach works well for me.

Playing the PHP Shell Game

My Bait-and-Switch configuration wouldn't work without the support of the Shell Game pattern.

The first step is to place C:\Program Files\php\bin in the execution path (Control Panel > System > Advanced > Environment Variables). None of the PHP installations should be placed on the path.

Next, create a set of Shell Game scripts that will take the place of the php.exe, php-cgi.exe and php-win.exe. Since I use perl, I make the process easier by adding PL to the PATHEXT environment variable and then running a batch file that contains these two lines:

assoc .pl=pl_auto_file
ftype pl_auto_file="C:\indigoperlperl\bin\perl.exe" "%%1" %%*

If you use ActivePerl, you will need to adjust the path to perl. Having done all the steps listed so far, you could now write a perl script called sample.pl, place it in the bin directory and execute it from any directory by typing "sample". Because we need to ensure that the PATH and PATHEXT variables are properly inherited by all processes, you may want to log out and back in again.

Here's the Perl script I use for the CLI version of PHP5:

#!/usr/bin/perl
# Command line (CLI) PHP 5

our $phpMainDir = "C:\\Program Files\\php";
our $phpDir = "$phpMainDir\\php5";
our $phpBinDir = "$phpDir";
our $phpExtDir  = "$phpDir\\ext";
our $phpBin = "$phpBinDir\\php.exe";
our $phpMibsDir = "$phpDir\\extras\\mibs";
our $pearDir = "$phpMainDir\\pear";
our $pearBin = "$phpMainDir\\bin\\php5.pl";

$ENV{'PATH'} = $phpDir . ';' . $ENV{'PATH'};
$ENV{'MIBDIRS'} = $phpMibsDir;
$ENV{'PHP_PEAR_BIN_DIR'} = $pearDir;
$ENV{'PHP_PEAR_DATA_DIR'} = "$pearDir\\pear\\data";
$ENV{'PHP_PEAR_DOC_DIR'} = "$pearDir\\pear\\docs";
$ENV{'PHP_PEAR_INSTALL_DIR'} = "$pearDir\\pear";
$ENV{'PHP_PEAR_PHP_BIN'} = $pearBin;
$ENV{'PHP_PEAR_SYSCONF_DIR'} = $pearDir;
$ENV{'PHP_PEAR_TEST_DIR'} = "$pearDir\\pear\\tests";

system "\"$phpBin\"", "-c", $phpDir, @ARGV;

The initial set of variable definitions set up a number of locations. The code is structured so that this is the only section that needs to be altered when creating CGI and CLI versions of PHP 4, 5 or GTK. GTK has no CGI version, so there are five scripts total. If you'd like to see all five versions, you can get them from this ZIP file (3KB).

We're not done yet! If you want to run a CGI version of PHP in Apache, you can just point Apache at the appropriate script. If you want to run PHP as an Apache module, however, you will also need to use the Shell Game pattern for the Apache executable. Start with the same script as for the CGI version of PHP you want use, delete the last line and append these instead:

our $apache = 
  "C:\\Program Files\\Apache Software Foundation\\Apache2.2\\bin\\httpd.exe";
system ""$apache"", @ARGV;

Save the result in a file called httpd.pl, place it in the Apache bin directory and change the Apache service to use "C:\indigoperlperl\bin\perl.exe" "C:\Program Files\Apache Software Foundation\Apache2.2\bin\httpd.pl" -k runservice. (I edited the entry by using a registry editor—does anyone know a better way?).

Finally, if you want to run some of the scripts in the pear directory (pear.bat, pecl.bat, phing.bat), you will need to modify the scripts to use the Perl scripts rather than directly calling the PHP executables. I'll leave this as an exercise for the reader.

One caveat: there are some programs (such as IDEs) that will ask you for the location of your installed PHP. Then they get very clever and try to determine if you have a valid installation. Well, the installation is valid, but the program won't be able to figure that out and will refuse to accept your choice. The solutions vary with the programs. Sometimes you can tell the program to set up an environment for the PHP subprocess. Sometimes you will need to write a Shell Game script for that tool. I haven't encountered many of these types of problems and haven't found any that I couldn't work around.


Interesting read

I've implemented something similar for my own system, although there are some implementation differences. One of the biggest things my system does is it auto-generates php.ini files from a global php.ini file and a special extensions.txt file that contains php.ini declarations as well as PHP version requirements. Mine looks something like:

php_sqlite.dll | 5.1+
php_svn.dll | 5.1.4+
php_gettext.dll | 4.3.8+

This saves me from having to maintain multiple disparate ini files, while allowing me to account for quirky extension behavior on certain versions of PHP.

I should note that XDebug is not PHP version independent; there are different versions per x.y branch.

P.S. stripslashes() is being run an extra time on your post, so the Windows paths are mangled


NTFS Link ShellExtension

There's also a great shell extension for the creation of junctions (they do also work on Vista), symbolic and/or hard links:

http://schinagl.priv.at/nt/hardlinkshellext/hardlinkshellext.html


Good article. Thx.

Good article. Thx.


single PEAR install = recipe for disaster

Hi,

Although most of what you suggest is quite good, having a single PEAR installation for all PHP versions is a recipe for disaster. Why? Many PEAR packages install different files based on the PHP version, or have a particular PHP version dependency. If you install using PHP 5, and then try to run that PEAR package in PHP 4, there is a good chance that it would simply fail with a fatal error.


Great tips, everyone!

Thanks for all the great comments, everyone! I'll adjust my install to take your comments into account. You demonstrate the value of writing these articles: we all get to learn something.

It's also nice to hear there is a good junction point interface for Vista.

Tony Freixas


Fixed backslashes

Edward, thanks for catching the backslash problem. I'm still trying to understand the problem (which only occurs on this production site and not on my PC), but I've worked around it.

Tony Freixas


PHP 5

I switched to PHP 5 many years ago - about 6 months after it was released. Wouldn't have it any other way!

Adam @ TalkPHP.com - PHP Community

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

More information about formatting options

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.

 

Privacy policy Tiger Heron LLCinfo@tigerheron.com • (503) 771-7724

Copyright © 2005-2007, Tiger Heron LLC