Sunday 17 May 2015

Getting PHP 5.x & 7-dev running on Windows/Apache

G'day:
Yesterday evening I discovered there are runnable versions of PHP 7-dev for Windows. I'd looked around for them before, but didn't spot them. Maybe they're new. I'm really struggling to get myself as far into the PHP loop as I am in the CFML one. Admittedly mostly due to lack of really trying.

Anyhow, I downloaded it and installed it (read: unzipped it), and indeed it seemed to a) work; b) do stuff PHP 5 can't do and PHP 7 is supposed to be able to do. So I figured "yeah, OK, so I do have PHP7 running". I'll write that lot up later on, once I have a decent play with it.

All my experiments last night - which were brief as I was at the bar @ LHR awaiting a flight - were running stuff via the CLI. This is fine with PHP but not how I'm used to doing stuff. Given I'm from a CFML background I'm used to running my test code in a browser, not a prompt, so I decided to get PHP7 running on Apache today. I've just managed to get it co-habitating with PHP 5. Win.

Here's what I had to do...


Caveat emptor

Firstly... these are not definitive instructions. I have no idea what I'm doing most of the time with PHP and Apache, and I just spend a lot of time googling stuff until I get my rig working. A lot of what I do is based on other people saying "do it this way", and as I don't have very good bullshit detection when it comes to PHP or Apache, I take their word for it. What I do know is I was not able to find one single set of instructions to achieve my goal here, so I'm documented what I did. It was a pain in the arse and took four Guinnesses to resolve (in a more familiar timescale, that's like 2h).

Re-organise

Firstly I had PHP running from C:\apps\php\. That lacks an element of foresight, so the first thing I did was to move it to C:\apps\php\5\. I thought about making it C:\apps\php\5.6.9\, but I thought that was perhaps a bit precise. I've only needed to co-run different versions of 5.x once, and I can do what I need to do from the CLI (it's just running unit tests: "PHP: unit testing private methods... PHP 5.5 version"). I can re-jig again if needs must.

The only thing I needed to do with that directory change was to update Apache's httpd.conf file to reflect the new dir:

LoadModule php5_module "c:/apps/php/5/php5apache2_4.dll"
AddHandler application/x-httpd-php .php
PHPIniDir "C:/apps/php/5/"


Before doing anything else, I verified that worked still. As a rule of thumb, if one is reconfiguring stuff... test every single step. That way one knows which step has cocked things up if things go wrong.

I'd previously downloaded the latest PHP7 build from http://windows.php.net/snapshots/, and had been messing around with it from the command-line. So I rehomed it from the dir I D/Led and unzipped it into, into C:\apps\php\7, and retested that. It was still happy running - from the CLI - from there. Good.

Apache (via modules)

Now... this is where my ignorance of how Apache works kicks in. Although in my favour I looked at what I was planning on doing and figured "well this won't work", but tried it anyhow.

First, I duplicated one of my PHP5-running vhosts, changing it to run from a host specifying php7 instead of just php:

<VirtualHost *:80>
    ServerName php.local
    ErrorLog "logs/php.local-error.log"
    CustomLog "logs/php.local-access.log" common
    ServerAdmin webmaster@php.local
    DocumentRoot "C:/webroots/shared/scratch/php/www/"
    <Directory "C:/webroots/shared/scratch/php/www/">
        Options Indexes FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>
<VirtualHost *:80>
    ServerName php7.local
    ErrorLog "logs/php7.local-error.log"
    CustomLog "logs/php7.local-access.log" common
    ServerAdmin webmaster@php7.local
    DocumentRoot "C:/webroots/shared/scratch/php/www/"
    <Directory "C:/webroots/shared/scratch/php/www/">
        Options Indexes FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

I just added "7" where the host file and log files etc were mentioned, and left the actual directories as is. Having also added a hosts file entry for php7.local, and restarting Apache, I was now able to browse to both php.local an php7.local A-OK. Both were still serving PHP 5.6.9, but that was cool: I hadn't broken anything.

Next I shifted the PHP 5 stuff from http.conf to each of my vhost entries in httpd-vhosts.conf, and changed the php7.local one to point to the PHP7 equivs:

# httpd.conf
# [...]
#LoadModule php5_module "c:/apps/php/5/php5apache2_4.dll"
#AddHandler application/x-httpd-php .php
#PHPIniDir "C:/apps/php/5/"

# httpd-vhosts.conf
<VirtualHost *:80>
    ServerName php.local
    ErrorLog "logs/php.local-error.log"
    CustomLog "logs/php.local-access.log" common
    ServerAdmin webmaster@php.local
    DocumentRoot "C:/webroots/shared/scratch/php/www/"
    <Directory "C:/webroots/shared/scratch/php/www/">
        Options Indexes FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>
    LoadModule php5_module "c:/apps/php/5/php5apache2_4.dll"
    AddHandler application/x-httpd-php .php
    PHPIniDir "C:/apps/php/5/"
