Title: Proposal: an Interactive Mode for phpcbf
Author: Alex Kirk
Published: September 29, 2025
Last modified: October 2, 2025

---

# Proposal: an Interactive Mode for phpcbf

September 29, 2025

Did you ever get a little annoyed at `phpcs`? Maybe you’re like me: I like the idea
of [PHP Code Sniffer](https://github.com/phpcsstandards/PHP_CodeSniffer) (phpcs),
but I think the user experience has room for improvement:

Especially when trying to get larger amounts of PHP code to comply with the coding
standards, it feels like this tool just scolds you and all you can do is to diligently
work away on each and every violation that it discovers.

And you might have gotten yourself into such a situation because [you realized well into the project that you forgot an important ruleset](https://github.com/akirk/friends/pull/329),
or when you’re merging codebases.

### Sniffs are Opinions

One important thing that we tend to forget: often we are in a position where we 
can actually make the decisions which rules to follow and which not. A ruleset prepared
by someone else might contain things we disagree with. Thus phpcs contains the ability
to exclude sniffs via its phpcs.xml configuration file, and I think it’s ok to make
use of that. But also, I think that phpcs could be more helpful to you in doing 
so.

When you encounter such an overly eager ruleset, you might complain that the ruleset
itself should be updated. But maybe you are not in a position to fight that battle,
so you can just find a solution for yourself.

### phpcbf can help fix things

The PHP Code Sniffer project comes with two main executables: `phpcs`, and `phpcbf`.
While `phpcs` complains, `phpcbf` tries to help you. Sniffs can provide auto-fixers
for violations they encounter and `phpcbf` applies them for you. But oftentimes 
it seems like there should be an auto-fixer but in the end, you have to fix it yourself.

A side-note here: maintaining the phpcs project is certainly not a piece of cake
since it’s a project heavily exposed to opinion. Many things have to be considered,
and it looks like there has been agreement that an auto-fixer can only be added 
if there is a single and clear solution to the problem.

Thus, there are a number of sniffs where the resolution of a violation needs a user
decision. This decision could be complex, like you need to rearrange code, or add
something that you forgot (like a type for a parameter). But I believe there are
also decisions to make where it’s a matter of choosing between several options.

### User Decision Required

One example of this is `Squiz.Commenting.InlineComment.InvalidEndChar`: “Inline 
comments must end in full-stops, exclamation marks, or question marks.” Let’s not
argue about its validity but about the reason it cannot be auto-fixed: There are
multiple valid end chars and an auto-fixer cannot know which one is the right one
here.

So, what happens if we make `phpcbf` interactive, so that it can ask the user to
select one of the potential fixes, and then just goes ahead and makes them?

Or, if you realize that you disagree with this sniff: what if `phpcbf` will just
quickly add an `<exclude ref="Sniff.Name"/>` to your `phpcs.xml`? Or it could help
you just disable the rule for the file? Or just ignore this line?

Many IDEs actually already have some of these ways of dealing with violations, [either natively](https://www.jetbrains.com/help/phpstorm/using-php-code-sniffer.html#fixing-issues)
or [through an extension](https://github.com/sdobreff/vscode-php-resolver/blob/main/README.md#phpcs-rules-ignore).
Bringing them to phpcbf itself would make this available to a wider audience and
solutions could be baked in to sniffs.

### Interactive Mode

All of these thoughts culminate in this idea to make `phpcbf` interactive. I didn’t
know until recently that phpcs has an interactive mode, too. But all it does is 
to process a file and pauses to allow the user to edit the file, and then to process
it again. It assists you in checking, but not in fixing.

So, here is how this could work. Let’s assume this `test.php`:

    ```notranslate
    <?php

    //helo
    class test
    {
    echo 'hello';
    }
    ```

This file has some problems as `phpcs` tells you (the selection of Sniffs here is
a bit arbitrary but to show the variety of options we’ll face):

 \e[1mFILE: test.php\e[0m ———————————————————————————————- \e[1mFOUND 6 ERRORS AFFECTING
4 LINES\e[0m ———————————————————————————————- 1 | \e[31mERROR\e[0m | [ ] You must
use “/**” style comments for a file comment 3 | \e[31mERROR\e[0m | [x] No space 
found before comment text; expected “// helo” but found “//helo” 3 | \e[31mERROR\
e[0m | [ ] Inline comments must start with a capital letter 3 | \e[31mERROR\e[0m
| [ ] Inline comments must end in full-stops, exclamation marks, or question marks
4 | \e[31mERROR\e[0m | [ ] Test name must begin with a capital letter 6 | \e[31mERROR\
e[0m | [x] Line indented incorrectly; expected at least 4 spaces, found 0 ———————————————————————————————-\
e[1mPHPCBF CAN FIX THE 2 MARKED SNIFF VIOLATIONS AUTOMATICALLY\e[0m ———————————————————————————————-

As you can see, some violations can be fixed, most cannot. The reason for those 
is that there is no auto-fixer implemented for them, and often the reason is that
there is no one-and-only-way to fix this.

So let’s see how interactive mode would work here. In this first case I’d like to
show a situation where there is no auto-fixer available. These would be the options:

 [1mPHPCBF INTERACTIVE MODE – test.php[0m [33mERRORS[0m at line 1, column 6:
You must use “/**” style comments for a file comment Sniff: PEAR.Commenting.FileComment.
WrongStyle [31m(Not auto-fixable)[0m [33m 1> <?php[0m [90m 2 [0m [90m 3 //
helo[0m Choose an action: [i] Add a phpcs:ignore to this line (phpcs:ignore) [d]
Disable this sniff for this file (phpcs:disable) [p] Project-wide: exclude this 
sniff in phpcs.xml [e] Edit the file manually [s] Skip this violation [q] Quit Action(
default: edit):

Now let’s look at a case where an auto-fixer is available. In non-interactive mode,`
phpcbf` would just fix it, but here you have the option to skip it, too. It would
be nice if it even could preview the auto-fix:

 [33mERRORS[0m at line 6, column 1: Line indented incorrectly; expected at least
4 spaces, found 0 Sniff: Generic.WhiteSpace.ScopeIndent.Incorrect [32m(Auto-fixable)[
0m [90m 4 class test[0m [90m 5 {[0m [31m 6- echo ‘hello’;[0m [32m 6+ echo‘
hello’;[0m [90m 7 }[0m [90m 8 [0m Choose an action: [f] Fix automatically [
i] Add a phpcs:ignore to this line (phpcs:ignore) [d] Disable this sniff for this
file (phpcs:disable) [p] Project-wide: exclude this sniff in phpcs.xml [e] Edit 
the file manually [s] Skip this violation [q] Quit Action (default: auto-fix):

Now let’s look come back to our example from above where it seems like an auto-fixer
could exist but it does not because there are multiple ways to fix it. For this 
one, we’d have an interactive fixer that gives the user the choice how to fix it:

 [33mERRORS[0m at line 3, column 1: Inline comments must end in full-stops, exclamation
marks, or question marks Sniff: Squiz.Commenting.InlineComment.InvalidEndChar [
36m(Interactive fixes available)[0m [1mInteractive Fix Options:[0m [1] Append
a . [90m 1 <?php[0m [90m 2 [0m [31m 3- // Helo[0m [32m 3+ // Helo.[0m [
90m 4 class test[0m [90m 5 {[0m [2] Append a ! [90m 1 <?php[0m [90m 2 [0m[
31m 3- // Helo[0m [32m 3+ // Helo![0m [90m 4 class test[0m [90m 5 {[0m [3]
Append a ? [90m 1 <?php[0m [90m 2 [0m [31m 3- // Helo[0m [32m 3+ // Helo?[
0m [90m 4 class test[0m [90m 5 {[0m [4] Remove comment [90m 1 <?php[0m [90m
2 [0m [31m 3- // Helo[0m [90m 4 class test[0m [90m 5 {[0m Choose an action:[
1] Apply: Append a . [2] Apply: Append a ! [3] Apply: Append a ? [4] Apply: Remove
comment [i] Add a phpcs:ignore to this line (phpcs:ignore) [d] Disable this sniff
for this file (phpcs:disable) [p] Project-wide: exclude this sniff in phpcs.xml [
e] Edit the file manually [s] Skip this violation [q] Quit Action (default: 1 “Append
a .”):

One could argue that you can do all of this today but I think with such an interactive
mode we can relieve much of the pain it causes to get a code base to comply by streamlining
what you need to do already today anyway: go through each violation and try to fix
it.

Some of the choices I haven’t described yet:

 * `i` would just append a `// phpcs:ignore Sniff.Name` to the line.
 * `d` would add a `// phpcs:disable Sniff.Name` at the beginning of the file.
 * `p` would add an `<exclude ref="Sniff.Name"/>` to your `phpcs.xml`.
 * `e` would open an editor at the line of the code

In summary, this is a proposal to improve the user experience. I think this could
transform an experience where `phpcs` is the tool that scolds you, to the tool that
assists you in unifying a codebase to certain standards.

Finally, expand this to see the whole run-down of an interactive `phpcbf -a` session.

 ❯ bat test.php [38;5;246m───────┬─────────────────────────────────────────────
───────────────────────────[0m [38;5;246m│ [0mFile: [1mtest.php[0m [38;5;246m
───────┼────────────────────────────────────────────────────────────────────────[
0m [38;5;246m 1[0m [38;5;246m│[0m [38;5;231m<?php[0m [38;5;246m 2[0m [38;5;246m
│[0m [38;5;246m 3[0m [38;5;246m│[0m [38;5;242m//[0m[38;5;242mhelo[0m [
38;5;246m 4[0m [38;5;246m│[0m [38;5;149mclass[0m[38;5;231m [0m[4;38;5;81mtest[
0m [38;5;246m 5[0m [38;5;246m│[0m [38;5;231m{[0m [38;5;246m 6[0m [38;5;246m
│[0m [38;5;167mecho[0m[38;5;231m [0m[38;5;186m’[0m[38;5;186mhello[0m[38;5;186m’[
0m[38;5;231m;[0m [38;5;246m 7[0m [38;5;246m│[0m [38;5;231m}[0m [38;5;246m
───────┴────────────────────────────────────────────────────────────────────────[
0m ❯ ./bin/phpcs test.php [1mFILE: test.php[0m ———————————————————————————————-[
1mFOUND 6 ERRORS AFFECTING 4 LINES[0m ———————————————————————————————- 1 | [31mERROR[
0m | [ ] You must use “/**” style comments for a file comment 3 | [31mERROR[0m
| [x] No space found before comment text; expected “// helo” but found “//helo” 
3 | [31mERROR[0m | [ ] Inline comments must start with a capital letter 3 | [
31mERROR[0m | [ ] Inline comments must end in full-stops, exclamation marks, or
question marks 4 | [31mERROR[0m | [ ] Test name must begin with a capital letter
6 | [31mERROR[0m | [x] Line indented incorrectly; expected at least 4 spaces, 
found 0 ———————————————————————————————- [1mPHPCBF CAN FIX THE 2 MARKED SNIFF VIOLATIONS
AUTOMATICALLY[0m ———————————————————————————————- ❯ ./bin/phpcbf -a test.php [
1mPHPCBF INTERACTIVE MODE – test.php[0m [33mERRORS[0m at line 1, column 6: You
must use “/**” style comments for a file comment Sniff: PEAR.Commenting.FileComment.
WrongStyle [31m(Not auto-fixable)[0m [33m 1> <?php[0m [90m 2 [0m [90m 3 //
helo[0m Choose an action: [i] Add a phpcs:ignore to this line (phpcs:ignore) [d]
Disable this sniff for this file (phpcs:disable) [p] Project-wide: exclude this 
sniff in phpcs.xml [e] Edit the file manually [s] Skip this violation [q] Quit Action(
default: edit): i Added phpcs:ignore comment to line 1 ——————————————————————————–[
33mERRORS[0m at line 6, column 1: Line indented incorrectly; expected at least 
4 spaces, found 0 Sniff: Generic.WhiteSpace.ScopeIndent.Incorrect [32m(Auto-fixable)[
0m [90m 4 class test[0m [90m 5 {[0m [31m 6- echo ‘hello’;[0m [32m 6+ echo‘
hello’;[0m [90m 7 }[0m [90m 8 [0m Choose an action: [f] Fix automatically [
i] Add a phpcs:ignore to this line (phpcs:ignore) [d] Disable this sniff for this
file (phpcs:disable) [p] Project-wide: exclude this sniff in phpcs.xml [e] Edit 
the file manually [s] Skip this violation [q] Quit Action (default: auto-fix): Auto-
fixing… [32mFixed![0m ——————————————————————————– [33mERRORS[0m at line 3, column
1: No space found before comment text; expected “// helo” but found “//helo” Sniff:
Squiz.Commenting.InlineComment.NoSpaceBefore [32m(Auto-fixable)[0m [90m 1 <?php[
0m [90m 2 [0m [31m 3- //helo[0m [32m 3+ // helo[0m [90m 4 class test[0m [
90m 5 {[0m Choose an action: [f] Fix automatically [i] Add a phpcs:ignore to this
line (phpcs:ignore) [d] Disable this sniff for this file (phpcs:disable) [p] Project-
wide: exclude this sniff in phpcs.xml [e] Edit the file manually [s] Skip this violation[
q] Quit Action (default: auto-fix): Auto-fixing… [32mFixed![0m ——————————————————————————–[
33mERRORS[0m at line 3, column 1: Inline comments must start with a capital letter
Sniff: Squiz.Commenting.InlineComment.NotCapital [36m(Interactive fixes available)[
0m [1mInteractive Fix Options:[0m [1] Suggestion [90m 1 <?php[0m [90m 2 [0m[
31m 3- // helo[0m [32m 3+ // Helo[0m [90m 4 class test[0m [90m 5 {[0m Choose
an action: [1] Apply: Suggestion [i] Add a phpcs:ignore to this line (phpcs:ignore)[
d] Disable this sniff for this file (phpcs:disable) [p] Project-wide: exclude this
sniff in phpcs.xml [e] Edit the file manually [s] Skip this violation [q] Quit Action(
default: 1 “Capitalize first letter”): Applying option 1… [32mFixed![0m ——————————————————————————–[
33mERRORS[0m at line 3, column 1: Inline comments must end in full-stops, exclamation
marks, or question marks Sniff: Squiz.Commenting.InlineComment.InvalidEndChar [
36m(Interactive fixes available)[0m [1mInteractive Fix Options:[0m [1] Append
a . [90m 1 <?php[0m [90m 2 [0m [31m 3- // Helo[0m [32m 3+ // Helo.[0m [
90m 4 class test[0m [90m 5 {[0m [2] Append a ! [90m 1 <?php[0m [90m 2 [0m[
31m 3- // Helo[0m [32m 3+ // Helo![0m [90m 4 class test[0m [90m 5 {[0m [3]
Append a ? [90m 1 <?php[0m [90m 2 [0m [31m 3- // Helo[0m [32m 3+ // Helo?[
0m [90m 4 class test[0m [90m 5 {[0m [4] Remove comment [90m 1 <?php[0m [90m
2 [0m [31m 3- // Helo[0m [90m 4 class test[0m [90m 5 {[0m Choose an action:[
1] Apply: Append a . [2] Apply: Append a ! [3] Apply: Append a ? [4] Apply: Remove
comment [i] Add a phpcs:ignore to this line (phpcs:ignore) [d] Disable this sniff
for this file (phpcs:disable) [p] Project-wide: exclude this sniff in phpcs.xml [
e] Edit the file manually [s] Skip this violation [q] Quit Action (default: 1 “Append
a .”): 2 Fixing with option 2… [32mFixed![0m ——————————————————————————– [33mERRORS[
0m at line 4, column 7: Test name must begin with a capital letter Sniff: PEAR.NamingConventions.
ValidClassName.StartWithCapital [36m(Interactive fixes available)[0m [1mInteractive
Fix Options:[0m [1] Suggestion [90m 2 [0m [90m 3 // Helo![0m [31m 4- class
test[0m [32m 4+ class Test[0m [90m 5 {[0m [90m 6 echo ‘hello’;[0m Choose 
an action: [1] Apply: Suggestion [i] Add a phpcs:ignore to this line (phpcs:ignore)[
d] Disable this sniff for this file (phpcs:disable) [p] Project-wide: exclude this
sniff in phpcs.xml [e] Edit the file manually [s] Skip this violation [q] Quit Action(
default: 1 “Capitalize first letter”): Applying option 1… [32mFixed![0m ——————————————————————————–[
1mPHPCBF RESULT SUMMARY[0m ———————————————————————- [1mFILE FIXED REMAINING[0m———————————————————————-
test.php 5 0 ———————————————————————- [1mA TOTAL OF 5 ERRORS WERE FIXED IN 1 FILE[
0m ———————————————————————- ❯ bat test.php [38;5;246m───────┬──────────────────
──────────────────────────────────────────────────────[0m [38;5;246m│ [0mFile:[
1mtest.php[0m [38;5;246m───────┼──────────────────────────────────────────────
──────────────────────────[0m [38;5;246m 1[0m [38;5;246m│[0m [38;5;231m<?php[
0m[38;5;231m [0m[38;5;242m//[0m[38;5;242m phpcs:ignore PEAR.Commenting.FileComment.
WrongStyle[0m [38;5;246m 2[0m [38;5;246m│[0m [38;5;246m 3[0m [38;5;246m│[
0m [38;5;242m//[0m[38;5;242m Helo![0m [38;5;246m 4[0m [38;5;246m│[0m [38;5;149mclass[
0m[38;5;231m [0m[4;38;5;81mTest[0m [38;5;246m 5[0m [38;5;246m│[0m [38;5;231m{[
0m [38;5;246m 6[0m [38;5;246m│[0m [38;5;231m [0m[38;5;167mecho[0m[38;5;231m[
0m[38;5;186m’[0m[38;5;186mhello[0m[38;5;186m’[0m[38;5;231m;[0m [38;5;246m
7[0m [38;5;246m│[0m [38;5;231m}[0m [38;5;246m───────┴─────────────────────
───────────────────────────────────────────────────[0m

For comparison, this is what regular mode produces:

 ❯ ./bin/phpcbf test.php [1mPHPCBF RESULT SUMMARY[0m ———————————————————————- [
1mFILE FIXED REMAINING[0m ———————————————————————- test.php 2 4 ———————————————————————-[
1mA TOTAL OF 2 ERRORS WERE FIXED IN 1 FILE[0m ———————————————————————- ❯ bat test.
php [38;5;246m───────┬─────────────────────────────────────────────────────────
───────────────[0m [38;5;246m│ [0mFile: [1mtest.php[0m [38;5;246m───────┼─
───────────────────────────────────────────────────────────────────────[0m [38;5;246m
1[0m [38;5;246m│[0m [38;5;231m<?php[0m [38;5;246m 2[0m [38;5;246m│[0m [
38;5;246m 3[0m [38;5;246m│[0m [38;5;242m//[0m[38;5;242m helo[0m [38;5;246m
4[0m [38;5;246m│[0m [38;5;149mclass[0m[38;5;231m [0m[4;38;5;81mtest[0m [
38;5;246m 5[0m [38;5;246m│[0m [38;5;231m{[0m [38;5;246m 6[0m [38;5;246m│[
0m [38;5;231m [0m[38;5;167mecho[0m[38;5;231m [0m[38;5;186m’[0m[38;5;186mhello[
0m[38;5;186m’[0m[38;5;231m;[0m [38;5;246m 7[0m [38;5;246m│[0m [38;5;231m}[
0m [38;5;246m───────┴──────────────────────────────────────────────────────────
──────────────[0m

You can find [the issue I created about this in the PHP CodeSniffer repo](https://github.com/PHPCSStandards/PHP_CodeSniffer/issues/1285),
and [my implementation on Github](https://github.com/PHPCSStandards/PHP_CodeSniffer/compare/4.x...akirk:PHP_CodeSniffer:phpcbf-interactive).
This is not yet a PR because [they prefer to have a discussion it first](https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/4.x/.github/CONTRIBUTING.md#requestingsubmitting-new-features)
but I worked on an implementation already so that you can experience how it feels
to use it and don’t just have to imagine it. Find the instructions to do so [on the bottom of the issue](https://github.com/PHPCSStandards/PHP_CodeSniffer/issues/1285).

PS: to make the terminal output look well in this post, I have made this [syntaxhighlighter-ansi](https://github.com/akirk/syntaxhighlighter-ansi)
plugin for the [SyntaxHighlighter Evolved plugin](https://wordpress.com/plugins/syntaxhighlighter)
for WordPress that will interpret the ANSI escape sequences from the terminal and
convert it to colorful output like you can see above.

[Code](https://alex.kirk.at/category/code/), [PHP](https://alex.kirk.at/category/code/php/)

Read this next

[npm install playground-step-library](https://alex.kirk.at/2025/09/05/npm-install-playground-step-library/)

### Leave a Reply 󠀁[Cancel reply](https://alex.kirk.at/2025/09/29/proposal-an-interactive-mode-for-phpcbf/?output_format=md#respond)󠁿

Only people in [my network](https://alex.kirk.at/friends/) can comment.

This site uses Akismet to reduce spam. [Learn how your comment data is processed.](https://akismet.com/privacy/)