Famous last words: uh ... the PHP 8 migration guide did mention there were some backwards compatible changes, right? On our training website I wrote about the Strange Case of ArrayObject. Now it's time to have a look at a very obscure reference in the migration guide referring to changes in custom PHP installation: the migration to pkg-config. Before I get into what changed and its significance, we need to step back for a moment and review the compile process.
Many of us are used to either having PHP already available on our web servers, or having it provided for us through an operating system package manager (e.g. for Redhat/Fedora/CentOS includes yum). In addition, many of us have used meta-packages such as XAMPP, providing not only PHP, but Apache, MariaDB and Perl as well. So the natural question arises: why bother compiling PHP from source?
The answer in most cases can be boiled down to two situations:
The Need for Speed
In this situation you need to produce a stripped-down version of PHP only containing exactly what's needed for a particular application to produce the lightest possible footprint and best performance.
Testing Future Versions
The second situation is where a DevOp needs to test drive an upcoming version of PHP that has not yet been released.
The source of the source (er ... pun intended ... I know: pretty bad, eh?) depends on what you're looking for. Here's the short list of locations:
Supported Stable Releases
PHP source code for released versions is available from the php.net downloads page: https://www.php.net/downloads.php. All currently supported stable versions area available here in the following formats: bzip2, gzip and tar compressed. You can also obtain the Windows binaries here as well.
Non Supported Historical Releases
If you're looking for an earlier version of PHP, all past versions are made available here: https://www.php.net/releases/. This page contains the source code for PHP versions 7, 5, 4 and even 3 (!). The formats available vary as some compression technologies were not available at the time. You'll also see, in some cases, Windows installer versions.
Master Source Repository
The master PHP source code repository was moved over to github some years ago. The main URL is this: https://github.com/php/php-src. Github allows you to download source code in either ZIP format or directly using the command `git clone` (only if you have `git` installed on your computer). To find the desired version PHP, you first need to switch to the correct branch. Branches are shown in ascending order, so you'll need to scroll down a bit to locate PHP 8.0.
For those who have not yet compiled their own version of PHP from source, after downloading the source code, the normal procedure is as follows:
Now, without a doubt, you're wondering what in heck is configure and what is make?
configure accepts a series of command line options and produces a makefile.
The makefile is a script that, as mentioned in the GNU documentation, tells make what to do.
An analogy might be to the relationship between a composer.json file and composer.
make is a tool that assembles strings of paths and directives, and calls the appropriate compiler. It can also copy and update configuration files as needed. make is not tied to C language, but is commonly used to ease the process of compiling C code.
The focus of this article is on changes (first introduced in PHP 7.4) made to configuration option flags during the custom compile process. Many options have switched from --with-xxx to --enable-xxx ... and vice versa. Also, in a number of cases, configure options that included a directory reference have been simplified. Thus, where you would formerly specify an option as --with-xxx-dir=/yyy you now use this syntax: --with-xxx.
It can get a little crazy. Here's one example: prior to PHP 7.4, when custom compiling PHP, you might specify the following configure option if your application needed XML as well as the Zip extensions:
# configure --enable-libxml --with-libxml-dir=/usr --with-libzip
When compiling PHP 8, however, the option flags are flipped around, and you end up with this:
# configure --with-libxml --enable-zip
Note that --with* and --enable* have been flipped. You'll also note that the former --with-libxml-dir=/usr option has been removed and rolled into the simplified form --with-libxml. I think, at this point, we can all agree that the second example is much more streamlined and concise, despite the crazy flipping between with and enable. However ... you are probably wondering, as was I, how is this possible? Hows does the compiler know where to find the associated OS libraries? In order to properly solve this mystery, I introduce a new player: pkg-config.
Okay ... first things first. What the heck is pkg-config? pkg-config is a tool used to help manage applications or library compile-time options. As with make, it's not specifically tied to C language, but is often used when compiling C source code. pkg-config manages a list of operating system packages and the location of files associated with each package. As for the followup question, what does this have to do with PHP, think back for a second to the example shown just above. Why should we, as PHP developers, have to instruct the compiler as to the location of the directory structure containing files for this or that operating system package ... when such a tool already exists? Click! Lightbulb goes on! This is where pkg-config comes into play. Let's now have a look at a list of configure options no longer needing a directory path as an argument.
This list is not complete, however, as there are a couple of other extensions affected by the change.
The list mentioned above clearly shows that your life as a custom PHP producer has just been made way easier: all you need to do is to make sure pkg-config knows where everything is located (more on that later!). So ... life is good, and can you now go get that beer? Uh ... not quite yet. There are a couple of other configure related changes that are going to make your life slightly more complicated. Sorry! OK, taking a deep breath, let's dive in.
The intl extension is needed if you maintain code for internationalized websites. It's also used by many frameworks, including Symfony, Magento, CakePHP and Laminas, to name a few. The previous configure option flag, --with-icu-dir, is now removed. However, if you enable it using the new configure option --enable-intl then libicu is required. Not only that, but the operating system ICU (International Components for Unicode) version must be 50.1 or greater.
Working with the libxml extension is a bit trickier. As I alluded in the simple configure example shown above, the --with-libxml-dir and --enable-libxml options have been removed, both are now rolled into a single option --with-libxml. Also, no directory path is required as the information is retrieved from pkg-config. The extension with the most complex changes is the GD extension.
First of all, to be sure everybody is on the same page, the GD extension is hands-down the most widely used PHP extension for handing graphics. GD originally stood for GIF Draw, but, early on, GIF support was actually withdrawn due to legal issues relating to a Unisys patent. The patent expired in 2004, however, and GIF processing returned to the extension. Historically the extension also provides support for JPG, PNG and SWF images. Accordingly, there are several OS libraries needed to make it all work. Prior to PHP 7.4, you needed to supply directory paths for all of these underlying libraries. This, to use the vernacular, was a pain in the butt (pain in the bottom for the English-speaking readers). Accordingly, the following changes are now in effect:
To further simplify matters these two options have been removed: --with-png-dir and --with-zlib-dir. However, you now need to ensure that the OS has both the libpng and zlib libraries. You also have the option to leverage a custom libgd using the --with-external-gd configure option, which does require a directory path. For an overview of configure option changes, have a look at this php.net documentation reference for PHP 7.4. Granted, it's for the previous version, but everything here is directly applicable to compiling PHP 8: https://www.php.net/manual/en/migration74.other-changes.php#migration74.other-changes.pkg-config. So that about wraps up the backwards incompatible changes when compiling PHP 8. Let's now have a look at an example of a custom compile using the Linux for PHP Docker image. (Aha ... here comes the shameless plug, right? You knew there had to be a catch!)
Shameless Plug: for those of you who have not yet encountered Linux for PHP (LfPHP for short),
it's a Docker image you can use for PHP testing and development that includes not only a custom-compiled version of PHP,
but also a complete LAMPP stack, including (among many other things) Apache 2.4, MariaDB, MongoDB, OpenSSL, OpenLDAP, PostgreSQL, Perl, Python and Ruby.
What many folks don't know (assuming you knew this much already!) is that a full set of tools are available allowing you to
download and compile your own version of PHP, including wget, git, gcc,
make, configure and, of course our new friend pkg-config.
Even More Shameless Plug: if you want to deploy your new LfPHP-based image to the Internet, have a look at our cost-effective LfPHP Cloud Services plans!
LfPHP images are based upon ASCLinux, a custom version of Linux developed by Andrew Caya, specifically designed for Docker and cloud environments. (Full disclosure: Andrew is the CEO of LfPHP Cloud Services, of which I am a partner.) What I like about this version of Linux is that it's lean and mean: stripped down, minus the usual overhead and baggage associated with X Windows desktops. This means, of course, that you can only interact via the command line. However as it's main purpose is to serve up web-based applications in a cloud environment, you can connect to it using your browser. Also, as it's a Docker container, you can mount a volume that maps to a directory on your host computer, allowing you to modify source code without having to mess with the command line inside the container. If you are interested in ASCLinux roots, have a look at the Linux from Scratch project. The current set of images are available here: https://hub.docker.com/u/asclinux. As you scroll down the page you'll notice many different versions of PHP are available (even including PHP 8.0-dev!). For the purposes this illustration we will compile a custom version of PHP 8.0.
There are two ways to compile a custom version of PHP. One technique is to download any of the current set of LfPHP images from docker hub, and then, either in your Dockerfile, or from a command line shell, use lfphp-compile, a utility included with LfPHP. This one command gives you a new version of PHP ... just like that! Here is the command line string you could execute to trigger the auto-compile option for any version of PHP. In this example we compile PHP 5.6.40. (Hint: the version number can be any number corresponding to one of the PHP branches you can find on https://github.com/php/php-src as shown above.)
docker run -dit -p 8181:80 asclinux/linuxforphp-8.2-ultimate:src /bin/bash -c "lfphp-compile 5.6.40"
You then simply wait for a few minutes for the compile to finish, and voila, instant any-version of PHP. If you're impatient, just find the running container ID, and open a command shell into the container to see what's going on:
docker container ls docker run -it CONTAINER_ID /bin/bash top
Here's a screen shot of the compile in process (using top):
But I will now throw a monkey wrench into the works: let's say that we also want to include the latest version of libzip. Further, let's say we want to specify our own compile flags. Using the auto-compile option won't work. Fortunately LfPHP also provides a manual compile option.
To perform a manual compile against the base LfPHP image, all you really need to do is to run the same docker command as above, but leave off the call to lfphp-compile:
docker run -dit -p 8181:80 asclinux/linuxforphp-8.2-ultimate:src /bin/bash
The next step is to download the source code. As mentioned earlier in this article, you can clone the PHP source repo, and checkout the desired branch. Alternatively, from github, locate and download the ZIP file.
cd /root wget https://github.com/php/php-src/archive/PHP-8.0.zip unzip PHP-8.0.zip
As the source code was drawn directly from github, there's one additional step needed: running buildconf. This step is not required if you download from https://php.net/downloads.php.
cd /root/php-src-PHP-8.0 ./buildconf --force
Here is the output:
root@5b8acc1dcf00 [ ~/php-src-PHP-8.0 ]# ./buildconf --force buildconf: Checking installation buildconf: autoconf version 2.69 (ok) buildconf: Forcing buildconf. The configure files will be regenerated. buildconf: Cleaning cache and configure files buildconf: Rebuilding configure buildconf: Rebuilding main/php_config.h.in buildconf: Run ./configure to proceed with customizing the PHP build.
Another difference between using source from github.com rather than php.net/downloads is that the directory structure after unzipping from github has a prefix php-src-.
We are now ready to rock N roll! Without further ado, let's compile PHP.
As mentioned above, the first step is to run configure. This requires a string of options that varies depending on what you want in your custom version. Use the built-in help to see what options are currently available configure --help. For the purposes of this article, here is the option string used (NOTE: the trailing backslash ("\") is used to indicate that the command should be all on a single line):
./configure \ --sysconfdir=/etc \ --localstatedir=/var \ --datadir=/usr/share/php \ --mandir=/usr/share/man \ --enable-fpm \ --with-fpm-user=apache \ --with-fpm-group=apache \ --with-config-file-path=/etc \ --with-zlib \ --enable-bcmath \ --with-bz2 \ --enable-calendar \ --enable-dba=shared \ --with-gdbm \ --with-gmp \ --enable-ftp \ --with-gettext=/usr \ --enable-mbstring \ --enable-pcntl \ --with-pspell \ --with-readline \ --with-snmp \ --with-mysql-sock=/run/mysqld/mysqld.sock \ --with-curl \ --with-openssl \ --with-openssl-dir=/usr \ --with-mhash \ --enable-intl \ --with-libdir=/lib64 \ --enable-sockets \ --with-libxml \ --enable-soap \ --enable-gd \ --with-jpeg \ --with-freetype \ --enable-exif \ --with-xsl \ --with-pgsql \ --with-pdo-mysql=/usr \ --with-pdo-pgsql \ --with-mysqli \ --with-pdo-dblib \ --with-ldap \ --with-ldap-sasl \ --enable-shmop \ --enable-sysvsem \ --enable-sysvshm \ --enable-sysvmsg \ --with-tidy \ --with-expat \ --with-enchant \ --with-imap=/usr/local/imap-2007f \ --with-imap-ssl=/usr/include/openssl \ --with-kerberos=/usr/include/krb5 \ --with-sodium=/usr \ --with-zip \ --enable-opcache \ --with-pear \ --with-ffi
Here is a screenshot from the start of the configure process:
You are now ready to proceed with the next set of commands. Bear in mind that each additional configure option causes another library to be compiled, thus lengthening the time it takes to run make and make test. Run make clean if you've already run make once unsuccessfully and need to do it again. make test is optional, however, for the greater good of the PHP community please run this and send any reports back to the PHP core team.
make make test make install
Here is the output from make install:
And, of course, the final test, see if PHP is installed and is the correct version:
At this point you are free to commit the container in order to preseve the image for future use. Instructions on that can be found on the Docker website: https://docs.docker.com/engine/reference/commandline/commit/. And, as the saying goes, that's all folks.
There are certain situations where a custom-compiled version of PHP is desired. One such is where you want to to strip it down to its bare minimum requirements in order to achieve the greatest possible speed. In other situations, developers, testing groups, and people involved with technical documentation may need access to a version of PHP that has not yet been officially released. One key takeaway you get from this article is that the configuration options have changed in PHP 8, with a greater reliance on the operating system utility pkg-config. In particular, configure options pertaining to the GD extension have radically changed.
An ideal tool for testing a custom PHP installation is Linux for PHP. As you read in this article, there is a src tagged image that allows you to do both an automated custom PHP installation as well as going through the manual process. An excellent overview of the LfPHP manual compile process can be found here: https://hub.docker.com/r/asclinux/linuxforphp-8.2-ultimate.
Finally, the notes for this article, along with a shell script and Dockerfile, can be found here: https://github.com/phpcl/lfphp_custom_php_source. Thanks for reading and happy compiling!