Skip to content

Instantly share code, notes, and snippets.

@command-tab
Last active December 18, 2015 19:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save command-tab/5279a6b0de8edc779fdb to your computer and use it in GitHub Desktop.
Save command-tab/5279a6b0de8edc779fdb to your computer and use it in GitHub Desktop.

So You Want to Develop PHP Locally

This guide is intended for developers who want to write and run PHP on their local Mac. It is expected that you have some familiarity with Terminal commands, as well as opening and saving text files with an editor of your choice.

Install Xcode Command Line Tools

Available from the Apple Developer Center, the Xcode Command Line Tools are all that Homebrew needs to build and install software on the Mac. The Xcode app is only needed if you intend to also to OS X or iOS development.

Install Homebrew

Installing Homebrew is as easy as running the installer script right from the Homebrew GitHub repo:

ruby -e "$(curl -fsSL https://raw.github.com/mxcl/homebrew/go)"

Then, to validate your Homebrew setup, run:

brew doctor

The ideal output from brew doctor is Your system is ready to brew. If you see that, you're ready to continue. If you see otherwise, follow the instructions provided by Homebrew and re-run brew doctor until it is satisfied.

Install nginx

Installing nginx with Homebrew is the simplest of Homebrew commands:

brew install nginx

Install nginx startup script

Homebrew's nginx details instruct you to setup the launchctl start/stop script as a plist file buried in your home directory. We're going to put them elsewhere, and here's why:

If you want nginx to run on the standard web port of 80 instead of a high port number like 8080, you'll need to run the nginx master process as root. Ports under 1024 are privileged ports, and require root privileges to bind to for listening. Thankfully, nginx's process model is such that the master process does not actually handle web requests, but only manages worker processes; If a malformed web request were to compromise an nginx process, it would be that of a worker process, and the scope of any damage would be limited to the domain of the user the worker is running as (i.e. your user, and not root, thus protecting the OS).

So, with that in mind, let's create a system level place for nginx's plist to reside, and then symlink the provided plist into that directory:

sudo mkdir -p /Library/LaunchDaemons
sudo cp /usr/local/opt/nginx/*.plist /Library/LaunchDaemons/homebrew.mxcl.nginx.plist
sudo chmod 644 /Library/LaunchDaemons/homebrew.mxcl.nginx.plist

Later, you'll encounter LaunchAgents and you may wonder how they differ. A good overview can be found in the launchd documentation, but the short answer is:

Location Purpose
~/Library/LaunchAgents Agents that run as your user when you log in
/Library/LaunchAgents Agents that run when anyone logs in
/Library/LaunchDaemons Agents that run with root privileges at boot
/System/Library/LaunchAgents System-wide agents that run with root privileges when anyone logs in
/System/Library/LaunchDaemons System-wide agents that run with root privileges at boot

Configure nginx

Like most UNIX services, nginx has a configuration file that lets you change almost any aspect of its behavior. Here's a simple configuration file that will start worker processes as your user. Drop the contents of this file in /usr/local/etc/nginx/nginx.conf, making sure to replace "collin" with the output of the whoami command as run in your Terminal:

user collin staff;
worker_processes 2;

events {
    worker_connections 1024;
}

http {
    include mime.types;
    default_type text/plain;
    server_tokens off;
    sendfile on;
    tcp_nopush on;
    keepalive_timeout 10;
    client_max_body_size 256m;
    gzip on;
    gzip_comp_level 2;
    gzip_proxied any;
    gzip_types text/plain text/css text/javascript application/json application/x-javascript text/xml application/xml application/xml+rss;
    index index.html index.php;
    include sites/*.conf;
}

The last line of that configuration file tells nginx to include all files ending in .conf in the sites directory. After configuring nginx to communicate with PHP, we'll setup such a site.

Also create a place for nginx logs to be written to:

rm -f /usr/local/var/log/nginx
mkdir -p /usr/local/var/log/nginx/

We'll use that location for logs for each site.

For the individual details about each site, create a directory to store the configuration files we will create later:

mkdir /usr/local/etc/nginx/sites

Similarly, create a place in your home directory to hold the content for each site:

mkdir -p ~/Sites

Configure nginx to communicate with PHP

We will also be installing PHP shortly, and nginx worker processes need to know how to route PHP requests to the php-fpm process that we will set up. Drop the contents of this file into /usr/local/etc/nginx/php.conf:

fastcgi_intercept_errors on;
location ~ \.php$
{
    fastcgi_split_path_info          ^(.+\.php)(/.+)$;

    fastcgi_param PATH_INFO          $fastcgi_path_info;
    fastcgi_param PATH_TRANSLATED    $document_root$fastcgi_path_info;
    fastcgi_param QUERY_STRING       $query_string;
    fastcgi_param REQUEST_METHOD     $request_method;
    fastcgi_param CONTENT_TYPE       $content_type;
    fastcgi_param CONTENT_LENGTH     $content_length;
    fastcgi_param SCRIPT_NAME        $fastcgi_script_name;
    fastcgi_param SCRIPT_FILENAME    $document_root$fastcgi_script_name;
    fastcgi_param REQUEST_URI        $request_uri;
    fastcgi_param DOCUMENT_URI       $document_uri;
    fastcgi_param DOCUMENT_ROOT      $document_root;
    fastcgi_param SERVER_PROTOCOL    $server_protocol;
    fastcgi_param GATEWAY_INTERFACE  CGI/1.1;
    fastcgi_param SERVER_SOFTWARE    nginx;
    fastcgi_param REMOTE_ADDR        $remote_addr;
    fastcgi_param REMOTE_PORT        $remote_port;
    fastcgi_param SERVER_ADDR        $server_addr;
    fastcgi_param SERVER_PORT        $server_port;
    fastcgi_param SERVER_NAME        $server_name;

    fastcgi_read_timeout             60;
    fastcgi_buffers                  256 16k;
    fastcgi_buffer_size              32k;

    fastcgi_pass                     unix:/usr/local/var/run/php-fpm.sock;
    fastcgi_index                    index.php;
}

Note that the location block handles requests that end in .php. Combined with the fastcgi_pass parameter, which references a UNIX socket, we are able to send PHP requests to a socket where php-fpm will be listening when we set it up shortly. The majority of the other options add parameters to the request that nginx will send to PHP. You may recognize them as global variables that PHP offers access to, and this is how request related parameters get from nginx to PHP.

Starting and stopping nginx

To start nginx, invoke launchctl with sudo, since nginx's master process runs as root. It will spawn worker processes as the user specified in nginx.conf.

sudo launchctl load /Library/LaunchDaemons/homebrew.mxcl.nginx.plist

Similarly, to stop nginx, run the same command, replacing load with unload:

sudo launchctl unload /Library/LaunchDaemons/homebrew.mxcl.nginx.plist

To verify that nginx started correctly, open the Activity Monitor application, choose All Processes from the top-right menu, and filter by "nginx". You should see one nginx process running as root (the master process) and two workers running as your user.

Install PHP

The standard PHP version maintained by Homebrew isn't nearly as full-featured as the one maintained by josegonzalez on GitHub, so we'll use his version:

brew tap homebrew/dupes
brew tap josegonzalez/homebrew-php
brew install php54 --with-fpm

That last brew install step will take some time; PHP takes a while to build from source, so if your Terminal appears stuck at make for a spell, that's why. Go make a sandwich or something.

If you'd also like to be able to manipulate images with Imagick, do step-by-step debugging, or work with MongoDB, you can also install those modules:

brew install php54-imagick
brew install php54-xdebug
brew install php54-mongo

You can run brew search php54 to see a full list of other modules that can be installed and used.

Install php-fpm startup script

As with nginx, PHP also has a startup script to install:

mkdir -p ~/Library/LaunchAgents
ln -sfv /usr/local/opt/php54/*.plist ~/Library/LaunchAgents

Note that this time, unlike with nginx, we're putting the plist into a directory in your home folder, so all of PHP's processes will run as your user. No root-level privileges are required, since it will be configured to listen on a UNIX socket.

Configure php

Here's a shortened (seriously) PHP configuration file you can drop into /usr/local/etc/php/5.4/php.ini:

[PHP]
engine = On
short_open_tag = Off
asp_tags = Off
precision = 14
y2k_compliance = On
output_buffering = 4096
zlib.output_compression = Off
implicit_flush = Off
unserialize_callback_func =
serialize_precision = 17
allow_call_time_pass_reference = Off
safe_mode = Off
safe_mode_gid = Off
safe_mode_include_dir =
safe_mode_exec_dir =
safe_mode_allowed_env_vars = PHP_
safe_mode_protected_env_vars = LD_LIBRARY_PATH
disable_functions =
disable_classes =
expose_php = On
max_execution_time = 30
max_input_time = 60
memory_limit = 512M
error_reporting = E_ALL
display_errors = On
display_startup_errors = On
log_errors = On
log_errors_max_len = 1024
ignore_repeated_errors = Off
ignore_repeated_source = Off
report_memleaks = On
track_errors = Off
html_errors = Off
error_log = /usr/local/var/log/php.error.log
variables_order = "GPCS"
request_order = "GP"
register_globals = Off
register_long_arrays = Off
register_argc_argv = Off
auto_globals_jit = On
post_max_size = 256M
magic_quotes_gpc = Off
magic_quotes_runtime = Off
magic_quotes_sybase = Off
auto_prepend_file =
auto_append_file =
default_mimetype = "text/html"
doc_root =
user_dir =
enable_dl = Off
file_uploads = On
upload_max_filesize = 256M
max_file_uploads = 20
allow_url_fopen = On
allow_url_include = Off
default_socket_timeout = 60

[Date]

[filter]

[iconv]

[intl]

[sqlite]

[sqlite3]

[Pcre]

[Pdo]

[Pdo_mysql]
pdo_mysql.cache_size = 2000
pdo_mysql.default_socket=/tmp/mysql.sock

[Phar]

[Syslog]
define_syslog_variables  = Off

[mail function]
SMTP = localhost
smtp_port = 25
mail.add_x_header = On

[SQL]
sql.safe_mode = Off

[MySQL]
mysql.allow_local_infile = On
mysql.allow_persistent = On
mysql.cache_size = 2000
mysql.max_persistent = -1
mysql.max_links = -1
mysql.default_port = 3306
mysql.default_socket = /tmp/mysql.sock
mysql.default_host =
mysql.default_user =
mysql.default_password =
mysql.connect_timeout = 60
mysql.trace_mode = Off

[MySQLi]
mysqli.max_persistent = -1
mysqli.allow_persistent = On
mysqli.max_links = -1
mysqli.cache_size = 2000
mysqli.default_port = 3306
mysqli.default_socket = /tmp/mysql.sock
mysqli.default_host =
mysqli.default_user =
mysqli.default_pw =
mysqli.reconnect = Off

[mysqlnd]
mysqlnd.collect_statistics = On
mysqlnd.collect_memory_statistics = Off

[OCI8]

[PostgresSQL]
pgsql.allow_persistent = On
pgsql.auto_reset_persistent = Off
pgsql.max_persistent = -1
pgsql.max_links = -1
pgsql.ignore_notice = 0
pgsql.log_notice = 0

[bcmath]
bcmath.scale = 0

[browscap]

[Session]
session.save_handler = files
session.use_cookies = 1
session.use_only_cookies = 1
session.name = PHPSESSID
session.auto_start = 0
session.cookie_lifetime = 0
session.cookie_path = /
session.cookie_domain =
session.cookie_httponly =
session.serialize_handler = php
session.gc_probability = 1
session.gc_divisor = 1000
session.gc_maxlifetime = 1440
session.bug_compat_42 = Off
session.bug_compat_warn = Off
session.referer_check =
session.entropy_length = 0
session.cache_limiter = nocache
session.cache_expire = 180
session.use_trans_sid = 0
session.hash_function = 0
session.hash_bits_per_character = 5
url_rewriter.tags = "a=href,area=href,frame=src,input=src,form=fakeentry"

[MSSQL]
mssql.allow_persistent = On
mssql.max_persistent = -1
mssql.max_links = -1
mssql.min_error_severity = 10
mssql.min_message_severity = 10
mssql.compatability_mode = Off
mssql.secure_connection = Off

[Assertion]

[COM]

[mbstring]

[gd]

[exif]

[Tidy]
tidy.clean_output = Off

[soap]
soap.wsdl_cache_enabled=1
soap.wsdl_cache_dir="/tmp"
soap.wsdl_cache_ttl=86400
soap.wsdl_cache_limit = 5

[sysvshm]

[ldap]
ldap.max_links = -1

[mcrypt]

[dba]

[xdebug]

Configure php-fpm

When built using the --with-fpm option like we've done, PHP will be built with the php-fpm process manager. Like nginx, it runs a set of processes to handle FastCGI requests that nginx sends their way over that UNIX socket. Now it's time to set up the PHP side of that logic. Drop the following file into /usr/local/etc/php/5.4/php-fpm.conf, again replacing "collin" with your username:

[global]
daemonize = no
error_log = /usr/local/var/log/php-fpm.error.log

[www]
user = collin
group = staff
listen = /usr/local/var/run/php-fpm.sock

pm = dynamic
pm.max_children = 50
pm.start_servers = 20
pm.min_spare_servers = 10
pm.max_spare_servers = 30

Worth noting:

  • daemonize is set to no because launchd expects processes to remain in the foreground so it can control them, while launchd runs in the background
  • listen is set to the same location as the socket nginx is sending to in its php.conf

Starting and stopping php-fpm

To start php-fpm, invoke launchctl without sudo, since php-fpm does not need to run as root:

launchctl load ~/Library/LaunchAgents/homebrew-php.josegonzalez.php54.plist

Similarly, to stop nginx, run the same command, replacing load with unload:

launchctl unload ~/Library/LaunchAgents/homebrew-php.josegonzalez.php54.plist

Add a site

You can follow this set of steps anytime you want to add another local website.

Add site web root

Create a directory to hold the site content (PHP code, CSS, images, etc.):

mkdir -p ~/Sites/example/public

Add site nginx config

Also create an nginx configuration file to inform nginx about that site. Here's one for an example website. Drop the following file into /usr/local/etc/nginx/sites/example.conf, replacing "collin" with your username (like we did with the base nginx configuration above):

server
{
    listen 80;
    server_name local.example.com;
    root /Users/collin/Sites/example/public;

    access_log /usr/local/var/log/nginx/example.access_log.txt;
    error_log /usr/local/var/log/nginx/example.error_log.txt;

    location /
    {
        autoindex on;
        index index.php;
        try_files $uri $uri/ /index.php?q=$uri&$args;
    }

    include php.conf;
}

Note that the last line of that file includes php.conf from the main nginx config directory. That will enable this site to use PHP content.

Add hosts file entry

To be able to visit this site, your browser needs to know what IP address the name "local.example.com" maps to. We'll manually edit /etc/hosts (sudo privileges required) to add this line at the top:

127.0.0.1  local.example.com

127.0.0.1 is a universal reference to "this computer", so we're telling the browser that local.example.com is this computer right here, being served by nginx.

Add some content

Create a new file at ~/Sites/example/public/index.php with some basic PHP content:

<?php

phpinfo();

Restart nginx

Because we changed nginx configurations, and it doesn't dynamically reload them, we need to restart nginx using the stop/start commands covered earlier:

sudo launchctl unload /Library/LaunchDaemons/homebrew.mxcl.nginx.plist
sudo launchctl load /Library/LaunchDaemons/homebrew.mxcl.nginx.plist

Then visit local.example.com and you should see a huge phpinfo page with all the details of your PHP installation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment