Don’t hesitate to contact us if you have any feedback.

PHPStan and PHP CS Fixer guide

Maintaining code quality is crucial. In this post, I’ll show you how to set up two essential PHP development tools: PHPStan for static analysis and PHP CS Fixer for automated code styling.

Prerequisites

Before we begin, make sure you have Composer installed on your system. Composer is a dependency manager for PHP that we’ll use to install and manage our code quality tools.

If you haven’t installed Composer yet, follow these steps:

  1. Visit https://getcomposer.org/download/
  2. Follow the installation instructions for your operating system
  3. Verify the installation by running composer --version in your terminal

You should also ensure your WordPress project has a composer.json file. If it doesn’t, create one by running:

composer init

PHPStan: Static Analysis for PHP

PHPStan helps catch bugs before they make it to production by performing static analysis on your PHP code. It’s particularly useful in WordPress development for maintaining code quality across plugins and themes.

Installation

First, install PHPStan via Composer:

composer require --dev phpstan/phpstan

Configuration

Create a phpstan.neon file in your project root:

parameters:
    level: 5
    excludePaths:
        - .php-cs-fixer.php
        - vendor/*

Let’s look at some key PHPStan configuration parameters:

  1. level: 5 – Sets the analysis strictness level (0-9). Level 5 provides a good balance between strictness and practicality for WordPress projects.
  2. excludePaths – Specifies which directories/files to exclude from analysis.

PHP CS Fixer: Automated Code Styling

PHP CS Fixer automatically fixes PHP coding standards issues. Here’s how to set it up:

Installation

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

Configuration

Create a .php-cs-fixer.php file in your project root (the config below is an example):

<?php

use PhpCsFixer\Config;
use PhpCsFixer\Finder;

$rules = [
    // Front usefull config
    'array_indentation' => true,
    'array_push' => true, // Risky when the function array_push is overridden.
    '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' => ['return']
    ],
    'control_structure_braces' => true,
    'braces_position' => [
        'functions_opening_brace' => 'same_line',
        'control_structures_opening_brace' => 'same_line',
        'anonymous_functions_opening_brace' => 'same_line',
        'classes_opening_brace' => 'same_line',
        'anonymous_classes_opening_brace' => 'same_line',
    ],
    'declare_parentheses' => true,
    'statement_indentation' => true,
    'no_multiple_statements_per_line' => true,
    'cast_spaces' => [
        'space' => 'single'
    ],
    'class_attributes_separation' => [
        'elements' => [
            'trait_import' => 'none'
        ]
    ],
    'concat_space' => [
        'spacing' => 'one'
    ],
    'constant_case' => true,
    'elseif' => true,
    'encoding' => true,
    'explicit_string_variable' => true,
    'full_opening_tag' => true,
    'heredoc_indentation' => [
        'indentation' => 'start_plus_one'
    ],
    'include' => true,
    'increment_style' => [
        'style' => 'post'
    ],
    'indentation_type' => true,
    'line_ending' => true,
    'linebreak_after_opening_tag' => true,
    'logical_operators' => true, // Risky, because you must double-check if using and/or with lower precedence was intentional.
    'lowercase_cast' => true,
    'lowercase_keywords' => true,
    'lowercase_static_reference' => true,
    'method_argument_space' => [
        'on_multiline' => 'ensure_fully_multiline',
        'keep_multiple_spaces_after_comma' => false,
        'attribute_placement' => 'same_line'
    ],
    'multiline_whitespace_before_semicolons' => [
        'strategy' => 'no_multi_line'
    ],
    'native_function_casing' => true,
    'no_alias_functions' => true,
    'no_alternative_syntax' => true, // Remove des endif & shit
    'no_closing_tag' => true,
    'no_empty_phpdoc' => true,
    'no_empty_statement' => true,
    'no_extra_blank_lines' => [
        'tokens' => [
            'extra',
            'throw',
            'use',
        ]
    ],
    'no_mixed_echo_print' => [
        'use' => 'echo'
    ],
    'no_multiline_whitespace_around_double_arrow' => true,
    'no_short_bool_cast' => true,
    'no_singleline_whitespace_before_semicolons' => true,
    'no_spaces_after_function_name' => true,
    'no_spaces_around_offset' => true,
    'no_trailing_comma_in_singleline' => true,
    'no_trailing_whitespace' => true,
    'no_trailing_whitespace_in_comment' => true,
    'no_unneeded_control_parentheses' => 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' => false,
    'object_operator_without_whitespace' => true,
    'short_scalar_cast' => true,
    'simple_to_complex_string_variable' => true,
    'simplified_null_return' => false,
    'single_import_per_statement' => true,
    'single_line_after_imports' => true,
    'single_line_comment_style' => [
        'comment_types' => ['hash']
    ],
    'single_quote' => true,
    'space_after_semicolon' => true,
    'spaces_inside_parentheses' => [
        'space' => 'none'
    ],
    'standardize_not_equals' => true,
    'switch_case_semicolon_to_colon' => true,
    'switch_case_space' => true,
    'ternary_operator_spaces' => true,
    'trailing_comma_in_multiline' => false,
    'trim_array_spaces' => true,
    'unary_operator_spaces' => true,
    'whitespace_after_comma_in_array' => true,
];

$finder = Finder::create()
    ->name('*.php')
    ->notName('*.blade.php')
    ->ignoreDotFiles(true)
    ->ignoreVCS(true);

$config = new Config();

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

Let’s examine five important PHP CS Fixer rules from the configuration:

  1. array_syntax: Forces the use of square bracket array syntax [] instead of array().
<?php

// Before
$array = array('item' => 'value');
// After
$array = ['item' => 'value'];
  1. concat_space: Ensures consistent spacing around string concatenation operators.
<?php

// Before
$string=$foo.'bar'.$baz;
// After
$string = $foo . 'bar' . $baz;
  1. no_alternative_syntax: Removes alternative syntax for control structures.
<?php

// Before
if ($condition):
    // code
endif;
// After
if ($condition) {
    // code
}
  1. explicit_string_variable: Makes variable syntax in strings explicit.
<?php

// Before
"$name's code"
// After
"{$name}'s code"
  1. braces_position: Controls the positioning of braces for functions, classes, and control structures.
<?php

// Ensures consistent brace placement
function example() {    // Opening brace on same line
    // code
}

Usage with Composer Scripts

To make the tools easier to use, add these scripts to your composer.json:

{
    "require-dev": {
        "friendsofphp/php-cs-fixer": "^3.72.0",
        "phpstan/phpstan": "^2.1.8"
    },
    "scripts": {
        "prettier:php": "vendor/bin/php-cs-fixer fix -v --show-progress=dots --using-cache=no --config=.php-cs-fixer.php",
        "lint:php": "vendor/bin/phpstan analyse -v --memory-limit=2048M",
        "lint-staged:php": [
            "vendor/bin/phpstan analyse --memory-limit=2048M",
            "vendor/bin/php-cs-fixer fix --using-cache=no --config=.php-cs-fixer.php"
        ]
    }
}

Now you can run these tools using simplified commands:

# Run PHPStan analysis
composer lint:php

# Fix code style with PHP CS Fixer
composer prettier:php

# Run both tools (useful for pre-commit hooks)
composer lint-staged:php

Key features of these commands:

  1. prettier:php: Runs PHP CS Fixer with verbose output (-v) and progress dots, disabling cache for consistent results
  2. lint:php: Runs PHPStan with increased memory limit for larger codebases
  3. lint-staged:php: Combines both tools, perfect for git hooks or CI/CD pipelines

Note: The memory limit (--memory-limit=2048M) is particularly useful for larger projects or when analyzing plugins with complex dependencies.

WordPress Integration

To use these tools in a WordPress environment, you’ll need a few additional configurations:

PHPStan WordPress Setup

  1. Install the WordPress extension:
composer require --dev szepeviktor/phpstan-wordpress
  1. Add to your phpstan.neon:
includes:
    - vendor/szepeviktor/phpstan-wordpress/extension.neon

parameters:
    # ... your existing parameters ...
    scanDirectories:
        - path/to/your/theme/or/plugin

This extension provides WordPress-specific function and class definitions to prevent false positives in your analysis.

These adjustments help align the tools with WordPress coding standards while maintaining the benefits of automated code quality checks.

Useful Resources

Conclusion

Setting up PHPStan and PHP CS Fixer in your WordPress development environment helps maintain code quality and consistency. These tools can catch potential issues early and ensure your code follows WordPress coding standards.

Remember to adjust the configuration files according to your specific needs and project requirements. The configurations provided here serve as a solid starting point for WordPress development.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *