Skip to content

08: Code Quality - ESLint meets PHP_CodeSniffer

Code Quality: ESLint meets PHP_CodeSniffer

Section titled “Code Quality: ESLint meets PHP_CodeSniffer”

The PHP ecosystem has a rich set of code quality tools similar to TypeScript’s ESLint, Prettier, and TypeScript compiler. This chapter maps your existing knowledge to PHP’s tooling landscape.

By the end of this chapter, you’ll be able to:

  • ✅ Use PHP_CodeSniffer (PHPCS) for code style checking
  • ✅ Apply PHPStan for static analysis (like TypeScript compiler)
  • ✅ Format code with PHP-CS-Fixer (like Prettier)
  • ✅ Configure quality tools for your project
  • ✅ Integrate tools with your editor
  • ✅ Set up pre-commit hooks
  • ✅ Run quality checks in CI/CD

📁 View Code Examples on GitHub

This chapter includes code quality tool examples:

  • PHP_CodeSniffer configuration
  • PHPStan/Psalm setup
  • PHP-CS-Fixer configuration
  • Quality check scripts

Setup the tools:

Terminal window
cd code/php-typescript-developers/chapter-08
composer install
composer check
TypeScript/JavaScriptPHPPurpose
ESLintPHP_CodeSniffer (PHPCS)Code style linting
PrettierPHP-CS-Fixer / Laravel PintCode formatting
TypeScript CompilerPHPStan / PsalmStatic analysis
SonarQubeSonarQube (same)Code quality platform
HuskyGrumPHP / HuskyGit hooks
Terminal window
npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin

.eslintrc.json:

{
"parser": "@typescript-eslint/parser",
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
],
"rules": {
"semi": ["error", "always"],
"quotes": ["error", "single"],
"indent": ["error", 2],
"no-unused-vars": "error",
"@typescript-eslint/no-explicit-any": "warn"
}
}

Run:

Terminal window
npx eslint src/
npx eslint src/ --fix
Terminal window
composer require --dev squizlabs/php_codesniffer

phpcs.xml:

