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.
Until now I was using Zend Server as my primary development environment. Since the last two major versions were becoming a bit of a resource hog. The much touted Z-Ray never really worked for me. That sealed its fate. I had to replace it with something nimbler and more convenient. I also wanted to no longer have to use MAMP to test my software across different PHP versions. I certainly did not want to have to go through the stop servers - change PHP version - start servers routine ever again. The process on Mac OS X is much more involving that what I had done on Windows last week but I was determined to succeed. And yes, I did succeed!
Disclaimer: the configuration I am proposing is only suitable for a local development server. Do try this at home and only at home – or at least not in a production server. The instructions below worked for me on Mac OS X Mavericks but should work on Lion and Mountain Lion. At the time of this writing OS X Yosemite is not released yet so your mileage may vary (read: serious breakage may occur). Another major pitfall is that the PHP binaries I am using are only supported on 64-bit Macs. If you have an old machine with an Intel Core Duo you're out of luck.
Warning: Pretty much all of the instructions have to be carried out using the command line. You can use Terminal (easy way to get there: type ⌘Space, type Terminal and hit ENTER) or another terminal emulator application such as iTerm2. If you don't feel comfortable using the command line stop reading now and download MAMP.
Setting up MySQL
Go to MySQL's download page and download the appropriate DMG package. For Mac OS X Lion and above it's the "Mac OS X 10.8 (x86, 64-bit), DMG Archive". For detailed installation instructions read MySQL's documentation page on Mac OS X installation. MySQL is installed to /usr/local/mysql
After installing MySQL, edit the file ~/.bash_profile Easy way to do it from the command line
open ~/.bash_profile
Add this line at the bottom of the file
export PATH=/usr/local/mysql/bin:$PATH
Close the terminal and open it again. Verify that you can access MySQL command line (note that it's a capital V in the command)
mysql -V
You should see something like
mysql Ver 14.14 Distrib 5.6.20, for osx10.8 (x86_64) using EditLine wrapper
Good to know: Even though MySQL auto-starts on boot, you can still start and stop MySQL from the Terminal using the commands
sudo /Library/StartupItems/MySQLCOM/MySQLCOM start sudo /Library/StartupItems/MySQLCOM/MySQLCOM stop
Pro tip: I recommend using Sequel Pro to manage your MySQL databases instead of phpMyAdmin. It's much faster.
Setting up PHP
We will be using the pre-built binaries offered at liip.ch. It's one of the binary distributions recommended on php.net and by far the easiest to install.
Install all versions of PHP you want to use. I chose all four of them. Please note that due to PHP 5.3 missing the FastCGI binary you must install it last.
curl -s http://php-osx.liip.ch/install.sh | bash -s 5.6 curl -s http://php-osx.liip.ch/install.sh | bash -s 5.5 curl -s http://php-osx.liip.ch/install.sh | bash -s 5.4 curl -s http://php-osx.liip.ch/install.sh | bash -s 5.3
Edit the file ~/.bash_profile The easy way to do it from the command line is typing
open ~/.bash_profile
Add these lines at the bottom of the file
export PHPRC=/usr/local/php5-5.4.32-20140822-093224/lib export PHP_INI_SCAN_DIR=/usr/local/php5-5.4.32-20140822-093224/php.d export PATH=/usr/local/php5-5.4.32-20140822-093224/bin:$PATH
Note that the php5-5.4.32-20140822-093224 part is most likely different on your machine. You can find out which directory to use by typing in the Terminal
ls /usr/local/ | grep php5-5.4
If you want to use a version different than PHP 5.4 as your default command line PHP executable just substitute 5.4 with 5.3, 5.5 or 5.6 in the command above.
Close the terminal application and open it again. Verify that you can access PHP command line
php -v
You should see something like
PHP 5.4.32 (cli) (built: Aug 22 2014 09:28:20) Copyright (c) 1997-2014 The PHP Group Zend Engine v2.4.0, Copyright (c) 1998-2014 Zend Technologies with Xdebug v2.2.3, Copyright (c) 2002-2013, by Derick Rethans
If you get an error you need to run the following commands in the Terminal
sudo su for d in `find /usr/local -type d -name "php5-*" -depth 1`; do \ for x in libtidy-0.99.0.dylib libintl.8.dylib libpng12.0.dylib libjpeg.7.dylib libxml2.2.dylib libfreetype.6.dylib; \ do install_name_tool -change /usr/local/php5/lib/$x $d/lib/$x $d/bin/php; \ done; \ done for d in `find /usr/local -type d -name "php5-*" -depth 1`; do \ for l in `find $d/lib/php/extensions -type f -name "*.so"`; do \ for x in libcurl.4.dylib libintl.8.dylib libidn.11.dylib libgmp.10.dylib libicui18n.46.dylib libicuuc.46.dylib \ libicudata.46.dylib libicuio.46.dylib libmcrypt.4.dylib libmemcached.11.dylib libmemcachedutil.2.dylib \ libsybdb.5.dylib libcurl.4.dylib libidn.11.dylib libintl.8.dylib libsybdb.5.dylib libpq.5.dylib \ libpq.5.dylib libssh2.1.dylib libexslt.0.dylib libxslt.1.dylib libxml2.2.dylib; \ do install_name_tool -change /usr/local/php5/lib/$x $d/lib/$x $l; \ done; \ done; \ done
These commands will relink the PHP binaries and extensions to use the correct directories for our multi-PHP server.
At this point all PHP versions try to load the same PHP modules from the PHP 5.4 directory. This won't work. Luckily, it's an easy fix with some command line kung-fu. In your trusted Terminal enter:
sudo su for d in `find /usr/local -type d -name "php5-*" -depth 1`; do \ pushd $d; massreplace='s/\/usr\/local\/php5\//\/usr\/local\/'${PWD##*/}'\//g'; sed -i .bak $massreplace $d/php.d/*.ini; popd; \ done
Setting up Apache
We will be using Apache 2.2 which is helpfully shipped with Max OS X. Unlike previous versions of Mac OS X there is no longer a GUI to activate it, so we'll have to do everything old school, using our trusted command line. That's the beauty of Mac OS X: no matter how fancy the interface grows, it's still a pretty standard BSD system under the hood. Being BSD we can go old school with it any time we want. That's exactly what makes a geek rejoice!
Enabling FastCGI support
We're using Apache 2.2 shipped with Mac OS X, but it doesn't have built-in support for FastCGI and we need it to support multiple PHP versions. No problem, we can work around that by using MacPorts to get the missing bits.
If you haven't done so already, install MacPorts. I had it installed since a long time ago so please forgive me if there's a missing step here.
Close and reopen the Terminal application. Now type
sudo port selfupdate sudo port install mod_fcgid
After mod_fcgid is downloaded (it takes several minutes to install all of the dependencies) we need to move it to somewhere Apache can find it.
sudo cp /opt/local/apache2/modules/mod_fcgid.so /usr/libexec/apache2
Edit the Apache configuration file /etc/apache2/httpd.conf You can only do that through the command line due to the advanced security model of Mac OS X Lion and later. I know, you can't use your mouse. You have to go really old school, using the keyboard. nano helpfully displays a list of common commands at the bottom rows. Press ⌃g to get the full list of keyboard commands. Remember that ^ stands for the ctrl (⌃) key and M stands for the alt (⌥) key. Therefore ^ k means hold down the ctrl key and press the K key, M a means hold down the alt key and press the A key and M A means hold down the alt and shift keys and press the A key. Congrats, you had your crash course in UNIX file editing. OK, back to the command to edit the file:
sudo nano /etc/apache2/httpd.conf
Find all the LoadModule lines. After the last one add
LoadModule fcgid_module libexec/apache2/mod_fcgid.so
Save and close the file by pressing ⌃x, then Y, then ENTER.
Now restart Apache
sudo apachectl -k restart
If you screwed up, Apache will complain. If you get no feedback after running this command it worked just fine. In true UNIX tradition you get a message only if something's wrong.
Auto-start Apache on boot
From the Terminal
sudo launchctl load -w /System/Library/LaunchDaemons/org.apache.httpd.plist
Changing the user and group of Apache
Normally Apache runs as user _www and group _www. However, if you are developing locally you may want to sacrifice security over convenience. After all, the web server only serves local requests (Mac OS X firewall blocks access to Apache by default). Edit the Apache configuration file /etc/apache2/httpd.conf like before
sudo nano /etc/apache2/httpd.conf
Find these lines
User _www Group _www
and change them to
User myuser Group staff
where myuser
is your username.
If you don't know what your username is, from another Terminal give the command
whoami
and Mac OS X will tell reply with your username.
Save and close the file by pressing ⌃x, then Y, then ENTER.
Finally, restart Apache
sudo apachectl -k restart
Configuring PHP
First we need to know which directories PHP 5.3, 5.4, 5.5 and 5.6 is installed in. From the terminal window do
ls /usr/local/ | grep php5
You should see something like
php5 php5-5.3.29-20140818-160244 php5-5.4.32-20140822-093224 php5-5.5.16-20140822-215341 php5-5.6.0RC4-20140818-155418
Note down these directory names. You will need to replace them in the instructions below.
Let's create a new web root inside your home directory
mkdir ~/Sites cd ~/Sites pwd
The final command will reply with something like
/Users/myuser/Sites
Copy that path. It's the absolute path to your web root and you will need it below.
Edit the Apache configuration file /etc/apache2/httpd.conf like before:
sudo nano /etc/apache2/httpd.conf
Find the line
Include /private/etc/apache2/other/*.conf
It should be the last line. Put a hash sign in front of it
#Include /private/etc/apache2/other/*.conf
This removes the single PHP version configuration which was added by the PHP installation step.
While we are at it, let's change the web root to the new directory we created before. Find this line
DocumentRoot "/Library/WebServer/Documents"
and change it to
DocumentRoot "/Users/myuser/Sites"
where /Users/myuser/Sites is the absolute path to your web root you previously noted down.
Important! Please remember to change all other instances of /Library/WebServer/Documents to /Users/myuser/Sites inside this file, especially the Directory open tag.
Next, find the line
#Include /private/etc/apache2/extra/httpd-default.conf
and remove the hash sign in front
Include /private/etc/apache2/extra/httpd-default.conf
Save and close the file by pressing ⌃x, then Y, then ENTER.
Now let's edit the httpd-default.conf Again, due to Mac OS X security model we need to do this from the command line
sudo nano /etc/apache2/extra/httpd-default.conf
Append these lines to the file
FcgidInitialEnv PATH "/usr/local/php5:/usr/sbin:/usr/local/bin:/usr/bin" FcgidInitialEnv TEMP "/tmp" FcgidInitialEnv TMP "/tmp" FcgidIOTimeout 64 FcgidConnectTimeout 16 FcgidMaxRequestsPerProcess 1000 FcgidMaxProcesses 50 FcgidMaxRequestLen 8131072 # Location of php.ini FcgidInitialEnv PHPRC "/usr/local/php5/lib" FcgidInitialEnv PHP_INI_SCAN_DIR "/usr/local/php5/php.d" FcgidInitialEnv PHP_FCGI_MAX_REQUESTS 1000
Save and close the file by pressing CTRL-x, then Y, then ENTER.
Now restart Apache
sudo apachectl -k restart
Test PHP
Create a new file named phpinfo.php inside the Sites directory under your home directory with the content
<?php phpinfo(); ?>
On your browser visit http://localhost/phpinfo.php You should see the PHP information page. If you have got all the instructions right you should see that right below the top line, showing the version of PHP, there is this line
php-osx.liip.ch by Liip (originally developed by www.local.ch)
If not, review all the instructions above, you missed a step.
The Virtual Hosts
Our goal is to have every site on our local server run under a different PHP version depending on the domain name we are using. The domain names we will be using are:
- local.web for PHP 5.4
- local53.web for PHP 5.3
- local55.web for PHP 5.5
- local56.web for PHP 5.6
You need to change your hosts file to have Mac OS X map these fake domains to your local server. The easiest way is using Gas Mask. Add these lines to the hosts file:
127.0.0.1 local.web www.local.web 127.0.0.1 local53.web www.local53.web 127.0.0.1 local55.web www.local55.web 127.0.0.1 local56.web www.local56.web
We will also be implementing dynamic virtual hosts. This means that when you have a directory ~/Sites/foobar you will be able to access this site both as http://local.web/foobar and http://foobar.local.web The latter is a better way to develop sites locally since most web scripts store image paths relative to the domain root. For this trick to work you’d need to add a few more lines to your hosts file:
127.0.0.1 foobar.local.web 127.0.0.1 foobar.local53.web 127.0.0.1 foobar.local55.web 127.0.0.1 foobar.local56.web
Now the site in the directory ~/Sites/foobar will be accessible as http://foobar.local.web (PHP 5.4), http://foobar.local53.web (PHP 5.3), http://foobar.local55.web (PHP 5.5) and http://foobar.local56.web (PHP 5.6). Cool! Let’s make the changes in our server’s configuration to make that happen.
Configure Apache for multiple PHP versions
Edit the /etc/apache2/httpd.conf. As we have already said, we have to use the Terminal
sudo nano /etc/apache2/httpd.conf
Find the line
# Include conf/extra/httpd-vhosts.conf
and remove the trailing hash sign so that it becomes
Include conf/extra/httpd-vhosts.conf
Save and close the file by pressing ⌃x, then Y, then ENTER.
Now edit the /etc/apache2/extra/httpd-vhosts.conf file with
sudo nano /etc/apache2/extra/httpd-vhosts.conf
and change its contents with:
NameVirtualHost *:80 # Default PHP virtual host (local.web) <VirtualHost *:80> ServerAdminThis email address is being protected from spambots. You need JavaScript enabled to view it. DocumentRoot "/Users/myuser/Sites" ServerName www.local.web FcgidInitialEnv PATH "/usr/local/php5-5.4.32-20140822-093224:/usr/sbin:/usr/local/bin:/usr/bin" FcgidInitialEnv PHPRC "/usr/local/php5-5.4.32-20140822-093224/lib" FcgidInitialEnv PHP_INI_SCAN_DIR "/usr/local/php5-5.4.32-20140822-093224/php.d" <Directory "/Users/myuser/Sites"> DirectoryIndex index.html index.php <Files ~ "\.php$"> AddHandler fcgid-script .php FcgidWrapper "/usr/local/php5-5.4.32-20140822-093224/bin/php-cgi" .php Options +ExecCGI order allow,deny allow from all deny from none </Files> </Directory> </VirtualHost> # Dynamic virtual hosts using vhost_alias, default PHP <VirtualHost *:80> ServerAlias *.local.web UseCanonicalName Off VirtualDocumentRoot "/Users/myuser/Sites/%1" FcgidInitialEnv PATH "/usr/local/php5-5.4.32-20140822-093224:/usr/sbin:/usr/local/bin:/usr/bin" FcgidInitialEnv PHPRC "/usr/local/php5-5.4.32-20140822-093224/lib" FcgidInitialEnv PHP_INI_SCAN_DIR "/usr/local/php5-5.4.32-20140822-093224/php.d" <Directory "/Users/myuser/Sites"> DirectoryIndex index.html index.php <Files ~ "\.php$"> AddHandler fcgid-script .php FcgidWrapper "/usr/local/php5-5.4.32-20140822-093224/bin/php-cgi" .php Options +ExecCGI order allow,deny allow from all deny from none </Files> </Directory> </VirtualHost> # PHP 5.5 virtual host (local55.web) <VirtualHost *:80> ServerAdminThis email address is being protected from spambots. You need JavaScript enabled to view it. DocumentRoot "/Users/myuser/Sites" ServerName www.local55.web FcgidInitialEnv PATH "/usr/local/php5-5.5.16-20140822-215341:/usr/sbin:/usr/local/bin:/usr/bin" FcgidInitialEnv PHPRC "/usr/local/php5-5.5.16-20140822-215341/lib" FcgidInitialEnv PHP_INI_SCAN_DIR "/usr/local/php5-5.5.16-20140822-215341/php.d" <Directory "/Users/myuser/Sites"> DirectoryIndex index.html index.php <Files ~ "\.php$"> AddHandler fcgid-script .php FcgidWrapper "/usr/local/php5-5.5.16-20140822-215341/bin/php-cgi" .php Options +ExecCGI order allow,deny allow from all deny from none </Files> </Directory> </VirtualHost> # Dynamic virtual hosts using vhost_alias, PHP 5.5 <VirtualHost *:80> ServerAlias *.local55.web UseCanonicalName Off VirtualDocumentRoot "/Users/myuser/Sites/%1" FcgidInitialEnv PATH "/usr/local/php5-5.5.16-20140822-215341:/usr/sbin:/usr/local/bin:/usr/bin" FcgidInitialEnv PHPRC "/usr/local/php5-5.5.16-20140822-215341/lib" FcgidInitialEnv PHP_INI_SCAN_DIR "/usr/local/php5-5.5.16-20140822-215341/php.d" <Directory "/Users/myuser/Sites"> DirectoryIndex index.html index.php <Files ~ "\.php$"> AddHandler fcgid-script .php FcgidWrapper "/usr/local/php5-5.5.16-20140822-215341/bin/php-cgi" .php Options +ExecCGI order allow,deny allow from all deny from none </Files> </Directory> </VirtualHost> # PHP 5.6 virtual host (local56.web) <VirtualHost *:80> ServerAdminThis email address is being protected from spambots. You need JavaScript enabled to view it. DocumentRoot "/Users/myuser/Sites" ServerName www.local56.web FcgidInitialEnv PATH "/usr/local/php5-5.6.0RC4-20140818-155418:/usr/sbin:/usr/local/bin:/usr/bin" FcgidInitialEnv PHPRC "/usr/local/php5-5.6.0RC4-20140818-155418/lib" FcgidInitialEnv PHP_INI_SCAN_DIR "/usr/local/php5-5.6.0RC4-20140818-155418/php.d" <Directory "/Users/myuser/Sites"> DirectoryIndex index.html index.php <Files ~ "\.php$"> AddHandler fcgid-script .php FcgidWrapper "/usr/local/php5-5.6.0RC4-20140818-155418/bin/php-cgi" .php Options +ExecCGI order allow,deny allow from all deny from none </Files> </Directory> </VirtualHost> # Dynamic virtual hosts using vhost_alias, PHP 5.6 <VirtualHost *:80> ServerAlias *.local56.web UseCanonicalName Off VirtualDocumentRoot "/Users/myuser/Sites/%1" FcgidInitialEnv PATH "/usr/local/php5-5.6.0RC4-20140818-155418:/usr/sbin:/usr/local/bin:/usr/bin" FcgidInitialEnv PHPRC "/usr/local/php5-5.6.0RC4-20140818-155418/lib" FcgidInitialEnv PHP_INI_SCAN_DIR "/usr/local/php5-5.6.0RC4-20140818-155418/php.d" <Directory "/Users/myuser/Sites"> DirectoryIndex index.html index.php <Files ~ "\.php$"> AddHandler fcgid-script .php FcgidWrapper "/usr/local/php5-5.6.0RC4-20140818-155418/bin/php-cgi" .php Options +ExecCGI order allow,deny allow from all deny from none </Files> </Directory> </VirtualHost>
PHP 5.3 is left as an exercise to the reader.
Please remember to change /Users/myuser/Sites with the path to your web root. You will most likely need to change the paths to each individual PHP version, e.g. php5-5.6.0RC4-20140818-155418 with whatever is the directory name of your PHP 5.6 version.
Save and close the file by pressing ⌃x, then Y, then ENTER.
Now, let's restart Apache one final time
sudo apachectl -k restart
Test the solution
We can now use four different domains to access the same content as different PHP versions. Let’s try it:
http://www.local.web/phpinfo.php reports PHP 5.4
http://www.local53.web/phpinfo.php reports PHP 5.3
http://www.local55.web/phpinfo.php reports PHP 5.5
http://www.local56.web/phpinfo.php reports PHP 5.6
All three URLs load the same file /Users/myuser/Sites/phpinfo.php.
Outro
At some point you'll probably need to change the PHP configuration. Inside each one of the /usr/local/php5-SOMETHING directories there's the php.d/99-liip-developer.ini file. This is where you are supposed to add your custom configuration variables. Since this file loads last it overrides the configuration in all other files. Also remember that each PHP version has its own INI file. Moreover, since PHP 5.3 loads as an Apache module it requires you to restart Apache before the changes have any effect.
If you are looking for the server access and error log files they can be found at the /var/log/apache2 directory. If you would rather remove your eye from its socket with a rusty fork instead of using the terminal to read the logs, I feel for you and have to propose a solution: just install Pimp My Log. It will make your life decidedly easier.
If you are trying to score extra geek points and make your PHP development process a tad easier you might also be interested in PHP Error, especially its "I want it outside my project, but running on all dev sites" configuration. Since you're running all PHP versions, except 5.3, as FastCGI you can put the PHP configuration value php_error.autorun = On to enable it or php_error.force_disabled = On to disable it inside your .htaccess file.
Finally, remember that you can always install this custom server alongside MAMP as long as MAMP is set to use its custom ports (8888 for the web server and 8889 for MySQL).
Good luck and have fun!
thanks for the post man.
Is there any other way to work it out?