Custom PHP versions in MAMP PRO (macOS)

I wanted to test my software against PHP 7.3 on macOS instead of just my Linux machines and / or Docker containers. According to MAMP’s support “You cannot compile your own entire versions of PHP in MAMP PRO”. This is of course a ridiculous lie; they do it themselves so surely I can! This article walks you step by step to my process of adding PHP 7.3.0 to MAMP 5.2 on this lazy Saturday afternoon.

Caveats

“It’s possible” does not mean “it’s easy”. This procedure is not for the faint of heart, if you don’t have experience building stuff from source (and troubleshoot cryptic compile error message) or if you’re in a mad dash to deliver software. You will stumble. You will fall. You will curse at your computer. You will be frustrated. You will be searching online cryptic error messages only to find a million pages from five years ago which do not apply. YOU WILL SUFFER. Is it worth it? Hell yeah. That’s how you get to learn stuff. If you want quick results just wait a few months for MAMP PRO to include a new version of PHP. It will save you time, money and frustration (and keep you blissfully unaware of the gruesome process of compiling software from scratch). Proceed at your own risk and peril.

Appsolute, the makers of MAMP, make it unnecessarily complicated – read: impossible – to compile your own Apache modules by not shipping the build headers for Apache. Therefore we are only building PHP CLI and CGI/FastCGI, not as an Apache module. This is not a big deal, though. The recommended way to run PHP 5.3 and later is through FastCGI. Just select the “Individual PHP version for every host (CGI mode)” option. FastCGI is also the only way PHP works on NginX.

Likewise, they do not ship build headers for anything except PHP itself – definitely not for any of the build dependencies. We are going to use Homebrew to install the required PHP build dependencies. The actual caveat is that you can’t just pluck the compiled PHP from your computer and install it on your mate’s. Not unless you also install the dependencies outlined below with Homebrew.

I compiled most PHP modules but I left some out because I don’t use them and don’t have the time to troubleshoot their build dependencies. This should not be an issue for most people.

Finally, you will need to edit your PHP version’s php.ini template file and manually enable additional PHP extensions. Unlike MAMP, I try to build most of the extensions as “shared”, i.e. you have to load them through php.ini. I sometimes need to disable stuff to see if my code fails gracefully.

Prerequisites

I have two Macs at my disposal. One Mac Mini which was upgraded from macOS Sierra to High Sierra to Mojave; and a MacBook Pro which was freshly installed with Mojave. I used the former to come up with the instructions and the second, “virgin” machine to verify that everything works. If you get compilation errors check what you have installed in Homebrew and where you have linked it to. This is the source of all problems and please don’t ask me to help you; just like you, I search the error message I am getting and plow through the results until I find something which seems like a promising solution. OK. Let’s get started, then!

Before you begin your quest to a custom PHP version in MAMP you need a sane build environment with all the PHP dependencies installed.

First, install XCode from the App Store. This is Apple’s all-in-one development package which includes system headers and the all important build toolchain (C compiler, automake, autoconf and so on and so forth). 

Now open XCode and accept the license. It will take a while. This is the part where XCode installs the software and header files we need to build PHP. If you forget to do that you will get compilation errors when building PHP. After it’s done installing you can quit XCode. We don’t need the actual application running.

Apple has made it unnecessarily complicated to build software from the command line by tucking away all of the libraries and headers in a non-standard folder. For macOS Mojave you need to run this from a Terminal

sudo installer -pkg /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg -target /

This puts everything into the standard folders, e.g. /usr/include. Please check that you have the /usr/include folder after running this command. If you don’t, PHP compilation WILL fail and you’ll spend two hours trying to figure out what the heck is going on. Been there. Obviously, if you do not have Mojave you need to replace the 10.14 in the command above with your macOS version number.

Next up, install Homebrew. If you’re not absolutely new to macOS you already know about it. For everyone else, Homebrew is the de facto package manager for macOS. It allows us to install and upgrade libraries and tools in a simple and sensible manner. Follow the instructions on the link above to install Homebrew. At the time of this writing (December 2018) you need to open Terminal and enter:

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

Now we will have to install the dependencies for building PHP and a bunch of its extensions. Open a Terminal and enter the following:

