Creating LAMP VM – PHP Installation

This is part of a series of posts on creating your own LAMP virtual machine for development purposes. Here we will cover the installation of PHP.

Estimated time (this post only): 3 hours
* if installing everything; most of this time is spent running the tests so skipping them greatly reduces this

PHP Apache Module

It is not strictly necessary to build any version of PHP as an Apache module. You could build all your versions as FPM (further down in this post). But for this guide we will install our oldest version as a module.

Build PHP Apache Module

Install the version of PHP we are going to use as an Apache module. I chose the outdated 5.3 version for this because I am only using this method for testing purposes, preferring FPM for my other installs. However, you could choose one of the others if you wish. The same configure line below should work, so just change the directory names.

Because you need the older autoconf to build PHP 5.3, you must do:

export PHP_AUTOCONF=/usr/bin/autoconf-2.13

By adding it to the .bashrc file this will be set even if we have to restart. This may cause problems when building newer versions of PHP, but don’t worry—I will remind you about it later:

sed -i '$ a\\nexport PHP_AUTOCONF=/usr/bin/autoconf-2.13' ~/.bashrc

The configure command has dozens of options. Depending on your requirements you may not wish to use the same options I did. My primary requirement was the PDO extension (to develop for multiple database engines). Since this is for development purposes you need to determine which extensions you need and alter your configure command accordingly. If your application is going to run on a shared host, check out their setup to ensure they have the extensions you need and configure PHP only with those extensions. Notice how I’ve broken this up into multiple lines. The first few lines are just setting up some basics and some extensions that are commonly installed. I also installed a few extensions that I needed for my own projects. I put the database stuff last. Technically this is all a single command (which may contain file paths you need to alter). The backslashes just allow us to break up the command into multiple sections for simplicity.

Before running configure as shown here, it is important that you read the notes I typed out below:

cd /usr/local/src/php-5.3.29

sudo -E ./configure --with-libdir=/lib64 --enable-bcmath --enable-calendar \
--enable-mbstring --enable-exif --enable-ftp --enable-libxml --enable-soap \
--enable-sockets --enable-zip --with-bz2 --with-xmlrpc --with-curl \
--with-curlwrappers --with-gettext --with-mcrypt --with-openssl --with-pspell \
--with-tidy --with-gd --enable-gd-native-ttf --with-jpeg-dir=/opt --with-png-dir=/opt \
--with-xpm-dir=/opt --with-freetype-dir=/opt --with-xsl --with-zlib-dir=/opt \
--with-imap=/usr/local/src/imap-2007f --with-imap-ssl --enable-pdo \
--with-mysqli=/opt/mysql56/bin/mysql_config --with-pdo-oci --with-pdo-firebird \
--with-pdo-mysql=/opt/mysql56 --with-pdo-pgsql=/usr/pgsql-9.4/bin \
--with-pdo-dblib --with-pdo-sqlite --with-pdo-ibm --with-pdo-informix \
--enable-sqlite-utf8 --with-sqlite \
--with-apxs2=/usr/local/apache2/bin/apxs

Some notes on the above configure options:

  • The -E switch is needed for sudo in order to preserve the environment variables for $INFORMIXDIR, $ORACLE_HOME, etc.
  • Leave out the PDO drivers for any databases you didn’t install:
    • DB2 (and/or Informix 11.10+): PDO_IBM
    • Firebird: PDO_FIREBIRD
    • Informix (versions < 11.10): PDO_INFORMIX
    • MySQL/MariaDB: PDO_MYSQL
    • Oracle: PDO_OCI
    • PostgreSQL: PDO_PGSQL
    • Sybase: PDO_DBLIB
  • PDO_IBM and PDO_INFORMIX cannot be installed under the same instance of PHP. Although I include both above for the sake of completeness, you must remove one or the other. In general, PDO_IBM is preferred.
  • You will get a warning that the PDO_IBM or PDO_INFORMIX extensions are unrecognized. This is expected at this point. If you need one of them it is critical that you received no other errors, since we will be running configure again (see below).
  • Some of these options are enabled by default and thus aren’t strictly necessary.
  • Most of the paths in the options should be good. IMAP hasn’t been updated in forever, so that path should be fine. The MySQL paths should be changed if needed. Only other one likely to change will be the PostgreSQL path if you installed a different version.
  • MySQLi is not technically needed if we are developing for PDO as I am, but many web apps out there use it, so I am including it anyway.
  • SQLite is also not needed for PDO, but I include it because it is often included on shared hosts. As of PHP 5.4 it is available only via PECL, so if building a newer version those two switches will generate a warning which you can ignore.
  • –with-curlwrappers is deprecated as of PHP 5.5 so that option will generate a warning for that version and higher, which you can ignore.

