Mid📖Теория3 min

PHP 7.2 и 7.3

Object type hint, sodium, flexible Heredoc, trailing commas, JSON_THROW_ON_ERROR

PHP 7.2 и 7.3

PHP 7.2 — Безопасность и типы

PHP 7.2 (ноябрь 2017) добавил новые возможности типизации и встроил криптографическую библиотеку Sodium.

Object type hint

<?php
declare(strict_types=1);

// Тип object — принимает любой объект
function serialize(object $obj): string
{
    return json_encode($obj);
}

function createFromArray(string $class, array $data): object
{
    $obj = new $class();
    foreach ($data as $key => $value) {
        $obj->$key = $value;
    }
    return $obj;
}

// Использование
$user = new stdClass();
$user->name = 'Alice';
echo serialize($user); // {"name":"Alice"}

Важно: object — это конкретный тип, не интерфейс. Он принимает экземпляр любого класса, но не скалярные значения и не массивы.

Расширение Sodium (libsodium)

<?php

// Sodium встроен в PHP 7.2+ (раньше был PECL-расширением)

// Шифрование (symmetric)
$key = sodium_crypto_secretbox_keygen();
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$message = 'Secret data';

$cipher = sodium_crypto_secretbox($message, $nonce, $key);
$decrypted = sodium_crypto_secretbox_open($cipher, $nonce, $key);

echo $decrypted; // Secret data

// Хеширование паролей (лучше чем password_hash для некоторых случаев)
$hash = sodium_crypto_pwhash_str(
    'my_password',
    SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE,
    SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE
);

$valid = sodium_crypto_pwhash_str_verify($hash, 'my_password'); // true

// Очистка чувствительных данных из памяти
sodium_memzero($key);
sodium_memzero($message);

Parameter type widening

<?php

// Дочерний класс может расширять типы параметров
class ParentClass
{
    public function process(int $value): void
    {
        echo $value;
    }
}

class ChildClass extends ParentClass
{
    // Убираем type hint — расширяем тип (было int, стало любой)
    public function process($value): void
    {
        echo $value;
    }
}

// Это соответствует принципу подстановки Лисков (LSP):
// Дочерний класс принимает ВСЁ, что принимает родитель + больше

Принцип: параметры можно расширять (contravariance), возвращаемые типы — сужать (covariance). Полная поддержка covariance/contravariance появилась в PHP 7.4.

Trailing comma в группировке use

<?php

use App\Models\{
    User,
    Order,
    Product,  // Trailing comma — теперь разрешена
};

Abstract method override

<?php

abstract class Base
{
    abstract function test(string $value): void;
}

abstract class Middle extends Base
{
    // Абстрактный метод можно переопределить в абстрактном классе
    abstract function test(string $value): void;
}

PHP 7.3 — Удобство разработчика

PHP 7.3 (декабрь 2018) сфокусировался на улучшении developer experience.

Flexible Heredoc/Nowdoc

<?php

// До PHP 7.3 — закрывающий маркер должен быть в начале строки
$old = <<<EOT
    Hello
    World
EOT;

// PHP 7.3+ — закрывающий маркер может быть с отступом
// Отступ закрывающего маркера определяет "базовый" отступ
$html = <<<HTML
    <div>
        <h1>Title</h1>
        <p>Content</p>
    </div>
    HTML;
// Результат: <div>\n    <h1>Title</h1>... (4 пробела убраны)

// Можно использовать в аргументах функций
$data = [
    'description' => <<<DESC
        This is a multi-line
        description text.
        DESC,
    'name' => 'Product',
];

// Nowdoc (без интерполяции) — те же правила
$sql = <<<'SQL'
    SELECT *
    FROM users
    WHERE active = 1
    SQL;

Ключевое правило: отступ закрывающего маркера определяет, сколько пробелов будет удалено из начала каждой строки. Если строка имеет меньший отступ, чем маркер, будет Parse error.

Trailing comma в вызовах функций

<?php

