MidТеория4 min

PHP 7.0 и 7.1

Скалярные type hints, return types, nullable types, void, iterable, multi-catch

PHP 7.0 — Революция производительности

PHP 7.0 (декабрь 2015) стал одним из самых значительных релизов в истории языка. Движок Zend Engine 3 обеспечил двукратный прирост производительности по сравнению с PHP 5.6 и значительное снижение потребления памяти.

Скалярные type hints

До PHP 7.0 type hints поддерживали только классы, интерфейсы, массивы и callable. Теперь доступны скалярные типы.

<?php
declare(strict_types=1);

// Скалярные type hints: int, float, string, bool
function calculateDiscount(float $price, int $percentage): float
{
    return $price * ($percentage / 100);
}

echo calculateDiscount(100.0, 15); // 15.0

// Без strict_types PHP приводит типы автоматически:
// calculateDiscount("100", "15") — сработает в coercive mode

// С strict_types=1 будет TypeError:
// calculateDiscount("100", "15"); // TypeError

Важно: declare(strict_types=1) влияет только на файл, в котором объявлен. Вызовы из других файлов без этой директивы работают в coercive mode.

Return type declarations

<?php
declare(strict_types=1);

function sum(int $a, int $b): int
{
    return $a + $b;
}

function getUser(int $id): array
{
    return ['id' => $id, 'name' => 'John'];
}

// Возврат неправильного типа вызывает TypeError
function broken(): int
{
    return "hello"; // TypeError в strict mode
}

Оператор spaceship <=>

Оператор комбинированного сравнения возвращает -1, 0 или 1.

<?php

// Сравнение чисел
echo 1 <=> 2;  // -1
echo 2 <=> 2;  //  0
echo 3 <=> 2;  //  1

// Удобно для сортировки
$users = [
    ['name' => 'Charlie', 'age' => 30],
    ['name' => 'Alice', 'age' => 25],
    ['name' => 'Bob', 'age' => 35],
];

// Сортировка по возрасту
usort($users, fn($a, $b) => $a['age'] <=> $b['age']);

// Сортировка по нескольким полям
usort($users, function ($a, $b) {
    return $a['age'] <=> $b['age']
        ?: $a['name'] <=> $b['name'];
});

Null coalescing operator ??

<?php

// До PHP 7.0
$username = isset($_GET['user']) ? $_GET['user'] : 'guest';

// PHP 7.0+
$username = $_GET['user'] ?? 'guest';

// Цепочка — берёт первое не-null значение
$config = $customConfig ?? $envConfig ?? $defaultConfig ?? 'fallback';

// Работает с массивами и свойствами объектов
$city = $user['address']['city'] ?? 'Unknown';
$name = $order?->customer?->name ?? 'Anonymous'; // в сочетании с ?-> (PHP 8.0)

Отличие от ?:: оператор ?? проверяет на null, а ?: проверяет на falsy (0, '', false, null, []).

Анонимные классы

<?php

// Полезно для одноразовых реализаций интерфейсов
interface Logger
{
    public function log(string $message): void;
}

function processData(Logger $logger): void
{
    $logger->log('Processing started');
    // ...
}

// Анонимный класс вместо создания отдельного файла
processData(new class implements Logger {
    public function log(string $message): void
    {
        echo "[LOG] $message\n";
    }
});

// Анонимные классы могут наследовать и использовать трейты
$obj = new class('value') extends ParentClass implements Interface1 {
    use SomeTrait;

    public function __construct(private string $value) {}
};

Группировка use declarations

<?php

// До PHP 7.0 — каждый use отдельно
use App\Models\User;
use App\Models\Order;
use App\Models\Product;

// PHP 7.0+ — группировка
use App\Models\{User, Order, Product};

// Можно группировать разные типы
use App\Services\{
    UserService,
    OrderService,
};

use function App\Helpers\{format_date, format_money};
use const App\Config\{DB_HOST, DB_PORT};

Обработка ошибок: Throwable, Error, TypeError

<?php

// PHP 7.0 ввёл иерархию: Throwable -> Error | Exception
try {
    // Фатальные ошибки теперь перехватываемые
    $result = undefinedFunction();
} catch (\Error $e) {
    echo "Error: " . $e->getMessage();
}

// TypeError при нарушении типов
try {
    function add(int $a, int $b): int { return $a + $b; }
    add("hello", "world"); // TypeError в strict mode
} catch (\TypeError $e) {
    echo "Type error: " . $e->getMessage();
}

// Перехват всего через Throwable
try {
    // ...
} catch (\Throwable $e) {
    // Ловит и Error, и Exception
    echo $e->getMessage();
}

PHP 7.1 — Уточнение типовой системы

PHP 7.1 (декабрь 2016) расширил систему типов nullable-типами и void.

Nullable types

<?php
declare(strict_types=1);

