Support XDebug step debugging
marc-mabe opened this issue · comments
Marc Bennewitz commented
Feature Request
Q | A |
---|---|
New Feature | yes |
RFC | no |
BC Break | no |
Summary
Debugging swoole applications is much harder as 1. XDebug step debugging does not work out-of-the box and 2. because printing debug messages get send into nirvana.
Both are not related a mezzio swoole but to the nature how the swoole webserver works.
I think I found a good way to support xdebug step debugging requests by manually reproducing what xdebug does normally to start a debug session on http request and would like to get your thoughts about this.
PS: Currently this is a prov-of-concept for step debugging only but it should also be possible for profiling and other debugging sessions.
Requirements to make it work:
- xdebug installed and
debug
mode enabled - coroutines turned off (Actually didn't test with coroutines turned on yet)
- Add
XDebugSessionRequestListener
as firstRequestEvent
listener índevelopment.config.php
class XDebugSessionRequestListener
{
private bool $isAvailable = false;
private string $startWithRequest = 'trigger';
private string $triggerValue = '';
public function __construct()
{
if (!\extension_loaded('xdebug')) {
return;
}
$mode = (string)(\getenv('XDEBUG_MODE') ?: \ini_get('xdebug.mode') ?? '');
if (!\in_array('debug', \explode(',', $mode), true)) {
return;
}
// env XDEBUG_CONFIG
$envConfig = [];
$envConfigEntries = \explode(' ', (string)\getenv('XDEBUG_CONFIG'));
foreach ($envConfigEntries as $envConfigEntry) {
$envConfigSplit = \explode('=', $envConfigEntry);
$envConfig[$envConfigSplit[0]] = $envConfigSplit[1] ?? null;
}
$startWithRequest = $envConfig['start_with_request'] ?? (string)\ini_get('xdebug.start_with_request');
if ($startWithRequest === 'no') {
return;
}
// Normalize "xdebug.start_with_request" setting
// as the value "default" depends on "xdebug.mode":
// debug: trigger
// gcstats: no
// profile: yes
// trace: trigger
// -> We already expect "xdebug.mode" to include "debug"
if ($startWithRequest === 'default') {
$startWithRequest = 'trigger';
}
$this->startWithRequest = $startWithRequest;
$this->triggerValue = $envConfig['trigger_value'] ?? (string)\ini_get('xdebug.trigger_value');
$this->isAvailable = true;
}
public function __invoke(RequestEvent $event): void
{
if (!$this->isAvailable) {
return;
}
// XDebug is configured to auto start on each request
if ($this->startWithRequest === 'yes') {
$this->start();
return;
}
if ($this->startWithRequest !== 'trigger') {
return;
}
$request = $event->getRequest();
$requestValue = $request->cookie['XDEBUG_TRIGGER']
?? $request->get['XDEBUG_TRIGGER']
?? $request->post['XDEBUG_TRIGGER']
?? null;
// legacy triggers
$requestValue ??= $request->cookie['XDEBUG_SESSION']
?? $request->get['XDEBUG_SESSION']
?? $request->post['XDEBUG_SESSION']
?? null;
if ($requestValue === $this->triggerValue || ($requestValue !== null && !$this->triggerValue)) {
$this->start();
}
}
private function start(): void
{
if (\function_exists('xdebug_connect_to_client')) {
// XDebug >= 3.1
\xdebug_connect_to_client();
} else {
// XDebug < 3.1 we have to set a breakpoint
\xdebug_break();
}
}
}