<?xml version="1.0"?>
<ruleset name="MyProject">
<description>Coding standard for my project</description>
<!-- Check all PHP files in src/ -->
<file>src</file>
<!-- Use PSR-12 standard -->
<rule ref="PSR12"/>
<!-- Custom rules -->
<rule ref="Generic.Files.LineLength">
<properties>
<property name="lineLimit" value="120"/>
<property name="absoluteLineLimit" value="150"/>
</properties>
</rule>
<!-- Ignore specific patterns -->
<exclude-pattern>*/vendor/*</exclude-pattern>
</ruleset>

Run:

Terminal window
vendor/bin/phpcs
vendor/bin/phpcbf # Automatic fix (like --fix)

Coding Standards:

  • PSR-1: Basic coding standard
  • PSR-12: Extended coding style (successor to PSR-2)
  • PEAR: PEAR coding standard
  • Zend: Zend Framework standard
  • Squiz: Squiz Labs standard
ESLint RulePHPCS RuleDescription
indentGeneric.WhiteSpace.ScopeIndentIndentation
semiSquiz.PHP.DiscouragedFunctionsSemicolons
quotesSquiz.Strings.DoubleQuoteUsageQuote style
no-unused-varsGeneric.CodeAnalysis.UnusedFunctionParameterUnused variables
max-lenGeneric.Files.LineLengthLine length
camelcaseSquiz.NamingConventions.ValidVariableNameNaming conventions

phpcs.xml with custom rules:

<?xml version="1.0"?>
<ruleset name="Custom">
<rule ref="PSR12">
<!-- Exclude specific rules -->
<exclude name="PSR2.Methods.FunctionCallSignature.SpaceAfterOpenBracket"/>
</rule>
<!-- Arrays must use short syntax -->
<rule ref="Generic.Arrays.DisallowLongArraySyntax"/>
<!-- No trailing whitespace -->
<rule ref="Squiz.WhiteSpace.SuperfluousWhitespace">
<properties>
<property name="ignoreBlankLines" value="true"/>
</properties>
</rule>
<!-- Class names must be StudlyCaps -->
<rule ref="Squiz.Classes.ValidClassName"/>
<!-- Methods must be camelCase -->
<rule ref="PSR1.Methods.CamelCapsMethodName"/>
</ruleset>
Terminal window
npm install --save-dev prettier

.prettierrc:

{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 100,
"arrowParens": "avoid"
}

Run:

Terminal window
npx prettier --write src/
Terminal window
composer require --dev friendsofphp/php-cs-fixer

.php-cs-fixer.php:

<?php
$finder = PhpCsFixer\Finder::create()
->in(__DIR__ . '/src')
->name('*.php');
return (new PhpCsFixer\Config())
->setRules([
'@PSR12' => true,
'array_syntax' => ['syntax' => 'short'],
'binary_operator_spaces' => [
'default' => 'single_space',
],
'blank_line_after_opening_tag' => true,
'concat_space' => ['spacing' => 'one'],
'function_typehint_space' => true,
'no_unused_imports' => true,
'ordered_imports' => ['sort_algorithm' => 'alpha'],
'single_quote' => true,
'trailing_comma_in_multiline' => [
'elements' => ['arrays', 'arguments', 'parameters'],
],
])
->setFinder($finder);

Run:

Terminal window
vendor/bin/php-cs-fixer fix
vendor/bin/php-cs-fixer fix --dry-run # Preview changes

Laravel Pint is a zero-config PHP code formatter built on PHP-CS-Fixer:

Terminal window
composer require --dev laravel/pint

Run:

Terminal window
vendor/bin/pint
vendor/bin/pint --test # Check without modifying

pint.json (optional):

{
"preset": "laravel",
"rules": {
"array_syntax": {
"syntax": "short"
},
"binary_operator_spaces": {
"default": "single_space"
}
}
}

Static Analysis: TypeScript vs PHPStan/Psalm

Section titled “Static Analysis: TypeScript vs PHPStan/Psalm”
Terminal window
npm install --save-dev typescript

tsconfig.json:

{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noUnusedLocals": true,
"noUnusedParameters": true
}
}

Run:

Terminal window
npx tsc --noEmit # Type check without compilation

PHPStan performs static analysis similar to TypeScript’s type checking:

Terminal window
composer require --dev phpstan/phpstan

phpstan.neon:

parameters:
level: 8 # 0 (loose) to 9 (max strict)
paths:
- src
excludePaths:
- src/legacy/*
ignoreErrors:
- '#Unsafe usage of new static#'
checkMissingIterableValueType: true
checkGenericClassInNonGenericObjectType: true

Run:

Terminal window
vendor/bin/phpstan analyse
vendor/bin/phpstan analyse --level=5

PHPStan Levels:

  • Level 0: Basic checks
  • Level 4: Check for unknown methods, properties
  • Level 6: Check for missing type hints
  • Level 8: Check for strict types
  • Level 9: Maximum strictness (mixed disallowed)

Psalm is another powerful static analysis tool (alternative to PHPStan):

Terminal window
composer require --dev vimeo/psalm

psalm.xml:

<?xml version="1.0"?>
<psalm
errorLevel="4"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
<projectFiles>
<directory name="src" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
</psalm>

Run:

Terminal window
vendor/bin/psalm
vendor/bin/psalm --show-info=true

Both PHPStan and Psalm support generic type annotations via docblocks:

<?php
/**
* @template T
* @param array<T> $items
* @return T|null
*/
function first(array $items): mixed {
return $items[0] ?? null;
}
/**
* @template T of object
*/
class Repository {
/**
* @param class-string<T> $className
* @return T
*/
public function find(string $className, int $id): object {
// ...
}
}
/** @var Repository<User> */
$userRepo = new Repository();
$user = $userRepo->find(User::class, 1); // PHPStan knows this is User

composer.json:

{
"require-dev": {
"squizlabs/php_codesniffer": "^3.7",
"friendsofphp/php-cs-fixer": "^3.35",
"phpstan/phpstan": "^1.10",
"vimeo/psalm": "^5.15"
},
"scripts": {
"lint": "phpcs",
"lint:fix": "phpcbf",
"format": "php-cs-fixer fix",
"analyse": "phpstan analyse",
"psalm": "psalm",
"check": [
"@lint",
"@analyse",
"@test"
]
}
}

Run quality checks:

Terminal window
composer lint # Check code style
composer lint:fix # Fix code style
composer format # Format code
composer analyse # Static analysis
composer check # Run all checks

TypeScript:

{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
}

PHP:

{
"php.validate.enable": true,
"php.validate.run": "onSave",
"[php]": {
"editor.defaultFormatter": "junstyle.php-cs-fixer",
"editor.formatOnSave": true
},
"phpstan.enabled": true,
"phpstan.level": "8"
}

Extensions:

  • PHP Intelephense
  • PHP CS Fixer
  • PHPStan
  • Psalm

PHPStorm has built-in support for all PHP quality tools:

  1. Settings → PHP → Quality Tools
  2. Configure paths to PHPCS, PHP-CS-Fixer, PHPStan
  3. Enable inspections
  4. Configure code style (PSR-12)
  5. Enable “Reformat Code” on save
Terminal window
npm install --save-dev husky lint-staged
npx husky install

