Code Quality: ESLint meets PHP_CodeSniffer
Overview
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.
Learning Objectives
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
Code Examples
📁 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:
cd code/php-typescript-developers/chapter-08
composer install
composer checkTool Ecosystem Comparison
| TypeScript/JavaScript | PHP | Purpose |
|---|---|---|
| ESLint | PHP_CodeSniffer (PHPCS) | Code style linting |
| Prettier | PHP-CS-Fixer / Laravel Pint | Code formatting |
| TypeScript Compiler | PHPStan / Psalm | Static analysis |
| SonarQube | SonarQube (same) | Code quality platform |
| Husky | GrumPHP / Husky | Git hooks |
Code Style: ESLint vs PHP_CodeSniffer
ESLint Setup
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:
npx eslint src/
npx eslint src/ --fixPHP_CodeSniffer Setup
composer require --dev squizlabs/php_codesnifferphpcs.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:
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 vs PHPCS Rules
| ESLint Rule | PHPCS Rule | Description |
|---|---|---|
indent | Generic.WhiteSpace.ScopeIndent | Indentation |
semi | Squiz.PHP.DiscouragedFunctions | Semicolons |
quotes | Squiz.Strings.DoubleQuoteUsage | Quote style |
no-unused-vars | Generic.CodeAnalysis.UnusedFunctionParameter | Unused variables |
max-len | Generic.Files.LineLength | Line length |
camelcase | Squiz.NamingConventions.ValidVariableName | Naming conventions |
Custom PHPCS Rules
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>Code Formatting: Prettier vs PHP-CS-Fixer
Prettier Setup
npm install --save-dev prettier.prettierrc:
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 100,
"arrowParens": "avoid"
}Run:
npx prettier --write src/PHP-CS-Fixer Setup
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:
vendor/bin/php-cs-fixer fix
vendor/bin/php-cs-fixer fix --dry-run # Preview changesLaravel Pint (Opinionated Alternative)
Laravel Pint is a zero-config PHP code formatter built on PHP-CS-Fixer:
composer require --dev laravel/pintRun:
vendor/bin/pint
vendor/bin/pint --test # Check without modifyingpint.json (optional):
{
"preset": "laravel",
"rules": {
"array_syntax": {
"syntax": "short"
},
"binary_operator_spaces": {
"default": "single_space"
}
}
}Static Analysis: TypeScript vs PHPStan/Psalm
TypeScript Compiler
npm install --save-dev typescripttsconfig.json:
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noUnusedLocals": true,
"noUnusedParameters": true
}
}Run:
npx tsc --noEmit # Type check without compilationPHPStan Setup
PHPStan performs static analysis similar to TypeScript's type checking:
composer require --dev phpstan/phpstanphpstan.neon:
parameters:
level: 8 # 0 (loose) to 9 (max strict)
paths:
- src
excludePaths:
- src/legacy/*
ignoreErrors:
- '#Unsafe usage of new static#'
checkMissingIterableValueType: true
checkGenericClassInNonGenericObjectType: trueRun:
vendor/bin/phpstan analyse
vendor/bin/phpstan analyse --level=5PHPStan 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 Setup
Psalm is another powerful static analysis tool (alternative to PHPStan):
composer require --dev vimeo/psalmpsalm.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:
vendor/bin/psalm
vendor/bin/psalm --show-info=trueGeneric Type Annotations
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 UserConfiguration Examples
Complete Project Setup
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:
composer lint # Check code style
composer lint:fix # Fix code style
composer format # Format code
composer analyse # Static analysis
composer check # Run all checksEditor Integration
VS Code
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
PHPStorm has built-in support for all PHP quality tools:
- Settings → PHP → Quality Tools
- Configure paths to PHPCS, PHP-CS-Fixer, PHPStan
- Enable inspections
- Configure code style (PSR-12)
- Enable "Reformat Code" on save
Pre-commit Hooks
Husky (TypeScript)
npm install --save-dev husky lint-staged
npx husky installpackage.json:
{
"lint-staged": {
"*.ts": [
"eslint --fix",
"prettier --write"
]
}
}.husky/pre-commit:
#!/bin/sh
npx lint-stagedGrumPHP (PHP)
composer require --dev phpro/grumphpgrumphp.yml:
grumphp:
tasks:
phpcs:
standard: PSR12
phpstan:
level: 8
phpunit:
always_execute: falseGrumPHP automatically installs git hooks.
Alternative: PHP-based Husky
composer require --dev brainmaestro/composer-git-hookscomposer.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"
}
}CI/CD Integration
GitHub Actions (TypeScript)
.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 testGitHub Actions (PHP)
.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.xmlPractical Example: Full Quality Setup
Project Structure
my-project/
├── src/
├── tests/
├── .php-cs-fixer.php
├── phpcs.xml
├── phpstan.neon
├── phpunit.xml
├── grumphp.yml
└── composer.jsoncomposer.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/"
}
}
}Run Quality Checks
# 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 qualityBest Practices
1. Start with Presets
ESLint:
{
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"]
}PHPCS:
<rule ref="PSR12"/>PHPStan:
parameters:
level: 5 # Start with level 5, increase gradually2. Enforce in CI
Don't just check locally—enforce in CI/CD:
# Fail build if quality checks don't pass
- run: composer lint
- run: composer analyse3. Gradual Adoption
If adding to existing project:
PHPStan with baseline:
vendor/bin/phpstan analyse --generate-baselineThis creates phpstan-baseline.neon with existing errors, allowing you to fix them incrementally.
4. Team Consistency
Commit configuration files:
- ✅
.php-cs-fixer.php - ✅
phpcs.xml - ✅
phpstan.neon - ✅
grumphp.yml
Key Takeaways
- PHP_CodeSniffer (PHPCS) = ESLint for code style checking
- PHP-CS-Fixer / Pint = Prettier for automatic formatting
- PHPStan / Psalm = TypeScript compiler for static analysis at levels 0-9
- PSR-12 is the standard coding style (like Airbnb/Standard style guide)
- GrumPHP provides git hooks (like Husky) to run checks pre-commit
- Editor integration improves development experience with real-time feedback
- CI/CD integration enforces quality standards automatically on PRs
- PHPStan level 9 is strictest - catches almost all type errors
phpcbfauto-fixes code style issues found by PHPCS- Combine all three tools for comprehensive quality: PHPCS (style), PHPStan (types), tests (behavior)
.phpcs.xmlandphpstan.neonconfigure rules like.eslintrcandtsconfig.json- Rector can automatically upgrade PHP code and refactor (like jscodeshift)
Comparison Table
| Feature | TypeScript/JS | PHP |
|---|---|---|
| Style Linting | ESLint | PHP_CodeSniffer |
| Formatting | Prettier | PHP-CS-Fixer / Pint |
| Static Analysis | TypeScript compiler | PHPStan / Psalm |
| Git Hooks | Husky | GrumPHP |
| CI Integration | GitHub Actions | GitHub Actions |
| Editor Support | VS Code + extensions | VS Code / PHPStorm |
| Standards | Airbnb, Standard | PSR-12 |
Next Steps
Now that you understand code quality tools, let's explore build tools and compilation.
Next Chapter: 09: Build Tools: TypeScript Compiler vs PHP
Resources
Questions or feedback? Open an issue on GitHub