За последние 10 лет я прошел путь от начинающего PHP-разработчика до архитектора в крупном веб-проекте. Одна из главных проблем с которой я сталкивался это непредсказуемость кода в больших проектах. Но всё изменилось, когда PHP начал активно развивать систему типов. В этой статье я покажу, как статическая типизация превратила наш код из «слабо предсказуемого» в «железобетонный» и как вы можете повторить этот успех.
Почему PHP и типы это больше не оксюморон
Раньше PHP ассоциировался со слабой типизацией, где "123"
спокойно превращалось в число, а null
мог появиться в самом неожиданном месте. Но начиная с PHP 7.0, язык совершил революцию:
- PHP 7.0: Скалярные типы (но опциональные)
- PHP 7.4: Типизированные свойства классов
- PHP 8.0: Union types,
mixed
,static
return - PHP 8.1: Перечисления (enums) и пересечения типов (intersection types)
Сегодня мы можем писать код, который предотвращает целые классы ошибок ещё на этапе разработки. Давайте разберемся, как это работает.
Как начать использовать типы
Шаг 1: Базовые аннотации типов
Допустим, у нас есть функция для расчета суммы заказа:
// Без типов function calculateTotal($items, $discount) { // ...магия вычислений... return $total; }
Проблема: если передать $discount = '10%'
вместо 10
, получим ошибку в рантайме. Решение:
declare(strict_types=1); function calculateTotal(array $items, float $discount): float { // Теперь PHP проверяет типы автоматически! return array_sum($items) * (1 - $discount); }
Что это даёт:
- Ошибка обнаружится при передаче неверного типа (например, строки вместо числа).
- IDE подскажет типы при использовании функции.
Шаг 2: Типизированные свойства классов
Раньше:
class User { public $id; // Что здесь? int? string? UUID? }
Теперь:
class User { public int $id; private string $email; protected DateTimeImmutable $createdAt; // Конструктор с типами: public function __construct(int $id, string $email) { $this->id = $id; $this->email = $email; $this->createdAt = new DateTimeImmutable(); } }
Плюсы:
- Нельзя случайно присвоить
$user->id = 'abc'
. - Автодокументирование кода.
Шаг 3: Строгий режим (strict_types=1)
Без strict
:
// Файл без declare(strict_types=1) function add(int $a, int $b): int { return $a + $b; } echo add("10", 20.5); // Работает! Преобразует строку и float в int
С strict
:
declare(strict_types=1); function add(int $a, int $b): int { return $a + $b; } echo add("10", 20.5); // Fatal error: TypeError
Почему это важно:
- Избегаем скрытых преобразований, которые могут привести к ошибкам логики.
- Делаем код более предсказуемым.
Optional или Strict
Результаты анализа 50 тысяч строк кода в нашем проекте:
Параметр | Без типов (PHP 5.6) | С типами (PHP 8.2, strict) |
---|---|---|
Ошибок в рантайме | 34 | 5 |
Время на рефакторинг | 8 часов | 2 часа |
Читаемость кода (по опросу) | 3.1/5 | 4.7/5 |
Рекомендации
1. Внедряйте типы постепенно
- Начните с нового кода.
- Используйте
/** @var type */
аннотации для легаси-кода. - Включайте
strict_types=1
в каждом файле.
2. Используйте статические анализаторы
- PHPStan или Psalm найдут проблемы, которые пропускает интерпретатор.
Пример настройки PHPStan:
# phpstan.neon parameters: level: 8 paths: - src/
3. Union types
Хорошо:
public function sendMessage(string|array $recipient): void {}
Плохо:
// Слишком абстрактно! public function process(mixed $data): string|int|array {}
4. Тестируйте с типами
Пишите юнит-тесты, которые проверяют не только логику, но и типы:
public function testCalculateTotal(): void { $result = calculateTotal([100, 200], 0.1); $this->assertSame(270.0, $result); $this->assertIsFloat($result); // Явная проверка типа }
Пример: переход легаси-кода на strict-типы
Было:
class OrderProcessor { public function process($orderData) { // ... много кода ... } }
Стало:
declare(strict_types=1); class OrderProcessor { public function process(OrderDto $orderData): void { // Теперь мы уверены в структуре $orderData } } // DTO-объект для валидации: class OrderDto { public function __construct( public readonly int $userId, public readonly array $items, public readonly ?string $promoCode = null ) {} }
Результат:
- Количество ошибок «Undefined index» уменьшилось на 90%.
- Новые разработчики быстрее вникают в код.
Статическая типизация в PHP это не ограничение, а свобода. Свобода от бесконечных проверок is_numeric
, от непонятных багов в 3 часа ночи, от страха вносить изменения в legacy-код.
Добавьте declare(strict_types=1)
в один файл, опишите типы для нового сервиса. Уверен, через месяц вы уже не захотите возвращаться к «вольностям» старого PHP.