Table of Contents

Finally got irritated enough to hack together something to help switch PHP versions quickly and easily on Windows. May be something useful to others (or not).

1)

The Python script (below) basically works by copying all required configurations files / dlls etc. from “source” directories you set up into the correct location under Windows or Apache. It finishes by restarting Apache for you. The strategy is one PHP version at a time, not multiple versions in parallel (which I’ve found too painful in practice).

It depends strongly on having the correct directory structure, under which you place a PHP Windows binary distribution and configuration files (e.g. php.ini, httpd.conf) specific to that distribution. It’s a small hassle to setup initially but once done, switching is effortless.

Warning: this is hacked together script and it drops things into your Windows system directory so if in doubt, read the code first before you use it! If still in doubt, dont use it

Requirements

You need Python and PythonWin installed.

http://www.python.org/download/ https://sourceforge.net/projects/pywin32/

Believe (can’t remember) “.py” files are associated with the Python interpreter automatically, when you run the installer.

You also need Apache 1.3x installed as a Windows service (the Apache installer does this by default).

Basic Setup

1. Create a root directory for all PHP installations e.g.

You’ll be placing binary PHP distros under this directory.

2. Next, under this directory create a directory php_dlls i.e.

3. Add the directory to your Windows “path” environment variable

Setting Up a PHP Version

1. Under the directory, extact PHP versions into subdirectories with the directory name ending in “_bin”.

For example if I download the PHP 4.4.0 ZIP (here), I extact it to the directory;

So that there’s PHP binary is at

2. Now inside this directory (e.g. ), I need to create three directories;

a. < this already exists for PHP 4.x Win releases but not PHP 5.x b. c.

3. Now you need to move some files around.

a. Copy or move to (if it’s a PHP 5 version then you need to end up with something like )

b. Make sure all .dlls, that do not begin with the name “php_” are in the dlls subdirectory - for PHP 4.x versions this is already done. For PHP 5.x you’ll need to put them by hand. For the record, in PHP 5.0.4 it’s the following files you need to copy to - fdftk.dll, fribidi.dll, gds32.dll, libeay32.dll, libmhash.dll, libmysql.dll, msql.dll, ntwdblib.dll, ssleay32.dll and yaz.dll.

c. Finally place a correctly configured (for this PHP version) php.ini and httpd.conf file in the “conf” subdirectory e.g.

There’s also a line commented out in the code below if you want to add your pear.ini file to that list

Hacking

Finally you need to hack this script a little, changing the three global variables at the start to point at various directories (no trailing slash). Watch out for backslashes in combination with these http://www.python.org/doc/current/ref/strings.html - you may need a double backslash if it could be mistaken for an escape sequence (or perhaps you know a smarter way to do it).

Running

Now put phpswitch.py somewhere like in your Windows path.

This will tell you the PHP versions available. Using the version strings it returns, you can switch like

or

Switching should be a matter of seconds, restarting Apache being the slowest stage.

The Code

import getopt, sys, os, re, shutil, time
import win32serviceutil, win32api, win32con
 
#--------------------------------------------------------------------
# Modify these
PHP_DIR = 'C:\php.net' # Base dir for PHP dirs
WIN_DIR = 'C:\windows' # Yep
APACHE_CONF_DIR = 'C:\\apache.org\Apache\conf' # Apache 1.3 conf dir
 
