# Slevomat Coding Standard
Slevomat Coding Standard is a comprehensive PHP_CodeSniffer standard that provides over 100 sniffs organized into three main categories: functional sniffs that improve code safety and behavior, cleaning sniffs that detect dead code, and formatting sniffs for consistent code appearance. Built for PHP 7.4+ and PHP 8.0+, it integrates seamlessly with PHP_CodeSniffer to enforce best practices across PHP projects.
The standard supports automatic fixing of most violations via `phpcbf`, local suppression of specific checks using `@phpcsSuppress` annotations, and extensive configuration options through XML rulesets. It's designed to complement existing coding standards by providing additional checks for modern PHP features including type hints, null coalescing operators, arrow functions, and PHP 8 attributes.
## Installation
Install via Composer and configure with a custom ruleset.
```bash
composer require --dev slevomat/coding-standard
```
```xml
```
```bash
# Run code sniffer
vendor/bin/phpcs --standard=ruleset.xml --extensions=php -sp src tests
# Auto-fix violations
vendor/bin/phpcbf --standard=ruleset.xml --extensions=php -sp src tests
```
## Type Hints
### DeclareStrictTypes
Enforces `declare(strict_types = 1)` at the top of each PHP file with configurable spacing.
```xml
```
```php
```
```php
// Before (violation)
/**
* @param int $id
* @param string $name
*/
public function findUser($id, $name) {}
// After (correct)
public function findUser(int $id, string $name): ?User {}
// Suppress when overriding parent method without type hints
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @param int $max
*/
public function createProgressBar($max = 0): ProgressBar {}
```
### ReturnTypeHint
Enforces native return type hints and detects useless `@return` annotations.
```xml
```
```php
// Before (violation)
/**
* @return User|null
*/
public function findById(int $id) {}
// After (correct)
public function findById(int $id): ?User {}
// Union types (PHP 8.0+)
public function process(int $id): User|array {}
// Never return type (PHP 8.1+)
public function fail(): never
{
throw new RuntimeException('Failed');
}
```
### PropertyTypeHint
Requires native property type hints and detects useless `@var` annotations.
```xml
```
```php
// Before (violation)
class User
{
/** @var string */
private $name;
/** @var int|null */
private $age;
}
// After (correct)
class User
{
private string $name;
private ?int $age;
}
```
### UnionTypeHintFormat
Enforces consistent formatting of union type hints.
```xml
```
```php
// Correct (with settings above)
public function process(string|int $value): User|null {}
// Using short nullable syntax
public function find(int $id): ?User {}
```
## Namespaces
### AlphabeticallySortedUses
Enforces alphabetically sorted `use` declarations with natural sorting.
```xml
```
```php
```
```php
// Before (violation - DateTime is unused)
use App\Entity\User;
use DateTime;
class UserService
{
public function create(User $user): void {}
}
// After (correct)
use App\Entity\User;
class UserService
{
public function create(User $user): void {}
}
```
### ReferenceUsedNamesOnly
Requires using `use` statements instead of fully qualified names.
```xml
```
```php
// Before (violation)
class UserService
{
public function now(): \DateTimeImmutable
{
return new \DateTimeImmutable();
}
}
// After (correct)
use DateTimeImmutable;
class UserService
{
public function now(): DateTimeImmutable
{
return new DateTimeImmutable();
}
}
```
### FullyQualifiedExceptions
Requires exceptions to be referenced with fully qualified names for clarity.
```xml
```
```php
// Correct - fully qualified exception names
try {
$this->process();
} catch (\App\Exception\ValidationException $e) {
// Handle validation error
} catch (\RuntimeException $e) {
// Handle runtime error
} catch (\Exception $e) {
// Handle general exception
}
```
## Classes
### ClassStructure
Enforces consistent ordering of class members (constants, properties, methods).
```xml
```
```php
class User
{
use TimestampableTrait;
public const STATUS_ACTIVE = 'active';
private const STATUS_INACTIVE = 'inactive';
private static int $instanceCount = 0;
private string $name;
private ?string $email = null;
public function __construct(string $name)
{
$this->name = $name;
self::$instanceCount++;
}
public static function create(string $name): self
{
return new self($name);
}
public function getName(): string
{
return $this->name;
}
private function validate(): bool
{
return strlen($this->name) > 0;
}
}
```
### ClassConstantVisibility
Requires visibility declaration for class constants.
```xml
```
```php
// Before (violation)
class Status
{
const ACTIVE = 'active';
const INACTIVE = 'inactive';
}
// After (correct)
class Status
{
public const ACTIVE = 'active';
private const INACTIVE = 'inactive';
}
```
### RequireConstructorPropertyPromotion
Requires PHP 8.0+ constructor property promotion.
```xml
```
```php
// Before (violation in PHP 8.0+)
class User
{
private string $name;
private int $age;
public function __construct(string $name, int $age)
{
$this->name = $name;
$this->age = $age;
}
}
// After (correct)
class User
{
public function __construct(
private string $name,
private int $age,
) {}
}
```
### ModernClassNameReference
Enforces using `::class` constant instead of magic methods.
```xml
```
```php
// Before (violation)
$className = get_class($object);
$parentClass = get_parent_class($this);
$class = __CLASS__;
// After (correct)
$className = $object::class;
$parentClass = parent::class;
$class = self::class;
```
## Control Structures
### EarlyExit
Requires early exit to reduce nesting and improve readability.
```xml
```
```php
// Before (violation)
public function process(array $items): void
{
if (count($items) > 0) {
foreach ($items as $item) {
if ($item->isValid()) {
$this->handle($item);
}
}
}
}
// After (correct)
public function process(array $items): void
{
if (count($items) === 0) {
return;
}
foreach ($items as $item) {
if (!$item->isValid()) {
continue;
}
$this->handle($item);
}
}
```
### RequireNullCoalesceOperator
Requires null coalesce operator `??` when possible.
```php
// Before (violation)
$value = isset($data['key']) ? $data['key'] : 'default';
$name = $user !== null ? $user->getName() : 'Anonymous';
// After (correct)
$value = $data['key'] ?? 'default';
$name = $user?->getName() ?? 'Anonymous';
```
### RequireNullCoalesceEqualOperator
Requires null coalesce equal operator `??=` when possible (PHP 7.4+).
```xml
```
```php
// Before (violation)
if ($this->cache === null) {
$this->cache = [];
}
$data['key'] = $data['key'] ?? 'default';
// After (correct)
$this->cache ??= [];
$data['key'] ??= 'default';
```
### DisallowYodaComparison
Disallows Yoda conditions for improved readability.
```php
// Before (violation - Yoda style)
if (null === $value) {}
if ('active' === $status) {}
// After (correct)
if ($value === null) {}
if ($status === 'active') {}
```
## Functions
### RequireArrowFunction
Requires arrow functions when possible (PHP 7.4+).
```xml
```
```php
// Before (violation)
$doubled = array_map(function ($n) {
return $n * 2;
}, $numbers);
$filtered = array_filter($users, function ($user) use ($minAge) {
return $user->getAge() >= $minAge;
});
// After (correct)
$doubled = array_map(fn($n) => $n * 2, $numbers);
$filtered = array_filter($users, fn($user) => $user->getAge() >= $minAge);
```
### StaticClosure
Reports closures not using `$this` that should be declared static.
```php
// Before (violation)
$callback = function (int $value): int {
return $value * 2;
};
// After (correct)
$callback = static function (int $value): int {
return $value * 2;
};
// Or using arrow function
$callback = static fn(int $value): int => $value * 2;
```
### RequireTrailingCommaInCall
Requires trailing commas in multi-line function calls.
```xml
```
```php
// Correct
$result = $this->service->process(
$param1,
$param2,
$param3,
);
$user = new User(
name: 'John',
email: 'john@example.com',
age: 30,
);
```
### UnusedParameter
Detects unused function/method parameters.
```xml
```
```php
// Violation - $logger is unused
public function process(Request $request, LoggerInterface $logger): Response
{
return new Response($request->getContent());
}
// Correct - prefix unused parameter with underscore
public function process(Request $request, LoggerInterface $_logger): Response
{
return new Response($request->getContent());
}
```
## Arrays
### TrailingArrayComma
Enforces trailing commas in multi-line arrays for cleaner diffs.
```xml
```
```php
// Correct
$config = [
'database' => [
'host' => 'localhost',
'port' => 3306,
'name' => 'app',
],
'cache' => [
'driver' => 'redis',
'prefix' => 'app_',
],
];
```
### AlphabeticallySortedByKeys
Requires array keys to be sorted alphabetically.
```php
// Before (violation)
$config = [
'name' => 'App',
'debug' => true,
'cache' => true,
];
// After (correct)
$config = [
'cache' => true,
'debug' => true,
'name' => 'App',
];
```
## Commenting
### DocCommentSpacing
Enforces consistent spacing in doc comments.
```xml
```
```php
/**
* Creates a new user in the system.
*
* @ORM\PrePersist
*
* @param string $name User's full name
* @param string $email User's email address
* @return User The created user entity
* @throws ValidationException If validation fails
*/
public function createUser(string $name, string $email): User
{
// Implementation
}
```
### ForbiddenAnnotations
Reports forbidden annotations that should not be used.
```xml
```
### UselessFunctionDocComment
Reports useless doc comments that don't add information beyond native types.
```php
// Before (useless - will be reported)
/**
* @param int $id
* @return User|null
*/
public function find(int $id): ?User {}
// After (correct - no doc comment needed)
public function find(int $id): ?User {}
// Useful doc comment (provides additional info)
/**
* Finds a user by their unique identifier.
*
* @param int $id The user's database ID
* @return User|null Returns null if user not found
* @throws DatabaseException If database connection fails
*/
public function find(int $id): ?User {}
```
## Exceptions
### DeadCatch
Finds unreachable catch blocks.
```php
// Violation - InvalidArgumentException catch is unreachable
try {
doStuff();
} catch (\Throwable $e) {
log($e);
} catch (\InvalidArgumentException $e) {
// This is never reached!
}
// Correct - more specific exceptions first
try {
doStuff();
} catch (\InvalidArgumentException $e) {
handleValidation($e);
} catch (\Throwable $e) {
log($e);
}
```
### ReferenceThrowableOnly
Enforces catching `Throwable` instead of `Exception` for PHP 7.0+.
```php
// Before (violation)
try {
$this->process();
} catch (Exception $e) {
$this->log($e);
}
// After (correct)
try {
$this->process();
} catch (\Throwable $e) {
$this->log($e);
}
```
### RequireNonCapturingCatch
Requires non-capturing catch when exception variable is unused (PHP 8.0+).
```php
// Before (violation - $e is unused)
try {
$this->process();
} catch (ValidationException $e) {
return false;
}
// After (correct)
try {
$this->process();
} catch (ValidationException) {
return false;
}
```
## Operators
### DisallowEqualOperators
Disallows loose comparison operators for type safety.
```php
// Before (violation)
if ($value == null) {}
if ($status != 'active') {}
// After (correct)
if ($value === null) {}
if ($status !== 'active') {}
```
### RequireCombinedAssignmentOperator
Requires combined assignment operators.
```php
// Before (violation)
$count = $count + 1;
$message = $message . ' suffix';
$total = $total * 2;
// After (correct)
$count += 1;
$message .= ' suffix';
$total *= 2;
```
## Variables
### UnusedVariable
Detects unused variables in code.
```xml
```
```php
// Violation
$result = $this->calculate();
// $result is never used
// Correct with foreach key-only usage
foreach ($data as $key => $value) {
echo $key; // $value intentionally unused
}
```
### UselessVariable
Detects and removes useless intermediate variables.
```php
// Before (violation)
public function getName(): string
{
$name = $this->name;
return $name;
}
// After (correct)
public function getName(): string
{
return $this->name;
}
```
## Suppressing Sniffs Locally
Suppress specific sniffs using `@phpcsSuppress` annotation.
```php
class UserController
{
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @param mixed $data
*/
public function process($data): void
{
// Process data that cannot have a native type hint
}
/**
* @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter
*/
public function handle(Request $request, Response $response): void
{
// Required by interface but response is unused
return $request->process();
}
}
```
## Summary
Slevomat Coding Standard is designed for modern PHP development, particularly PHP 7.4+ and PHP 8.0+ projects. Key use cases include enforcing strict typing across entire codebases, maintaining consistent code structure within classes, detecting dead code and unused imports, and ensuring proper use of modern PHP features like constructor property promotion, arrow functions, and null coalescing operators. The standard integrates with CI/CD pipelines through PHP_CodeSniffer's CLI tools and supports incremental adoption by allowing selective sniff inclusion.
Integration patterns typically involve creating a project-specific `ruleset.xml` that references desired sniffs with customized settings, running `phpcs` in pre-commit hooks or CI pipelines, and using `phpcbf` for automatic fixing. The standard works well alongside static analyzers like PHPStan and Psalm, providing complementary checks. For legacy projects, sniffs can be enabled incrementally, using exclusions and suppressions to manage the transition while maintaining consistent standards for new code.