Zen PHP Development Installation and Setup on a Mac

I’m the kind of guy who hates wasted effort, especially when it comes to software. I hate to spend $2k+ on a computer only to have software houses use that extra power to add (yet) another layer of abstraction to reduce development costs, and make my computer slower.

This makes me love Macs and iPhones. Apple has been faithful to a complex but fast C-based computer programming language (Objective C) and has taken the time (and cost) to fine-tune and perfect their code. They are also building an even more efficient future by creating and investing in technologies such as OpenCL, LLVM, Swift, Metal, and the Apple File System.

As a result, my vintage/obsolete 2Gb late 2010 Macbook Air is running the latest macOS Sierra almost just as smoothly as the latest Mac hardware, and it’s my go-to Mac for travel. Could I use it for Web development? Can we setup a PHP development environment with the littlest amount of overhead, so that everything is fast and smooth, even on that good old Macbook Air?

Let’s find out.

First of all: What is Zen?

“Zen” may not mean the same for you as it does for me.
For me, a Zen development environment…

…is safe: I install as few applications/utilities as possible, and the few I have to install come from sources I trust. In this article I trusted Apple and Oracle.

…is easy to update: a built-in operating-system / application update mechanism should be able to update all or most of my setup. In this article only MySQL will need to be manually updated.

…doesn’t make your Mac slower when you’re not developing: You have a life outside your developer persona. And your $2k+ Macbook Pro should open Facebook just as fast as your iPad.

…doesn’t take up lots of space on your SSD storage: If I need a 50kb utility, why would I need to install a 5Gb “Utility Package”?

…mimics the typical resources you’d get on a Web hosting plan: My target resources are Linux, Apache, MySQL, PHP (LAMP) and the ability to send e-mail messages. I try to avoid the latest-and-greatest PHP features as the Web application would be less portable across Web hosts, so my target is a high PHP 5 version, but nothing specific. If you have other needs, you may need to find a more flexible solution (say, Homebrew).

In this article I will not go into the IDE you should be using. There are many out there: Coda, Sublime Text, Atom, and my personal favorite, PHPStorm. Measure your own value for money and go for it. Don’t look back. No regrets. :-)

The Mac as a development platform

Unlike Windows, but very much like Unix/Linux, the Mac is very developer-friendly, and we’re going to see that most of the things we need are easily added to our system using standard macOS commands and apps.

There are many alternatives to this setup, though.

Some developers prefer to use virtual machines for Web development. They are useful to ensure a development platform that perfectly matches your production server, but they’re not Zen for me: they duplicate operating system installations, waste RAM by keeping two separate operating systems running, and so on. In fact you don’t really want an exact match of your production server because you want detailed error logging, PHP debugging services and so on.

Some other developers prefer to use MAMP or some Homebrew setup. These allow you to be very specific in which software versions you’re using, but they’re not Zen for me: we would be doubling the number of installed Apache, PHP and other resources, while keeping the ones that came with macOS disabled and wasted. Plus, you need to trust a myriad of software sources that you’re installing on your Mac with access to your data.

macOS is better than that. Let’s start setting it up. :-)


Setting up Linux

macOS is built on a modified version of FreeBSD and FreeBSD is a Unix-compatible operating system, just like Linux. So the Mac has got you covered there.

It’s even got a terminal shell (command line) that you can use, just like regular Unix systems. Just search for the application named “Terminal” and run it. Try it now, as we’re going to be spending lots of time in this application.

Terminal

A quick note: you stop most command-line programs by pressing Ctrl+C. Some others you quit by pressing Q, or something specific for them.

Sudo and Nano

Occasionally we’re going to need to change protected system files. The easiest way to do so is to use a command-line text editor, with “super-user” permissions (“super-user” is Unix terminology for computer administrator).

In Terminal, type:

sudo nano -w

Remember to hit “Enter” (↩︎) at the end of a command. You may need to type your Mac login password, if asked. Nothing is shown on screen while you type your password, this is normal. Just hit Enter again when you’re done.

sudo stands for “super-user do” and should prefix any command that you want to run with super-user permissions. This is why you need to type your password, but it only works if you’ve logged into your Mac with an administrator user. Alternatively, you can select which standard users can run Sudo.

