VGTech is a blog where the developers and devops of Norways most visited website share code and tricks of the trade… Read more



Are you brilliant? We're hiring. Read more

How I set up my local PHP development environment on Mac OSX Yosemite in three easy steps

PHP

When I first started writing this post, I considered giving it a title such as “How to set up local PHP development with dynamically configured mass virtual hosting on Apache 2.4”, “Quick and easy prototyping using Liip PHP, Dnsmasq or Proxy Auto Configuration” or even “The Ultimate Guide to Rapid Development on OSX 10.10”. I did not.

In my daily job as a Development Manager, I don’t get to code very much, but when I do, I want to have a setup that allows me to quickly create development projects and prototypes in the ~/Sites folder and have them show up as vhosts automagically, without having to edit any configuration file(s).

I also want to make sure my pseudo-toplevel domain .dev resolves to localhost and any domains/subdomains i choose to create lead into the relevant web root folder.

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:

Show code
curl -s http://php-osx.liip.ch/install.sh | bash -s 5.6

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):

Show code
export PATH=/usr/local/php5/bin:$PATH

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:

Show code
. ~/.bash_profile && php -v

Thus rendering:

Show code
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

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:

Show code
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

And then this line in /private/etc/apache2/extra/httpd-userdir.conf:

Show code
Line 16: Include /private/etc/apache2/users/*.conf

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.

Show code
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>

I create the ~/Sites folder. If it were already present, I would not have to do so. Obviously.

Show code
mkdir ~/Sites

Restart the Apache server:

Show code
sudo apachectl -k restart

If I were to type:

Show code
curl -I http://localhost

in my terminal window, I would expect to see:

Show code
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

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?):

Show code
brew install dnsmasq

When the installation is done, I copy the template configuration file:

Show code
cp /usr/local/opt/dnsmasq/dnsmasq.conf.example /usr/local/etc/dnsmasq.conf

I insert the following line into /usr/local/etc/dnsmasq.conf (I can safely delete all existing lines if I want to):

Show code
address=/dev/127.0.0.1

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:

Show code
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

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:

Show code
sudo mkdir -p /etc/resolver

Then I create the configuration file and add the configuration to it:

Show code
sudo tee /etc/resolver/dev >/dev/null <<EOF
nameserver 127.0.0.1
EOF

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:

Show code
mkdir -p ~/Sites/demosite/{site/web,api/web,shop/web}

Then I create an index.html for each subdomain (site, api and shop), and fill it with some meaningful text:

Show code
for f in site api shop; do echo "This is the response from $f" > ~/Sites/demosite/$f/web/index.html; done;
Show code
demosite
├── api
│   └── web
│       └── index.html
├── shop
│   └── web
│       └── index.html
└── site
    └── web
        └── index.html

Now, when I run a few cURL requests:

Show code
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>

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:

Show code
domain
└── subdomain
    └── web
        └── index.html

 

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.

Development Manager at VG. I love @eliwie, my guitars and furry things that go "bump" in the night. @erlandwiencke


14 comments

  • docker fan

    I would have done this in Docker, so other colleagues could benefit from this and not spend time doing this on their own machines.

    You could also use it for your home office, sales people etc...


    • Erland Wiencke

      Erland Wiencke

      Hi Pål,

      Thank you for your comment.

      Docker is interesting indeed. Your suggestion might very well be my next post.

      Best regards,
      Erland

  • Abraham Estrada

    Dnsmasq gave me problems when I was on the road and didn't have internet connection.


    • Erland Wiencke

      Erland Wiencke

      Hi Abraham,

      Thanks for commenting.

      You are right, it seems that there is no DNS configuration available when your network adapter is down. I haven't found a fix for this (yet).

      Best regards,
      Erland

  • David Kuridža

    Nicely written!

    Can you please share the reason of going with dnsmasq rather than hosts file?

    Much obliged.
    d.


    • Erland Wiencke

      Erland Wiencke

      Hi David,

      Thank you for asking.

      I mention the reason (albeit a bit convoluted) in the second paragraph: "I want to have a setup that allows me to quickly create development projects and prototypes in the ~/Sites folder and have them show up as vhosts automagically, without having to edit any configuration file(s)."

      The key principle being that I do not want to have to edit the hosts file or apache configuration when I start new projects.

      It is not a perfect solution, as you can see in the comments, and I will try to figure out a better way.

      Best regards,
      Erland

  • Jan

    Hi,

    in my case i needed to enable vhost_alias_module in my httpd.conf:

    Uncomment
    "LoadModule vhost_alias_module libexec/apache2/mod_vhost_alias.so"

    Then restart apache:
    "sudo apachectl -k restart"

    From then everything works like magic.

    Cheers.


    • Erland Wiencke

      Erland Wiencke

      Hi Jan,

      Thank you for sharing. You are right; I have forgotten this vital step in my post. I shall rectify with all due haste.

      Best regards,
      Erland

  • EJTH

    Because of the fact that dnsmasq doesnt really work when there is no connectivity, I just prefer to have a simple createvhost script that does the following:

    1. Did you remember to sudo (I always forget :-))?
    2. Ask for hostname and webroot dir if not specified in arguments
    3. Create a new apache conf in sites-enabled/ dir
    4. Create new line in /etc/hosts
    5. restarts apache

    It just seems more hassle free to do it with good old hosts and a seperate config for each vhost.


    • Erland Wiencke

      Erland Wiencke

      Hi,

      Thank you for commenting.

      Yes, it might be a good idea to remove Dnsmasq from the equation entirely, since it is rather unhelpful in offline situations.

      An automated script-based solution is something I will check out. Stay tuned!

      Best regards,
      Erland

  • Brian Gilbert

    As of Yosemite dnsmasq will not resolve any of your domains when the computer is not connected to any networks, here is a video I made illustrating the issue:

    https://www.youtube.com/watch?v=3m9OI_AjCx8

    In my mind this is a bug in the way that DNS resolution behaves since the change to discoveryd


    • Erland Wiencke

      Erland Wiencke

      Hi Brian,

      Thanks for your input. Because of this behaviour I will probably start looking elsewhere for my local dev setup. There might be a blog post in the (semi) near future.

      Best regards,
      Erland

  • Ikomrad

    You don't have an IDE or source control in your development environment ?


    • Erland Wiencke

      Erland Wiencke

      Hi,

      Indeed I do. I currently use PHPStorm and all my code is in Git.

      Best regards,
      Erland

Leave your comment