Skip to content

[ php-wasm ] Xdebug and Devtools #2435

@mho22

Description

@mho22

What is supported

PHP.wasm Xdebug is currently supported in PHP.wasm versions from 7.2 to 8.4. It has been introduced in the Node version of PHP.wasm. [ Asyncify and JSPI ]. Practically : any script using the loadNodeRuntime can enable Xdebug.

It is not implemented in the Web version of PHP.wasm. For now.

PHP.wasm Devtools was recently enabled through a module that bridges the Chrome DevTools Protocol and Xdebug common debugger protocol, called DBGp. This allows the browser and the debugger to communicate with each other.

Concretely, to use it, we first need to start the bridge. it will open the devtools on your browser, and listen for Xdebug entries. These entries are instantly sent when a PHP runtime is loaded with Xdebug enabled.

With that in mind, we implemented the @php-wasm/xdebug-bridge in PHP.wasm CLI and Playground CLI. The next section will explain how to use them.

How to use it

Let’s say we have a php file named test.php at the root of our project :

test.php

<?php

$test = 42; // Set a breakpoint on this line

echo "Output!\n";

function test() {
	echo "Hello Xdebug World!\n";
}

test();

Debugging the file with php

First things first. Let’s use our new @php-wasm/xdebug-bridge with a normal PHP binary :

> npx xdebug-bridge

Starting Xdebug Bridge...
Connect Chrome DevTools to CDP at:
devtools://devtools/bundled/inspector.html?ws=localhost:9229

...

Chrome connected! Initializing Xdebug receiver...
Xdebug receiver running on port 9003
Running a PHP script with Xdebug enabled..

In another terminal let’s see if Xdebug is enabled first

> php -v 

PHP 8.3.23 (cli) (built: Jul  4 2025 03:18:09) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.3.23, Copyright (c) Zend Technologies
    with Xdebug v3.3.1, Copyright (c) 2002-2023, by Derick Rethans
    with Zend OPcache v8.3.23, Copyright (c), by Zend Technologies

Then run the script :

> php test.php
Image

Note : I am using Laravel Herd's PHP binary.

Debugging the file with @php-wasm/node

First we load the runtime with Xdebug, then we start the bridge. Finally we run the script.

node.js

import { PHP } from '@php-wasm/universal';
import { loadNodeRuntime } from '@php-wasm/node';
import { startBridge } from '@php-wasm/xdebug-bridge';


const php = new PHP( await loadNodeRuntime( '8.4', { withXdebug : true } ) );

const bridge = await startBridge( { phpInstance : php } );

bridge.start();

await php.runStream( { scriptPath : `test.php` } );

Let's run the script :

> node scripts/node.js

Starting Xdebug Bridge...
Connect Chrome DevTools to CDP at:
devtools://devtools/bundled/inspector.html?ws=localhost:9229

...

Chrome connected! Initializing Xdebug receiver...
Xdebug receiver running on port 9003
Running a PHP script with Xdebug enabled..
Image Image

Debugging the file with @php-wasm/cli

Let’s see if Xdebug is enabled first :

> npx cli --xdebug -v

PHP 8.4.10-dev (cli) (built: Jul 22 2025 09:30:58) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.4.10-dev, Copyright (c) Zend Technologies
    with Xdebug v3.4.6-dev, Copyright (c) 2002-2025, by Derick Rethans
    with Zend OPcache v8.4.10-dev, Copyright (c), by Zend Technologies

Then run with xdebug and experimental-devtools options :

> npx cli --xdebug --experimental-devtools test.php

Starting Xdebug Bridge...
Connect Chrome DevTools to CDP at:
devtools://devtools/bundled/inspector.html?ws=localhost:9229

...

Chrome connected! Initializing Xdebug receiver...
Xdebug receiver running on port 9003
Running a PHP script with Xdebug enabled..
Image Image

Debugging the file with `@wp-playground/cli``

We run the cli with Xdebug and devtools enable. Finally we run the script.

playground.js

import { runCLI } from "@wp-playground/cli";

const script = `<?php

$test = 42;

echo "Output!\n";

function test() {
	echo "Hello Xdebug World!\n";
}

test();
`;

const cliServer = await runCLI( { php : '8.4', xdebug : true, experimentalDevtools : true } );

await cliServer.playground.writeFile( `test.php`, script );

await cliServer.playground.run( { scriptPath : `test.php` } );

Then we run the script:

> node playground.js

Starting a PHP server...
Setting up WordPress undefined
Resolved WordPress release URL: https://downloads.w.org/release/wordpress-6.8.2.zip
Fetching SQLite integration plugin...
Booting WordPress...
Booted!
Running the Blueprint...
Running the Blueprint – 100%
Finished running the blueprint
WordPress is running on http://127.0.0.1:53478
Connect Chrome DevTools to CDP at:
devtools://devtools/bundled/inspector.html?ws=localhost:9229

...

Chrome connected! Initializing Xdebug receiver...
Xdebug receiver running on port 9003
Running a PHP script with Xdebug enabled...
Image Image

Note : It will need 22 step overs before reaching the test.php file in this configuration.

What are the current limitations

  • First and foremost : This is only usable on Google Chrome.

  • Xdebug is not available yet on @php-wasm/web.

  • Firefox is no longer supporting CDP since end of 2024. We will need to implement BiDi Webdriver instead.

  • The files appear in the browser stack when they are called. You won’t find a complete file tree directly when opening the browser devtools.

  • When starting the debugging phase, it stops at the first line of the first PHP file Xdebug finds. When using @php-wasm/cli, it is your specific file, but when it is @wp-playground/cli it is the /internal/shared/auto_prepend_file.php and you need to step over 22 times to reach your specific file.

  • There is a known issue that Adam is currently correcting in the right Scope section of the Devtools. When opening an array it always shows the same array again and again.

  • I am currently working on a way to hide excluded paths [ e.g. /internal/shared ]. A pull request is currently on the works.

  • There is a mysterious problem with disabling or removing breakpoints when a breakpoint has been created. At least on my machine. I found a tweak to remove them but it was terrible. I had to add a breakpoint on each line. then delete each breakpoint and disabling that unsuppressed breakpoint. The breakpoint disappeared after disabling it… Strange.

  • It is not currently possible to quiet the logs from the bridge. And it is relatively verbose.

How to report problems

I guess we could invite people to post issues with the [Devtools] prefix in the title? I suppose people will mainly use Xdebug and the Chrome Devtools with the Playground CLI.

We should then analyze these issues and report them in the Follow-up I made for Xdebug itself.

Related links


This is a first draft. Don't hesitate to improve this directly.

cc @fellyph @adamziel

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions