Pull to refresh

Отладка PHP приложений на удаленном хосте при помощи XDebug и vim в Linux

Reading time 5 min
Views 13K

Введение


В PHP приложениях отладка при помощи var_dump, debug_backtrace и прочих полезных функций не всегда удобна, и возникает потребность в полноценном отладчике. Эта статья — для тех, кто по каким-либо причинам не хочет использовать IDE, поддерживающие отладку PHP приложений из коробки, вроде NetBeans или PhpStorm, а хочет использовать для этих целей vim, и при этом отладка происходит на удаленном хосте.

Для vim существует плагин «DBGp client», но он позволяет нормально отлаживать только в случае, когда пути до всех файлов на удаленной и на локальной машинах одинаковые. Например, если локальной машине у вас есть:
/home/user/application/
/home/user/framework/

а на удаленной машине они расположены в:
/var/www/html/application/
/var/www/framework/

то отладить приложение при помощи «DBGp client» не получится, так как он ничего не знает о другом расположении исходников.

В данной статье мы рассмотрим:
  1. Кратко — настройку всего необходимого для удаленной отладки приложения.
  2. Модификацию плагина для поддержки различных путей.
  3. Кратко — использование отладчика.

Установка


Настраиваем vim на локальном хосте

Скачайте и установите плагин:
$ cd ~/.vim
$ wget www.vim.org/scripts/download_script.php?src_id=7285 -O debugger.zip
$ unzip debugger.zip
$ rm debugger.zip


Внимание: vim должен быть собран с поддержкой python, проверить это можно при помощи ":version", в выводе должна быть строка "+python".

Устанавливаем и настраиваем Xdebug на удаленном хосте

Установите расширение Xdebug любым удобным для вас способом. Например, на Debian Squeeze это делается просто:
# apt-get install php5-xdebug
А в общем случае, лучше почитать официальную инструкцию по установке.

Настройте подключение к локальному debug-клиенту — для этого в php.ini пропишите следующие строчки, заменив 192.168.1.110 на IP локальной машины (при необходимости порт тоже можно перенастроить):
xdebug.remote_enable = 1
xdebug.remote_port = 9000
xdebug.remote_host = 192.168.1.110


Настраиваем соответствие файлов

Идея простая — при отправке запроса дебагеру (например, на установку breakpoint), мы должны преобразовывать путь на локальном хосте в путь на удаленном хосте, и наоборот, при получении информации от дебагера (например, о том что сейчас он находится на какой-то строке какого-то файла), мы должны сделать обратное преобразование.
Дописываем в конец файла debugger.py следующий код:
class FileMapping: 
    def __init__(self, mapping_file): 
        self.local_to_remote = {} 
        self.remote_to_local = {} 

        mapping = open(mapping_file, 'r') 
        for line in mapping: 
            local, remote = line.split(' ') 

            local = local.strip() 
            remote = remote.strip() 

            if not (local in self.local_to_remote): 
                self.local_to_remote[local] = [] 
            self.local_to_remote[local].append(remote) 

            if not (remote in self.remote_to_local): 
                self.remote_to_local[remote] = [] 
            self.remote_to_local[remote].append(local) 

    def local_to_remote_file(self, local): 
        for local_path in self.local_to_remote.keys(): 
            if local.startswith(local_path): 
                # use the first mapping as we don't know which one we should take 
                remote_path = self.local_to_remote[local_path][0] 
                return remote_path + local[len(local_path):] 

    def remote_to_local_file(self, remote): 
        for remote_path in self.remote_to_local.keys(): 
            if remote.startswith(remote_path): 
                for local_path in self.remote_to_local[remote_path]: 
                    local = local_path + remote[len(remote_path):] 
                    # use the first existing file 
                    if os.path.exists(local): 
                        return local 
        return None 

file_mapping = FileMapping('/home/alexey/mapping')


А в файл mapping (/home/alexey/mapping — замените на свой путь) записываем соответствие локальный и удаленных директорий, например:
/home/alexey/framework /var/www/framework
/home/alexey/application /var/www/html


