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.

Improve PHP performance on Windows

Developing PHP applications which support a variety of servers and environments requires me to occasionally develop on Windows. The first thing someone notice when switching from Linux or macOS to a Windows machine for PHP development is that it’s so darned slow. Even on the exact same machine, Linux is a good 2x to 5x faster than Windows. Most of the sluggishness observer, however, is down to configuration and can be mitigated. This article explains some of the tricks I used to make PHP faster on my Windows development machines.

Read more “Improve PHP performance on Windows”

Kubuntu on the Surface Book

Kubuntu on the Surface Book

I’ve had a Surface Book with an i7 processor since November 2016. I liked the idea of a laptop with detachable tablet, the touchscreen and that it comes with a pressure sensitive pen. It makes for a neat, if slightly expensive, machine. It has a fatal flaw though: Windows 10. I tried to like Windows, I really did, but it’s just so. darned. slow. File access is inexcusably slow, especially for a device equipped with lightning fast NVMe storage. In real world usage it proved 4 to 10 times slower than Linux running on the same machine… off a USB 3 attached SSD. This relegated the Surface Book to a role of a secondary machine for me, while my primary was a cheap i5 laptop with inexpensive m2 SATA3 SSD storage (and yes, there is a huge maximum performance difference to the NVMe storage, not to mention the processor speed).

To cut a long story short, I wanted to unleash the true potential of the Surface Book using Kubuntu Linux. The end  goal is having a dual boot Windows and Linux laptop with the Linux side sporting encrypted storage (because of GDPR requirements) and working keyboard, touchpad, WiFi, card reader and touch screen at the very least. I’m glad to say, mission accomplished! I’m writing this article using Kubuntu on my Surface Book. Now, this is a comprehensive How To of what I did. I am writing this mainly for me to remember all the small details and in hope I can help some of you out there. Most, if not all, of what I am writing here also applies to Surface Pro 2 and above, Surface Book 2 (2018) and Surface Laptop.

Read more “Kubuntu on the Surface Book”

Safer backups with write-only Amazon S3 credentials

Photo of backup tapes

We can all agree that taking backups is important. We also agree that backups on the same medium as the backed up content are as good as no backup at all. This has led a lot of us to store our backups to the cloud, predominantly on cheap Amazon S3 storage. But how can we make sure that should the content server be compromised our backups will not be abused or deleted?

Read more “Safer backups with write-only Amazon S3 credentials”

Forge your own SSL certificates for local development

Photo of man forging a hot piece of iron with a hammer.

A while back I had written two blog posts about setting up an Apache, MySQL and multiple, simultaneous PHP versions environment for macOS -or Linux, same concept- and for Windows. In the meantime HTTPS has been promoted to a near necessity and so being able to build and test a site on HTTPS is very desirable. Well, as it turns out, it’s perfectly possible too!

Read more “Forge your own SSL certificates for local development”

Secure your Amazon CloudFront CDN using Let’s Encrypt SSL certificates

Amazon Web Services secured with Let's Encrypt certificates

Amazon Web Services (AWS) offers a wealth of services for site owners. A service I particularly enjoy is the inexpensive CloudFront CDN which lets me deliver static content, like downloads and update information for my software, very fast to people across the world. What became apparent is that while it was fast and cheap, it wasn’t the most secure solution. Anyone could forge the update response and mislead my users to downloading a modified package full of malware. The solution was to use an SSL certificate with the CDN, ensuring the integrity of the downloads and update information. For this purpose I used Let’s Encrypt™ which allows you to create properly singed SSL certificates for free. The process is non-obvious so I’m documenting this for you.

Read more “Secure your Amazon CloudFront CDN using Let’s Encrypt SSL certificates”

Making a portable full installation of Ubuntu on a USB HDD

Close up photo of a USB pen drive

I regularly have the need to try things out on Linux. Sometimes a virtual machine won’t cut it for me typically due to memory, disk and performance limitations. Moreover, a decent, up-to-date, bootable Linux environment is a great backup in case all of my other computers are broken, infected or stolen. That entails having the Linux installation on an external, USB-attached hard disk drive which can boot with relative ease on any UEFI-enabled PC (driver compatibility notwithstanding). Moreover, all the preparatory work has to be performed using a single-boot Windows computer without ending up having a dual boot system. It sounds tough. It is tough, but I’m writing this from my portable Ubuntu Linux installation running off a USB-attached SSD!

Read more “Making a portable full installation of Ubuntu on a USB HDD”

Add the SSH2 extension to PHP 7.0 on MAMP

If you want to use SSH or SFTP with PHP you need the SSH2 extension. Unfortunately MAMP doesn’t come with it out of the box. Last year I had written about how to add the SSH2 extension to MAMP, on PHP 5.6. In the meantime two major changes ocurred which pretty much nullified the process: OS X El Capitan was released requiring new prerequisites to be installed and PHP 7 was included which requires an entirely new approach to installing SSH2 (it’s no longer as simple as using pecl). In this article we’ll discuss the process required to get the SSH2 extension installed on PHP7 in MAMP.

Read more “Add the SSH2 extension to PHP 7.0 on MAMP”

Add the SSH2 extension to MAMP

If you want to use SSH or SFTP with PHP you need the SSH2 extension. Unfortunately MAMP doesn’t come with it out of the box. Moreover, it comes with no PHP sources making it a bit complicated to use pecl to install the extension. So here I document it, mostly for my future self and in the hope that I’ll spare a poor soul the trouble. The middle part of the instructions is a prerequisite for installing any other PHP extension, so here’s another utility to this.

Read more “Add the SSH2 extension to MAMP”