Make Your Own Bespoke WAMP Development Environment

I'm a Windows user, so I tend to develop on a local WAMP stack and test on other platforms via virtual machines, installed both locally and on a CI server.

I could use a pre-configured WAMP package like XAMPP or WAMP Server. Indeed, I do use these programs whenever I need to get something up-and-running quickly on a new PC. But in general I prefer to build my own bespoke server.

This is a comprehensive tutorial on how to install the Apache HTTP Server, PHP and MySQL on a Windows 10 PC. There are also instructions for setting up other useful services such as Memcached and Xdebug.

Apache HTTP Server

Apache doesn't provide binaries for its HTTP Server application for Windows, but several third-party services do. I use the binaries provided by Apache Lounge.

I'm using the 64-bit version of Windows 10, so I use the 64-bit version of the Apache HTTP Server. At the time of writing this article the latest stable version is 2.4.23. So the relevant download file is httpd-2.4.23-win64-VC14.zip.

The contents of the download archive are extracted and installed at C:\WAMP\apache\2.4.23.

This version of Apache has one dependency, Microsoft Visual C++ Redistributable for Visual Studio 2015 x64. If this is not listed under Settings > System > Apps > Features, then it can be downloaded from here.

Configuring Apache

By default, the Apache server thinks that the root directory of the server is c:/Apache24. Open C:\WAMP\apache\2.4.23\conf\httpd.conf and change the ServerRoot setting, as below. Use forward slashes as directory delimiters in Apache configs. Search the configuration file for any other references to c:/Apache24 and change those to the correct path, too.

ServerRoot "c:/WAMP/apache/2.4.23"

Find the ServerName setting and set its value to localhost:80. This is not mandatory, but it will put a stop to warnings that the server "could not reliably determine the server's fully qualified domain name", which you will otherwise get whenever you start or restart the server.

ServerName localhost:80

Create the directory C:\WAMP\hosts\htdocs. This will act as the server's document root, from where public documents are served. This is a different location from Apache's default, so the DocumentRoot setting will need adjusting.

DocumentRoot "c:/WAMP/hosts/htdocs"
<Directory "c:/WAMP/hosts/htdocs">

Navigate to C:\WAMP\apache\2.4.23\htdocs and copy the index.html file to C:\WAMP\hosts\htdocs. You can delete Apache's default htdocs directory.

I like to have a central directory, C:\WAMP\logs, that contains all of the logs generated by my local development environment: the HTTP server, scripting languages, databases and other services. These are the settings that I use for Apache's log file paths:

ErrorLog "C:/WAMP/logs/apache/error.log"
CustomLog "C:/WAMP/logs/apache/access.log" common

Windows Firewall should block incoming connections for both Apache (port 80 and 443) and MySQL (port 3306). But for a bit of extra protection you can configure Apache to accept only requests from the local machine. In httpd.conf, inside the directory block <Directory "c:/WAMP/hosts/htdocs">, at the end of the block there should be a line that says "Require all granted". This means that anyone can connect to the server. Change this to "Require local granted" to grant access from the local machine only.

Also within the <Directory "c:/WAMP/hosts/htdocs"> block, you might choose to change AllowOverride from None to All. This will allow Apache to be configured on a directory-by-directory basis using .htaccess files. My preference is to leave this set to None, and I enable it on a directory-by-directory basis through the config files for each Virtual Host, which we will get to shortly.

Set the following values for the Listen directive, which is near the top of the httpd.conf file. If both your firewall and the access control directives fail, the server still won't be open to the whole internet because this configures it to listen only for local traffic.

Listen 127.0.0.1:80
Listen [::1]:80

Finally, enable any Apache modules that your applications need. These are the modules that I enable by default:

LoadModule deflate_module modules/mod_deflate.so
LoadModule env_module modules/mod_env.so
LoadModule expires_module modules/mod_expires.so
LoadModule filter_module modules/mod_filter.so
LoadModule headers_module modules/mod_headers.so
LoadModule rewrite_module modules/mod_rewrite.so

Running Apache

From the Windows command line, change directory to C:\WAMP\apache\2.4.23\bin and run httpd.exe. "Allow access" over the default networks if you are asked to do so by the Windows Firewall.

If you get an error message that says "ServerRoot must be a valid directory", check the configuration in httpd.conf. If you get an error dialog stating that MSVCR110.dll is missing from your system, then you've installed the wrong version of Microsoft Visual C++ Redistributable for Visual Studio. The most common error, however, is that the server cannot bind to port 80. See my notes on troubleshooting port 80, later in this article.

If there are no errors, only a flashing cursor, the Apache HTTP Server has started successfully. Open a web browser and type "http://localhost" in the address bar. You should see a page that says "It works".

To stop the server, key Ctrl+C in the console.

You must leave the console open to keep Apache running. To avoid this, install the Apache HTTP Server as a Windows service. Open a command prompt with administrator privileges, change to Apache's bin directory, and type:

httpd.exe -k install

The response should look something like:

Installing the 'Apache2.4' service
The 'Apache2.4' service is successfully installed.
Testing httpd.conf....
Errors reported here must be corrected before the service can be started.

If do not want the server to be run automatically at Windows start-up, type:

sc config Apache2.4 start= demand

The response should be:

[SC] ChangeServiceConfig SUCCESS