Просматриваем код плагина в поисках мест, где от Xdebug приходят имена файла. В итоге, все они сводятся к вызову одного метода — set_srcview, в начало которого мы и добавляем изменение имени файла:
def set_srcview(self, file, line):

    """ set srcview windows to file:line and replace current sign """

    file = file_mapping.remote_to_local_file(file)

Теперь ищем места, где наоборот от debug-клиента к Xdebug передаются имена файлов. Таких мест два:
1. Класс Debugger, метод run, заменяем строку
'-t line -f ' + self.breakpt.getfile(bno) + ' -n ' + str(self.breakpt.getline(bno)) + ' -s enabled', \

на
'-t line -f ' + file_mapping.local_to_remote_file(self.breakpt.getfile(bno)) + ' -n ' + str(self.breakpt.getline(bno)) + ' -s enabled', \

2. Класс Debugger, метод mark, заменяем строку:
'-t line -f ' + self.breakpt.getfile(bno) + ' -n ' + str(self.breakpt.getline(bno)), \

на
'-t line -f ' + file_mapping.local_to_remote_file(self.breakpt.getfile(bno)) + ' -n ' + str(self.breakpt.getline(bno)), \


Уже поправленный debugger.py можно взять тут.

Использование


Запускаем отладку

Чтобы начать отладку web-приложения, необходимо запустить vim и нажать <F5>.
Далее, в течение 5 секунд (ниже описано, как увеличить интервал), необходимо запустить какой-либо PHP скрипт, передав GET-ом переменную XDEBUG_SESSION_START со значением 1, например просто открыв соответствующую страницу в браузере, например:
webdev/debug.php?XDEBUG_SESSION_START=1
Альтернативно в php.ini можно задать переменную xdebug.remote_autostart. В таком случае, при запуске любого PHP скрипта, Xdebug будет пытаться подключиться к debug-клиенту.
Подробнее можно прочитать в официальной документации Xdebug.
В итоге должно получиться что-то вроде этого — должен открыться скрипт, который вы запустили:



Окна справа — сверху вниз:
  1. WATCH WINDOW — просмотр контекста
  2. HELP WINDOW — краткое описание возможностей
  3. STACK WINDOW — стек вызова функций
  4. TRACE WINDOW — лог общения debug-клиента c Xdebug, полезно посмотреть если отладка не заработала

Настройка под себя

Таймаут можно задать в том же скрипте, найдя в debugger.py строку:
serv.listen(5) 

и заменив 5 на нужное вам количество секунд.
Комбинации клавиш задаются в debugger.vim, например себе я переназначил <F5> на нажатие «,dr»:
map ,dr :python debugger_run()<cr>
Текст в HELP WINDOW можно поменять в классе HelpWindow, методе on_create.

Навигация по коду

Подробно расписывать не буду, отличия от других отладчиков тут минимальные:
  • Step into (<F2>) — шаг с заходом внутрь функций.
  • Step over (<F3>) — шаг без захода внутрь функций.
  • Step out (<F4>) — выход из функции по стеку вверх.
  • Run (<F5>) — продолжить выполнение до следующего breakpoint.
  • Stack up (:Up) — переход по стеку вверх (смотрите STACK WINDOW).
  • Stack down (:Dn) — переход по стеку вниз (смотрите STACK WINDOW).

Просмотр текущего состояния

  • Property get (<F12>) — получить значение переменной (надо поставить курсор на нужную переменную и нажать <F12>).
  • Context get (<F11>) — получить весь текущий контекст (грубо говоря, все переменные, доступные в данном контексте).
  • Eval (,e) — выполнить произвольное выражение в текущем контексте и получить его значение.

На скриншоте — WATCH WINDOW с выполнеными Context get и Eval:



Установка breakpoints

Toggle breakpoint (:Bp) — установить breakpoint в текущей строке, или удалить, если он уже есть.
На скриншоте — зеленая строка — это строка с breakpoint, красная — это текущая строка:



Resize

Дополнительно по <F1> можно переключатся между двумя режимами — полный, когда показываются различные вспомогательные окна справа, и простым — когда остается только окно с кодом.
Tags:
Hubs:
+27
Comments 31
Comments Comments 31

Articles