brew install argon2
brew install aspell
brew install autoconf
brew install curl-openssl
brew install enchant
brew install freetds
brew install freetype
brew link --overwrite freetype
brew install gd
brew install gdbm
brew install gettext
brew install glib
brew install gmp
brew install icu4c
brew install imap-uw
brew install jpeg
brew install libpng
brew install libpq
brew install libsodium
brew install libssh2
brew install libxml2
brew install libxslt
brew install libzip
brew install mhash
brew install net-snmp
brew install openldap
brew install openssl
brew install pcre
brew install pkgconfig
brew link pkgconfig
brew install readline
brew install sqlite
brew install tidy-html5
brew install unixodbc
brew install webp

Please note that I have only tried compiling PHP 7.1, 7.2 and 7.3. If you are compiling an older or newer version of PHP you may have to add more build dependencies.

MAMP’s directory structure

Let’s pause for a second and understand what we are doing. MAMP stores its files in several different places. These locations are not documented but they are not that hard to reverse engineer.

Every PHP version is stored in /Applications/MAMP/bin/php/phpX.Y.Z where X.Y.Z is your PHP version. Major caveat, X, Y and Z must all be integers without anything else. This means you cannot install alphas, betas or RC versions unless you lie. For example, if you were to install PHP 7.3.0RC6 you’d be installing it as php7.3.0. Anyway. This folder contains a mostly standard PHP installation folder layout. The exceptions are two directories.

The /Applications/MAMP/bin/php/phpX.Y.Z/conf folder contains the pear.conf file (which we don’t use), the default php.ini file (which is not used) and the default php.ini.temp MAMP-specific template file (which is NOT used). This folder must exist but its contents are not used during MAMP’s regular operation, they are only used when MAMP is installing a new PHP version.

The /Applications/MAMP/bin/php/phpX.Y.Z/modules folder is where MAMP expects to find the PHP Apache module file (libphp7.so). Major caveat! If the file does not exist MAMP will not show this PHP version in its interface. But we can’t build that file. So… are we screwed? Well, no. We can just create a zero byte file and MAMP will work happily.

There’s another important location: /Users/your_user/Library/Application Support/appsolute/MAMP PRO/templates/phpX.Y.Z.ini.temp. This is the MAMP-specific php.ini template file you edit through MAMP’s File, Edit Template, PHP (php.ini) submenu. This file is parsed when MAMP is starting the servers, creating the real php.ini file in /Library/Application Support/appsolute/MAMP PRO/conf/phpX.Y.Z.ini. Note that the REAL file is in /Library, NOT /Users/your_user/Library.

Remember the location of the real, generated php.ini file when you want to call PHP from the command line. If you do not specify this configuration file with the -c switch then the default /Applications/MAMP/bin/php/phpX.Y.Z/conf/php.ini file will be used instead which is NOT the one you configure through MAMP PRO’s interface! That is, call PHP CLI as:

php -c /Library/Application\ Support/appsolute/MAMP\ PRO/conf/phpX.Y.Z.ini your-script.php

Compile your own PHP version

Go ahead and download the PHP version you want from the official PHP site. I downloaded and extracted PHP 7.3.0 inside ~/php, a subfolder I created for this reason.

You can blindly use the following code in Terminal changing just the first line. Replace 7.3.0 with your PHP version.

MY_PHP_VERSION=7.3.0

# Download and extract PHP
mkdir ~/php
cd ~/php
curl "http://it2.php.net/get/php-$MY_PHP_VERSION.tar.bz2/from/this/mirror" -o "php-$MY_PHP_VERSION.tar.bz2" -L
tar xjf php-$MY_PHP_VERSION.tar.bz2
cd ~/php/php-$MY_PHP_VERSION

# Important! We need to tell the system to use HomeBrew's pkg-config
# to get the correct dependencies during the build process.
export PATH=/usr/local/bin/:$PATH