Assuming everything went well (except the expected warnings about PDO_IBM/PDO_INFORMIX), if you need one of those drivers, you must now do (again editing your configure command as needed):

sudo rm configure
sudo -E ./buildconf --force

sudo -E ./configure --with-libdir=/lib64 --enable-bcmath --enable-calendar \
--enable-mbstring --enable-exif --enable-ftp --enable-libxml --enable-soap \
--enable-sockets --enable-zip --with-bz2 --with-xmlrpc --with-curl \
--with-curlwrappers --with-gettext --with-mcrypt --with-openssl --with-pspell \
--with-tidy --with-gd --enable-gd-native-ttf --with-jpeg-dir=/opt --with-png-dir=/opt \
--with-xpm-dir=/opt --with-freetype-dir=/opt --with-xsl --with-zlib-dir=/opt \
--with-imap=/usr/local/src/imap-2007f --with-imap-ssl --enable-pdo \
--with-mysqli=/opt/mysql56/bin/mysql_config --with-pdo-oci --with-pdo-firebird \
--with-pdo-mysql=/opt/mysql56 --with-pdo-pgsql=/usr/pgsql-9.4/bin \
--with-pdo-dblib --with-pdo-sqlite --with-pdo-ibm --with-pdo-informix \
--enable-sqlite-utf8 --with-sqlite \
--with-apxs2=/usr/local/apache2/bin/apxs

If anything goes wrong at all in the above or during the make process below, do the following before running configure again:

sudo -E make clean

If all went well (you did read the notes above right?), you can now install:

sudo -E make
sudo -E make install
sudo -E make test

The test phase will take a while. I have tried to cover all steps necessary to avoid errors but if you run into something I didn’t anticipate Google is your friend (or leave a comment below and I will try to address your problem). Test failures are to be expected, especially since this is an old version of PHP. You should start up all database engines that you are building support for (especially Oracle; if it isn’t running “make test” will take hours with all the failures as I learned the hard way). Below are the failures I encountered (short version: all these test failures are expected because they were made for old versions of software and you can ignore them).

If you want a simple solution for avoiding most of these errors, I have uploaded an archive with updated test files (should remove all except the red-colored test failures). To apply it just do the following (from the PHP 5.3 source directory, where you should already be) and you should see fewer errors:

sudo wget http://supremerulerofearth.com/wp-content/uploads/2015/08/php_53_test_patches.zip
sudo unzip -o php_53_test_patches.zip
  • The following tests can be fixed by overwriting them with copies from the 5.4+ version of PHP:
    • ext/dom/tests/DOMDocument_validate_external_dtd.phpt
    • ext/sockets/tests/socket_strerror.phpt
  • The following tests can be fixed by overwriting them with copies from the 5.6+ version of PHP:
    • ext/standard/tests/file/bug52820.phpt
    • ext/standard/tests/network/http-stream.phpt
    • ext/standard/tests/streams/bug49936.phpt
    • ext/standard/tests/strings/setlocale_variation2.phpt
  • ext/fileinfo/tests/finfo_file_002.phpt—for some reason this test fails because a PPT file is identified as Microsoft Word rather than Microsoft PowerPoint.
  • ext/openssl/tests/026.phpt—a misplaced bracket caused a failure with this test. I found an updated script which addressed this.
  • ext/openssl/tests/bug36732.phpt—I received a signature verification error. A lot of people get this apparently, but I found no solution.
  • ext/spl/tests/SplObjectStorage_unserialize_bad.phpt—for some reason this returned three keys in the array rather than two, but the first two had the expected values. I couldn’t find any information on what causes this, but SPL appears to work normally.
  • Various database tests may fail due to you having set a password (as you should have) that the tests don’t know.
  • EXPECTED FAILED TEST SUMMARY section can be ignored.

If you want to be thorough you can save the test output to a text file and go through each failed test result.

Configure PHP Apache Module

Create a default php.ini file. You can choose between the development and production versions. The biggest difference between the two is error reporting. With the development version, PHP errors will appear in your web browser (very unsafe to do this on any production system as it exposes PHP code to the public). You can change any of the options in the file at any time. So run only one of the following commands as desired:

sudo cp php.ini-development /usr/local/lib/php.ini
sudo cp php.ini-production /usr/local/lib/php.ini

Now edit the httpd.conf file:

