mezzio / mezzio-swoole

Swoole support for Mezzio

Home Page:https://docs.mezzio.dev/mezzio-swoole/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Support XDebug step debugging

marc-mabe opened this issue · comments

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 first RequestEvent listener ín development.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();
        }
    }
}