Vim и Neovim как PHP IDE в 2026 году: LSP, автодополнение, отладка
Как собрать рабочую PHP-среду на Neovim без десятка устаревших плагинов: LSP-серверы вместо тегов, nvim-dap для Xdebug и статический анализ прямо в редакторе.

Десять лет назад собрать в Vim что-то похожее на полноценную IDE для PHP означало вручную подключать десяток плагинов: один генерировал теги ctags, второй подключал автодополнение по словарю, третий худо-бедно показывал ошибки синтаксиса. В 2026 году задача стала проще: протокол LSP и встроенный клиент Neovim закрывают большую часть того, для чего раньше нужны были отдельные плагины. Разберём, из чего собрать рабочий PHP IDE на Neovim сегодня, без избыточных зависимостей.
Почему LSP заменил теги и автодополнение по словарю
Ctags строит индекс классов и функций статическим разбором файла, точность у него ограничена: он не понимает типы, не следит за use-импортами и путается в динамических вызовах. LSP-серверы для PHP, такие как Intelephense и phpactor, разбирают проект так же, как это делает полноценная IDE: со знанием типов, контекстным автодополнением и переходом к определению через реальный анализ, а не грубый поиск по тексту. Для PHP 8.3 и 8.4 оба сервера поддерживают enum, readonly-свойства и новые атрибуты без дополнительной настройки.
- Intelephense: закрытый, но бесплатный в базовой версии сервер, быстрый старт, хорошая поддержка Laravel и Symfony из коробки
- phpactor: открытый сервер на PHP, медленнее на холодном старте больших проектов, но глубже умеет в рефакторинг
- оба подключаются в Neovim через встроенный
vim.lspбез отдельного плагина-клиента, нужен только их запуск
Автодополнение и навигация: nvim-cmp или blink.cmp
LSP-сервер сам по себе только отвечает на запросы по протоколу, показать меню автодополнения в редакторе должен отдельный плагин. В 2026 году стандартом остаётся nvim-cmp, более новая и быстрая альтернатива: blink.cmp, написанный с упором на минимальные задержки меню. Разница на практике заметна только на очень больших файлах, выбор между ними скорее дело вкуса.
lua require('lspconfig').intelephense.setup({})
nnoremap gd <cmd>lua vim.lsp.buf.definition()<cr>
nnoremap gr <cmd>lua vim.lsp.buf.references()<cr>После такой настройки gd переходит к определению класса или метода под курсором, а gr показывает все места, где он используется, именно это раньше делали ctags с заметно меньшей точностью.
.phpactor.json или intelephense.json с путями до стабов фреймворка, иначе автодополнение по фасадам и магическим методам будет работать заметно хуже.Отладка с Xdebug через nvim-dap
Для отладки в 2026 году используется nvim-dap, клиент протокола Debug Adapter Protocol, с адаптером vscode-php-debug, который умеет говорить с Xdebug. Настройка требует трёх частей: Xdebug должен быть включён в режиме debug в php.ini, адаптер должен слушать порт (по умолчанию 9003), а в Neovim должна быть конфигурация путей, которая связывает локальный путь к проекту с путём внутри контейнера, если разработка идёт в Docker.
| Параметр php.ini | Значение | Зачем |
|---|---|---|
| xdebug.mode | debug | включает отладку вместо профилирования |
| xdebug.client_port | 9003 | порт, на котором слушает dap-адаптер |
| xdebug.start_with_request | yes | сессия стартует при каждом запросе, а не по флагу в URL |
После такой настройки точки останова ставятся клавишей, привязанной к require('dap').toggle_breakpoint(), а запуск отладки открывает панель со стеком вызовов и переменными прямо во вкладке Neovim, без переключения в браузер.
Тесты и статический анализ без выхода из редактора
Статический анализ окупается быстрее отладчика: PHPStan или Psalm находят часть ошибок ещё до запуска кода. Плагин neotest с адаптером neotest-phpunit запускает тесты под курсором и показывает результат прямо в буфере, а линтеры подключаются через nvim-lint или тот же LSP-сервер, если он умеет отдавать диагностику PHPStan напрямую.
neotestиneotest-phpunit: запуск одного теста, файла или всего набора без переключения в терминалnvim-lintс PHPStan: диагностика прямо в редакторе, подчёркивание проблемных строк- PHP CodeSniffer как форматтер через
conform.nvimили совместимые обёртки
Что по-прежнему не нужно тащить в конфиг
Отдельный плагин-клиент автодополнения на базе omnifunc, генератор ctags через git-хуки и ручные снипеты для PHPDoc, типичные для статей пятилетней давности, в 2026 году избыточны: LSP закрывает навигацию и подсказки типов, а генерацию PHPDoc делают сами языковые серверы через code actions. Из старого стека стоит оставить разве что менеджер плагинов (lazy.nvim вместо устаревшего vim-plug) и vim-fugitive для git, они не связаны с PHP напрямую и остаются полезными сами по себе.
Форматирование и структура проекта: что берёт на себя LSP-сервер
Помимо навигации и автодополнения, Intelephense и phpactor умеют выполнять код-действия: добавить отсутствующий use для класса, сгенерировать конструктор по свойствам, переименовать переменную во всех её использованиях в пределах файла и за его пределами. Эти операции раньше требовали отдельных плагинов для рефакторинга, таких как vim-php-refactoring-toolbox, которые работали через простые регулярные подстановки и легко ломались на нетривиальном коде. LSP-сервер видит реальное дерево разбора файла, поэтому переименование символа корректно обходит строки и комментарии, где совпадает только текст, но не сам идентификатор.
Форматирование кода по стандарту PSR-12 в 2026 году чаще доверяют отдельному инструменту вроде PHP-CS-Fixer, запущенному как внешняя команда при сохранении файла, а не встроенным в LSP правилам. Это разделение ответственности удобнее: языковой сервер занимается анализом и навигацией, а форматтер, единым для редактора, CI и pre-commit хука, гарантирует одинаковый стиль кода независимо от того, кто и в каком редакторе его написал.
Несколько LSP-серверов на один проект и монорепозитории
В реальных проектах PHP почти никогда не живёт один: рядом есть JavaScript для фронтенда, YAML для конфигов Symfony, Docker-файлы для окружения. Встроенный клиент Neovim без проблем держит несколько LSP-серверов одновременно, каждый привязан к своему типу файла, и переключение между ними происходит автоматически по открытому буферу, без ручной настройки на каждый файл. Единственное, за чем стоит следить, это за потреблением памяти: Intelephense на большом монорепозитории с несколькими сервисами способен занимать заметный объём оперативной памяти, если проиндексировать сразу всё дерево, поэтому в монорепозитории разумнее указать серверу конкретную рабочую директорию сервиса, а не корень всего репозитория.
Для проектов, где PHP-сервисы физически разнесены по подкаталогам одного репозитория, помогает настройка нескольких корней проекта (workspace roots) в конфигурации LSP-клиента: каждый сервис получает свой экземпляр языкового сервера со своим composer.json и своими зависимостями, вместо одного сервера, который пытается разобраться сразу во всех пространствах имён монорепозитория и неизбежно путается в конфликтующих версиях одних и тех же пакетов.
Проверить, что всё работает правильно, просто: команда :LspInfo в Neovim показывает, какие серверы прикреплены к текущему буферу и в каком они состоянии. Если автодополнение вдруг перестало работать после переключения между сервисами монорепозитория, почти всегда причина именно здесь, прикреплён сервер с чужим корнем проекта, и решается это либо перезапуском клиента в текущем буфере, либо явным указанием корня в конфигурации.
Частые вопросы
Что выбрать для PHP в 2026 году: Intelephense или phpactor?
Для старта с Laravel или Symfony проще начать с Intelephense: он быстрее стартует и из коробки понимает стабы популярных фреймворков. Phpactor стоит добавить, если нужен глубокий рефакторинг, например перенос класса с обновлением всех ссылок или извлечение метода, в этом он заметно сильнее.
Нужен ли ctags, если уже настроен LSP-сервер?
Для PHP, как правило, нет. LSP-сервер строит собственный индекс и даёт точную навигацию с учётом типов, ctags в сравнении с ним грубее и может путаться в неймспейсах. Ctags остаётся полезным для языков без хорошего LSP-сервера или как резервный вариант на очень больших проектах.
Можно ли отлаживать PHP в Docker-контейнере через nvim-dap?
Да, но нужно явно сопоставить путь проекта на хосте с путём внутри контейнера в конфигурации адаптера через pathMappings, иначе точки останова не будут срабатывать: Xdebug сообщает отладчику путь внутри контейнера, а Neovim по умолчанию ищет файл на хосте.