sudo nano /usr/local/apache2/conf/httpd.conf

Look for the following at the end of the LoadModule lines (add it if it isn’t there, but it should have been added automatically):

LoadModule php5_module        modules/libphp5.so

A little further down you will see a section that looks something like:

<IfModule dir_module>
    DirectoryIndex index.html
</IfModule>

Add “index.php” to this as follows:

<IfModule dir_module>
    DirectoryIndex index.html index.php
</IfModule>

At the end of this file add the following so that files with a “.php” extension will be parsed by the PHP module, then save and exit:

<FilesMatch \.php$>
    SetHandler application/x-httpd-php
</FilesMatch>

Now restart Apache:

sudo service apache2 restart

Test PHP Apache Module

Now let’s make sure PHP is actually working:

sudo rm /usr/local/apache2/htdocs/index.html
printf "<?php\nphpinfo();\n?>" | sudo tee /usr/local/apache2/htdocs/index.php

On your host system now, visit your default Apache web page (remember, we tested this earlier) by going to http://192.168.56.10/ (or whatever the static IP of your VM is). If all went well, you should see a lengthy document with all the information on your PHP build and its extensions. If it doesn’t show up for some reason, you may try restarting Apache again and waiting a few seconds.


PHP-FPM

PHP-FPM is not an Apache module, but rather a separate process which Apache can access via ports or sockets (I prefer sockets). This means you can have as many instances of PHP-FPM as you wish in order to use different versions/builds of PHP.

Build PHP-FPM

The first thing to note is that from PHP 5.4 on, we no longer need to use that old version of autoconf we used to build PHP 5.3. So do the following (should be safe to do even if you didn’t add the line to .bashrc since that command will just fail):

sed -i '/export PHP_AUTOCONF=/d' ~/.bashrc
unset PHP_AUTOCONF

Now for your configure command. For simplicity I have kept it exactly the same as the previous configure command except we need to leave out the –with-apxs2 switch and add several for building as FPM. The prefix switch determines where this version of PHP will be installed. Basically, you can use these same options for every version of PHP you are installing so long as you change the prefix. (See the previous notes.)

cd /usr/local/src/php-5.4.43
 
sudo -E ./configure --with-libdir=/lib64 --enable-bcmath --enable-calendar \
--enable-mbstring --enable-exif --enable-ftp --enable-libxml --enable-soap \
--enable-sockets --enable-zip --with-bz2 --with-xmlrpc --with-curl \
--with-curlwrappers --with-gettext --with-mcrypt --with-openssl --with-pspell \
--with-tidy --with-gd --enable-gd-native-ttf --with-jpeg-dir=/opt --with-png-dir=/opt \
--with-xpm-dir=/opt --with-freetype-dir=/opt --with-xsl --with-zlib-dir=/opt \
--with-imap=/usr/local/src/imap-2007f --with-imap-ssl --enable-pdo \
--with-mysqli=/opt/mysql56/bin/mysql_config --with-pdo-oci --with-pdo-firebird \
--with-pdo-mysql=/opt/mysql56 --with-pdo-pgsql=/usr/pgsql-9.4/bin \
--with-pdo-dblib --with-pdo-sqlite --with-pdo-ibm --with-pdo-informix \
--enable-sqlite-utf8 --with-sqlite \
--enable-fpm --with-fpm-user=php --with-fpm-group=php --prefix=/opt/php-5.4.43

Assuming everything went well (except the expected warnings about PDO_IBM/PDO_INFORMIX), if you need one of those drivers, you must now do (again editing your configure command as needed):

sudo rm configure
sudo -E ./buildconf --force

sudo -E ./configure --with-libdir=/lib64 --enable-bcmath --enable-calendar \
--enable-mbstring --enable-exif --enable-ftp --enable-libxml --enable-soap \
--enable-sockets --enable-zip --with-bz2 --with-xmlrpc --with-curl \
--with-curlwrappers --with-gettext --with-mcrypt --with-openssl --with-pspell \
--with-tidy --with-gd --enable-gd-native-ttf --with-jpeg-dir=/opt --with-png-dir=/opt \
--with-xpm-dir=/opt --with-freetype-dir=/opt --with-xsl --with-zlib-dir=/opt \
--with-imap=/usr/local/src/imap-2007f --with-imap-ssl --enable-pdo \
--with-mysqli=/opt/mysql56/bin/mysql_config --with-pdo-oci --with-pdo-firebird \
--with-pdo-mysql=/opt/mysql56 --with-pdo-pgsql=/usr/pgsql-9.4/bin \
--with-pdo-dblib --with-pdo-sqlite --with-pdo-ibm --with-pdo-informix \
--enable-sqlite-utf8 --with-sqlite \
--enable-fpm --with-fpm-user=php --with-fpm-group=php --prefix=/opt/php-5.4.43

