{"id":207,"date":"2021-10-22T11:26:59","date_gmt":"2021-10-22T01:26:59","guid":{"rendered":"https:\/\/tstarling.com\/blog\/?p=207"},"modified":"2021-11-01T10:02:39","modified_gmt":"2021-11-01T00:02:39","slug":"xdebug-on-demand","status":"publish","type":"post","link":"https:\/\/tstarling.com\/blog\/2021\/10\/xdebug-on-demand\/","title":{"rendered":"Xdebug on demand"},"content":{"rendered":"\n<p>My MediaWiki test instance normally runs without Xdebug, since that gives good performance. But when I request a debug session in PHPStorm, Xdebug is automatically enabled.<\/p>\n\n\n\n<p>I don&#8217;t often share config snippets since they are so specific to the way I have things set up. You will most likely have to adapt this to your situation. But at least it should help you to know that conditionally enabling Xdebug is possible.<\/p>\n\n\n\n<p>I use PHP-FPM which, unlike Apache mod-php, allows you to run multiple versions of PHP. I use Apache to detect Xdebug&#8217;s cookie or query string. Then I have a separate instance of PHP-FPM just for Xdebug, with a separate php.ini.<\/p>\n\n\n\n<p>I&#8217;m using Xdebug 3.0. I upgraded from Xdebug 2 for this project since I figured there is no point putting a lot of effort into an Xdebug 2 installation which I would almost certainly have to upgrade within a year.<\/p>\n\n\n\n<p>I have a file called <code>\/etc\/apache2\/php.conf<\/code> along the lines of:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;FilesMatch \".+\\.php$\"&gt;\n\t&lt;If \"%{HTTP_COOKIE} =~ \/XDEBUG_SESSION\/ || %{QUERY_STRING} =~ \/XDEBUG_SESSION_START=\/ || %{REQUEST_URI} =~ \/intellij_phpdebug_validator\/\"&gt;\n\t\tSetHandler \"proxy:unix:\/run\/php\/php8.0-fpm-xdebug.sock|fcgi:\/\/localhost\"\n\t&lt;\/If&gt;\n\t&lt;Else&gt;\n\t\tSetHandler \"proxy:unix:\/run\/php\/php8.0-fpm.sock|fcgi:\/\/localhost\"\n\t&lt;\/Else&gt;\n&lt;\/FilesMatch&gt;\n<\/code><\/pre>\n\n\n\n<p>I include that into any VirtualHost directives that need PHP execution.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;VirtualHost *:443&gt;\n\tServerName test.internal\n\t...\n\tInclude php.conf\n&lt;\/VirtualHost&gt;<\/code><\/pre>\n\n\n\n<p>The non-Xdebug instance of PHP-FPM is installed in the usual way, in my case using <a href=\"https:\/\/launchpad.net\/~ondrej\/+archive\/ubuntu\/php\">Ond\u00c5\u2122ej Sur\u00c3\u00bd&#8217;s PPA<\/a>. I copied the systemd service file for PHP-FPM from <code>\/lib\/systemd\/system\/php8.0-fpm.service<\/code> to <code>\/etc\/systemd\/system\/php-fpm-xdebug.service<\/code> and changed the paths to avoid conflicts:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91;Unit]\nDescription=The PHP 8.0 FastCGI Process Manager (with xdebug)\nDocumentation=man:php-fpm8.0(8)\nAfter=network.target\n\n&#91;Service]\nType=notify\nExecStart=\/usr\/sbin\/php-fpm8.0 --nodaemonize --fpm-config \/etc\/php\/8.0\/fpm-xdebug\/php-fpm.conf -c \/etc\/php\/8.0\/fpm-xdebug\nExecStartPost=-\/usr\/lib\/php\/php-fpm-socket-helper install \/run\/php\/php-fpm-xdebug.sock \/etc\/php\/8.0\/fpm-xdebug\/pool.d\/www.conf 80\nExecStopPost=-\/usr\/lib\/php\/php-fpm-socket-helper remove \/run\/php\/php-fpm-xdebug.sock \/etc\/php\/8.0\/fpm-xdebug\/pool.d\/www.conf 80\nExecReload=\/bin\/kill -USR2 $MAINPID\n\n&#91;Install]\nWantedBy=multi-user.target<\/code><\/pre>\n\n\n\n<p><code>\/etc\/php\/8.0\/fpm-xdebug<\/code> is the configuration directory for the new instance of PHP-FPM, initially based on a copy of <code>\/etc\/php\/8.0\/fpm<\/code>. To avoid loading Xdebug into the non-Xdebug instance, I commented out the zend_extension line in <code>\/etc\/php\/8.0\/mods-available\/xdebug.ini<\/code> , since the package has a bug which recreates the <code>conf.d<\/code> symlinks if you delete them. <\/p>\n\n\n\n<p>I don&#8217;t usually use the default <code>php.ini<\/code>, I just wipe it after package installation and start with an empty file, since that makes it easier to see which defaults I&#8217;ve overridden. You lose the documentation comments, but I&#8217;m able to read the manual, so that&#8217;s fine.<\/p>\n\n\n\n<p>My <code>\/etc\/php\/8.0\/fpm-xdebug\/php.ini<\/code> has:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>zend_extension = xdebug.so\nxdebug.mode = debug\nxdebug.client_host = 169.254.1.1<\/code><\/pre>\n\n\n\n<p>Replace 169.254.1.1 with the address of the host on which your IDE runs, as seen by the container. I am using systemd-nspawn with bridged networking, which allows PHP to connect to the host using link-local addresses.<\/p>\n\n\n\n<p>A few more little things to avoid conflicts with the main PHP-FPM instance. Like <code>php.ini<\/code>, I started with empty files, not default files. <code>\/etc\/php\/8.0\/fpm-xdebug\/php-fpm.conf<\/code> contains only:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91;global]\npid = \/run\/php\/php8.0-fpm-xdebug.pid\nerror_log = \/var\/log\/php8.0-fpm-xdebug.log\n\ninclude=\/etc\/php\/8.0\/fpm-xdebug\/pool.d\/*.conf<\/code><\/pre>\n\n\n\n<p>And <code>\/etc\/php\/8.0\/fpm-xdebug\/pool.d\/www.conf<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91;www]\nuser = www-data\ngroup = www-data\nlisten = \/run\/php\/php8.0-fpm-xdebug.sock\nlisten.owner = www-data\nlisten.group = www-data\npm = dynamic\npm.max_children = 5\npm.start_servers = 1\npm.min_spare_servers = 1\npm.max_spare_servers = 3<\/code><\/pre>\n\n\n\n<p>I think that&#8217;s it. Now it should just be a matter of<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>systemctl daemon-reload\nsystemctl start php-fpm-xdebug\nsystemctl enable php-fpm-xdebug\nsystemctl reload apache2<\/code><\/pre>\n\n\n\n<p>To debug a web request, set a breakpoint, then in Run &gt; Edit Configurations, create a &#8220;PHP Web Page&#8221; configuration. The URL you use here does not have to be the exact one you want to debug, it just has to be in the same cookie domain. Then Run &gt; Debug&#8230; and select the configuration. This will spawn a browser tab which activates Xdebug with the XDEBUG_SESSION_START query string parameter. Xdebug modifies the response to set the XDEBUG_SESSION cookie.<\/p>\n\n\n\n<p>Since XDebug 3.1, the cookie has no expiry time set, but you can append XDEBUG_SESSION_STOP=1 to the query string to cause it to remove its session cookie. With no session cookie, you will automatically be back to PHP without Xdebug.<\/p>\n\n\n\n<p>You can get PHPStorm to run command-line scripts via SSH, but I find it&#8217;s most convenient to run them in the usual way, with the PHPStorm option &#8220;Listen for PHP Debug Connections&#8221; enabled.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"274\" height=\"78\" src=\"https:\/\/tstarling.com\/blog\/wp-content\/uploads\/2021\/10\/start-listening.png\" alt=\"\" class=\"wp-image-208\"\/><\/figure>\n\n\n\n<p>I use a shell script wrapper along the lines of:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>XDEBUG_TRIGGER=1 PHP_IDE_CONFIG=serverName=test.internal \\\n  php -dzend_extension=xdebug.so -dxdebug.mode=debug -dxdebug.client_port=9000 \\\n  -dxdebug.client_host=169.254.1.1 \"$@\"<\/code><\/pre>\n\n\n\n<p>The <code>serverName<\/code> configuration variable here is necessary to select the right path mappings \u00e2\u20ac\u201d it must correspond to the name of the server in PHPStorm&#8217;s File &gt; Settings &gt; PHP &gt; Servers.<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>My MediaWiki test instance normally runs without Xdebug, since that gives good performance. But when I request a debug session in PHPStorm, Xdebug is automatically enabled. I don&#8217;t often share config snippets since they are so specific to the way I have things set up. You will most likely have to adapt this to your &#8230;<\/p>\n<p><a href=\"https:\/\/tstarling.com\/blog\/2021\/10\/xdebug-on-demand\/\" class=\"more-link\">Continue reading &lsquo;Xdebug on demand&rsquo; &raquo;<\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4],"tags":[],"class_list":["post-207","post","type-post","status-publish","format-standard","hentry","category-web-development"],"_links":{"self":[{"href":"https:\/\/tstarling.com\/blog\/wp-json\/wp\/v2\/posts\/207","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tstarling.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tstarling.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tstarling.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/tstarling.com\/blog\/wp-json\/wp\/v2\/comments?post=207"}],"version-history":[{"count":3,"href":"https:\/\/tstarling.com\/blog\/wp-json\/wp\/v2\/posts\/207\/revisions"}],"predecessor-version":[{"id":214,"href":"https:\/\/tstarling.com\/blog\/wp-json\/wp\/v2\/posts\/207\/revisions\/214"}],"wp:attachment":[{"href":"https:\/\/tstarling.com\/blog\/wp-json\/wp\/v2\/media?parent=207"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tstarling.com\/blog\/wp-json\/wp\/v2\/categories?post=207"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tstarling.com\/blog\/wp-json\/wp\/v2\/tags?post=207"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}