// Знак ? перед типом означает "тип ИЛИ null"
function findUser(int $id): ?User
{
    $user = UserRepository::find($id);

    return $user; // User или null
}

// Nullable параметр
function setNickname(?string $nickname): void
{
    // $nickname может быть string или null
}

setNickname('Neo');   // OK
setNickname(null);    // OK
setNickname();        // Ошибка! null и отсутствие аргумента — разные вещи

Подвох для экзамена: ?string $param и string $param = null — не одно и то же. Первый ТРЕБУЕТ аргумент (string или null), второй допускает пропуск аргумента.

Void return type

<?php

function sendEmail(string $to, string $subject): void
{
    // Отправляем email
    mail($to, $subject, '...');

    // return; — можно (без значения)
    // return null; — ОШИБКА! void не допускает даже null
}

// void нельзя использовать как тип параметра
// function broken(void $param) {} // Parse error

Iterable pseudo-type

<?php

// iterable принимает array или Traversable
function processItems(iterable $items): void
{
    foreach ($items as $item) {
        echo $item . "\n";
    }
}

processItems([1, 2, 3]);                        // array — OK
processItems(new ArrayIterator([4, 5, 6]));      // Traversable — OK

// В return type
function getItems(): iterable
{
    yield 'first';
    yield 'second';
    // Генератор реализует Traversable
}

Видимость констант класса

<?php

class Config
{
    // До PHP 7.1 — все константы были public
    public const VERSION = '1.0.0';
    protected const DB_HOST = 'localhost';
    private const DB_PASSWORD = 'secret';

    public function getDbHost(): string
    {
        return self::DB_HOST;
    }
}

echo Config::VERSION;      // OK
// echo Config::DB_HOST;   // Fatal error: Cannot access protected constant
// echo Config::DB_PASSWORD; // Fatal error: Cannot access private constant

Multi-catch exceptions

<?php

try {
    $data = json_decode($input, true, 512, JSON_THROW_ON_ERROR);
    $db->query($sql);
} catch (JsonException | PDOException $e) {
    // Один блок catch для нескольких типов исключений
    logger()->error('Data processing failed', [
        'exception' => $e::class,
        'message'   => $e->getMessage(),
    ]);
} catch (RuntimeException $e) {
    // Отдельная обработка
    logger()->critical('Runtime error', ['error' => $e->getMessage()]);
}

Symmetric array destructuring

<?php

// До PHP 7.1 — list()
list($first, $second) = [1, 2];

// PHP 7.1+ — короткий синтаксис []
[$first, $second] = [1, 2];

// С ключами (тоже новинка 7.1!)
['name' => $name, 'age' => $age] = ['name' => 'Alice', 'age' => 30];
echo "$name is $age years old"; // Alice is 30 years old

// В foreach
$users = [
    ['name' => 'Alice', 'role' => 'admin'],
    ['name' => 'Bob', 'role' => 'user'],
];

foreach ($users as ['name' => $name, 'role' => $role]) {
    echo "$name ($role)\n";
}

// Вложенная деструктуризация
[[$a, $b], [$c, $d]] = [[1, 2], [3, 4]];

Отрицательные строковые смещения

<?php

$str = 'Hello World';

echo $str[-1];  // d (последний символ)
echo $str[-5];  // W

// Работает и в функциях
echo substr($str, -5);    // World
echo strpos($str, 'o', -6); // 7 (поиск с конца)

Сводная таблица: PHP 7.0 vs 7.1

Возможность PHP 7.0 PHP 7.1
Скалярные type hints int, float, string, bool --
Return types Да + void
Nullable types -- ?int, ?string
?? (null coalescing) Да --
<=> (spaceship) Да --
Анонимные классы Да --
iterable -- Да
Multi-catch -- catch (A | B $e)
Видимость констант -- public/protected/private const
Array destructuring [] -- Да + ключи
Отрицательные смещения строк -- Да
Группировка use Да --
Throwable / Error Да --

Типичные вопросы на экзамене

  1. Что вернёт 1 <=> 1? Ответ: 0
  2. Можно ли использовать void как тип параметра? Нет, только как return type.
  3. Разница между ?? и ?:? ?? проверяет на null, ?: на falsy.
  4. Что произойдёт при return null в void-функции? Fatal error.
  5. ?string $x vs string $x = null? Первый требует аргумент, второй допускает пропуск.

Практические задания

Задание 1: Spaceship сортировка

Отсортируйте массив пользователей сначала по роли (admin > editor > viewer), затем по имени.

Задание 2: Null coalescing

Напишите функцию getConfig(string $key): mixed, которая ищет значение сначала в $_ENV, потом в файле конфигурации, потом возвращает default.

Задание 3: Nullable types

Реализуйте UserRepository с методом findByEmail(?string $email): ?User, который обрабатывает null-email корректно.