To view the service, key Windows+R and type services.msc to bring up the Windows Services dialog. The service will be named Apache2.4.

If later you need to remove the Apache2.4 service, run the following command with administrator privileges:

httpd -k uninstall

Controlling Apache

With Apache installed as a service, you can control it with the following commands. To start the server:

httpd -k start

To restart the server:

httpd -k restart

To stop the server:

httpd -k stop

With Apache installed as a service, you can also control it using Apache Monitor, a simple GUI client. Apache Monitor is shipped with the server as an .exe file located in Apache's bin directory. Simply place a shortcut in your Programs > Start-up directory to have it run automatically on Windows startup. It'll open unobtrusively in your system tray.

Troubleshooting port 80

One of the most common problems in setting up the Apache HTTP Server is getting it to bind to port 80.

If you are attempting to start or restart the server from the Apache Monitor program, the error message will say, obscurely: "The requested operation has failed". Try starting Apache from the command line to get more information about the nature of the error.

httpd -k restart

If Apache's CLI reports that the server can't bind to port 80, it means that something else is using that port. Skype is usually the culprit. From Skype, go to Tools > Options > Advanced > Connection, and uncheck the box that says "Use port 80 and 443 for additional incoming connections". Restart Skype for the change to take effect.

Other commonly-used Windows services that are known to bind to port 80 are:

  • SQL Server Reporting Services
  • Web Deployment Agent Service
  • BranchCache
  • Sync Share Service
  • World Wide Web Publishing Service

Stop these services from the Windows Services dialog (Windows+R, services.msc). Better still, set their start-up mode to "Manual" so they don't cause the same problem next time you reboot your computer. Also check that you are not already running other web server applications such as IIS, WebMatrix, TomCat, and indeed other instances of Apache.

If none of these actions frees up port 80, find out what is binding itself to port 80 by running the following commands:

netstat -ona | findstr 0.0.0.0:80
netstat -ona | findstr 127.0.0.1:80

If either command gives you output similar to the following, this identifies the process that is listening on port 80:

TCP    127.0.0.1:80    0.0.0.0:0    LISTENING    9620

Note the PID number, on the end of the line. Open Task Manager (Windows+R, taskmgr.exe), click the details tab, and find the application with a matching program identifier, and shut it down.

If you cannot identify any application bound to port 80, check that the Apache2.4 service is not disabled. Run services.msc and check the start-up setting of the Apache2.4 service. It should be set to "Manual" or "Automatic start-up".

Setup Virtual Hosts

This section explains how to setup Virtual Hosts for the Apache HTTP Server. The instructions are specifically for Apache 2.4. Earlier versions of Apache require a slightly different configuration.

The Apache HTTP Server provides a single configuration file for all Virtual Hosts. In my local WAMP stack, the path to this file is C:\WAMP\apache\2.4.23\conf\extra\httpd-vhosts.conf. This configuration file is not enabled by default. To enable it, un-comment the following line in the server's main configuration file, httpd.conf:

Include conf/extra/httpd-vhosts.conf

However, I prefer to maintain a single Virtual Host configuration file per hosted service. For example, if a project was made up of three microservices served from www.example.com, api.example.com and static.example.com, I would have a separate Virtual Host configuration file for each. I keep all of my Virtual Host configs in C:\WAMP\hosts\conf.

  • C:\WAMP\hosts\conf\www.example.com.conf
  • C:\WAMP\hosts\conf\api.example.com.conf
  • C:\WAMP\hosts\conf\static.example.com.conf

To have the server automatically load all configuration files in the C:\WAMP\hosts\conf directory at start-up, add the following to the server's httpd.conf file.

Include "c:/WAMP/hosts/conf/*.conf"

This is the template that I use for Virtual Host configuration files:

<VirtualHost *:80>
    ServerName   www.example.com
    ServerAlias      example.com

    DocumentRoot "C:/WAMP/hosts/htdocs/www.example.com/public"

    <Directory "C:/WAMP/hosts/htdocs/www.example.com/public">
        AllowOverride All
    </Directory>
</VirtualHost>

Remember to restart Apache whenever you make changes to any Virtual Host configurations.

DNS hack

When you open www.example.com in a web browser, you want to see the response from your local development server rather than throwing the request out to the Internet. You'll need to hack the Windows hosts file to map the www.example.com hostname to your machine's local IP address (127.0.0.1), bypassing the global domain name system.

Open the hosts file, C:\Windows\System32\drivers\etc\hosts, in a text editor. Be sure to open the file with administrator privileges, so you have the necessary permissions to save changes.

Add your hostname-to-IP mappings to the bottom of the file, as in the example below. You will need a separate statement for each hostname. Sub-domain wildcards like *.example.com do not work here.

127.0.0.1   example.com
127.0.0.1   www.example.com

You might need to clear your browser's cache and restart it. Then try navigating to www.example.com. The request should get routed to your local installation of the Apache HTTP Server, which will attempt to resolve the request by reading the contents of the Virtual Host's DocumentRoot directory.

Fixing "localhost"

As soon as you have one Virtual Host configured, the Apache HTTP Server will attempt to resolve all requests that don't match a declared ServerName or ServerAlias to the default Virtual Host. The default Virtual Host is, simply, the first <VirtualHost> block that Apache reads.

