Run All the PHPs

I have my development machines set up to be able to run sites in any version of PHP from 5.3 through the upcoming PHP 7.2 release, and easily switch between them. This solution uses Apache and real instances of PHP-FPM running on my machine, so there's no virtual machine overheads like with Vagrant. If you want something similar, read on…

I'm using macOS, so have stuck to using the built-in Apache 2.4, since it's what I know best. I don't doubt you could achieve the same results using nginx, but since I don't know that as well, I've not tried. I also work on a number of legacy projects that use .htaccess files, so sticking to Apache was the easy choice.

Also of note is that I have Dnsmasq configured to allow me to use wildcard domains like project-name.php53.localhost. You can follow a guide to set up Dnsmasq for local development, though these days you should not use the .dev gTLD since it's a real gTLD as of 2014. Various places online recommend you use .localhost instead.

Install all the PHPs

This guide uses the excellent homebrew to install the various versions of PHP, so you'll need to install that if you've not already.

# PHP version are in a brew "tap"
brew tap homebrew/php;

# Unlink all installed versions of PHP first
for php in php53 php54 php55 php56 php70 php71 php72; do
    brew unlink $php;
done;

# Install each version, link it, add xdebug, then unlink ready for the next one
for php in php53 php54 php55 php56 php70 php71 php72; do
    brew install $php;
    brew link $php;
    brew install $php-xdebug;
    brew unlink $php $php-xdebug;
done;

# Clean up after ourselves
brew cleanup;

PHP-FPM Configuration

Make a directory for your unix sockets to live in with useful permissions

mkdir -p /usr/local/var/run/php-fpm
sudo chown $(whoami):_www /usr/local/var/run/php-fpm
sudo chmod 0770 /usr/local/var/run/php-fpm

A word of warning: do not use /var/run on macOS since it is mounted as tmpfs, and it is emptied on each reboot.

Homebrew puts all the configuration files in /usr/local/etc/php, grouped by version number. PHP >= 7.0 will have a www.conf file within a php-fpm.d directory, earlier versions will have the options directly in php-fpm.conf.

  • Set the listen option to a version-specific path file /usr/local/var/run/php-fpm/php53.sock
  • Uncomment listen.mode = 0660 so that Apache and your user can access the socket
  • Set pm.status_path to a version-specific path such as /status-php53

Finally, you can start up each PHP-FPM daemon:

for php in php53 php54 php55 php56 php70 php71 php72; do
    brew services start $php;
done

If your services don't stay up, you can run them directly to see what the error is:

/usr/local/opt/php53/sbin/php53-fpm start

Apache Configuration

At a minimum, you will need to define a Proxy per version you want to run, and enable the relevant proxy modules.

I also define status entries, and a default handler to my most-commonly used version.

First, ensure the following lines in your httpd.conf are uncommented:

LoadModule proxy_module libexec/apache2/mod_proxy.so
LoadModule proxy_fcgi_module libexec/apache2/mod_proxy_fcgi.so

Then, you can start defining your proxy entries:

# Define a Proxy entry for each version
<Proxy "fcgi://php53">
    ProxyPass "unix:/usr/local/var/run/php-fpm/php53.sock|fcgi://php53"
</Proxy>

# Optionally set up the status handler
<Location "/status-php53">
    SetHandler "proxy:fcgi://php53"
</Location>

# ...

<Proxy "fcgi://php71">
    ProxyPass "unix:/usr/local/var/run/php-fpm/php71.sock|fcgi://php71"
</Proxy>

<Location "/status-php71">
    SetHandler "proxy:fcgi://php71"
</Location>

# Use index.php by default over html files
DirectoryIndex /index.php index.php index.html

# This is the "default" version to use when not using a version-specific hostname
<FilesMatch "\.php$">
    SetHandler "proxy:fcgi://php71"
</FilesMatch>

Combined with the wildcard DNS setup mentioned at the start of this post, we can now set up one VirtualHost per version of PHP. Ensure you use the correct path in VirtualDocumentRoot for your particular setup.

<VirtualHost *:80>
    ServerAlias *.php53.localhost
    VirtualDocumentRoot /Users/YOUR_USERNAME/Sites/%1/web

    <FilesMatch "\.php$">
        SetHandler "proxy:fcgi://php53"
    </FilesMatch>
</VirtualHost>

# ...

<VirtualHost *:80>
    ServerAlias *.php71.localhost
    VirtualDocumentRoot /Users/YOUR_USERNAME/Sites/%1/web

    <FilesMatch "\.php$">
        SetHandler "proxy:fcgi://php71"
    </FilesMatch>
</VirtualHost>

You'll need to restart Apache before these changes take effect: sudo apachectl restart.

If Apache fails to start, double-check your paths and included modules. You can check your config files' syntax with httpd -t.

If it starts, make sure you can load your sites and/or status pages, and you're done!

Conclusion

Whilst this set-up takes a little time, it has proved massively useful for me many times over. I work on many different sites, running on different versions of PHP, and setting up each site now takes only a few moments.

This setup has also been invaluable when working to upgrade a site to run on a new version. Getting the site working on its existing version, then opening the exact same site code in a new tab running on the latest stable version of PHP easily is extremely useful.

If you have any questions or corrections, please let me know!

Run All the PHPs
Mat Gadd