An elegant way to fix your PHP code to follow standards automatically

Apr 2022

The friendsofphp/php-cs-fixer package is the best tool for fixing your PHP code to follow standards and maintain clean and consistent code style. You can use Composer to install PHP CS Fixer into your PHP project:

composer require friendsofphp/php-cs-fixer --dev

You should create a configuration file named .php-cs-fixer.dist.php in the root directory of your project. If you're working on a Laravel project, it is recommended to use Laravel's set rules. Here is an example of what the configuration file generally looks like:

<?php

use PhpCsFixer\Config;
use PhpCsFixer\Finder;

$rules = [
    'array_indentation' => true,
    'array_syntax' => ['syntax' => 'short'],
    'binary_operator_spaces' => [
        'default' => 'single_space',
    ],
    'blank_line_after_namespace' => true,
    'blank_line_after_opening_tag' => true,
    'blank_line_before_statement' => [
        'statements' => [
            'continue',
            'return',
        ],
    ],
    'blank_line_between_import_groups' => true,
    'blank_lines_before_namespace' => true,
    'braces_position' => [
        'control_structures_opening_brace' => 'same_line',
        'functions_opening_brace' => 'next_line_unless_newline_at_signature_end',
        'anonymous_functions_opening_brace' => 'same_line',
        'classes_opening_brace' => 'next_line_unless_newline_at_signature_end',
        'anonymous_classes_opening_brace' => 'next_line_unless_newline_at_signature_end',
        'allow_single_line_empty_anonymous_classes' => false,
        'allow_single_line_anonymous_functions' => false,
    ],
    'cast_spaces' => true,
    'class_attributes_separation' => [
        'elements' => [
            'const' => 'one',
            'method' => 'one',
            'property' => 'one',
            'trait_import' => 'none',
        ],
    ],
    'class_definition' => [
        'multi_line_extends_each_single_line' => true,
        'single_item_single_line' => true,
        'single_line' => true,
    ],
    'clean_namespace' => true,
    'compact_nullable_type_declaration' => true,
    'concat_space' => [
        'spacing' => 'none',
    ],
    'constant_case' => ['case' => 'lower'],
    'control_structure_braces' => true,
    'control_structure_continuation_position' => [
        'position' => 'same_line',
    ],
    'declare_equal_normalize' => true,
    'declare_parentheses' => true,
    'elseif' => true,
    'encoding' => true,
    'full_opening_tag' => true,
    'fully_qualified_strict_types' => true,
    'function_declaration' => true,
    'general_phpdoc_tag_rename' => true,
    'heredoc_to_nowdoc' => true,
    'include' => true,
    'increment_style' => ['style' => 'post'],
    'indentation_type' => true,
    'integer_literal_case' => true,
    'lambda_not_used_import' => true,
    'line_ending' => true,
    'linebreak_after_opening_tag' => true,
    'list_syntax' => true,
    'lowercase_cast' => true,
    'lowercase_keywords' => true,
    'lowercase_static_reference' => true,
    'magic_constant_casing' => true,
    'magic_method_casing' => true,
    'method_argument_space' => [
        'on_multiline' => 'ignore',
    ],
    'method_chaining_indentation' => true,
    'multiline_whitespace_before_semicolons' => [
        'strategy' => 'no_multi_line',
    ],
    'native_function_casing' => true,
    'native_type_declaration_casing' => true,
    'no_alias_functions' => true,
    'no_alias_language_construct_call' => true,
    'no_alternative_syntax' => true,
    'no_binary_string' => true,
    'no_blank_lines_after_class_opening' => true,
    'no_blank_lines_after_phpdoc' => true,
    'no_closing_tag' => true,
    'no_empty_phpdoc' => true,
    'no_empty_statement' => true,
    'no_extra_blank_lines' => [
        'tokens' => [
            'extra',
            'throw',
            'use',
        ],
    ],
    'no_leading_import_slash' => true,
    'no_leading_namespace_whitespace' => true,
    'no_mixed_echo_print' => [
        'use' => 'echo',
    ],
    'no_multiline_whitespace_around_double_arrow' => true,
    'no_multiple_statements_per_line' => true,
    'no_short_bool_cast' => true,
    'no_singleline_whitespace_before_semicolons' => true,
    'no_space_around_double_colon' => true,
    'no_spaces_after_function_name' => true,
    'no_spaces_around_offset' => [
        'positions' => ['inside', 'outside'],
    ],
    'no_superfluous_phpdoc_tags' => [
        'allow_mixed' => true,
        'allow_unused_params' => true,
    ],
    'no_trailing_comma_in_singleline' => true,
    'no_trailing_whitespace' => true,
    'no_trailing_whitespace_in_comment' => true,
    'no_unneeded_control_parentheses' => [
        'statements' => ['break', 'clone', 'continue', 'echo_print', 'return', 'switch_case', 'yield'],
    ],
    'no_unneeded_braces' => true,
    'no_unreachable_default_argument_value' => true,
    'no_unset_cast' => true,
    'no_unused_imports' => true,
    'no_useless_return' => true,
    'no_whitespace_before_comma_in_array' => true,
    'no_whitespace_in_blank_line' => true,
    'normalize_index_brace' => true,
    'not_operator_with_successor_space' => true,
    'nullable_type_declaration' => true,
    'nullable_type_declaration_for_default_null_value' => [
        'use_nullable_type_declaration' => false,
    ],
    'object_operator_without_whitespace' => true,
    'ordered_imports' => ['sort_algorithm' => 'alpha', 'imports_order' => ['const', 'class', 'function']],
    'ordered_interfaces' => true,
    'ordered_traits' => true,
    'phpdoc_indent' => true,
    'phpdoc_inline_tag_normalizer' => true,
    'phpdoc_no_access' => true,
    'phpdoc_no_package' => true,
    'phpdoc_no_useless_inheritdoc' => true,
    'phpdoc_order' => [
        'order' => ['param', 'return', 'throws'],
    ],
    'phpdoc_scalar' => true,
    'phpdoc_separation' => [
        'groups' => [
            ['deprecated', 'link', 'see', 'since'],
            ['author', 'copyright', 'license'],
            ['category', 'package', 'subpackage'],
            ['property', 'property-read', 'property-write'],
            ['param', 'return'],
        ],
    ],
    'phpdoc_single_line_var_spacing' => true,
    'phpdoc_summary' => false,
    'phpdoc_tag_type' => [
        'tags' => [
            'inheritdoc' => 'inline',
        ],
    ],
    'phpdoc_to_comment' => false,
    'phpdoc_trim' => true,
    'phpdoc_types' => true,
    'phpdoc_var_without_name' => true,
    'psr_autoloading' => false,
    'return_type_declaration' => ['space_before' => 'none'],
    'self_accessor' => false,
    'self_static_accessor' => true,
    'short_scalar_cast' => true,
    'simplified_null_return' => false,
    'single_blank_line_at_eof' => true,
    'single_class_element_per_statement' => [
        'elements' => ['const', 'property'],
    ],
    'single_import_per_statement' => true,
    'single_line_after_imports' => true,
    'single_line_comment_style' => [
        'comment_types' => ['hash'],
    ],
    'single_quote' => true,
    'single_space_around_construct' => true,
    'space_after_semicolon' => true,
    'spaces_inside_parentheses' => true,
    'standardize_not_equals' => true,
    'statement_indentation' => true,
    'switch_case_semicolon_to_colon' => true,
    'switch_case_space' => true,
    'ternary_operator_spaces' => true,
    'trailing_comma_in_multiline' => ['elements' => ['arrays']],
    'trim_array_spaces' => true,
    'type_declaration_spaces' => true,
    'types_spaces' => true,
    'unary_operator_spaces' => true,
    'visibility_required' => [
        'elements' => ['method', 'property'],
    ],
    'whitespace_after_comma_in_array' => true,
    'yoda_style' => [
        'always_move_variable' => false,
        'equal' => false,
        'identical' => false,
        'less_and_greater' => false,
    ],
];

