strict_types, union types, nullable
Три ключевые темы
strict_types— как PHP проверяет типы.- Union types — один из нескольких типов.
- Nullable — значение или
null.
strict_types
Директива на уровне файла:
<?php
declare(strict_types=1);
Должна быть первой инструкцией (после <?php и комментариев).
Что меняется
Без strict:
function sum(int $a, int $b): int
{
return $a + $b;
}
sum('5', '10'); // 15 — строки стали int
sum(true, false); // 1
sum(3.5, 2.5); // 6 (float → int с округлением?)
Со strict:
<?php
declare(strict_types=1);
function sum(int $a, int $b): int
{
return $a + $b;
}
sum('5', '10'); // TypeError
sum(3.5, 2.5); // TypeError (float → int неявно)
sum(5, 10); // 15
Что не покрывает strict_types
Только вызовы функций в текущем файле. Не влияет на:
- Арифметические операции.
- Сравнение.
- Присваивание.
- Внутренние функции PHP (некоторые).
Пример:
<?php
declare(strict_types=1);
$result = '5' + 3; // 8 — без ошибки
Арифметика не проверяется.
Правило
Всегда включай declare(strict_types=1) в начале каждого PHP-файла.
Исключение — шаблоны (Blade), конфиги с return [].
Union types
Один из нескольких типов (PHP 8.0+):
function formatId(int|string $id): string
{
return (string) $id;
}
formatId(123); // '123'
formatId('abc'); // 'abc'
Порядок типов
Не важен для PHP, но соглашение:
int|string // хорошо
string|int // тоже работает
Принято — примитивы сначала, null в конце.
С null
function f(int|string|null $x): void { }
Эквивалент ?:
function f(int|string|null $x): void { }
?type — только для одного типа:
?string // string|null — ok
?string|int // ошибка — ? работает только с одним типом
string|int|null // правильно
Возврат
function parse(string $input): int|false
{
return is_numeric($input) ? (int)$input : false;
}
Типичный паттерн — вернуть значение или false.
Ещё пример:
function findUser(int $id): User|null
{
return User::find($id);
}
Массивы и union
function sum(array|int $values): int
{
if (is_int($values)) {
return $values;
}
return array_sum($values);
}
Сужение типов
PHP проверяет типы в блоке if:
function format(int|string $value): string
{
if (is_int($value)) {
return number_format($value);
}
// PHP знает, что здесь $value — string
return strtoupper($value);
}
Анализаторы (PHPStan) тоже понимают.
Union с классами
function save(User|Order $entity): void
{
if ($entity instanceof User) {
// User
} else {
// Order
}
}
Nullable
?type — короткая запись type|null:
function greet(?string $name): string
{
return $name ?? 'Гость';
}
Со значением по умолчанию
function greet(?string $name = null): string
{
return $name ?? 'Гость';
}
greet(); // Гость
greet('Иван'); // Иван
greet(null); // Гость
Иначе:
function greet(string $name = null): string
// устаревший синтаксис, не используй
Возврат nullable
function findUser(int $id): ?User
{
return User::query()->find($id);
}
Правило: если может вернуть null — указывай явно.
Проверка null
?? — coalescing:
$name = $user->name ?? 'Гость';
?-> — nullsafe (PHP 8.0+):
$city = $user?->address?->city;
Явная проверка:
if ($user !== null) {
echo $user->name;
}
if (is_null($user)) { }
if ($user === null) { }
Массивы и null
function first(array $items): ?string
{
return $items[0] ?? null;
}
?? vs isset:
$data = ['a' => null];
$data['a'] ?? 'default'; // 'default' (null → default)
isset($data['a']); // false
array_key_exists('a', $data); // true
?? срабатывает при null и отсутствии ключа.
Nullsafe в цепочке
$city = $order?->user?->address?->city;
Если любой в цепочке null — результат null, без ошибок.
Без ?->:
$city = $order && $order->user && $order->user->address
? $order->user->address->city
: null;
Дефолты через ??=
$count ??= 0;
$config['host'] ??= 'localhost';
Присваивает, только если null.
Практика
<?php
declare(strict_types=1);
final class User
{
public function __construct(
public readonly int $id,
public readonly string $name,
public readonly ?string $email = null,
) {}
}
function findUser(int $id): ?User
{
// имитация
return $id === 1 ? new User(1, 'Иван', 'ivan@example.com') : null;
}
function formatEmail(?string $email): string
{
return $email ?? 'email не указан';
}
$user = findUser(1);
if ($user !== null) {
echo $user->name . ': ' . formatEmail($user->email) . "\n";
}
$missing = findUser(99);
echo $missing?->name ?? 'Пользователь не найден'; // Пользователь не найден
Частые ошибки
1. ? с union:
?string|int // ошибка
string|int|null // правильно
2. Возврат null без указания:
function f(): string { return null; } // TypeError
function f(): ?string { return null; } // ok
3. Забыть strict_types:
<?php
// без declare
function f(int $x): void {}
f('5'); // сработает без ошибки
4. ?? не работает с пустой строкой:
$value = '';
$result = $value ?? 'default'; // '' (не 'default')
$result = $value ?: 'default'; // 'default'
Итог
declare(strict_types=1)— всегда.- Union types —
int|string,User|Order. - Nullable —
?stringдля одного типа,|nullдля union. ??— для null-значений.?->— nullsafe.- Анализаторы — PHPStan, Psalm — ловят ошибки типов.
Дальше — match и switch: новый синтаксис.