Стиль кода: PSR-1, PSR-12, PER CS
PSR-1: Basic Coding Standard
PSR-1 определяет минимальные правила, которым должен соответствовать любой PHP-код:
Основные требования
- Теги: только
<?phpи<?=(короткие теги<?запрещены) - Кодировка: файлы в UTF-8 без BOM
- Один файл: либо объявляет символы (классы, функции, константы), либо выполняет побочные эффекты (вывод, изменение настроек), но не оба сразу
- Namespace: обязательно для классов
- Имена классов:
StudlyCaps(PascalCase) - Константы:
UPPER_CASEс подчёркиваниями - Методы:
camelCase
<?php
// ❌ BAD: side effects AND declarations in same file
declare(strict_types=1);
ini_set('display_errors', '0'); // Side effect
echo 'Initializing...'; // Side effect
class Config // Declaration
{
public const MAX_RETRIES = 3;
}
<?php
// ✅ GOOD: only declarations
declare(strict_types=1);
namespace App\Config;
final class AppConfig
{
public const MAX_RETRIES = 3;
public const DEFAULT_TIMEOUT = 30;
public function getRetryDelay(): int
{
return 1000;
}
}
Именование по PSR-1
<?php
declare(strict_types=1);
namespace App\Service;
// Class: StudlyCaps (PascalCase)
final class OrderProcessor
{
// Constants: UPPER_CASE
public const MAX_ITEMS = 100;
public const DEFAULT_CURRENCY = 'USD';
// Methods: camelCase
public function processOrder(int $orderId): void {}
public function calculateTotal(): int
{
return 0;
}
// Properties: no strict rule in PSR-1
// PSR-12 recommends camelCase
private int $itemCount = 0;
private string $lastError = '';
}
PSR-12: Extended Coding Style
PSR-12 расширяет PSR-1 детальными правилами форматирования. Заменил устаревший PSR-2.
Файлы
<?php
// 1. Opening <?php tag on first line
// 2. declare(strict_types=1) on next line (blank line after)
// 3. Namespace declaration (blank line after)
// 4. Use imports grouped and sorted
// 5. Blank line before class body
declare(strict_types=1);
namespace App\Controller;
use App\DTO\CreateUserDTO;
use App\Service\UserService;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
final class UserController
{
// Class body
}
// Blank line at end of file, single newline
Фигурные скобки
<?php
declare(strict_types=1);
namespace App\Example;
// Classes/interfaces/traits: opening brace on NEXT line
final class PaymentService
{ // Next line
// Methods: opening brace on NEXT line
public function charge(int $amount): bool
{ // Next line
// Control structures: opening brace on SAME line
if ($amount <= 0) { // Same line
return false;
}
for ($i = 0; $i < 3; $i++) { // Same line
// retry logic
}
while ($this->isProcessing()) { // Same line
usleep(100_000);
}
switch ($amount) { // Same line
case 0:
return false;
default:
return true;
}
return true;
}
}
Видимость и модификаторы
<?php
declare(strict_types=1);
namespace App\Example;
// Visibility MUST be declared on all properties and methods
abstract class BaseService
{
// Order: abstract/final, then visibility, then static/readonly
abstract protected function validate(): bool;
final public static function create(): static
{
return new static();
}
// Property order: visibility, then static/readonly, then type
protected readonly string $name;
private static int $instanceCount = 0;
}
Пробелы и отступы
<?php
declare(strict_types=1);
namespace App\Example;
final class FormattingRules
{
// 4 spaces indentation (NO tabs)
// No trailing whitespace
// Max line length: soft limit 120 chars
public function methodArguments(
string $firstName, // One argument per line if multiline
string $lastName,
int $age,
): string { // Closing paren + opening brace on same line
return "{$firstName} {$lastName}, age {$age}";
}
public function controlStructures(): void
{
// Space after keywords, no space after function name
if ($a === $b) { // Space after 'if', space before '{'
// ...
} elseif ($a > $b) { // 'elseif' not 'else if'
// ...
} else {
// ...
}
// Ternary and null coalesce
$value = $condition ? 'yes' : 'no';
$name = $input ?? 'default';
// Closures
$closure = function (int $x, int $y) use ($multiplier): int {
return ($x + $y) * $multiplier;
};
// Arrow functions
$double = fn(int $n): int => $n * 2;
}
// Return type after colon with space
public function getItems(): array
{
return [];
}
// Nullable types
public function findUser(int $id): ?User
{
return null;
}
// Union types (PHP 8.0+)
public function parse(string|int $value): string
{
return (string) $value;
}
}
Группировка use-импортов
<?php
declare(strict_types=1);
namespace App\Service;
// Group 1: Classes/interfaces
use App\Entity\Order;
use App\Repository\OrderRepository;
use Psr\Log\LoggerInterface;
// Group 2: Functions (blank line between groups)
use function array_map;
use function sprintf;
// Group 3: Constants
use const PHP_INT_MAX;
use const PHP_EOL;
PER Coding Style 2.0
PER CS (PHP Evolving Recommendation) -- наследник PSR-12, разработанный для поддержки новых возможностей PHP 8.x. Это "живой" стандарт, который обновляется вместе с языком.
Новые правила для PHP 8.x
<?php
declare(strict_types=1);
namespace App\Example;
// Enums: same brace rules as classes
enum Status: string
{
case Active = 'active';
case Inactive = 'inactive';
// Methods in enums follow same rules
public function label(): string
{
return match ($this) {
self::Active => 'Active',
self::Inactive => 'Inactive',
};
}
}
// Readonly classes (PHP 8.2+)
final readonly class Money
{
// Order: final, readonly, class
public function __construct(
public int $amount,
public string $currency,
) {}
}
// Intersection types (PHP 8.1+)
function process(Countable&Iterator $collection): void
{
// ...
}
// DNF types (PHP 8.2+)
function handle((Countable&Iterator)|null $items): void
{
// ...
}
// Named arguments in calls
$user = new User(
name: 'John',
email: '[email protected]',
role: Role::Admin,
);
// Match expression formatting
$result = match (true) {
$value < 0 => 'negative',
$value === 0 => 'zero',
$value > 0 => 'positive',
};
// First-class callables (PHP 8.1+)
$callback = strlen(...);
$mapper = $this->transform(...);
Trailing comma
PER CS рекомендует trailing comma в многострочных конструкциях:
<?php
declare(strict_types=1);
// Function declarations
function createUser(
string $name,
string $email,
int $age, // Trailing comma
): User {
// ...
}
// Function calls
$result = createUser(
name: 'John',
email: '[email protected]',
age: 30, // Trailing comma
);
// Arrays (always)
$config = [
'debug' => true,
'cache' => false,
'timeout' => 30, // Trailing comma
];
// Closure use
$fn = function () use (
$serviceA,
$serviceB,
$logger, // Trailing comma
): void {
// ...
};
PHP CS Fixer
PHP CS Fixer -- основной инструмент для автоматического форматирования кода. Настройка для PSR-12 и PER CS:
<?php
// .php-cs-fixer.dist.php
declare(strict_types=1);
$finder = (new PhpCsFixer\Finder())
->in([
__DIR__ . '/src',
__DIR__ . '/tests',
])
->exclude([
'var',
'vendor',
]);
return (new PhpCsFixer\Config())
->setRules([
// PER CS 2.0 ruleset (includes PSR-12)
'@PER-CS2.0' => true,
// Strict types
'declare_strict_types' => true,
// Modern PHP
'final_class' => true,
'self_static_accessor' => true,
'global_namespace_import' => [
'import_classes' => true,
'import_functions' => false,
'import_constants' => false,
],
// Clean code
'no_unused_imports' => true,
'ordered_imports' => ['sort_algorithm' => 'alpha'],
'trailing_comma_in_multiline' => [
'elements' => ['arguments', 'arrays', 'parameters'],
],
'single_quote' => true,
'no_empty_statement' => true,
'no_extra_blank_lines' => true,
])
->setFinder($finder)
->setRiskyAllowed(true);
Запуск PHP CS Fixer
# Check without fixing
php-cs-fixer fix --dry-run --diff
# Fix all files
php-cs-fixer fix
# Fix specific file
php-cs-fixer fix src/Service/OrderService.php
# Check with verbose output
php-cs-fixer fix --dry-run --diff --verbose
phpcs vs php-cs-fixer
| Аспект | PHP_CodeSniffer (phpcs) | PHP CS Fixer |
|---|---|---|
| Подход | Находит нарушения | Находит и исправляет |
| Исправление | phpcbf (отдельная утилита) |
Встроено (fix) |
| Конфигурация | XML (phpcs.xml) |
PHP (.php-cs-fixer.dist.php) |
| Правила | Sniffs | Fixers |
| Популярность | Старше, больше sniffs | Чаще в Symfony-проектах |
| Скорость | Быстрее проверка | Быстрее исправление |
phpcs конфигурация
<?xml version="1.0"?>
<!-- phpcs.xml.dist -->
<ruleset name="Project">
<description>Project coding standard</description>
<file>src/</file>
<file>tests/</file>
<exclude-pattern>vendor/</exclude-pattern>
<rule ref="PSR12"/>
<rule ref="Generic.Files.LineLength">
<properties>
<property name="lineLimit" value="120"/>
<property name="absoluteLineLimit" value="150"/>
</properties>
</rule>
</ruleset>
# Check code style
phpcs
# Fix automatically (what it can)
phpcbf
Рекомендация: В современных PHP-проектах (особенно с Symfony) используйте PHP CS Fixer с набором правил
@PER-CS2.0. Это актуальный стандарт, поддерживающий все возможности PHP 8.4. Добавьте проверку в CI/CD pipeline.