Одной из первых задач, когда я начинал разработку idea, была интеграция xdebug в свой инструмент разработки и реализация основных возможностей работы дебагера. Первым делом надо было изучить документация по протоколу xdebug. В нем используется общий протокол дебагера DBGP. В целом ничего сложного, есть сокет через который мы отправляем команды в сам xdebug он нам отвечает в формате xml, парсим xml и получаем результат. Все просто подумал Я, но как всегда не без приключений.
Первая проблема возникла от недопонимания в какой момент создается сокет, тот самый порт 9000. Читаем документацию пункт 5.3 и 5.4 первый говорит про стандартный порт второй пункт говорит о инициализации подключения и что xdebug отвечает при готовности объектом <init>
Скрытый текст
The IDE listens on port 9000 for debugger connections, unless the IDE is using a proxy, in which case it may listen on any port. In that case, the IDE will tell the proxy which port it is listening on, and the proxy should listen on port 9000. While this document defines port 9000 as the standard DBGP port, an implementation may support the use of any port. Current implementations accept various forms of configuration that allow this port to be defined.
Окей подумал Я, написал метод подключения к порту а порта то и нет и дальше пошли netstat, telnet и так далее для понимания где этот порт находиться вообще и почему его нет в сиситеме. В конце концов я понял что заблуждаюсь, скачал консольный клиент xdebug, и порт появился в системе а скрипт как по волшебству остановился… Мда подумал я и понял что порт должна открыть idea а xdebug сам подключиться к этому порту. Странно что такой момент не упомянут в документации, либо я просто этот момент не увидел. В общем создал я в первую очередь метод который открывает и прослушивает порт 9000.
Скрытый текст
// запускаем в отдельном потоке иначе будет блокриованно выполнение программы public void startDebugging() { Thread thread = new Thread(() -> { startDebugNewThread(); }); thread.start(); } // создан xdebugClient который создает сокет public XDebugClient(String host, int port) throws IOException { this.serverSocket = new ServerSocket(port); this.socket = this.serverSocket.accept();// this.socket.setSoTimeout(1000); this.in = new BufferedReader(new InputStreamReader(socket.getInputStream())); this.out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); this.transactionId = "4"; // В простом примере будем использовать статический ID this.listenerExecutor = Executors.newSingleThreadExecutor(); }// и дальше listener уже считывает сообщения из socket'а/** * Запускает цикл прослушивания в новом фоновом потоке. * Этот метод НЕ блокирует текущий поток. */public void startListening() { // Используем ExecutorService для управления потоком listenerExecutor.submit(() -> { System.out.println("[DEBUG] Listener thread started."); while (true) { try { String message = readMessage(); if (message == null) { continue; } System.out.println("Received from Xdebug: " + message); ObjectContainer.eventDispatcher.dispatch(new RawMessageHitEvent(message)); XDebugChainFactory.create().readMessage(message); } catch (IOException e) { // Обрабатываем разрыв соединения или ошибки чтения if (!socket.isClosed()) { System.err.println("Error in listener thread: " + e.getMessage()); // Можно уведомить UI об ошибке соединения XdebugEventListener listener = XDebugDto.xdebugEventListener; SwingUtilities.invokeLater(() -> { listener.onError(Map.of("error", "Connection lost", "details", e.getMessage())); }); } System.out.println("ОШибка xdebug"); System.err.println(e.getMessage()); } catch (Exception e) { System.err.println("error parser: " + e.getMessage()); } } });}
Таким образом мы научились читать сообщения, но почему то в первое время после отсылки команды run в сокет xdebug разрывал соединение. Абсолютно непонятно если честно, я включил логи, попробовал создавать порт вручную, подключаясь через telnet, получилось лишь получать сообщение init, любая команда либо не доходила до xdebug либо просто рвал соединение и не отвечал. Пошел читать документацию, и видимо по собственной не внимательности не заметил что нужен NULL символ после каждой команды тем самым был написан метод который отправляет команду и при этом в конце ее подставляет NULL.
Скрытый текст
private void sendCommand(String command, Map<String, String> args) throws IOException { StringBuilder cmd = new StringBuilder(command); Random rand = new Random(); cmd.append(" -i ").append(rand.nextInt(1000)); // -i это transaction_id if (args != null) { for (Map.Entry<String, String> entry : args.entrySet()) { cmd.append(" ").append(entry.getKey()).append(" ").append(entry.getValue()); } } // cmd.append("\n"); cmd.append("\0"); out.write(cmd.toString()); out.flush(); System.out.println("Sent to Xdebug: " + cmd.toString().trim()); }
И наконец то команды начали уходить в наш xdebug если вы заметили я не напрямую их обрабатываю а просто выкидываю event в диспетчер, в котором мой код подхватывает данные события обсерверами и уже асинхронно может отобразить переменную, открыть файл, поставить указатель точки остановки на строке где остановлен php.

Переменные слева сделаны с помощью TreeView javafx, а вот просмотр переменных это уже WebView по клику на переменную просто вызывается eval в xdebug с print_r и именем переменной. В строке чуть выше так же возможно выполнить произвольные команды внутри скрипта на данной точки остановки.

Переменная uninitialized не установлена потому что точка остановки стоит до нее, и я не стал их фильтровать в своей idea.
В качестве заключения приведу ссылку на документацию по протоколу DBGP https://xdebug.org/docs/dbgp а так же в спойлере конфигурация xdebug с включенными логами
Скрытый текст
zend_extension=xdebug.soxdebug.mode=debug;xdebug.discover_client_host = 1xdebug.start_with_request=yes ; Или trigger, но start_with_request проще для стартаxdebug.client_host=127.0.0.1 ; IP вашей машины, где запущен редактор Javaxdebug.client_port=9003xdebug.log="/tmp/x.log"xdebug.log_level=7xdebug.cli_color=1
ссылка на оригинал статьи https://habr.com/ru/articles/1043900/