The nano command or program is a text editor very simular to macOS’ own TextEdit, but made for the command-line. After the nano command we have the “-w” option to Nano. This tells Nano not to wrap long lines to make them fit the screen. That is better suited for editing configuration files, as we’ll be doing throughout this article.

You should now be seeing something like this:

You use the arrow keys and type, as you would in TextEdit. A useful keyboard shortcut is Ctrl+W which helps you search for text in the currently open file.

You leave/exit this program by pressing Ctrl+X, as shown in the bottom-left corner (^ typically means Ctrl in Unix terminology). If you made any changes to the text file, Nano will ask you if you want to save the file before leaving.

Setting up Apache

Apache comes pre-installed in macOS. In Terminal, type:

httpd -v

Remember to hit “Enter” (↩︎) at the end of a command.
You should get something like:

Server version: Apache/2.4.23 (Unix)
Server built: Aug 8 2016 16:31:34

We’re going to need to know our user name from the operating system point of view. Type:

whoami

Take note of the name that is displayed on screen after you hit “Enter”. That is your user name.

Now let’s open Apache’s configuration file.

sudo nano -w /etc/apache2/httpd.conf

Add or modify the following lines (use Ctrl+W to search, if you wish).

Here:

  • myuser is you user name, as you took note of before,
  • address@yoursite.com is a real e-mail address you own,
  • /path/to/yoursite is the full path where your local Web site files are.
User myuser
LoadModule php5_module libexec/apache2/libphp5.so
ServerAdmin address@yoursite.com
DocumentRoot "/path/to/yoursite"
Options None
<Directory />
Options None
AllowOverride None
Order Allow,Deny
Deny from All
</Directory>
<Directory /path/to/yoursite>
Options ExecCGI IncludesNOEXEC SymLinksIfOwnerMatch
AllowOverride All
<IfVersion >= 2.4>
Require all granted
Satisfy all
</IfVersion>
Order Allow,Deny
Allow from All
</Directory>

Exit and save.

Finally, re (re)start Apache to get the latest configuration:

sudo apachectl restart

You can now check wether your site works by going to localhost in your browser.

But remember, there is still stuff to do, so not everything may be working at this point.

.htaccess

You may want to use per-directory Apache configuration files. These should be named “.htaccess”. However, once you name a file as such, they disappear from macOS’ Finder, and the easiest way to edit them becomes to use the command-line. Some synchronization services will also skip those files because they’re hidden.

My solution for this issue is to tell Apache that there is an alternative name for these files which is “!htaccess” (leading exclamation mark rather than dot). If you like this, then add/modify these lines in httpd.conf as well:

AccessFileName .htaccess !htaccess
<FilesMatch "^[.!]([Hh][Tt]|[Dd][Ss]_[Ss])">
Require all denied
</FilesMatch>

Remember to save the file and restart Apache.

Setting up MySQL

This database server was pre-installed on macOS up to macOS Lion (10.7).

On later macOS versions, you can download MySQL Community Server for free from its Web site. Select the DMG Archive as it’s easier to install.

The server has no application you can run. Like Apache, it is a service that runs in the background. You will, however, find a new preference pane in System Preferences for MySQL.

The MySQL Preferences pane

As I do little Web development these days, I tend to leave the MySQL server not running on startup, and prefer to come to this pane and start it manually when I need to.

Your IDE may take care of connecting to MySQL and allow you to create users and database catalogues. Instead, you may prefer MySQL’s “native” application for this, like I do: MySQL Workbench.

Setting up PHP

The PHP interpreter is a development tool that is included with Xcode. If you’re running macOS version 10.9 (Mavericks) or higher you can install Xcode’s command-line tools, including PHP, without installing Xcode itself.

xcode-select --install

If this doesn’t work for you, just go to your Mac’s App Store app, search for Xcode and install it. Open it and install the command-line tools.

Let’s add the ability to debug a PHP script and do some MySQL fine-tuning. First check what is the latest version of the PHP debugger extension:

ls /usr/lib/php/extensions/

Take note of the directory that looks something like “no-debug-non-zts-20131226” but has the latest date as part of its name.

