NeoVim for PHP Development in 2026: Native LSP, Intelephense, and the Modern Setup
Set up a powerful PHP development environment in NeoVim 0.11+ using native LSP, Intelephense, Phpactor, Mason, and Treesitter — no more plugin sprawl.
The PHP tooling ecosystem for NeoVim has quietly matured into something genuinely impressive. If you have been putting off switching from VS Code or PhpStorm because you assumed the NeoVim experience was still a patchwork of brittle plugins and manual configuration, 2026 is a good time to look again. The release of Neovim 0.11 in March 2025 simplified the LSP story considerably, and the broader plugin ecosystem has settled around a handful of stable, well-maintained tools that work together without constant babysitting.
This is a practical walkthrough of setting up a modern PHP development environment in NeoVim — the kind of setup you can rely on for a day of serious work.
What Changed in Neovim 0.11
Before 0.11, the standard approach was to install nvim-lspconfig — a plugin that shipped configurations for dozens of language servers — and call require('lspconfig').intelephense.setup({}) to enable PHP support. It worked, but it added an abstraction layer that occasionally caused subtle issues, especially on version bumps.
Neovim 0.11 introduced a native LSP configuration API. The plugin-level dependency on nvim-lspconfig is no longer strictly required for basic setups. Instead, you create a server config file and enable it directly:
-- ~/.config/nvim/lsp/intelephense.lua
return {
cmd = { 'intelephense', '--stdio' },
filetypes = { 'php' },
root_markers = { 'composer.json', '.git' },
}
Then in your init.lua:
vim.lsp.enable({ 'intelephense' })
vim.diagnostic.config({
virtual_text = true,
signs = true,
underline = true,
update_in_insert = false,
})
Neovim scans your runtime path, finds the server config, and wires it up automatically. No plugin call, no setup function. For simple projects this is genuinely all you need.
That said, nvim-lspconfig is not obsolete — it still ships useful defaults, and many plugin configurations depend on it. The point is that the floor has been lowered: you can now have a working LSP setup with minimal infrastructure.
Choosing a PHP Language Server: Intelephense vs Phpactor
PHP developers have two serious LSP options, and they are not mutually exclusive.
Intelephense is the more broadly recommended choice for day-to-day completions, hover documentation, and go-to-definition. It handles large codebases well and understands Laravel’s magic methods better than most alternatives. Install it via npm:
npm install -g intelephense
The free tier covers most use cases. The premium license ($13 one-time) unlocks go-to-implementation, method signature help for inherited methods, and a handful of other features that matter in deeply class-hierarchy-heavy code.
Phpactor complements Intelephense rather than replacing it. Where Phpactor shines is refactoring: it can rename variables and methods across files, extract interfaces, generate constructors, implement interface methods, and more. These are “code action” capabilities, and Phpactor’s implementation is more thorough than Intelephense’s.
A common configuration uses Intelephense for diagnostics, hover, and completions, then routes code actions specifically through Phpactor:
-- lsp/phpactor.lua
return {
cmd = { 'phpactor', 'language-server' },
filetypes = { 'php' },
root_markers = { 'composer.json', '.git' },
-- Only handle code actions; let intelephense own the rest
capabilities = {
textDocument = {
completion = { dynamicRegistration = false },
hover = { dynamicRegistration = false },
},
},
}
Install Phpactor via Composer:
composer global require phpactor/phpactor
Managing Servers and Tools with Mason
Installing language servers, linters, and formatters manually becomes error-prone across machines. Mason.nvim solves this by providing a package manager inside Neovim itself. Running :Mason opens an interactive UI where you can install, update, and remove tools.
For PHP work, the useful Mason packages are:
intelephense— PHP language serverphpactor— refactoring LSPphp-cs-fixer— PSR-12 formattingphpstan— static analysis (orpsalmdepending on your project)blade-formatter— if you work with Laravel Blade templates
The companion plugin mason-lspconfig.nvim can automatically wire installed servers into your LSP config, which is useful if you want a zero-friction setup for new machines.
Treesitter for PHP
LSP handles intelligence; Treesitter handles syntax. Install the PHP grammar:
:TSInstall php phpdoc
With phpdoc installed alongside php, Treesitter correctly highlights docblock annotations, which matters when you are navigating PHPDoc-heavy code or reading code where return types and parameter types live primarily in comments rather than native PHP type declarations.
Treesitter also powers features like structural text objects, which let you move between functions and classes using ]m / [m motions in plugins like nvim-treesitter-textobjects.
Autocompletion with nvim-cmp
Neovim’s LSP sends completions, but something needs to display them. nvim-cmp is the standard choice. A minimal PHP-relevant setup:
local cmp = require('cmp')
cmp.setup({
sources = cmp.config.sources({
{ name = 'nvim_lsp' },
{ name = 'luasnip' },
{ name = 'buffer' },
{ name = 'path' },
}),
mapping = cmp.mapping.preset.insert({
['<C-Space>'] = cmp.mapping.complete(),
['<CR>'] = cmp.mapping.confirm({ select = true }),
['<Tab>'] = cmp.mapping.select_next_item(),
['<S-Tab>'] = cmp.mapping.select_prev_item(),
}),
})
For Laravel projects, cmp-laravel-routes adds route name completions, and a Blade-aware snippet collection makes view work tolerable without dedicated Blade LSP support.
Formatting with Conform.nvim
Running PHP CS Fixer manually is friction you do not need. conform.nvim integrates formatters directly into Neovim’s save workflow:
require('conform').setup({
formatters_by_ft = {
php = { 'php_cs_fixer' },
blade = { 'blade_formatter' },
},
format_on_save = {
timeout_ms = 3000,
lsp_fallback = true,
},
})
With a .php-cs-fixer.php config at your project root, your code auto-formats on write using whatever rule set your project specifies — PER-CS, PSR-12, or a custom configuration.
Static Analysis Integration
PHPStan does not run as a language server by default, but the none-ls.nvim plugin (a maintained fork of null-ls) can pipe PHPStan output into Neovim’s diagnostic system:
local null_ls = require('null-ls')
null_ls.setup({
sources = {
null_ls.builtins.diagnostics.phpstan.with({
args = { 'analyse', '--error-format=json', '--no-progress', '$FILENAME' },
timeout = 15000,
}),
},
})
This surfaces PHPStan errors as standard diagnostics, letting you navigate them with ]d / [d the same way you would LSP errors.
Keymaps Worth Having
A few keymaps that pay dividends in PHP work:
local opts = { noremap = true, silent = true }
-- LSP
vim.keymap.set('n', 'gd', vim.lsp.buf.definition, opts)
vim.keymap.set('n', 'gr', vim.lsp.buf.references, opts)
vim.keymap.set('n', 'gi', vim.lsp.buf.implementation, opts)
vim.keymap.set('n', 'K', vim.lsp.buf.hover, opts)
vim.keymap.set('n', '<leader>rn', vim.lsp.buf.rename, opts)
vim.keymap.set('n', '<leader>ca', vim.lsp.buf.code_action, opts)
-- Diagnostics
vim.keymap.set('n', ']d', vim.diagnostic.goto_next, opts)
vim.keymap.set('n', '[d', vim.diagnostic.goto_prev, opts)
vim.keymap.set('n', '<leader>e', vim.diagnostic.open_float, opts)
The <leader>ca binding is particularly valuable in PHP — that is your entry point to Phpactor’s refactoring actions when working in a class file.
Getting the Most Out of This Setup
A few things that are easy to miss: first, make sure Intelephense’s storagePath setting points somewhere outside your project tree, or it will leave .intelephense directories scattered through your repos. Second, if you use Laravel, adding laravel to Intelephense’s stubs list improves completions for facades and helpers significantly. Third, Phpactor’s index can become stale on large projects — running :!phpactor cache:clear from within Neovim clears it.
The NeoVim PHP setup in 2026 is genuinely competitive with dedicated IDEs for most day-to-day work. The gaps that remain — some Blade template completions, a few edge cases in Laravel magic method resolution — are small enough that they rarely interrupt flow. For developers who already live in the terminal, there is no longer a meaningful productivity argument for staying out of NeoVim.