Developers tend to love messing about with their local development environments to see what fits their project requirements and workflow best. Since its launch in 2016, Laravel Valet has quickly become a popular choice and it has become my go-to environment when developing locally on my Mac because of its ease of use and speed. Unfortunately, Laravel Valet is for Mac only. If you’re on Windows, you’re out of luck but may want to check out Matt’s post on WordPress development on Windows.
Valet differs from other dev environments because it chooses to run its stack directly on your Mac instead of offloading the environment to a virtual machine or Docker. It relies on native installations of PHP, Nginx, MySQL, and DnsMasq installed using Homebrew which is what makes it so fast.
In this article, we’re going to take a look behind the scenes to see how Valet works and what is going on in the background when you run Valet commands.
Installation
As part of the Valet installation process you need to:
- Install PHP using Homebrew (
brew install php
) - Install the
laravel/valet
Composer package (composer global require laravel/valet
) - Run the
valet install
command
So what happens when the valet install
command is run? Well, we’re going to use the Valet GitHub repository in this article to discover what is going on behind the scenes.
The main valet bash file is the first code to be invoked when we run a valet
command. It basically handles invoking Ngrok if the share
command is used or it uses PHP to run the cli/valet.php
file. Taking a look in the cli/valet.php
file we can see this file does some setup work and registers all of the other Valet commands. If you take a look you can see the install
command does a bunch of stuff:
$app->command('install', function () {
Nginx::stop();
Configuration::install();
Nginx::install();
PhpFpm::install();
DnsMasq::install(Configuration::read()['tld']);
Nginx::restart();
Valet::symlinkToUsersBin();
output(PHP_EOL.'<info>Valet installed successfully!</info>');
})->descriptions('Install the Valet services');
We’re going to take a look at some of the more interesting parts of how Laravel Valet is set up, starting with Nginx.
Nginx
If you look at the install methods of the Nginx class you can see that it creates a global nginx.conf
at /usr/local/etc/nginx/nginx.conf
. The nginx.conf
includes a valet.conf file which is significant as this config is used to serve all Valet sites. Looking in valet.conf
you’ll notice that Nginx is proxying requests to PHP using FastCGI as you might expect:
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass "unix:VALET_HOME_PATH/valet.sock";
fastcgi_index "VALET_SERVER_PATH";
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME "VALET_SERVER_PATH";
}
The VALET_SERVER_PATH
is resolved to the server.php
file located in the root of the Valet install. Configuring Nginx with a single vhost and forwarding all requests to the server.php
file is important as it handles a unique aspect of Laravel Valet: drivers.
Drivers
The concept of “Drivers” in Laravel Valet is a pretty genius idea. Instead of having to configure Nginx configs across different sites to get them to work properly, a “driver” is simply a PHP file that is used by Valet to determine what type of site a given request is looking for and how to serve it. Out of the box Valet has drivers for a bunch of popular applications (e.g. Laravel, WordPress, Drupal, Statamic, Craft, etc.).
A driver file works by using three methods to determine:
- If this driver should be used to serve the current request
- If the request is for a static file
- The path to the front controller of the site (usually
index.php
)
For example, the WordPressValetDriver
uses the serves()
method to check for the existence of a wp-config.php
file:
/**
* Determine if the driver serves the request.
*
* @param string $sitePath
* @param string $siteName
* @param string $uri
* @return bool
*/
public function serves($sitePath, $siteName, $uri)
{
return file_exists($sitePath.'/wp-config.php') || file_exists($sitePath.'/wp-config-sample.php');
}
Going back to the server.php
file you can see this file does a few things to process a request:
- It loads the Valet
config.json
file (which contains the TLD and any paths created by thevalet park
command) - It generates the site path, name, URI etc.
- It then passes this info to the relevant driver which uses it to either serve a static file or load the front controller
If you’re interested, you can have a look at the ValetDriver
class to see how the driver is used to process a request.
DnsMasq
Another really nice feature of Valet is that it proxies all requests on the *.test
domain to your local machine using DnsMasq. This means that you don’t ever need to worry about updating /etc/hosts
to point your sites at 127.0.0.1
anymore.
From the DnsMasq
class we can see that it does this by creating a file in /etc/resolver
with the name of the TLD (test
by default) and the content:
nameserver 127.0.0.1
This will tell Mac to use our local DnsMasq install as the nameserver for *.test
domains. Then it updates the main DnsMasq config to load ~/.config/valet/dnsmasq.conf
. Finally, it creates the valet dnsmasq.conf
file with the following content:
address=/.test/127.0.0.1
listen-address=127.0.0.1
DnsMasq is now configured to send all requests for *.test
domains to Valet.
Over to You
Although we’ve taken a peek behind the curtain of Valet’s internals there are still plenty of Valet features we haven’t covered here, including:
- “Securing” sites using self-signed certificates
- Sharing sites using Ngrok
- Using
.valet-env.php
for site-specific env variables
I’d encourage you to keep looking at the Laravel Valet source code to see how these features are implemented. I actually think it’s an important part of a developer’s job to understand what is happening behind the scenes when you use tools like Valet, not just to make you a more informed developer, but also so that you can be better prepared to debug issues when things go wrong. Knowledge is power after all.
Do you use Laravel Valet? Have you ever looked at the source code for the open-source tools you use? Do you think you should? Let us know in the comments.