$finder = Finder::create()
    ->in(__DIR__)
    ->name('*.php')
    ->notName([
        '_ide_helper_actions.php',
        '_ide_helper_models.php',
        '_ide_helper.php',
        '.phpstorm.meta.php',
        '*.blade.php',
    ])
    ->exclude([
        'storage',
        'bootstrap/cache',
        'node_modules',
    ])
    ->ignoreDotFiles(true)
    ->ignoreVCS(true);

return (new Config)
    ->setFinder($finder)
    ->setRules($rules)
    ->setRiskyAllowed(true)
    ->setUsingCache(true);

Alternatively, you can use PSR12 set rules which is a popular choice:

<?php

use PhpCsFixer\Config;
use PhpCsFixer\Finder;

$rules = [
    '@PSR12' => true,
    'no_unused_imports' => true,
];

$finder = Finder::create()
    ->in([
        __DIR__ . '/src',
        __DIR__ . '/tests',
    ])
    ->name('*.php')
    ->ignoreDotFiles(true)
    ->ignoreVCS(true);

return (new Config)
    ->setFinder($finder)
    ->setRules($rules)
    ->setRiskyAllowed(true)
    ->setUsingCache(true);

Then run the fix command to fix the code:

./vendor/bin/php-cs-fixer fix --config .php-cs-fixer.dist.php

Enjoy the benefits of clean and consistent code!

However, there's a problem so far. The fix command will always scan your entire project every time it runs. In most cases, you only need to lint your staged files – the files you want to commit. To solve this, you can use lint-staged.

Install lint-staged using npm:

npm install lint-staged -D

Lint-staged can be configured in various ways. A popular choice is to use a .lintstagedrc file:

{
  "**/*.php": [
    "./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php"
  ]
}

Instead of running the fix command directly, you can now use git add to add the changed files to the index and then run the command:

npx lint-staged

Much faster, isn't it?

But you still need to run the command manually before each commit. Don't worry, let's make it easier with git hooks!

Let's install Husky:

npm install husky -D

Husky is quite straightforward. It registers some git hooks for you after npm install.

Edit the package.json file, add the prepare script, and run it once:

npm set-script prepare "husky install"
npm run prepare

Add a pre-commit hook for npx lint-staged:

npx husky add .husky/pre-commit "npx lint-staged"
git add .husky/pre-commit

Now the lint command will run automatically every time you commit your code.

Tada! Enjoy the benefits of automated code formatting and linting.

My Newsletter

I send out an email every so often about cool stuff I'm working on or launching. If you dig, go ahead and sign up!

    Follow the RSS Feed.