package.json:

{
"lint-staged": {
"*.ts": [
"eslint --fix",
"prettier --write"
]
}
}

.husky/pre-commit:

#!/bin/sh
npx lint-staged
Terminal window
composer require --dev phpro/grumphp

grumphp.yml:

grumphp:
tasks:
phpcs:
standard: PSR12
phpstan:
level: 8
phpunit:
always_execute: false

GrumPHP automatically installs git hooks.

Terminal window
composer require --dev brainmaestro/composer-git-hooks

composer.json:

{
"extra": {
"hooks": {
"pre-commit": [
"vendor/bin/phpcs",
"vendor/bin/phpstan analyse"
],
"pre-push": [
"vendor/bin/phpunit"
]
}
},
"scripts": {
"post-install-cmd": "vendor/bin/cghooks add --ignore-lock",
"post-update-cmd": "vendor/bin/cghooks update"
}
}

.github/workflows/lint.yml:

name: Lint
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- run: npm ci
- run: npm run lint
- run: npx tsc --noEmit
- run: npm test

.github/workflows/quality.yml:

name: Code Quality
on: [push, pull_request]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
coverage: xdebug
- name: Install dependencies
run: composer install --prefer-dist --no-progress
- name: Code Style (PHPCS)
run: vendor/bin/phpcs
- name: Static Analysis (PHPStan)
run: vendor/bin/phpstan analyse
- name: Run Tests
run: vendor/bin/phpunit --coverage-clover coverage.xml
my-project/
├── src/
├── tests/
├── .php-cs-fixer.php
├── phpcs.xml
├── phpstan.neon
├── phpunit.xml
├── grumphp.yml
└── composer.json
{
"require": {
"php": "^8.1"
},
"require-dev": {
"phpunit/phpunit": "^10.0",
"squizlabs/php_codesniffer": "^3.7",
"friendsofphp/php-cs-fixer": "^3.35",
"phpstan/phpstan": "^1.10",
"phpro/grumphp": "^2.0"
},
"scripts": {
"test": "phpunit",
"lint": "phpcs",
"lint:fix": "phpcbf",
"format": "php-cs-fixer fix",
"analyse": "phpstan analyse --level=8",
"quality": [
"@lint",
"@analyse",
"@test"
]
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
}
}
Terminal window
# Check code style
composer lint
# Fix code style automatically
composer lint:fix
# Format code
composer format
# Run static analysis
composer analyse
# Run all quality checks
composer quality

ESLint:

{
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"]
}

PHPCS:

<rule ref="PSR12"/>

PHPStan:

parameters:
level: 5 # Start with level 5, increase gradually

Don’t just check locally—enforce in CI/CD:

# Fail build if quality checks don't pass
- run: composer lint
- run: composer analyse

If adding to existing project:

PHPStan with baseline:

Terminal window
vendor/bin/phpstan analyse --generate-baseline

This creates phpstan-baseline.neon with existing errors, allowing you to fix them incrementally.

Commit configuration files:

  • .php-cs-fixer.php
  • phpcs.xml
  • phpstan.neon
  • grumphp.yml
  1. PHP_CodeSniffer (PHPCS) = ESLint for code style checking
  2. PHP-CS-Fixer / Pint = Prettier for automatic formatting
  3. PHPStan / Psalm = TypeScript compiler for static analysis at levels 0-9
  4. PSR-12 is the standard coding style (like Airbnb/Standard style guide)
  5. GrumPHP provides git hooks (like Husky) to run checks pre-commit
  6. Editor integration improves development experience with real-time feedback
  7. CI/CD integration enforces quality standards automatically on PRs
  8. PHPStan level 9 is strictest - catches almost all type errors
  9. phpcbf auto-fixes code style issues found by PHPCS
  10. Combine all three tools for comprehensive quality: PHPCS (style), PHPStan (types), tests (behavior)
  11. .phpcs.xml and phpstan.neon configure rules like .eslintrc and tsconfig.json
  12. Rector can automatically upgrade PHP code and refactor (like jscodeshift)
FeatureTypeScript/JSPHP
Style LintingESLintPHP_CodeSniffer
FormattingPrettierPHP-CS-Fixer / Pint
Static AnalysisTypeScript compilerPHPStan / Psalm
Git HooksHuskyGrumPHP
CI IntegrationGitHub ActionsGitHub Actions
Editor SupportVS Code + extensionsVS Code / PHPStorm
StandardsAirbnb, StandardPSR-12

Now that you understand code quality tools, let’s explore build tools and compilation.

Next Chapter: 09: Build Tools: TypeScript Compiler vs PHP


Questions or feedback? Open an issue on GitHub