Now let’s open PHP configuration file:

sudo nano -w /etc/php.ini

Add or modify the following lines (use Ctrl+W to search, if you wish). Here, zts-directory is the directory name you took note of earlier.

pdo_mysql.default_socket = /tmp/mysql.sock
mysql.default_socket = /tmp/mysql.sock
mysqli.default_socket = /tmp/mysql.sock
zend_extension="/usr/lib/php/extensions/zts-directory/xdebug.so"
xdebug.remote_enable=1
xdebug.remote_host=127.0.0.1
xdebug.remote_port=9000
xdebug.remote_autostart=1
xdebug.extended_info=1
; Disable features to speed up Xdebug and PHP:
xdebug.coverage_enable=0
xdebug.profiler_enable=0
xdebug.profiler_enable_trigger=0
xdebug.trace_enable_trigger=0

Exit and save.

Test your PHP configuration:

php -v

You should get about 4 lines of version information, without any error messages.

PEAR and PECL

The PHP Extension and Application Repository (PEAR) and the PHP Extension Community Library (PECL) are PHP library repositories that are also bundled with macOS. But you need to set them up.

sudo php /usr/lib/php/install-pear-nozlib.phar
pear config-set php_ini /etc/php.ini
pecl config-set php_ini /etc/php.ini

You may want to add /usr/lib/php/pear to the include_path of php.ini.

PHPDocumentor

PHPDocumentor is a PHP PEAR module that generates human-readable manuals from documentation embedded in your own PHP file comments.

If you’ve already set up PEAR and PECL, installing it is easy:

sudo pear channel-discover pear.phpdoc.org
sudo pear install phpdoc/phpDocumentor-alpha

You must also increase memory_limit to at least 128M in php.ini.

Setting up the ability to send e-mail messages

Sites send e-mails all the time: to newly registered users, to confirm password changes, etc. Wouldn’t it be nice if you could test this on your development machine?

It turns out you can. Out of the box.

There are two major contenders for this task in Unix/Linux systems: Sendmail and Postfix. The latter is my personal preference due to its compatibility with Sendmail but better performance and security. This is also what macOS provides.

Let’s start by configuring an e-mail address for your Mac. Because your Mac is (likely) not part of a domain name, it will send some e-mails with a sender that is not understood outside of your Mac. This means that any errors delivering such messages cannot be sent back, and you won’t receive them.

By giving your Mac a real e-mail address, this problem is solved. We do this in Postfix’s aliases and generic mapping files. Let’s start with aliases.

sudo nano -w /etc/aliases

Change the root user’s e-mail address to a real e-mail address you own (address@yoursite.com, in this screenshot).

Editing /etc/aliases

Exit and save. Now compile this file:

sudo newaliases

Now let’s move on to the generic mapping file. But first, a few changes to main.cf.

sudo nano -w /etc/postfix/main.cf

Add or modify the following lines (use Ctrl+W to search, if you wish).

queue_directory = /var/spool/postfix
data_directory = /var/lib/postfix
smtp_generic_maps = hash:/etc/postfix/generic

Exit and save. Now let’s find you computer’s host name.

hostname

Take note of the name that is shown in Terminal. That is your computer’s host name.

Now let’s open the generic mapping file.

sudo nano -w /etc/postfix/generic

Add the following entries at the end of the file, where mymac is your computer’s host name and address@yoursite.com is a real e-mail address you own:

@mymac.home             address@yoursite.com
@mymac.local address@yoursite.com
@mymac.localdomain address@yoursite.com
@mymac address@yoursite.com
@localhost address@yoursite.com
@localhost.local address@yoursite.com
@localhost.localdomain address@yoursite.com
@localdomain.local address@yoursite.com
@localdomain address@yoursite.com

Exit and save. Compile it:

sudo postmap /etc/postfix/generic

And now check everything is correct.

sudo postfix check

If you only get “unused parameter” warnings, then you’re ok.

In macOS, the Postfix service doesn’t run constantly. This reduces its memory footprint. Instead, any changes to specific Postfix files that hold e-mail pending to be delivered cause Postfix to be started, if it’s not yet running. Let’s set that up.

sudo nano -w /System/Library/LaunchDaemons/org.postfix.master.plist

