Updating PHP 5 (or any other) Apps with Docker

PHP5, released in 2014, has reached its end of life in 2018, but many hosters continued to support it. If you have any custom PHP applications, you might still be running them on a server that has a PHP5 runtime.

The upgrade path for these applications might not be obvious, but I found a way that should make it easier, so in this article we're going to have a look at running PHP simultaneously with PHP5 and PHP7 using docker and docker-compose.

PHP 7 Breaking Changes

PHP 7 introduced lots of breaking changes like different variable accessing, foreach loops and also deprecating and removing functions like:

  • call_user_method()
  • call_user_method_array()
  • ereg() - Regular expression match
  • eregi() — Case insensitive regular expression match
  • ereg_replace() - Replace regular expression
  • eregi_replace() - Replace regular expression case insensitive

Alright, so this means that we need to take all those functions and replace them with their new and improved counterpart, enter Docker!

Here's the example PHP code we'll use in this example (index.php):

<?php

// will not work in PHP7
$string = 'XYZ';
if (eregi('z', $string)) {
    echo "'$string' contains a 'z' or 'Z'!";
}

// will work in PHP7
$string_new = 'XZY';
preg_match('/z|Z/', $string_new, $matches);
if(count($matches)>0){
    echo "'$string_new' contains a 'z' or 'Z'!";
}
?>

Docker PHP5 / PHP7 in parallel

For my case, I needed to port some code from PHP5 to PHP7, because running outdated software is an easy security vulnerability.

I needed a way to run the code in parallel with different PHP versions, to be sure that A: it still works, and B: it works as it did before.

Even though some changes are breaking like the removal of the eregi() function, the alternative function that you're supposed to use, already works fine on PHP5.

So basically what I had to do was to get it running in both versions (for the most part). I created two directories for the different Dockerfiles and then used docker-compose up to run them both at once.

The Dockerfiles for the different PHP versions we can lock by the following differences:

# Base image
FROM php:5.6-apache

and

# Base image
FROM php:7.3-apache

The full Dockerfiles are pasted below.

Docker Compose: Mounting the Code

To mount our code into the different containers we'll make use of the volumues property of our services. Lest's have a look at our docker-compose.yml and break it down below:

version: "3.1"
services:
  web1:
    build: docker/php5-6-apache2
    container_name: php5-container
    volumes:
      - ./php-project-src:/var/www/html/public
    ports:
      - "8080:80"
  web2:
    build: docker/php7-3-apache2
    container_name: php7-container
    volumes:
      - ./php-project-src:/var/www/html/public
    ports:
      - "8081:80"

As you can see it has two nearly identical sections describing the services web1 and web2, which both mount the source code from the php-project-src directory into the container's web server directory at /var/www/html/public and they each get their own Dockerfile from the different directory through the build property.

If I know run docker-compose up, I get the output of both docker containers and they're listening on localhost:8080 and localhost:8081.

Bash output:

Starting php5-container ... done
Starting php7-container ... done
Attaching to php7-container, php5-container
php5-container | [Sun Feb 09 21:12:13.770033 2020] [mpm_prefork:notice] [pid 1] AH00163: Apache/2.4.25 (Debian) PHP/5.6.40 configured -- resuming normal operations
php5-container | [Sun Feb 09 21:12:13.770070 2020] [core:notice] [pid 1] AH00094: Command line: 'apache2 -D FOREGROUND'
php7-container | [Sun Feb 09 21:12:13.782147 2020] [mpm_prefork:notice] [pid 1] AH00163: Apache/2.4.38 (Debian) PHP/7.3.11 configured -- resuming normal operations

In my browser I can now see that the PHP7 container at port 8081 is not happy with me attempting to use the horrible and deprecated eregi function:

Fatal error: Uncaught Error: Call to undefined function eregi() in /var/www/html/public/index.php:5 Stack trace: #0 {main} thrown in /var/www/html/public/index.php on line 5

While at http://localhost:8080 everything is tip top and the output is:

'XYZ' contains a 'z' or 'Z'!
'XZY' contains a 'z' or 'Z'!

That's it, now we can work on the source code, comment and uncomment parts and hack away until our project is fully ported to PHP7!

If you found this useful, consider saying hello on twitter or joining a code stream on twitch :)

Full PHP5, PHP7 Dockerfiles

# Base image
FROM php:5.6-apache

# Fix debconf warnings upon build
ARG DEBIAN_FRONTEND=noninteractive

# Run apt update and install some dependancies needed for docker-php-ext
RUN apt update &amp;&amp; apt install -y apt-utils sendmail mariadb-client pngquant unzip zip libpng-dev libmcrypt-dev git \
  curl libicu-dev libxml2-dev libssl-dev

# Install PHP extensions
# RUN docker-php-ext-install mysqli bcmath gd intl xml curl pdo_mysql pdo_sqlite hash zip dom session opcache

# Update web root to public
# See: https://hub.docker.com/_/php#changing-documentroot-or-other-apache-configuration
ENV APACHE_DOCUMENT_ROOT /var/www/html/public
RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf
RUN sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf

# Enable mod_rewrite
RUN a2enmod rewrite
# Base image
FROM php:7.3-apache

# Fix debconf warnings upon build
ARG DEBIAN_FRONTEND=noninteractive

# Run apt update and install some dependancies needed for docker-php-ext
RUN apt update &amp;&amp; apt install -y apt-utils sendmail mariadb-client pngquant unzip zip libpng-dev libmcrypt-dev git \
  curl libicu-dev libxml2-dev libssl-dev sqlite3

# Install PHP extensions
RUN docker-php-ext-install mysqli bcmath gd intl xml pdo_mysql hash dom session opcache

# Update web root to public
# See: https://hub.docker.com/_/php#changing-documentroot-or-other-apache-configuration
ENV APACHE_DOCUMENT_ROOT /var/www/html/public
RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf
RUN sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf

# Enable mod_rewrite
RUN a2enmod rewrite
Tagged with: #docker #Linux #PHP

Thank you for reading! If you have any comments, additions or questions, please tweet or toot them at me!