Введение
В 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» не получится, так как он ничего не знает о другом расположении исходников.
В данной статье мы рассмотрим:
- Кратко — настройку всего необходимого для удаленной отладки приложения.
- Модификацию плагина для поддержки различных путей.
- Кратко — использование отладчика.
Установка
Настраиваем 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.
В итоге должно получиться что-то вроде этого — должен открыться скрипт, который вы запустили:
Окна справа — сверху вниз:
- WATCH WINDOW — просмотр контекста
- HELP WINDOW — краткое описание возможностей
- STACK WINDOW — стек вызова функций
- 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> можно переключатся между двумя режимами — полный, когда показываются различные вспомогательные окна справа, и простым — когда остается только окно с кодом.