// Trailing comma теперь разрешена в вызовах функций
function createUser(
    string $name,
    string $email,
    int $age,
) {
    // Trailing comma в объявлении была с PHP 7.0
}

// Теперь и в вызове:
$user = createUser(
    'Alice',
    '[email protected]',
    30,  // Trailing comma — OK с PHP 7.3
);

// Упрощает diff при добавлении аргументов
// и уменьшает merge conflicts

Новые функции для массивов

<?php

$array = ['a' => 1, 'b' => 2, 'c' => 3];

// array_key_first() — первый ключ без reset()
$firstKey = array_key_first($array); // 'a'

// array_key_last() — последний ключ без end()
$lastKey = array_key_last($array); // 'c'

// До PHP 7.3 приходилось делать:
$oldFirst = array_keys($array)[0];
// или
reset($array);
$oldFirst = key($array);

// is_countable() — проверка, можно ли передать в count()
$value = 'hello';
if (is_countable($value)) {
    echo count($value);
} else {
    echo "Not countable";
}

// Раньше count() на не-countable выдавал Warning
// Теперь можно проверить заранее

JSON_THROW_ON_ERROR

<?php

// До PHP 7.3 — нужно проверять json_last_error()
$data = json_decode($invalid);
if (json_last_error() !== JSON_ERROR_NONE) {
    throw new RuntimeException(json_last_error_msg());
}

// PHP 7.3+ — выброс исключения автоматически
try {
    $data = json_decode('invalid json', true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
    echo "JSON error: " . $e->getMessage();
    // "JSON error: Syntax error"
}

// Работает и для json_encode
try {
    $json = json_encode("\xB1\x31", JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
    echo "Encoding error: " . $e->getMessage();
}

Для экзамена: JSON_THROW_ON_ERROR добавлен в PHP 7.3, класс JsonException наследует \Exception (не \RuntimeException).

Миграция на PCRE2

<?php

// PHP 7.3 перешёл с PCRE на PCRE2
// Основное отличие — более строгие правила

// Раньше работало, теперь Warning:
// preg_match('/[\w-.]+/', $string);
// Дефис должен быть экранирован или стоять в начале/конце
preg_match('/[\w\-.]+/', $string); // Правильно

// PCRE2 также добавил поддержку Unicode 11

Ссылочное присваивание с list()

<?php

$array = [1, 2];

// PHP 7.3 — деструктуризация по ссылке
[&$a, &$b] = $array;

$a = 10;
echo $array[0]; // 10 — изменился оригинальный массив

Сводная таблица: PHP 7.2 vs 7.3

Возможность PHP 7.2 PHP 7.3
object type hint Да --
Sodium extension Встроен --
Parameter type widening Да --
Flexible Heredoc/Nowdoc -- Да
Trailing comma в вызовах -- Да
array_key_first/last() -- Да
is_countable() -- Да
JSON_THROW_ON_ERROR -- Да
PCRE2 -- Да
Ссылки в list() -- Да

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

  1. Что такое parameter type widening? Дочерний класс может убрать или расширить тип параметра метода родителя.
  2. Чем JSON_THROW_ON_ERROR лучше json_last_error()? Исключение прерывает поток выполнения, его нельзя случайно пропустить.
  3. Какой класс исключения бросает json_decode с JSON_THROW_ON_ERROR? JsonException.
  4. Как работает отступ в Flexible Heredoc? Отступ закрывающего маркера определяет базовый уровень, который удаляется из всех строк.
  5. Что вернёт array_key_first([])? null.

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

Задание 1: Sodium

Напишите функцию encryptMessage(string $message, string $password): string с использованием Sodium для симметричного шифрования.

Задание 2: JSON обработка

Создайте класс JsonParser с методом parse(string $json): array, который использует JSON_THROW_ON_ERROR и оборачивает JsonException в доменное исключение.

Задание 3: Flexible Heredoc

Перепишите шаблон email-письма, используя Flexible Heredoc с правильными отступами.