Laravel Sail - Xdebug

With Sail, you can start debugging your Laravel application in less than 5 minutes!

But, first lets understand the use cases/ modes:

  1. Debugging - Stop a script mid execution to inspect variables
  2. Profiling - Look at slow bottlenecks in a request
  3. Coverage - Monitor how much code is tested

What will we learn? Link to heading

  • Why modern Xdebug is so easy
  • How does Sail do it?
  • The Xdebug settings
  • Using the debugger in the browser
  • Using the profiler
  • Using code coverage for tests
  • xdebug_info()

Why modern Xdebug is so easy Link to heading

The current version, Xdebug 3, has one key difference that makes using it easy:

export XDEBUG_CONFIG="client_host=192.168.42.34 log=/tmp/xdebug.log"

Instead of using php.ini files, you can configure Xdebug using environment variables.

This means you can enable the debugger, use it for a few requests, then disable it. All without delving into the server internals, or re-building instances.

How does Sail do it? Link to heading

Lets have a look at laravel/sail on GitHub to see how its taking advantage of this new configuration.

First, we see a Dockerfile for each supported PHP version. Looking inside runtimes/8.2/Dockerfile, we find:

apt-get install php8.2-xdebug

It is following the instructions provided by Xdebug for installation on a Ubuntu distribution.

Next, we see the docker-compose.stub has two environment variables:

environment:
    XDEBUG_MODE: '${SAIL_XDEBUG_MODE:-off}'
    XDEBUG_CONFIG: '${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}'

These keys and values are ’nearly’ identical to the ones Xdebug uses!

And that’s it! Docker Compose passes these environment variables to the Ubuntu container, and Xdebug uses them.

Well thats not completely it, there’s one extra thing…

In artisan, theres a command for sail debug ... to activate the debugger:

ARGS+=(exec -u sail -e XDEBUG_SESSION=1)

So, again it is following the instructions provided by Xdebug for activating the debugger in the command line.

The Xdebug settings Link to heading

Only a select set of settings can be set through this XDEBUG_CONFIG variable. Some more useful settings are:

  • cli_color=0
  • client_host=localhost
  • client_port=9003
  • idekey=*complex*
  • log=/tmp/xdebug.log
  • log_level=7
  • mode=develop
  • output_dir=/tmp
  • profiler_output_name=cachegrind.out.%p
Note
The mode is set using the XDEBUG_MODE and used like: XDEBUG_MODE=develop,debug

To use each setting, you can add them like this:

XDEBUG_CONFIG="client_host=192.168.42.34 log=/tmp/xdebug.log"

You can find documentation for all of the settings here: https://xdebug.org/docs/all_settings

Using the debugger in the browser Link to heading

The debugger is the most documented usage of Xdebug, so I wont write too much. You can see:

Or, if you use macOS, PHPStorm and Chrome, you can use my setup:

  • Add SAIL_XDEBUG_MODE=develop,debug to your .env
  • Run ./vendor/bin/sail up
  • On the PHPStorm toolbar, toggle Start Listening for PHP Debug Connections
  • Again, on the toolbar, toggle Break at first line in PHP scripts
  • Install Chrome Xdebug helper extension
  • Visit http://localhost, with Chrome extension set to Debug

Using the profiler Link to heading

The profiler allows you to visualise bottlenecks in your PHP code execution, for example slow running functions or high memory usage.

So, lets set it up:

First, update your .env variables:

SAIL_XDEBUG_MODE=profile
SAIL_XDEBUG_CONFIG="client_host=host.docker.internal output_dir=/var/www/html/storage/app profiler_output_name=cachegrind.out.%R.%u"
Note
I dont use the default profiler_output_name. See my StackOverflow question for more info. But simply, Clockwork required 1 file per request.

Next, composer require itsgoingd/clockwork --dev, and visit http://localhost/clockwork to start listening for requests to profile!

Finally, stop/ start Sail and start making web requests to your application!

Using Clockwork, you can view the call graph for your request.

Webgrind Link to heading

If you want a quick’n’dirty way to visualise your profiles, I use Webgrind (Because the popular kcachegrind doesnt work on macOS).

Using the configuration above, you can run:

docker run --rm -v /path/to/project/storage/app:/tmp -p 2000:80 jokkedk/webgrind:latest

Then open http://localhost in your browser load the profile and inspect it!

Using code coverage for tests Link to heading

The code coverage allows you to output the amount of code executed during a request. Its useful to see the % code coverage of your test suite.

This is well documented over at Laravel News.

Or, a tldr;

  • Set SAIL_XDEBUG_MODE=coverage
  • Run php artisan test --coverage
  • To generate a HTML file report, edit phpunit.xml:
<coverage processUncoveredFiles="true">
    <include>
        <directory suffix=".php">./app</directory>
    </include>
    <report>
        <html outputDirectory="storage/app"/>
    </report>
</coverage>

xdebug_info() Link to heading

With Xdebug 3, we can use xdebug_info() to check what config Xdebug is using.

If we set SAIL_XDEBUG_MODE=debug in our .env file and stop/ start Sail, you should see a page like the phpinfo() output.

This is useful for checking your configuration has loaded correctly.

Conclusion Link to heading

Because Xdebug is way easier to use than it used to be, and the debugger/ profiler are invaluable tools for local development, I think this post adds a lot of value on top of the existing documentation.

You may be like me, having spent hours in StackOverflow trying to resolve issues with Xdebug and Chrome/ PHPStorm/ Docker.

But, after reading this article, you can see just how easy it is and start improving your local dev workflow, as well as improving performance bottlenecks before you get to production.

Outro Link to heading

This blog is to help me remember what I have done, hopefully teach others and get feedback and learn from others.

Please let me know if you have tried this, or have any different thoughts on Twitter @natenatters.