#--------------------------------------------------------------------
class Service:
    def __init__(self):
        self.machine = os.environ.get('COMPUTERNAME');
        
    def running(self, service):
        return win32serviceutil.QueryServiceStatus(service, self.machine)[1] == 4
 
    def stop(self, service, timeout=1):
        servname = self.__getSrvNam(service)
        
        msg = "Can't stop, %s not running"%servname
        self.__waitUntilStarted(service, timeout, msg)
        
        win32serviceutil.StopService(service, self.machine)
        
        msg = "Failed to stop, %s (???)"%servname
        self.__waitUntilStopped(service, timeout, msg)
        
        return True
        
    def start(self, service, timeout = 3):
        servname = self.__getSrvNam(service)
        
        msg = "Can't start, %s is already running"%servname
        self.__waitUntilStopped(service, timeout, msg);
        
        win32serviceutil.StartService(service, self.machine)
        
        msg = "Can't start %s (???)"%servname
        self.__waitUntilStarted(service, timeout, msg);
        
        return True
 
    def restart(self, service, timeout = 3):
        servname = self.__getSrvNam(service)
        
        msg = "Can't restart, %s is not running"%servname
        self.__waitUntilStarted(service, timeout, msg);
        
        win32serviceutil.RestartService(service, self.machine)
        
        msg = "Unable to restart %s (???)"%servname
        self.__waitUntilStarted(service, timeout, msg);
        
        return True
 
    def getShortName(self, longName):
        hkey = win32api.RegOpenKey(
            win32con.HKEY_LOCAL_MACHINE, 
            "SYSTEM\\CurrentControlSet\\Services", 
            0, 
            win32con.KEY_ALL_ACCESS
            )
        num = win32api.RegQueryInfoKey(hkey)[0]
 
        # loop through number of subkeys
        for x in range(0, num):
            # find service name, open subkey
            svc = win32api.RegEnumKey(hkey, x)
            skey = win32api.RegOpenKey(
                win32con.HKEY_LOCAL_MACHINE,
                "SYSTEM\\CurrentControlSet\\Services\\%s" % svc,
                0,
                win32con.KEY_ALL_ACCESS
                )
                
            try:
                # find short name
                shortName = str(win32api.RegQueryValueEx(skey, "DisplayName")[0])
                if shortName == longName:
                    return svc
    
            except win32api.error: 
                # in case there is no key called DisplayName
                pass
 
        return None
        
    def __getSrvNam(self, service):
        return 'service (%s) on machine(%s)'%(service, self.machine)
    
    def __waitUntilStopped(self, service, timeout, errmsg):
        time.sleep(1)
        begin = int(time.time())
        while 1:
            now = int(time.time());
            running = self.running(service);
            if not running:
                return True;
            elif running and (now - begin) > timeout:
                raise ServiceException(errmsg)
            else:
                time.sleep(1)
 
    def __waitUntilStarted(self, service, timeout, errmsg):
        time.sleep(1)
        begin = int(time.time())
        while 1:
            now = int(time.time());
            running = self.running(service)
            if running:
                return
            elif not running and (now - begin) > timeout:
                raise ServiceException(errmsg)
            else:
                time.sleep(1)
 
class ServiceException(Exception):
    
    def __init__(self,value):
        self.value = value
        
    def __str__(self):
        return "[Service Error] "+str(self.value)
 
#--------------------------------------------------------------------
def usage():
    print "Switch PHP versions"
    print "e.g. phpswitch.py 5_0_4"
    displayVersions()
 
#--------------------------------------------------------------------
def getPHPVersions():
    versions = {}
    if not os.path.isdir(PHP_DIR):
        print "PHP_DIR: Unable to read from "+str(PHP_DIR)
        sys.exit(1)
    
    p = re.compile('^(.*)_bin$')
    for entry in os.listdir(PHP_DIR):
        if os.path.isdir(PHP_DIR+os.sep+entry):
            m = p.match(entry)
            if m:
                versions[m.group(1)] = m.group(0)
    return versions
 
#--------------------------------------------------------------------
def displayVersions():
    versions = "Available versions:"
    sep = " "
    for v in VERSIONS.keys():
        versions += sep+v
        sep = ", "
    print versions
 
#--------------------------------------------------------------------
def deployConfigs(phpdir):
    deploydir = phpdir + os.sep + 'conf'
    
    if not os.path.isdir(deploydir):
        print "Unable to locate "+str(deploydir)
        sys.exit(1)
    
    conf_files = {
        deploydir + os.sep + 'php.ini': WIN_DIR + os.sep + 'php.ini',
        # Uncomment below if you're want to move PEAR ini's around
        #deploydir + os.sep + 'pear.ini': WIN_DIR + os.sep + 'pear.ini',
        deploydir + os.sep + 'httpd.conf': APACHE_CONF_DIR + os.sep + 'httpd.conf',
    }
    
    for conf_file in conf_files:
        if not os.path.isfile(conf_file):
            print "Unable to locate "+str(conf_file)
            sys.exit(1)
        try:
            shutil.copy(conf_file,conf_files[conf_file])
            print conf_file + " deployed"
        except Exception, e:
            print "Error copying "+conf_file+": "+str(e)
    