Under this line:

<string>/var/spool/postfix/maildrop</string>

Add the following two lines, if they’re not already there:

<string>/var/spool/postfix/active</string>
<string>/var/spool/postfix/deferred</string>

Exit and save. Now restart this system so that these changes take effect.

sudo launchctl unload  /System/Library/LaunchDaemons/org.postfix.master.plist
sudo launchctl load -w /System/Library/LaunchDaemons/org.postfix.master.plist

Both commands should be typed on a single line: i.e., you press “Enter” only after “plist”.

That’s it! Now when PHP calls the Postfix executable (or its Sendmail alias), the message is added to the maildrop file, macOS notices a change to that file and starts the main Postfix service to deliver that message.


One last thing…

local.yoursite.com

Let’s add one more thing to make our (developer) lives easier.

Using the predefined DNS name localhost for your Web site has two issues: first, browsers recognize this as a special name and relax some checks, so your Web site tests may not work the same way as on a live site, hiding bugs. Second, it makes it difficult to test more than one site at a time, even if you create separate folders for each, as you may prefer to use root-based URLs in your code, and site localhost/yoursite would interfere with localhost/customersite.

The solution is simple: we create special domain names that will map to our own Mac rather than the Internet. If my site on the Internet has the address www.yoursite.com, I would use local.yoursite.com as its local copy on my Mac, but you can use any domain name you prefer.

This is done by editing the /etc/hosts file. See its contents now by typing the command:

cat /etc/hosts

The /etc/hosts file predates the DNS. In fact, DNS was created to fix these huge hosts files that had to exist on every Internet-connected computer.

Its syntax is very simple:

  • Lines beginning with # are comments
  • Empty lines are ignored
  • Remaining lines must have an IP (v4) number followed by a domain name, and map that domain name to that IP

You can see that by default, the localhost name is defined in the hosts file in this exact syntax. 127.0.0.1 is the reserved IP number that always means “this computer”.

So, we now edit that file:

sudo nano -w /etc/hosts

We add the following line at the end:

127.0.0.1 local.yoursite.com

Remember to leave and save the file. Nothing needs to be restarted.

Now, every time we visit local.yoursite.com on a browser, the browser will try to connect to the local computer, i.e., to the local Apache. As long as Apache recognizes the domain as a different site, it will show different content for that site.

In order for Apache to do so, let’s edit its configuration file.

sudo nano -w /etc/apache2/httpd.conf

Delete the entire existing <Directory /path/to/yoursite></Directory>section.

Add or modify the following lines.

Here:

  • address@yoursite.com is a real e-mail address you own,
  • /path/to/yoursite is the full path where your local Web site files are.
<VirtualHost *:80>
ServerName local.yoursite.com
ServerAdmin "address@yoursite.com"
DocumentRoot "/path/to/yoursite"
Options +ExecCGI
php_admin_value open_basedir "/path/to/yoursite/:/tmp/"
php_admin_value sendmail_from "address@yoursite.com"
php_admin_value user_agent "PHP @ local.yoursite.com"
<Directory /path/to/yoursite>
Options ExecCGI IncludesNOEXEC SymLinksIfOwnerMatch
AllowOverride All
<IfVersion < 2.4>
Order Allow,Deny
Allow from All
</IfVersion>
<IfVersion >= 2.4>
Require all granted
</IfVersion>
</Directory>
</VirtualHost>

You should repeat this set of lines for each local domain you wish to have served by Apache.

Exit and save.

Finally, (re)start Apache to get the latest configuration:

sudo apachectl restart

Now going to local.yoursite.com should work!

If you work on a few sites like I do, this built-in solution is best for you. If you work on multiple sites for customers, then you can either just create a single local.test.com where you move each site you’re working on in and out of its root path — which frankly I think it’s the easiest thing to do — or use Chris Mallinson’s dnsmasq solution, which still requires you to setup a new virtual site in Apache for each site you work on.

Ta-da!

And that’s it! No bloat, just using macOS’ built-in features and MySQL, you have a full blown PHP development environment. PEAR, PECL, PHPDocumentor, Zend debugging and full DNS names included.

Now that’s what I call Zen!