The consequence of this is that http://localhost will now route to your default Virtual Host and not the server's default DocumentRoot, which is configured in the main httpd.conf file. To restore http://localhost, you will need to create a dedicated Virtual Host configuration for this hostname.

<VirtualHost *:80>
    ServerName   localhost
    
    DocumentRoot "C:/WAMP/hosts/htdocs"

    <Directory "C:/WAMP/hosts/htdocs/localhost/public">
        AllowOverride None
    </Directory>
</VirtualHost>

Using non-standard ports locally

Often, I use different hostnames to distinguish between development code served from a local Virtual Host, and production code served from a remote server. For example, "www.example.com" will return the production website, while "www.example.dev" will be routed to my local machine.

An alternative approach is to serve local apps from a non-standard port. For example, you might use port 8080 instead of 80, and 9090 instead of 443 for SSL connections. So "http://www.example.com" (on default port 80) returns the remote production site, while "http://www.example.com:8080" returns my local development version of the same app.

The other advantage of using using non-standard ports locally is you can avoid port conflicts with other programs such as Skype and some virus protection software.

To apply bespoke ports globally, modify httpd.conf as follows:

Listen 127.0.0.1:8080
Listen [::1]:8080

ServerName localhost:8080

And in extra/httpd-ssl.conf:

Listen 9090

<VirtualHost _default_:9090>
    ServerName localhost:9090

Similar changes will need to be made to individual Virtual Host directives. Application code may also need to be modified, for example so that routing systems can take into account the non-standard ports.

PHP

The next step in building a bespoke WAMP development stack is to install a PHP runtime.

The instructions below explain how to install Zend PHP.

Zend PHP, not to be confused with Zend Framework, is the main implementation of the PHP programming language, the one based on the Zend Engine interpreter. There are alternative runtime environments for PHP, notably PHP-FPM and HHVM. This tutorial does not cover the installation of these alternative PHP implementations.

PHP binaries

Download the latest PHP binaries from http://windows.php.net/download/. Choose the thread-safe version (the non thread-safe version is for running PHP as a CGI library) and choose the version that matches Apache's bit-version. So, if you're running 64bit Apache, you will need a matching 64bit PHP image. (The PHP website does not provide 64bit binaries for some older releases – e.g. PHP 5.4 – but other people have compiled 64bit Windows binaries from the PHP source and they can be downloaded from elsewhere on the web. Have a Google.)

If you want, you can download and install multiple versions of PHP. This may be necessary if you are working on several projects that each require a different version of PHP. At the time of writing this tutorial, I had installed PHP 5.6.23 (php-5.6.23-Win32-VC11-x64.zip) and PHP 7.0.11 (php-7.0.11-Win32-VC14-x64.zip). The following instructions are correct for PHP 5.6.23. Just replace references to "5.6.23" with whatever release you are installing.

Create the directory C:\WAMP\php\5.6.23 and extract the contents of the archive file to it.

You'll also need to install the appropriate C++ Redistributable for Visual Studio. These are available free-of-charge from Microsoft's website. Recent versions of PHP are built with VC9 (Visual Studio 2008), VC11 (Visual Studio 2012) and VC14 (Visual Studio 2015) compilers. So, for PHP 5.6, which is compiled with VC11, you will need the C++ Redistributable for Visual Studio 2012. Refer to the information in the left column on the PHP for Windows download page: http://windows.php.net/download/.

php.ini

In C:\WAMP\php\5.6.23, copy php.ini-development and rename it php.ini. Open the new file in a text editor and double-check the following settings, which are the ones that typically vary between development and production servers. You want the installation to be optimised for development.

error_reporting = E_ALL
display_errors = On
display_startup_errors = On
log_errors = On
track_errors = On

It is recommended that you set a default timezone, so PHP does not have to rely on the system's timezone settings for date() functions. PHP's timezone can be configured at runtime, too, by calling the date_default_timezone_set() method. But it is still a good idea to have a default timezone set in PHP's global configuration.

date.timezone = 'UTC'

If you get an error in PHP 5.6 saying "Automatically populating $HTTP_RAW_POST_DATA is deprecated", you should uncomment the following line in php.ini. This setting was removed entirely in PHP 7.

always_populate_raw_post_data = -1

Also, importantly, uncomment the extension_dir line. This sets the location of PHP's extensions. It's supposed to be possible to set the extension directory as a relative path to php.ini, but I find that in some contexts PHP fails to load extensions when I use a relative path. Setting an absolute path seems to be much more reliable.

extension_dir = "C:\WAMP\php\5.6.23\ext"

Enable any extensions that your app will need. Most of my projects depend on mbstring and GD2 and often cURL.

extension=php_curl.dll
extension=php_fileinfo.dll
extension=php_gd2.dll
extension=php_intl.dll
extension=php_mbstring.dll

Finally, set a convenient location for PHP error logs:

error_log = "C:\WAMP\logs\php\error.log"

Apache module

To run PHP 5 as a module of the Apache HTTP Server, add the following lines to Apache's httpd.conf file, immediately after the LoadModule declarations.

LoadModule php5_module "C:/WAMP/php/5.6.23/php5apache2_4.dll"

<IfModule php5_module>
    DirectoryIndex index.html index.php
    AddHandler application/x-httpd-php .php
    PHPIniDir "C:/WAMP/php/5.6.23"
</IfModule>

For PHP 7:

LoadModule php7_module "C:/WAMP/php/7.0.11/php7apache2_4.dll"

<IfModule php7_module>
    DirectoryIndex index.html index.php
    AddHandler application/x-httpd-php .php
    PHPIniDir "C:/WAMP/php/7.0.11"
</IfModule>

If the PHP DLL file does not exist, you've downloaded the non thread-safe version of PHP. You need the thread-safe version!

You can only run one version of PHP at a time. To swap from one version to the other, change the path to the DLL file in httpd.conf and restart the server.

If Apache is installed as a Windows Service, you can test the configuration from the command line. Navigate to Apache's bin directory and type:

httpd -t

If you get an error similar to the following, check that the path to the DLL file is correct, and that you have installed the approproate C++ Redistributable for Visual Studio as required by your version of PHP.

Syntax error on line 182 of C:/WAMP/apache/2.4.23/conf/httpd.conf: Cannot load C:/WAMP/php/5_6_23/php5apache2_4.dll into server: The specified module could not be found.

Restart the Apache server. If you don't see any errors in the console, the configuration is valid. To check that PHP is indeed running inside the Apache HTTP Server program, save a file called info.php to localhost's DocumentRoot directory, with the following content:

<?php
    phpinfo();
    exit;

Open http://localhost/info.php in a browser. You should see a page detailing PHP's configuration. If you get something like "Internal server error" that means something is very wrong with the server configuration. Check Apache's error logs and debug from there.

Running PHP from the command line

To run PHP from the command line, all you need to do is add PHP's installation directory, e.g. C:\WAMP\php\5.6.23\, to the Windows global path environment variable.

I recommend that you create a subdirectory called binC:\WAMP\php\5.6.23\bin\ – and add this to the Windows global path environment variable, too. The "bin" directory on Unix systems stands for "binaries", but we will use this directory to store any general utility programs that run on PHP. Later you will add various .phar and .bat files to this directory so that you can use tools like PHPUnit and Composer from the command line.

You might need to restart your computer, or at least logout and login again, for changes to the Windows system variables to take effect.

MySQL

The last core component of the WAMP stack is MySQL.

MySQL Installer

From the MySQL Installer download page, grab the "web community" installer for your chosen version of MySQL. For most of my projects the minimum required version is 5.6. At the time of writing this article, the latest stable release is 5.6.31, so the download file for that is mysql-installer-web-community-5.6.31.0.msi.

This gives you a fancy application called MySQL Installer that allows you to download and install any MySQL Community version, current or historic, as well as supporting software such as MySQL Workbench, a native GUI client that is far superior to clunky web-based tools like phpMyAdmin.

Install and run MySQL Installer. Select the option to install the MySQL Server, and choose which version of the server you want to install. Select the 64-bit edition. Even though MySQL's bit version does not need to match Apache's and PHP's (MySQL runs as an entirely independent service), the 64-bit version will perform better, especially with big datasets.

The following instructions are correct for MySQL Server 5.6.31, but they are not likely to have changed much for more recent versions.

Click Advanced Options and set the install directory to C:\WAMP\mysql\5.6.31. Set the data directory to C:\WAMP\mysql\5.6.31\data. Change "5.6.31" to whatever version of MySQL Server you are installing.

Run the installation.

After installation, you'll be presented with some configuration options. Choose "Development Machine", and disable "Open firewall port for network access" unless you want to access the database from another machine. Optionally, set a password for the root MySQL user. If MySQL will be accessed only locally and your firewall blocks incoming connections on port 3306, then the password is not really necessary.

Use MySQL's default port of 3306, unless you've got something else listening on that port such as another version of MySQL or MariaDB.

There's an option to "Start the MySQL Server at System Startup". With this enabled, MySQL will be installed as a Windows service rather than an application. If you turn this off, you will need to manually start MySQL every time you need it. If you will be frequently interacting with local MySQL databases, it might make better sense to have MySQL running in the background by default. Also, configuring MySQL as a Windows Service will make it possible to use MySQL Notifier, a handy little GUI, to start and stop the service.

Don't forget to configure MySQL to dump its log files somewhere that's easy to access. I use the following log paths:

  • Error Log path: C:\WAMP\logs\mysql\error.log
  • General Log path: C:\WAMP\logs\mysql\general.log
  • Slow Query Log path: C:\WAMP\logs\mysql\slow-query.log (2 or 3 seconds recommended)

Controlling the MySQL Server

To start the server, from the command line run mysqld:

cd C:\WAMP\mysql\5.6.31\bin
mysqld

If MySQL was installed as a service, you can close the command line window, since MySQL will keep running in the background. To shutdown the MySQL Server again:

cd C:\WAMP\mysql\5.6.31\bin
mysqladmin -u root shutdown

If the root user has a password, use the following command instead, and enter the root password when prompted:

mysqladmin -u root -p shutdown

If you want to be able to run MySQL's command line tools from any directory, just add MySQL's bin directory to the system's path environment variable, and restart your computer. To test, try logging in to MySQL from a random directory.

mysql -u root -p

There's a handy GUI, called MySQL Notifier, which sits in your system tray and can be used to start, stop and restart the MySQL Server at the click of a mouse. It integrates nicely with MySQL Workbench, too. Both MySQL Notifier and MySQL Workbench can be installed via the god-like MySQL Installer.

PHP extensions

