# 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.