Prerequisites
If you want to follow this step-by-step rendition, you are required to have the following:
- A Mac, with OSX 10.10+ (Yosemite) installed
- A working install of Apache 2.4 (comes preinstalled with OSX Yosemite)
- Homebrew installed
- A text editor and some fingers
Step 1 – installing (my preferred version of) PHP
From our good friends at Liip comes several binary PHP packages perfectly suited for development, each representing different versions of PHP, from 5.3 to the current stable version (at the time of writing version 5.6.4). I am going to install the latest version, so I open my terminal shell and execute:
1 2 3 | [code language="bash"] curl -s http://php-osx.liip.ch/install.sh | bash -s 5.6 [/code] |
This install doesn’t overwrite the PHP installed by Apple, so I have to add the path to the new PHP binary by writing this into my ~/.profile
or ~/.bash_profile
(or equivalent):
1 2 3 | [code language="text"] export PATH=/usr/local/php5/bin:$PATH [/code] |
I want to confirm that I have the freshly installed PHP executable to do my bidding, so I write into the terminal shell and press enter:
1 | [code language="bash"]. ~/.bash_profile && php -v[/code] |
Thus rendering:
1 2 3 4 5 6 | [code language="bash"] PHP 5.6.4 (cli) (built: Dec 24 2014 12:05:33) Copyright (c) 1997-2014 The PHP Group Zend Engine v2.6.0, Copyright (c) 1998-2014 Zend Technologies with Zend OPcache v7.0.4-dev, Copyright (c) 1999-2014, by Zend Technologies with Xdebug v2.2.5, Copyright (c) 2002-2014, by Derick Rethans |
1 | [/code] |
Step 2 – enable dynamic hosting under ~/Sites
To allow my hosts to reside in the ~/Sites
folder, I uncomment these three lines in /private/etc/apache/httpd.conf
:
1 2 3 4 5 | [code language="text"] LIne 160: LoadModule vhost_alias_module libexec/apache2/mod_vhost_alias.so Line 166: LoadModule userdir_module libexec/apache2/mod_userdir.so Line 493: Include /private/etc/apache2/extra/httpd-userdir.conf [/code] |
And then this line in /private/etc/apache2/extra/httpd-userdir.conf
:
1 | [code language="text"]Line 16: Include /private/etc/apache2/users/*.conf[/code] |
I create the file /private/etc/apache/users/YOURUSERNAME.conf
. I notice that this requires root privileges, so I use the sudo
command. Lines 15 to 31 are the interesting ones, as they are using wild cards in VirtualDocumentRoot, thus allowing me to resolve any combination of (in my case, maximum two) subdomains. This will be explained in the final step.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | [code language="text"] User YOURUSERNAME Group staff <Directory "/Users/YOURUSERNAME/Sites/"> AllowOverride All Options Indexes MultiViews FollowSymLinks Require all granted </Directory> <VirtualHost *:80> ServerName localhost DocumentRoot "/Users/YOURUSERNAME/Sites" </VirtualHost> <VirtualHost *:80> VirtualDocumentRoot "/Users/YOURUSERNAME/Sites/%2/site/web" ServerName sub.domain.dev ServerAlias www.*.dev </VirtualHost> <VirtualHost *:80> VirtualDocumentRoot "/Users/YOURUSERNAME/Sites/%2/%1/web" ServerName sub.domain.dev ServerAlias *.*.dev </VirtualHost> <VirtualHost *:80> VirtualDocumentRoot "/Users/YOURUSERNAME/Sites/%1/site/web" ServerName domain.dev ServerAlias *.dev </VirtualHost> [/code] |
I create the ~/Sites
folder. If it were already present, I would not have to do so. Obviously.
1 | [code language="bash"]mkdir ~/Sites[/code] |
Restart the Apache server:
1 | [code language="bash"]sudo apachectl -k restart[/code] |
If I were to type:
1 | [code language="bash"]curl -I http://localhost[/code] |
in my terminal window, I would expect to see:
1 2 3 4 5 6 | [code language="bash"] HTTP/1.1 200 OK Date: Thu, 15 Jan 2015 15:30:21 GMT Server: Apache/2.4.9 (Unix) PHP/5.6.4 Content-Type: text/html;charset=UTF-8 [/code] |
Step 3 – add a local DNS server
I want domains like www.foobar.dev
, api.foobar.dev
and foobar.dev
to end up on my local web server and finally in nice and warm web roots. As if by magic. For this, I need my development machine to respond to DNS requests for any arbitrary domain using the .dev
Top Level Domain.
Dnsmasq, I choose you (why not a .PAC file?):
1 2 3 | [code language="bash"] brew install dnsmasq [/code] |
When the installation is done, I copy the template configuration file:
1 2 3 | [code language="bash"] cp /usr/local/opt/dnsmasq/dnsmasq.conf.example /usr/local/etc/dnsmasq.conf [/code] |
I insert the following line into /usr/local/etc/dnsmasq.conf
(I can safely delete all existing lines if I want to):
1 2 3 | [code language="text"] address=/dev/127.0.0.1 [/code] |
This instructs Dnsmasq to respond to all requests ending in .dev
with 127.0.0.1
.
I also run these three commands to copy the deamon config file to its proper place, change the owner and load dnsmasq immediately:
1 2 3 4 5 | [code language="bash"] sudo cp -fv /usr/local/opt/dnsmasq/*.plist /Library/LaunchDaemons sudo chown root /Library/LaunchDaemons/homebrew.mxcl.dnsmasq.plist sudo launchctl load /Library/LaunchDaemons/homebrew.mxcl.dnsmasq.plist [/code] |
So far, I have told my machine to respond nicely, now I have to tell it to send DNS queries for .dev
(and only .dev
) to Dnsmasq. I do this by creating a resolver configuration. First I create the directory:
1 2 3 | [code language="bash"] sudo mkdir -p /etc/resolver [/code] |
Then I create the configuration file and add the configuration to it:
1 2 3 4 5 | [code language="bash"] sudo tee /etc/resolver/dev >/dev/null <<EOF nameserver 127.0.0.1 EOF [/code] |
Step 3a – testing it
This is the part where I test and see if the stuff works. I start by creating a nifty folder structure for my testproject in ~/Sites
:
1 2 3 | [code language="bash"] mkdir -p ~/Sites/demosite/{site/web,api/web,shop/web} [/code] |
Then I create an index.html for each subdomain (site, api and shop), and fill it with some meaningful text:
1 2 3 | [code language="bash"] for f in site api shop; do echo "This is the response from $f" > ~/Sites/demosite/$f/web/index.html; done; [/code] |
1 2 3 4 5 6 7 8 9 10 11 12 | [code language="bash"] demosite ├── api │ └── web │ └── index.html ├── shop │ └── web │ └── index.html └── site └── web └── index.html [/code] |
Now, when I run a few cURL requests:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | [code language="bash"] curl http://demosite.dev This is the response from site curl http://api.demosite.dev This is the response from api curl http://shop.demosite.dev This is the response from shop curl http://docs.demosite.dev <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> <html><head> <title>404 Not Found</title> </head><body> <h1>Not Found</h1> <p>The requested URL / was not found on this server.</p> </body></html> [/code] |
So What?
I am able to create new web projects fairly quickly, just by creating a few folders in ~/Sites
, adhering to this general pattern http://subdomain.domain.dev
:
1 2 3 4 5 6 | [code language="bash"] domain └── subdomain └── web └── index.html [/code] |
Post Scriptum
Why do I use Dnsmasq and not a simple .PAC file? Well, while a .PAC file certainly will do the job for browsing the .dev
sites I create, in a browser and certain other user agents, it does not play well with some (actually most) of the PHP-based testing frameworks I am using.
When testing a site from the command line using Behat, Mink and Goutte, any .PAC file implemented through OSX’s Network settings, or in a browser plugin, will not work; cURL is used, and it simply doesn’t know about this file.
This is some very tiny text to let you know that you have reached the end of this post.