You now have MySQL running as a standalone service or application. To complete the setup of a basic WAMP development environment, PHP needs to be able to communicate with MySQL. This is a simple matter of enabling one or two PHP extensions, which is done via PHP's php.ini file.

The PDO (PHP Data Objects) extension is recommended for this purpose. It is a generic abstraction layer for all sorts of relational database management systems, not just MySQL. There's one PDO extension per RDBMS. I use SQLite quite a bit, so I enable the extension for that, too. I still come across codebases that depend on the older mysqli (MySQL Improved) extension, so I tend to have this enabled by default as well.

extension=php_pdo_mysql.dll
extension=php_pdo_sqlite.dll
extension=php_mysqli.dll

If PHP is running as an Apache module, restart the Apache HTTP Server for these changes to come into effect. If PHP is running under CGI, restart the standalone PHP service.

Test

Here's a test script to check that you can connect to the MySQL Server from PHP:

<?php
    $dsn = 'mysql:host=localhost;dbname=data';
    $username = 'root';
    $password = ''; // Type root's password here, if applicable

    try {
        $dbh = new \PDO($dsn, $username, $password);
    } catch (\PDOException $e) {
        echo 'Connection failed: ' . $e->getMessage();
        die();
    }
    $dbh = null; // Close the connection.
    echo 'Connected OK!';
    die();

If you get a PDOException with the message "Could not find driver", it means the PDO MySQL extension has failed to load. pdo_mysql will not show up in the output from phpinfo(), either. Check your configuration, particularly the path to PHP's extensions.

Composer

The instructions above have given you a basic WAMP development environment. The rest of this article will explain how to add some optional extras, starting with Composer, the de facto package manager for PHP application development.

I prefer to install Composer manually, rather than use the Windows installer. Navigate to PHP's bin directory, e.g. C:\WAMP\php\5.6.23\bin, and run the following commands. The setup process will check that PHP is configured appropriately to run Composer.

php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
php composer-setup.php

A file called composer.phar should now exist in PHP's bin directory. If it does, you can delete the Composer setup script.

php -r "unlink('composer-setup.php');"

The OpenSSL extension must be enabled in PHP for Composer to work correctly. In php.ini:

extension=php_openssl.dll

To use Composer, from PHP's bin directory run the following command:

php composer.php

You should see the following output and a list of available sub-commands.

   ______
  / ____/___  ____ ___  ____  ____  ________  _____
 / /   / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/
/ /___/ /_/ / / / / / / /_/ / /_/ (__  )  __/ /
\____/\____/_/ /_/ /_/ .___/\____/____/\___/_/
                    /_/
Composer version 1.4.1 2017-03-10 09:29:45

For more convenience, you want to be able to run the composer command from any directory. To enable this, create a new composer.bat file alongside composer.phar using the following command:

echo @php "%~dp0composer.phar" %*>composer.bat

Check the installation by typing the following in the Windows terminal from any directory:

composer

The output should be the same as before.

PEAR

PEAR is the original and official package manager for the PHP ecosystem, though it has fallen out of favour since Composer entered the scene. I still use it, though mostly for installing global PECL extensions rather than application-level packages.

Create a new directory to contain the PEAR system: C:\\WAMP\\pear. I like to keep my installation of PEAR separate from PHP. I find this more convenient because it keeps PEAR available even if I switch to a different version of PHP.

Go to http://pear.php.net/go-pear.phar and save the PHAR file to the new pear directory. Change to that directory and run the go-pear.phar program.

cd C:\WAMP\pear
php go-pear.phar

You will be asked: "Are you installing a system-wide PEAR or a local copy?" Type "local" and hit Enter. Confirm by typing "yes".

You will be shown a list of locations where PEAR's files will be installed. Press Enter to accept all of the default locations.

When prompted, type "Y" to confirm that you want PHP's include_path setting to be updated automatically. Doing so means you will be able to include PEAR and PEAR-compatible packages in any PHP application using require_once(). You will need to restart your web server so that changes made to php.ini will come into effect.

On success you will be told that "the pear command is now at your service...". You should now be able to use the pear and pecl commands. Try running the pear command. If everything has been installed correctly, the response will be a list of available sub-commands.

pear

To use the pear and pecl commands from any location, be sure to add C:\WAMP\pear to the Windows system path environment variable. You might need to reboot your machine for this change to come into effect.

Check that PEAR packages can be included in any local PHP application by running the following script:

require_once 'System.php';
var_dump(class_exists('System', false));

System.php is shipped with every PEAR installation so it should now be available to your local PHP applications. The output of this script should be:

bool(true)

If the output reads "require_once(System.php): failed to open stream", you will need to fix the include_path setting in PHP's php.ini config file. It needs to point to your local PEAR directory. Example:

include_path=".;C:\WAMP\pear"

You can delete the go-pear.phar installation program:

php -r "unlink('go-pear.phar');"

PHPUnit

PHPUnit is a widely-used unit testing framework for PHP programs, and no *AMP development environment would be complete without it.

You have two options. Yo can install PHPUnit globally, or locally on a project-by-project basis. Or you can do what I do: both.

Global installation

Download the latest PHAR from https://phpunit.de. Copy the file to PHP's bin directory and rename it phpunit.phar, so the final destination is C:\WAMP\php\5.6.23\bin\phpunit.phar.

Open a terminal and create a wrapping batch script, phpunit.bat:

echo @php "%~dp0phpunit.phar" %* > phpunit.bat

Confirm that you can run phpunit from any location:

phpunit --version

Local installation

If you are using Composer to manage your project's dependencies, the easiest thing to do is add PHPUnit to each project's composer.json configuration file. The advantage of this approach is that you can install different versions of PHPUnit, as required by each project. The following configuration will install PHPUnit 5.3 in development environments:

"require-dev": {
    "phpunit/phpunit": "5.3.*"
}

Composer adds command line tools to the vendor/bin directory, so the command to run PHPUnit from your project's root directory will be:

vendor/bin/phpunit

phpDocumentor

phpDocumentor remains the best, and most actively developed, documentation generator for PHP source code. As with PHPUnit, I tend to keep a global installation up-to-date with the latest release, and then I install specific versions on a project-by-project basis via Composer.

Global installation

There are two ways to install phpDocumentor globally: by installing the PHAR in a directory included in the system path, or by using PEAR.

Get the PHAR here: http://phpdoc.org/phpDocumentor.phar. Add it to PHP's bin directory and rename it phpdoc.phar. Create a wrapping batch script with the following command:

echo @php "%~dp0phpdoc.phar" %* > phpdoc.bat

Alternatively, if you have PEAR installed, run the following commands:

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

Check that the installation works globally by running phpdoc from any directory.

Local installation

Use Composer to install different versions of phpDocumentor on a project-by-project basis. Add a configuration similar to the following to each project's composer.json file.

"require-dev": {
    "phpdocumentor/phpdocumentor": "2.*"
}

After installing the project's dependencies, run the following command from the project's root directory:

vendor/bin/phpdoc.php

PHP Coding Standards Fixer

A final utility that I like to have installed globally and readily available to all of my PHP projects is the invaluable PHP Coding Standards Fixer. It is made by Sensio Labs, the people who brought us Symfony and many other great tools.

Go to http://cs.sensiolabs.org/ and download the PHAR to PHP's bin directory. Create a wrapping batch script in the normal way:

echo @php "%~dp0php-cs-fixer.phar" %* > php-cs-fixer.bat

Run php-cs-fixer in the console to get a list of available sub-commands.

PHP CS Fixer is also available via Composer:

"require-dev": {
    "friendsofphp/php-cs-fixer": "2.*"
}

Enable SSL

When a production web app is served over HTTPS, I usually find it convenient to serve my local development version over HTTPS also.

To use the https:// scheme to serve locally-hosted applications, you need to enable SSL in your WAMP stack and generate SSL certificates for each site. Here's how.

Install OpenSSL

When running web applications in development or test environments, or over small LANs, you can avoid the costs associated with buying SSL certificates by making your own self-signed certificates. Self-signed certificates are not appropriate for public production applications. Although self-signed certificates implement full encryption, any web browser connecting to a site with a homemade certificate will not recognise the authority that signed the certificate. All modern web browers will render a warning that the site's certificate should not be trusted and require end users to verify the certificate before proceeding to the website. For this reason, public-facing applications should serve SSL certficates that are signed by a Trusted Certificate Authority (CA) such as Verisign. But this is not necessary for any HTTP service that is not publicly accessible.

To generate self-signed certificates we will use the OpenSSL library. The OpenSSL toolkit is needed to generate first an RSA Private Key and a Certificate Signing Requst (CSR), and then finally the self-signed certificate itself.

Go to https://slproweb.com/products/Win32OpenSSL.html, scroll down and choose the latest Win64 openSSL installer (not the "light" version).