#--------------------------------------------------------------------
def deployDlls(phpdir):
    source_dlldir = phpdir + os.sep + 'dlls'
    target_dlldir = PHP_DIR + os.sep + 'php_dlls'
    
    if not os.path.isdir(source_dlldir):
        print "SOURCE: Unable to read from "+str(source_dlldir)
        sys.exit(1)
    
    if not os.path.isdir(target_dlldir):
        print "TARGET: Unable to write to "+str(target_dlldir)
        sys.exit(1)
    
    p = re.compile('^.*\.dll$',re.I)
    for entry in os.listdir(target_dlldir):
        m = p.match(entry)
        if m:
            try:
                os.unlink(target_dlldir + os.sep + entry)
            except Exception, e:
                print "Unable to delete "+target_dlldir + os.sep + entry + ": "+str(e)
    
    for entry in os.listdir(source_dlldir):
        m = p.match(entry)
        if m:
            try:
                shutil.copy(source_dlldir + os.sep + entry, target_dlldir)
                print str(entry) + " deployed to "+str(target_dlldir)
            except Exception, e:
                print "Error copying "+str(entry)+": "+str(e)
 
#--------------------------------------------------------------------
def stopApache():
    S = Service()
    if S.running('apache'):
        print "Stopping apache"
        S.stop('apache',5)
 
#--------------------------------------------------------------------
def deployPHPTS(phpdir):
    source_dlldir = phpdir + os.sep + 'phpts'
    target_dlldir = WIN_DIR + os.sep + 'system32'
    
    if not os.path.isdir(source_dlldir):
        print "SOURCE: Unable to read from "+str(source_dlldir)
        sys.exit(1)
    
    if not os.path.isdir(target_dlldir):
        print "TARGET: Unable to write to "+str(target_dlldir)
        sys.exit(1)
    
    p = re.compile('^.*ts\.dll$',re.I)
    
    for entry in os.listdir(source_dlldir):
        m = p.match(entry)
        if m:
            try:
                shutil.copy(source_dlldir + os.sep + entry, target_dlldir)
                print str(entry) + " deployed to "+str(target_dlldir)
            except Exception, e:
                print "Error copying "+str(entry)+": "+str(e)
 
#--------------------------------------------------------------------
def startApache():
    S = Service()
    if S.running('apache'):
        print "Restarting apache"
        S.restart('apache')
    else:
        print "Starting apache"
        S.start('apache',5)
 
#--------------------------------------------------------------------
if __name__ == '__main__':
    VERSIONS = getPHPVersions()
    OPTS, ARGS = getopt.getopt(sys.argv[1:], 'hv')
    for OPT in OPTS:
        if OPT[0] == '-h':
            usage()
            sys.exit(0)
        if OPT[0] == '-v':
            displayVersions()
            sys.exit(0)
    
    try:
        VERSION = ARGS[0]
    except:
        print "PHP version required as script argument"
        sys.exit(1)
    
    
    if not VERSIONS.has_key(VERSION):
        print "PHP version "+str(VERSION)+" not found"
        displayVersions()
        sys.exit(1)
    
    PHP_DIR_VERSION = PHP_DIR + os.sep + VERSIONS[VERSION]
    
    deployConfigs(PHP_DIR_VERSION)
    deployDlls(PHP_DIR_VERSION)
    stopApache()
    deployPHPTS(PHP_DIR_VERSION)
    startApache()

Updated (06 Aug 2005) fixed small bug in directory name


develop/switching_php_versions_on_windows_with_python.txt · Last modified: 2005/10/15 21:47