PHP_CS in Neovim

I recently switched to only Neovim as my editor of choice and I hit a few issues with formatting PHP code to follow the WordPress coding standards. I didn’t find a simple guide to follow that worked for my case so I decided to write one which I can refer to in the future 🙂

My existing setup:

  • Neovim as editor
  • lazy.nvim as a package manager
  • macOS with brew as package manager
  • php v8.3.3 (maybe 5.4+ is required for this to work, but I haven’t tested it.)

With that, here is everything needed to make it work:

Composer & packages

If not installed, install composer, which we will need to install globally the phpcs package: brew install composer

Make sure it all works by running composer.

Install phpcs globally by following the guide on https://github.com/squizlabs/PHP_CodeSniffer. When the setup is done, phpcs will run on each PHP file with a PHP coding standard we provide. If desired, you can set it up to run on a command like <leader>pcf (for php code fix) or <leader>pcc (for php code check maybe).

Make sure phpcs and phpcbf are available globally in your system as commands. So add them to PATH. Example:

export PATH="/Users/alex/vendor/bin:$PATH". Don’t forget to source bashrc or zshrc or whatever you use.

Install WordPress coding standards:

https://github.com/WordPress/WordPress-Coding-Standards – Follow the guide for installation as a global installation. The package we will be using is WordPress but there is no need to do anything more than just installing it right now. We will set it up in neovim later.

A time to say this:

  • PHPCS – Lints, shows issues. Works live as you edit files.
  • PHPCBF – Fixes issues. can be run pre-commit or as you save your file.

Test if everything works so far:

Create a PHP file and write some code that is not formatted by the WordPress coding standards. Example:

<?php

$a = 5;
$aa = 5; // We want the equals to align.

// Docblock must be set
function($a) // spacing for ( $a) and { on same line
{
}

To test it, in your terminal run phpcs --standard=WordPress file.php

If it all works, you should see output with all errors found like this:

FILE: /Users/alex/Development/Projects/php/test.php
-------------------------------------------------------------------------------------------
FOUND 4 ERRORS AFFECTING 4 LINES
-------------------------------------------------------------------------------------------
  1 | ERROR | Missing file doc comment
  6 | ERROR | Inline comments must end in full-stops, exclamation marks, or question marks
  8 | ERROR | Inline comments must end in full-stops, exclamation marks, or question marks
 10 | ERROR | PHP syntax error: syntax error, unexpected end of file
-------------------------------------------------------------------------------------------

Time: 76ms; Memory: 10MB

This is the linting. Now let’s try to auto-fix the errors. `phpcbf --standard=WordPress file.php`

PHPCBF RESULT SUMMARY
----------------------------------------------------------------------
FILE                                                  FIXED  REMAINING
----------------------------------------------------------------------
/Users/alex/Development/Projects/php/test.php         5      4
----------------------------------------------------------------------
A TOTAL OF 5 ERRORS WERE FIXED IN 1 FILE
----------------------------------------------------------------------

Time: 271ms; Memory: 12MB

5 errors fixed! 🎉 If you run cat test.php or just view the file you should see it like this:

<?php

$a  = 5;
$aa = 5; // We want the equals to align.

// Docblock must be set
function ( $a ) {
	// spacing for ( $a) and { on same line
}

So much better! Of course, there are issues phpcbf can’t fix, like the doc block comments. This is left to the developer.

Setup phpcs in Neovim

You’ve likely already had PHPCS working, but we had to go through it just in case. Or just to make sure it’s set correctly in your terminal.

First, let’s install praem90/nvim-phpcsf. With lazy.nvim it’s quite easy, all I did was to add the new “phpcs.lua” file in my /lazy folder:

return {
    {
        "praem90/nvim-phpcsf",
        config = function()
        end
    },
}

Restart nvim to have lazy.nvim install nvim-phpcsf. Next, from the docs we see that we need to setup some global variables to point where phpcs is located:

return {
    -- PHPCS installation. 
    {
        "praem90/nvim-phpcsf",
        config = function()
            vim.g.nvim_phpcs_config_phpcs_path = 'phpcs'
            vim.g.nvim_phpcs_config_phpcbf_path = 'phpcbf'
            vim.g.nvim_phpcs_config_phpcs_standard = 'WordPress' -- or path to your ruleset phpcs.xml
        end
    },
}

See the last line – the standard is set to “WordPress”. If you work on something else this is the place you want to setup your standards. I haven’t yet needed to change it so I don’t have a ready solution. It is possible that phpcs will automatically read from the config file of your project, but again, not tested.

Now, to test it out, open again your test.php file using nvim and run the following command: lua require'phpcs'.cbf() You should see the file getting fixed.

Troubleshooting

  1. If you encounter an error for missing JSON implementation/module/include, you will need to get it in your lua config unforunately. See the issue here: https://github.com/praem90/nvim-phpcsf/issues/4. I had the same issue and this fixed it for me. Read your error message about the places it searches for the JSON file and pick one; you can wget the file in it. Restart and it should work.

Optional – format on save

You can setup a command to run PHPCS or PHPCBF whenever needed. For my setup, I have it “on save”, but might change in the future if it gets in the way with generating too large PRs.

The auto-format setup follows after the global variables. Also, this is the full phpcs.lua file I have, nothing is removed.

return {
    -- PHPCS installation. 
    -- https://github.com/WordPress/WordPress-Coding-Standards?tab=readme-ov-file#installation
    {
        "praem90/nvim-phpcsf",
        config = function()
            vim.api.nvim_create_augroup("PHPCSGroup", {clear = true})

            vim.g.nvim_phpcs_config_phpcs_path = 'phpcs'
            vim.g.nvim_phpcs_config_phpcbf_path = 'phpcbf'
            vim.g.nvim_phpcs_config_phpcs_standard = 'WordPress' -- or path to your ruleset phpcs.xml

            -- Setup auto formatting for php files using phpcs
            vim.api.nvim_create_autocmd({"BufWritePost", "BufReadPost", "InsertLeave"}, {
                group = "PHPCSGroup",
                pattern = "*.php",
                command = "lua require'phpcs'.cs()",
            })
            vim.api.nvim_create_autocmd("BufWritePost", {
                group = "PHPCSGroup",
                pattern = "*.php",
                command = "lua require'phpcs'.cbf()",
            })

            vim.keymap.set("n", "<leader>lp", function() require("phpcs").cbf() end, {
                silent = true,
                noremap = true,
                desc = "PHP Fix"
            })
        end
    },
}

I have commented “what does this do?” as I didn’t really look into it or have much of Neovim knowledge 🙂 But I kept it there as I believe there is some organizational thing going on with the groups. Might not be needed though.

What’s important – on the correct event as described from the package (BufWritePost), we run CBF to format our file. On file changes, we use PHPCS to lint it and show errors.

Here is a screenshot from my editor on a mostly formatted test.php file:

So PHPCBF did the alignment and spacing and all, but didn’t touch the dot at the end or adding a Docblock for my function definition.

I hope this helped!