(Other builds of the OpenSSL software are available elsewhere on the web, for example: http://gnuwin32.sourceforge.net/packages/openssl.htm. The original source files can be obtained by following the links to the OpenSSL project repository from https://www.openssl.org/.)

Run the installer.

During the installation, select the option to copy OpenSSL DLLs to the OpenSSL binaries (bin) directory. At the end of the installation process, add this directory to the Windows system path environment variable. This will make the openssl command work globally. You might need to reboot your machine before you can use OpenSSL at the terminal.

Generate a private key

An RSA Private Key and Certificate Signing Request (CSR) are required to create an SSL certificate. The following command generates a 2048 bit RSA key which is encrypted using Triple-DES and stored in a PEM format so that it is readable as ASCII text.

openssl genrsa -des3 -out server.pass.key 2048

At the prompt, enter a PEM passphrase. It can be just four characters long (the minimum allowed) because in the next step we will remove the password from the key file, anyway. The side effect of using a password-protected private key is that the HTTP server will ask for the password every time the server is started.

To remove the Triple-DES encryption from the key, run the following command:

openssl rsa -in server.pass.key -out server.key

At the prompt, enter the password you set in the previous step. Now, the server.pass.key file is the original password-protected private key, and server.key is the unencrypted version. You can delete the server.pass.key file as you no longer need it.

Generate a CSR

You can now make a Certificate Signed Request using your private key. Issue the following command:

openssl req -new -key server.key -out server.csr

Following the openssl req command you will be prompted for several pieces of information. These are the X.509 attributes of the certificate. One of the prompts will be for the "Common Name", which must be the fully-qualified domain name of the application that the certificate will protect. For example, if the application's URL will be https://api.example.com, then the Common Name entry must be api.example.com. If there is a mismatch between the application's hostname and the common name of its certificate, browsers will render warnings when users try to access the application's secure page. This is not a major issue in development environments, but it's worth configuring your self-signed SSL certificates properly so no developer wastes time trying to debug the error.

Optionally, you can use one certificate to protect multiple hostnames. Instructions are provided in the next step. If you want to do this, I recommend that you set the CSR's Common Name to "localhost".

The openssl req command will also ask for a challenge password or passphrase. It is recommended that you leave the password empty by pressing Return at this step. To repeat, the problem with using certificates with passphrases is that the passphrase must be typed every time the HTTP server starts up. Passphrase-protected certificates are not even used in production for this reason.

Generate a certificate

Next, generate a self-signed certificate from the server.key and server.csr files made in the previous steps.

openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt

If you want to use a single certificate to protect multiple hostnames, an extensions file must be used. Here's an example:

[ localhost_http ]
nsCertType      = server
keyUsage        = digitalSignature,nonRepudiation,keyEncipherment
extendedKeyUsage        = serverAuth
subjectKeyIdentifier    = hash
authorityKeyIdentifier  = keyid,issuer
subjectAltName          = @localhost_http_subject
[ localhost_http_subject ]
DNS.1 = example.com
DNS.2 = www.example.com
DNS.3 = api.example.com
DNS.4 = static.example.com

The command to generate a certificate from an extension file is:

openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt -extfile /path/to/extensions.file -extensions localhost_http

You can now delete the CSR and extensions files. For the purpose of a self-signed certificate, the CSR's role is complete. You only need to keep the CSR file if you plan to get your certificate signed by a trusted CA, in which case the CSR file will be sent to the CA.

You just need to keep the private key (server.key) and the certificate (server.crt).

Copy the key and certificate to a suitable location in your WAMP directory, something like:

C:\WAMP\hosts\ssl\www.example.com\

Configure the Apache HTTP server

Edit Apache's conf/httpd.conf file and un-comment the following three lines:

LoadModule socache_shmcb_module modules/mod_socache_shmcb.so
LoadModule ssl_module modules/mod_ssl.so
Include conf/extra/httpd-ssl.conf

Save and close the file.

Open conf/extra/httpd-ssl.conf in a text editor. Find the <VirtualHost _default_:443> block, and comment-out the whole block by prepending each line with a hash # character. Leave all of the "SSL global context" settings as-is.

Update the relevant virtual host confgurations. For example, to serve www.example.com locally over HTTPs, the name-based virtual host configuration will need to look something like this:

<VirtualHost *:80>
    ServerName  www.example.com
    ServerAlias     example.com

    Redirect permanent / https://www.example.com/
</VirtualHost>

<VirtualHost *:443>
    ServerName  www.example.com
    ServerAlias     example.com

    DocumentRoot "C:/WAMP/hosts/htdocs/www.example.com/public"

    <Directory "C:/WAMP/hosts/htdocs/www.example.com/public">
        AllowOverride All
    </Directory>

    SSLEngine on
    SSLCertificateFile    "C:/WAMP/hosts/ssl/www.example.com/server.crt"
    SSLCertificateKeyFile "C:/WAMP/hosts/ssl/www.example.com/server.key"
</VirtualHost>

Test the configuration by issuing the following command from Apache's bin directory. You're looking for a response that says "Syntax OK".

httpd -t

Configure PHP

For applications built on PHP, you'll also need to enable PHP's openssl extension. Open the php.ini file and un-comment the following line by removing the semi-colon from the beginning of the line.

extension=php_openssl.dll

Test it

Restart your HTTP server and web browser, and type the equivalent of https://www.example.com into the address bar. You should see a "Connection is Insecure / Untrusted" warning. That's OK. It means that the HTTPS connection is working fine. The reason why you see this message is because you created your own self-signed certificate rather than one created by a trusted certificate provider.

You can disable this error message permanently, so that you will always be given immediate access to your application when connecting via a browser or any other HTTP client such as Postman. To do that, you need to import your certificate to your browser's list of trusted certificate authorities.

The instructions vary from one browser to another.

For Chrome, go to chrome://settings/search#ssl and click on Manage Certificates. Go to the Trusted Root Certification Authorities tab and click Import. Follow the instructions to import your application's server.crt file and add it to the list of Trust Root Certificate Authorities. Close the window and restart Chrome for the changes to take effect.

A similar process used to work for Firefox, but it seems you can no longer import self-signed certificates into Firefox. Instead, from the "Your connection is not secure" error page, click Advanced, then Add Exception, and tick the box to "Permanently store this exception". You will need to do this for every domain and sub-domain, even if they all share the same certificate.

Xdebug

Xdebug is a powerful open source debugger and profiler for PHP. It is highly recommended for development environments.

Go to https://xdebug.org/wizard.php and follow the instructions to download the Xdebug extension that is appropriate for your system. Save the .dll file to PHP's ext directory and rename it php_xdebug.dll.

Open php.ini in a text editor. Add the following to the very bottom of the file.

[Xdebug]

zend_extension = "C:\WAMP\php\5.6.23\ext\php_xdebug.dll"
xdebug.remote_enable=true
xdebug.remote_host=localhost
xdebug.remote_port=10000
xdebug.remote_handler=dbgp

Restart Apache, run phpinfo(), and check the output to verify that the Xdebug extension is loaded. Another way to check that it is working is to check the output of var_dump(). Xdebug overloads this function with its own version, adding type colours.

<?php

var_dump($_SERVER);
exit;

Enable profiling

One of Xdebug’s most powerful features is its ability to profile a PHP script and produce detailed statistics on how long each function call or line of code takes to execute. This can be very useful for finding performance bottlenecks in complex scripts.

To turn on script profiling, add the following to the [Xdebug] block in php.ini.

xdebug.profiler_enable=1
xdebug.profiler_output_dir="C:\WAMP\logs\php\profiler"
xdebug.profiler_output_name="cachegrind.out.%t-%s"
xdebug.profiler_append = 0
xdebug.profiler_enable_trigger = 0

Make sure that the output directory exists.

Finally, find the implicit_flush setting and turn it On:

implicit_flush = On

Restart Apache.

At this point, Xdebug profiling is active. Every PHP script that you run will be profiled and the results will be placed in the "C:\WAMP\logs\php\profiler" as a cachegrind file. You need a GUI to understand the contents of these files. There are a few options, but by far the most convenient is WebGrind, which is web-based so it can be installed on your localhost. Go to https://github.com/jokkedk/webgrind and download the latest archive of the master branch. Extract the contents of the archive to "C:\WAMP\htdocs\apps\webgrind". Load a web browser at https://localhost/apps/webgrind/, and follow the instructions to start profiling.

Xdebug profiling will significantly slow down the execution of PHP scripts, and is well known to hurt the performance of the Composer package manager especially. You might want to turn off profiling until you specifically need to run a performance audit on something. Simply comment-out the relevant lines in php.ini.

Memcached

Memcached is an in-memory key/value store for small chunks of arbitrary data. It is commonly used to speed up web applications by caching results from database calls, API lookups, and page renderings.

To use Memcached in a web application, two things need to be installed:

  • The Memcached daemon.
  • A client library for your programming language.

Installing the Memcached daemon

Designed with Linux in mind, the official Memcached website doesn't provide any Windows binaries. But other people have compiled Windows binaries from the Memcached source code and published them online. Below are links to Windows 32 and 64 bit binary versions of Memcached. Other sources are available – have a Google!

Copy the memcached.exe file, plus any DLL files included in the package, somewhere convenient such as C:\WAMP\memcached\1.4.4, where "1.4.4" is the daemon's version number. Right-click on the EXE file, click Properties, click the Compatibility tab, and near the bottom of the dialog select "Run this program as administrator".

From the instalation directory, start the Memcached server with the following command.

memcached.exe -d start

If you get an error message that says "The program can't start because MSVCR71.dll is missing from your computer", search for this file on your computer or download it from dll-files.com. Copy the file to Memcached's directory. Try again.

To have the Memcached server start automatically at bootup, run the following command. This installs Memcached as a service.

memcached.exe -d install

The Memcached server is now installed and listening on port 11211.

Increase the memory limit

By default, the Memcached server is set to use no more than 64Mb of memory, which is too small for many applications. To increase the limit, you need to modify the Windows Registry. The RegScanner utility written by Nir Sofer makes it easy to track down and edit the relevant settings. Search for "memcached" then change all relevant "ImagePath" entries as below.

"C:/path/to/memcached.exe" -d runservice -m 512

This changes Memcached's memory limit to 512Mb. Restart the Memcached service for these changes to take effect.

How to use Memcached in PHP scripts

PHP does not support Memcached out-of-the-box. So, for PHP applications to be able to interact with the Memcached service, you need to install a PHP extension.

There are two such PHP extensions. They are confusingly named memcache (without the "d") and memcached (with the "d"). I will refer to them as pecl-memcache and pecl-memcached, to distinguish the client applications from the daemon.

The two extensions have different authors and different APIs. The pecl-memcache extension is older and has fewer features. The pecl-memcached extension is newer, more feature-rich, and is dependant upon the libmemcached library which is well maintained. You should be aware that the two extensions are not compatible with one another. Both libraries serialize data differently, so caches written with one library cannot be read with the other.

Getting hold of Windows binaries for either library is not easy. The PECL repository provides Windows DLLs for the most recent release of the pecl-memcache extension but only for PHP versions 5.3 to 5.6. There are no DLLs available at all for the pecl-memcached extension. So, if you want to run pecl-memcache on PHP 7, or pecl-memcached on any version of PHP, you will need to go to the effort of compiling the extensions from their source code.

Once you've got hold of a DLL, copy it to PHP's ext directory. Edit php.ini to enable the extension.

extension=php_memcache.dll

Restart the Apache HTTP Server.

Test

The following script writes something to Memcached that expires in 10 seconds, then immediately retrieves the value from the cache and prints it.

<?php

// You might need to set "localhost" to "127.0.0.1"
$memcache = new \Memcache();
$memcache->connect('localhost', 11211);

echo "Memcached version: ".$memcache->getVersion()."\n\n";

$tmp = new \stdClass();
$tmp->str_attr = 'test';
$tmp_object->int_attr = 123;
$memcache->set('key', $tmp, false, 10);

var_dump($memcache->get('key'));

GUIs

There are lots of GUIs for Memcached, like FastNoSQL and Keylord. One of my favourites is a simple PHP application written by Artur Ejsmont. I haven't seen any updates since 2009, but it still works like a charm.