Debugging PHP App in NeoVim with Launch Configuration
By: AjiYakin | #Development #Documentation
Sometimes I need to debug PHP code in two different situations: a CLI (command-line) app and a web server app. While the way to run each one is slightly different, the steps are subtle, and I often forget the correct order for each case.
NeoVim Plugin
mfussenegger/nvim-dap
kristijanhusak/vim-dadbod-ui
Project Directory Structure
Assuming we have this project directory structure:
src
└── App.php -- This is the entry point for CLI app
index.php -- This is the entry point for Server app
composer.json
flake.nix -- Nix flake file (NixOS)
.user.ini -- PHP ini configuration
.vscode
└── launch.json -- Launch configuration
I am currently using Nix package manager to setup environment for each project that I have. With nix it is much more convenient to setup PHP environment since I can enable and include necessary PHP extension that I need to have, in this case I need to have xdebug extension.
flake.nix
:
{
description = "debugphp";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
nix-shell.url = "github:loophp/nix-shell";
systems.url = "github:nix-systems/default";
};
outputs =
inputs@{
self,
flake-parts,
systems,
...
}:
flake-parts.lib.mkFlake { inherit inputs; } {
systems = import systems;
perSystem =
{
config,
self',
inputs',
pkgs,
system,
lib,
...
}:
let
php = pkgs.api.buildPhpFromComposer {
src = inputs.self;
php = pkgs.php83; # Change to php56, php70, ..., php81, php82, php83 etc.
};
in
{
_module.args.pkgs = import self.inputs.nixpkgs {
inherit system;
overlays = [ inputs.nix-shell.overlays.default ];
config.allowUnfree = true;
};
devShells.default = pkgs.mkShellNoCC {
name = "php-devshell";
buildInputs = [
php
php.packages.composer
pkgs.phpunit
];
};
apps = {
# nix run .#composer -- --version
composer = {
type = "app";
program = lib.getExe (
pkgs.writeShellApplication {
name = "composer";
runtimeInputs = [
php
php.packages.composer
];
text = ''
${lib.getExe php.packages.composer} "$@"
'';
}
);
};
# nix run .#phpunit -- --version
phpunit = {
type = "app";
program = lib.getExe (
pkgs.writeShellApplication {
name = "phpunit";
runtimeInputs = [ php ];
text = ''
${lib.getExe pkgs.phpunit} "$@"
'';
}
);
};
};
};
};
}
composer.json
:
{
"name": "ajiyakin/debugphp",
"description": "Sample project to demonstrate how to debug PHP in NeoVim",
"type": "project",
"require": {
"guzzlehttp/guzzle": "^7.9"
},
"require-dev": {
"phpunit/phpunit": "^8.5",
"ext-xdebug": "*",
"phpunit/php-code-coverage": "^7.0"
},
"license": "MIT",
"autoload": {
"psr-4": {
"Ajiyakin\\Debugphp\\": "src/"
}
},
"authors": [
{
"name": "AjiYakin",
"email": "ajiyakin91@gmail.com"
}
]
}
.vscode/launch.json
:
{
"$schema": "https://raw.githubusercontent.com/mfussenegger/dapconfig-schema/master/dapconfig-schema.json",
"version": "0.2.0",
"configurations": [
{
"name": "Listen for Xdebug",
"type": "php",
"request": "launch",
"port": 9003
},
{
"name": "Built-in Server with Xdebug",
"type": "php",
"request": "launch",
"runtimeArgs": [
"-S", "localhost:8080"
],
"port": 9003
}
]
}
.user.ini
(per-directory php ini configuration file):
xdebug.mode=debug
xdebug.client_host=0.0.0.0
xdebug.client_port=9003
xdebug.start_with_request=yes
xdebug.idekey=NEOVIM
For CLI App
Here is running order for CLI App:
- Add breakpoint in
App.php
- Run the Listen for Xdebug from launch configuration
- Run the cli with command:
XDEBUG_CONFIG="idekey=NEOVIM" php -c .user.ini ./vendor/bin/phpunit src/App.php
For Server App
It is much more simple to run server app:
- Add breakpoint in
index.php
- Run Built-in Server with Xdebug
- Trigger breakpoint by sending request to the corresponding routing/endpoint.
Notes
I am not sure why the server is not shutting down when I stop the debugger for server app, I will need to figure out later, but a workaround for this is to manually kill the server with this command:
sudo kill -9 $(lsof -t -i tcp:8080)