# Compile the new PHP version and install it under MAMP's folder
./configure \
--prefix=/Applications/MAMP/bin/php/php$MY_PHP_VERSION \
--sysconfdir=/Applications/MAMP/bin/php/php$MY_PHP_VERSION/conf \
--enable-phpdbg \
--enable-phpdbg-webhelper \
--with-layout=GNU \
--with-config-file-path=/Library/Application\ Support/appsolute/MAMP\ PRO/conf \
--enable-cli \
--enable-cgi \
--enable-ipv6 \
--enable-libxml \
--with-openssl=$(brew --prefix openssl) \
--with-kerberos \
--with-pcre-regex \
--with-pcre-jit \
--with-zlib \
--with-bz2 \
--enable-bcmath \
--enable-calendar \
--enable-ctype \
--with-curl=shared,$(brew --prefix curl-openssl) \
--enable-dba=shared \
--with-gdbm=$(brew --prefix gdbm) \
--enable-inifile \
--enable-flatfile \
--enable-dom \
--enable-exif \
--enable-fileinfo \
--enable-filter \
--enable-ftp \
--with-openssl-dir=$(brew --prefix openssl) \
--with-gd=shared \
--with-gettext=$(brew --prefix gettext) \
--with-gmp=$(brew --prefix gmp) \
--with-mhash=$(brew --prefix mhash) \
--with-iconv \
--with-imap=shared,$(brew --prefix imap-uw) \
--with-imap-ssl=$(brew --prefix openssl) \
--enable-intl=shared \
--enable-json \
--with-ldap=shared,$(brew --prefix openldap) \
--enable-mbstring \
--enable-mbregex \
--with-mysqli=shared,mysqlnd \
--with-unixODBC=$(brew --prefix unixodbc) \
--enable-opcache \
--enable-opcache-file \
--enable-pcntl \
--enable-pdo \
--with-pdo=shared \
--with-pdo-sqlite=shared \
--with-pdo-mysql=shared,mysqlnd \
--with-pdo-odbc=shared,unixODBC,$(brew --prefix unixodbc) \
--with-pdo-pgsql=$(brew --prefix libpq) \
--with-pgsql=$(brew --prefix libpq) \
--enable-phar \
--enable-posix \
--with-pspell=shared,$(brew --prefix aspell) \
--enable-shmop=shared \
--enable-soap=shared \
--enable-sockets \
--with-sodium=$(brew --prefix libsodium) \
--with-password-argon2=$(brew --prefix argon2) \
--enable-sysvmsg=shared \
--enable-sysvsem=shared \
--enable-sysvshm=shared \
--enable-tokenizer \
--enable-wddx \
--enable-xml \
--with-xmlrpc=shared \
--with-xsl \
--with-pear \
&& make -j 8 \
&& make install -j 8

# Create the expected configuration folder
mkdir /Applications/MAMP/bin/php/php$MY_PHP_VERSION/conf
# Create a php.ini template, copying the one used by MAMP's PHP 7.2.10
cp /Applications/MAMP/bin/php/php7.2.10/conf/php.ini.temp /Applications/MAMP/bin/php/php$MY_PHP_VERSION/conf/php.ini.temp
# Copy the php.ini template to its expected folder
cp /Applications/MAMP/bin/php/php$MY_PHP_VERSION/conf/php.ini.temp ~/Library/Application\ Support/appsolute/MAMP\ PRO/templates/php$MY_PHP_VERSION.ini.temp
# Create a default php.ini file
cp php.ini-development /Applications/MAMP/bin/php/php$MY_PHP_VERSION/conf/php.ini
# Copy the default php.ini file to its expected folder
mkdir /Applications/MAMP/conf/php$MY_PHP_VERSION
cp /Applications/MAMP/bin/php/php$MY_PHP_VERSION/conf/php.ini /Applications/MAMP/conf/php$MY_PHP_VERSION/php.ini
# Create the CGI helper
cat << EOF > /Applications/MAMP/fcgi-bin/php$MY_PHP_VERSION.fcgi
#!/bin/sh
export PHP_FCGI_CHILDREN=4
export PHP_FCGI_MAX_REQUESTS=200
exec /Applications/MAMP/bin/php/php$MY_PHP_VERSION/bin/php-cgi -c "/Library/Application Support/appsolute/MAMP PRO/conf/php$MY_PHP_VERSION.ini"
EOF

# Fake installation of Apache PHP module. THIS IS JUST TO TRICK MAMP.
mkdir /Applications/MAMP/bin/php/php$MY_PHP_VERSION/modules
touch mkdir /Applications/MAMP/bin/php/php$MY_PHP_VERSION/modules/libphp7.so

Tip: The make commands have a -j 8 switch. This means “run up to 8 tasks in parallel”. That’s because I have a Mac Mini with a 4-core Intel Core i7 processor with Hyperthreading. This means each core can run two threads at the same time, thus 4 cores x 2 threads per core = 8 threads simultaneously. If you have a dual-core processor, as in with most MacBook Pro and Air machines, set this to -j 4. Note that this utilises your processor optimally, reduces the compilation time but will make your Mac run hot.

At this point you need to stop MAMP’s servers, quit MAMP PRO completely and then start it again. Now go to the PHP tab. Here’s your new PHP version!