</VirtualHost>
<VirtualHost *:80>
    ServerName php7.local
    ErrorLog "logs/php7.local-error.log"
    CustomLog "logs/php7.local-access.log" common
    ServerAdmin webmaster@php7.local
    DocumentRoot "C:/webroots/shared/scratch/php/www/"
    <Directory "C:/webroots/shared/scratch/php/www/">
        Options Indexes FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>
    LoadModule php7_module "c:/apps/php/7/php7apache2_4.dll"
    AddHandler application/x-httpd-php .php
    PHPIniDir "C:/apps/php/7/"
</VirtualHost>

And when I restarted Apache it went something along the lines of "nofuckoff". I figured I was wishfully thinking there. I figured the two PHP modules would clash or something.

So I googled.

Apache (via CGI)

As far as I can tell if one wants to run different versions of PHP simultaneously on Apache, one cannot use Apache modules, one has to use CGI mode instead. This basically sends all PHP-destined requests to an actual executable to process the code. I can't see how this is performant (and from my reading it is not), but I don't care on this machine: it's just my dev rig.

OK then... CGI mode it is.

It took me a while to find how to get the CGI module working with modern versions of Apache and PHP. It's really not "the way it's done" these days. Anyhow, eventually I found what I needed to download, and a Windows 64-bit binary of same (as I reside in the 21stC, life is too short for me to conscion actually compiling source code for shit like this), over at the Apache Lounge. The modules download there includes mod_fcgid which was what I needed.

Installation of that was simply to copy the .mod file to the Apache modules directory.

Then I needed to use it.

After a chunk more of googling I eventually landed on this config:

#httpd.conf
# [...]
LoadModule fcgid_module modules/mod_fcgid.so

# httpd-vhost.conf
# [...]
<VirtualHost *:80>
    ServerName php.local
    ErrorLog "logs/php.local-error.log"
    CustomLog "logs/php.local-access.log" common
    ServerAdmin webmaster@php.local
    DocumentRoot "C:/webroots/shared/scratch/php/www/"
    <Directory "C:/webroots/shared/scratch/php/www/">
        Options Indexes FollowSymLinks
        Options +ExecCGI
        AllowOverride All
        Require all granted
    </Directory>
    FcgidInitialEnv PHPRC "C:/apps/php/5/"
    AddHandler fcgid-script .php
    FcgidWrapper "C:/apps/php/5/php-cgi.exe" .php
</VirtualHost>

The key points here are:

  • loading the FastCGI module in the http.conf file;
  • telling the vhost to use it;
  • and - this took bloody ages to work out - telling the vhost it's actually allowed to run CGI requests.
Without doing that last part Apache starts OK, but all PHP requests will give a 403 on any PHP request, eg:

Forbidden

You don't have permission to access /phpinfo.php on this server.

Oh blimey... almost forgot the last step. One also needs to reconfigure PHP slightly to work with CGI requests rather than using the module. These two settings in php.ini needed changing:

[...]
doc_root =
[...]
cgi.fix_pathinfo=1
[...]

IE:
  • get rid of the doc_root setting. It confuses CGI mode as it looks there for the requested file. I this is blank, it uses the actually requested path
  • enable that fix_pathinfo setting. It's disabled by default

If you don't do that, you get "No input file specified".

Sit. rep.

OK, so at this point what have I achieved? I have achieved... making everything work like how it used to work before I started. PHP 5 stuff serves. Like it did. A coupla hours ago.

I still haven't got PHP7 working.

PHP 7 via CGI

Still: this should be easy... now just make those equivalent adjustments to my PHP7 vhost and its php.ini file. Yeah, that would have been cool. It didn't work though. What I got was this (in the Windows application log):

Fault bucket 86081508625, type 4
Event Name: APPCRASH
Response: Not available
Cab Id: 0

Problem signature:
P1: php-cgi.exe
P2: 7.0.0.0
P3: 5503431e
P4: php-cgi.exe
P5: 7.0.0.0
P6: 5503431e
P7: c0000005
P8: 00000000000020a1
P9:
P10:


(and some other shit I declined to look at).

Humph.

PHP 7 via Apache module


I googled about but could find nothing about whether one should expect PHP7 to run in CGI mode a) yet; b) at all. So I figured... OK, what if I run PHP5 via CGI, but run PHP7 via an Apache module?

I hastily revised my httpd.conf thus:

LoadModule php7_module "c:/apps/php/7/php7apache2_4.dll"
AddHandler application/x-httpd-php .php
PHPIniDir "C:/apps/php/7/"

And removed all the CGI stuff from my PHP7 vhost.

Boom

I restarted Apache, and now on http://php.local/phpinfo.php (which just calls phpinfo()) I get this:



And if I browse to http://php7.local/phpinfo.php, I get this:


Win!

Bottom line

It's obviously less than ideal to have to have my PHP5 sites running via CGI, but... well... it's a dev machine so it doesn't really matter. At least I can test code on both versions of PHP now, and - more importantly - it's now easier for me to check out some new stuff in PHP 7, which I intend to do in the not-to-distant future.

Righto.

--
Adam