If anything goes wrong at all in the above or during the make process below, do the following before running configure again:

sudo -E make clean

Now make, install, and test just as we did before:

sudo -E make
sudo -E make install
sudo -E make test

For PHP 5.4.43 I only had a few tests fail. Nothing worth noting.

Configure PHP-FPM

Create a default php.ini file. You can choose between the development and production versions. The biggest difference is error reporting. With the development version, PHP errors will appear in your web browser (very unsafe to do this on any production system as it exposes PHP code to the public). You can change any of the options in the file at any time. So run only one of the following commands as desired:

sudo cp php.ini-development /opt/php-5.4.43/lib/php.ini
sudo cp php.ini-production /opt/php-5.4.43/lib/php.ini

You also need to make a default php-fpm.conf file:

sudo cp /opt/php-5.4.43/etc/php-fpm.conf.default /opt/php-5.4.43/etc/php-fpm.conf
sudo mkdir /opt/php-5.4.43/etc/fpm.d
sudo nano /opt/php-5.4.43/etc/php-fpm.conf

You need to change a few things in this file. First, just un-comment the following lines (remove the semicolon at the beginning):

include=etc/fpm.d/*.conf
pid = run/php-fpm.pid

Next edit the following line:

listen = 127.0.0.1:9000

We want to change this to listen on a socket rather than a TCP port. Both methods have their advantages in terms of performance and security. If you stick with ports as is the default above, each PHP instance has to be unique. But sockets are easier and arguably more secure (though there can be issues with servers that have very high usage, but since this is a development machine that isn’t a concern):

listen = var/run/php.sock

Shortly after this, you will see three lines that start with “listen.” These determine the owner and permissions of the socket we set above. The default values won’t work because Apache isn’t running as the same user as PHP (your browser will return a 503 error). Assuming you are using the default values for Apache (you can check these in “httpd.conf” if this doesn’t work), you will need to un-comment and edit these three lines as follows:

listen.owner = daemon
listen.group = daemon
listen.mode = 0660

Way down in the file, find and un-comment “security.limit_extensions” and append a new file extension we want to use for testing this specific version of PHP. For example:

security.limit_extensions = .php .php3 .php4 .php5 .php54

After saving that file, you also need to edit the Apache configuration:

sudo nano /usr/local/apache2/conf/httpd.conf

First you need to un-comment (remove the pound sign) from the following lines:

LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so

At the end of this file add the following (replacing the extension in the first line with whatever you added to “security.limit_extensions” and the path in the second line to match your installation):

<FilesMatch \.php54$>
    SetHandler "proxy:unix:/opt/php-5.4.43/var/run/php.sock|fcgi://localhost"
</FilesMatch>

Create an init script for this PHP installation:

sudo touch /etc/init.d/php-5.4.43-fpm
sudo chmod +x /etc/init.d/php-5.4.43-fpm
sudo nano /etc/init.d/php-5.4.43-fpm

Paste the following in there (changing directories to match):

#! /bin/sh
### BEGIN INIT INFO
# Provides:          php-5.4.43-fpm
# Required-Start:    $all
# Required-Stop:     $all
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: starts php-5.4.43-fpm
# Description:       starts the PHP FastCGI Process Manager daemon
### END INIT INFO
php_fpm_BIN=/opt/php-5.4.43/sbin/php-fpm
php_fpm_CONF=/opt/php-5.4.43/etc/php-fpm.conf
php_fpm_PID=/opt/php-5.4.43/var/run/php-fpm.pid
php_opts="--fpm-config $php_fpm_CONF"
wait_for_pid () {
        try=0
        while test $try -lt 35 ; do
                case "$1" in
                        'created')
                        if [ -f "$2" ] ; then
                                try=''
                                break
                        fi
                        ;;
                        'removed')
                        if [ ! -f "$2" ] ; then
                                try=''
                                break
                        fi
                        ;;
                esac
                echo -n .
                try=`expr $try + 1`
                sleep 1
        done
}
case "$1" in
        start)
                echo -n "Starting php-fpm "
                $php_fpm_BIN $php_opts
                if [ "$?" != 0 ] ; then
                        echo " failed"
                        exit 1
                fi
                wait_for_pid created $php_fpm_PID
                if [ -n "$try" ] ; then
                        echo " failed"
                        exit 1
                else
                        echo " done"
                fi
        ;;
        stop)
                echo -n "Gracefully shutting down php-fpm "
                if [ ! -r $php_fpm_PID ] ; then
                        echo "warning, no pid file found - php-fpm is not running ?"
                        exit 1
                fi
                kill -QUIT `cat $php_fpm_PID`
                wait_for_pid removed $php_fpm_PID
                if [ -n "$try" ] ; then
                        echo " failed. Use force-exit"
                        exit 1
                else
                        echo " done"
                       echo " done"
                fi
        ;;
        force-quit)
                echo -n "Terminating php-fpm "
                if [ ! -r $php_fpm_PID ] ; then
                        echo "warning, no pid file found - php-fpm is not running ?"
                        exit 1
                fi
                kill -TERM `cat $php_fpm_PID`
                wait_for_pid removed $php_fpm_PID
                if [ -n "$try" ] ; then
                        echo " failed"
                        exit 1
                else
                        echo " done"
                fi
        ;;
        restart)
                $0 stop
                $0 start
        ;;
        reload)
                echo -n "Reload service php-fpm "
                if [ ! -r $php_fpm_PID ] ; then
                        echo "warning, no pid file found - php-fpm is not running ?"
                        exit 1
                fi
                kill -USR2 `cat $php_fpm_PID`
                echo " done"
        ;;
        *)
                echo "Usage: $0 {start|stop|force-quit|restart|reload}"
                exit 1
        ;;
esac

Now start the new PHP service and restart Apache:

sudo service php-5.4.43-fpm start
sudo service apache2 restart

Now test it:

sudo cp /usr/local/apache2/htdocs/index.php /usr/local/apache2/htdocs/test.php54

And then open that test file in your host system’s web browser by going to “http://192.168.56.10/test.php54” to see the information on your PHP 5.4 installation.

Starting/Stopping PHP-FPM

To start/stop/restart PHP 5.4 manually:

sudo service php-5.4.43-fpm start
sudo service php-5.4.43-fpm stop
sudo service php-5.4.43-fpm restart

To set/unset PHP 5.4 to load on startup:

sudo chkconfig php-5.4.43-fpm on
sudo chkconfig php-5.4.43-fpm off

Other PHP Versions

You can follow all of the above steps for the remaining versions of PHP. Of course you need to install to a different directory for each (change the “prefix” switch for the configure line) and assign a different service name, etc. You can simplify all of these things. For example: instead of using “php-5.4.43” you could use “php-5.4” instead if you don’t plan to install any other versions of that branch of PHP.

In case there is any confusion, the options in httpd.conf are what determines the handler for each file type while he “security.limit_extensions” directive in php-fpm.conf only limits which extensions that handler will parse if they are passed to it. In other words, it doesn’t matter if every PHP instance has the same extensions in php-fpm.conf but it will cause issues if you have multiple entries in httpd.conf passing the same extension to multiple handlers. Just follow the instructions here for now. We will get more into how to configure this later.

PHP 7.0 Notes

I was unable to compile PDO_IBM/PDO_Informix statically with 7.0 beta2/beta3 easily. I was successful one time after much trial and error with editing the source files, but I can’t tell you now how I did it. Hopefully these extensions will be supported when 7.0 reaches GA status.

Most of the configuration options are not in etc/php-fpm.conf anymore. Instead, only uncomment the “pid=” line and then do the following to alter the other options (change directory as needed):

sudo cp /opt/php-7.0.0beta2/etc/php-fpm.d/www.conf.default /opt/php-7.0.0beta2/etc/php-fpm.d/www.conf
sudo nano /opt/php-7.0.0beta2/etc/php-fpm.d/www.conf

httpd.conf Contents

Once finished, the last bit of your httpd.conf might look something like this:

<FilesMatch \.php54$>
    SetHandler "proxy:unix:/opt/php-5.4.43/var/run/php.sock|fcgi://localhost"
</FilesMatch>

<FilesMatch \.php55$>
    SetHandler "proxy:unix:/opt/php-5.5.27/var/run/php.sock|fcgi://localhost"
</FilesMatch>

<FilesMatch \.php56$>
    SetHandler "proxy:unix:/opt/php-5.6.11/var/run/php.sock|fcgi://localhost"
</FilesMatch>

<FilesMatch \.php7$>
    SetHandler "proxy:unix:/opt/php-7.0.0beta2/var/run/php.sock|fcgi://localhost"
</FilesMatch>

Leave a Reply