Before you get all too excited you should go to File, Edit Template, PHP (php.ini) and select your PHP version. You need to edit the extension=… lines. You can find the extensions you have compiled under the /Applications/MAMP/bin/php/php7.3.0/lib/php/20180731 folder. Remember that 7.3.0 and 20180731 in that path depend on which PHP version you have compiled. You are interested only in the .so files.

My section of extension loads looks like this:

;MAMP_apc_MAMPextension=apcu.so
;MAMP_apc_MAMPextension=apc.so
extension=curl.so
extension=dba.so
extension=gd.so
extension=imap.so
extension=intl.so
extension=ldap.so
extension=mysqli.so
extension=pdo_mysql.so
extension=pdo_odbc.so
extension=pdo_sqlite.so
extension=pspell.so
extension=shmop.so
extension=soap.so
extension=sysvmsg.so
extension=sysvmem.so
extension=sysvshm.so
extension=xmlrpc.so
;MAMP_Imagick_MAMPextension=imagick.so
;MAMP_Tidy_MAMPextension=tidy.so
;MAMP_Oauth_MAMPextension=oauth.so
;MAMP_Igbinary_MAMPextension=igbinary.so
;MAMP_Memcached_MAMPextension=memcached.so
;MAMP_Redis_MAMPextension=redis.so
;extension=uploadprogress.so
;extension=yaml.so

Note that all of the extensions controlled by MAMP are actually not built since they are either not part of the PHP distribution (they are installed through PECL) or I didn’t bother building them. You will see how to build them in the next section.

Compiling additional PHP extensions

You can do that by typing the following in Terminal (change the first line to reflect your PHP version):

MY_PHP_VERSION=7.3.0
export PATH=/usr/local/bin/:$PATH
export PATH=/Applications/MAMP/bin/php/php$MY_PHP_VERSION/bin:$PATH
export PHP_PEAR_PHP_BIN="/Applications/MAMP/bin/php/php$MY_PHP_VERSION/bin/php"
# You only need to run channel-update after the first time you install a new PHP version
pecl channel-update pecl.php.net

Now on the same Terminal session you can install PHP extensions using pecl install. For example:

pecl install redis

or

pecl install igbinary

For some extensions you need to pass an explicit version number. For example, for the all important XDebug debugging extension you need to run:

pecl install xdebug-2.7.0beta1

Some extensions may need prerequisites to be installed. For example:

brew install pcre
brew link pcre
pecl install oauth

At the time of this writing not all PHP extensions are compatible with PHP 7.3. I was unable to compile memcached, apc, apcu and ssh2. That’s what you get for trying the bleeding edge. This is also my response to the people asking “why are you not using PHP 7.3 in production yet?”. It’s simply not ready if you rely on certain extensions. That’s why there’s an overlap of actively maintained releases, to give time to developers to adapt their extensions and PHP software developers to test before migrating to an infrastructure of unknown compatibility status. That’s the whole point of self-compiling the very latest PHP version: get our software ready.

Set up NginX and PHP for development on Mac OS X

If you want to start developing PHP applications, or merely work on your PHP-based site off-line, on Mac OS X you can easily do so. In this how-to we’ll see how you can set up NginX, a high performance web server, with the PHP version shipped with Mac OS X itself to create a local web server. In case you’re wondering, you can of course use it in parallel with MAMP, XAMPP or even the multi-PHP version server I’ve described in an earlier post.

Read more “Set up NginX and PHP for development on Mac OS X”

Apache, MySQL, PHP server on Mac OS X with multiple, simultaneous PHP versions

My last week’s blog post on running Apache, MySQL, PHP server on Windows with multiple, simultaneous PHP versions seems to have been a smash hit. This week we’ll be doing the same thing on Mac OS X. For those of you who didn’t click the link, I decided it would be a cool, geeky project to implement an Apache-MySQL-PHP web server without using a pre-packaged server like MAMP or Zend Server. My goal was to have the same sites run under different versions of PHP by just visiting a different URL on my browser. This makes cross-PHP testing of sites a piece of cake.

Read more “Apache, MySQL, PHP server on Mac OS X with multiple, simultaneous PHP versions”

Tools of the trade – Mac OS X edition

I’ve written in the past about the various tool I had been using to produce the web software I publish. I have recently converted to Mac OS X. I had promised you that I’d evaluate my web development experience on that platform and come back with a blog post about it. Here you go! This post is about all the tools I am using day after day and how they improved my productivity as a web developer.

Read more “Tools of the trade – Mac OS X edition”