Помимо JIT, мы можем ожидать множество функций и улучшений в PHP 8. Следующий список – это отобранный нами выбор предстоящих дополнений и изменений, которые должны сделать PHP более надежным и эффективным.
Черты определяются как «механизм повторного использования кода в языках с одним наследованием, таких как PHP». Как правило, они используются для объявления методов, которые могут использоваться в нескольких классах.
Черта также может содержать абстрактные методы. Эти методы просто объявляют сигнатуру метода, но реализация метода должна быть сделана внутри класса с использованием признака.
Согласно руководству по PHP:
«Черты поддерживают использование абстрактных методов для наложения требований на выставочный класс».
Это также означает, что сигнатуры методов должны совпадать. Другими словами, тип и количество обязательных аргументов должны быть одинаковыми.
В любом случае, по словам Никиты Попова, автора RFC, проверка подписи в настоящее время проводится только местно:
- Она не применяется в наиболее распространенном случае, когда реализация метода обеспечивается классом using.
- Она применяется, если реализация происходит из родительского класса.
- Она применяется, если реализация происходит из дочернего класса.
Следующий пример от Никиты относится к первому случаю (не принудительная подпись):
trait T { abstract public function test(int $x); } class C { use T; // Разрешено, но не должно быть связано с недопустимым типом. public function test(string $x) {} }
С учетом вышесказанного, RFC предлагает всегда выдавать фатальную ошибку, если метод реализации не совместим с методом абстрактной черты, независимо от его происхождения:
Fatal error: Declaration of C::test(string $x) must be compatible with T::test(int $x) in /path/to/your/test.php on line 10
Этот RFC был единогласно одобрен.
В PHP ошибки наследования из-за несовместимых сигнатур методов выдают либо фатальную ошибку, либо предупреждение в зависимости от того, что является причиной ошибки.
Если класс реализует интерфейс, несовместимые сигнатуры методов приводят к фатальной ошибке. Согласно документации Object Interfaces:
«Класс, реализующий интерфейс, должен использовать сигнатуру метода, совместимую с LSP (принцип подстановки Лискова). Невыполнение этого требования приведет к фатальной ошибке ».
Вот пример ошибки наследования с интерфейсом:
interface I { public function method(array $a); } class C implements I { public function method(int $a) {} }
В PHP 7.4 приведенный выше код выдаст следующую ошибку:
Fatal error: Declaration of C::method(int $a) must be compatible with I::method(array $a) in /path/to/your/test.php on line 7
Функция в дочернем классе с несовместимой подписью выдаст предупреждение. Смотрите следующий код из RFC:
class C1 { public function method(array $a) {} } class C2 extends C1 { public function method(int $a) {} }
В PHP 7.4 приведенный выше код будет просто выдавать предупреждение:
Warning: Declaration of C2::method(int $a) should be compatible with C1::method(array $a) in /path/to/your/test.php on line 7
Теперь RFC предлагает всегда выдавать фатальную ошибку для несовместимых сигнатур методов. В PHP 8 код, который мы видели выше, будет подсказывать следующее:
Fatal error: Declaration of C2::method(int $a) must be compatible with C1::method(array $a) in /path/to/your/test.php on line 7
В PHP, если массив начинается с отрицательного индекса ( start_index < 0), следующие индексы будут начинаться с 0. Посмотрите на следующий пример:
$a = array_fill(-5, 4, true); var_dump($a);
В PHP 7.4 результат будет следующим:
array(4) { [-5]=> bool(true) [0]=> bool(true) [1]=> bool(true) [2]=> bool(true) }
Теперь RFC предлагает изменить вещи так, чтобы второй индекс был в start_index + 1 зависимости от значения start_index.
В PHP 8 приведенный выше код приведет к следующему массиву:
array(4) { [-5]=> bool(true) [-4]=> bool(true) [-3]=> bool(true) [-2]=> bool(true) }
В PHP 8 массивы, начинающиеся с отрицательного индекса, меняют свое поведение. Узнайте больше о обратной несовместимости в RFC.
Типы объединения принимают значения, которые могут быть разных типов. В настоящее время PHP не обеспечивает поддержку типов объединения, за исключением синтаксиса ?Type и специального типа iterable.
До PHP 8 типы объединения могли быть указаны только в аннотациях phpdoc, как показано в следующем примере из RFC:
class Number { /** * @var int|float $number */ private $number; /** * @param int|float $number */ public function setNumber($number) { $this->number = $number; } /** * @return int|float */ public function getNumber() { return $this->number; } }
Теперь RFC 2.0 для типов Union предлагает добавить поддержку типов объединения в сигнатурах функций, чтобы мы больше не полагались на встроенную документацию, а T1|T2|… вместо этого определяли бы типы объединения с помощью синтаксиса:
class Number { private int|float $number; public function setNumber(int|float $number): void { $this->number = $number; } public function getNumber(): int|float { return $this->number; } }
Как объяснил Никита Попов в RFC,
«Поддержка типов объединения в языке позволяет нам перемещать больше информации о типах из phpdoc в сигнатуры функций, что дает следующие преимущества:
- Типы на самом деле обязательны, поэтому ошибки могут быть выявлены на ранней стадии.
- Поскольку они применяются принудительно, информация о типах с меньшей вероятностью устареет или пропустит крайние случаи.
- Типы проверяются во время наследования, следуя принципу подстановки Лискова.
- Типы доступны через Reflection.
- Синтаксис гораздо менее шаблонный, чем phpdoc.
Типы объединения поддерживают все доступные типы с некоторыми ограничениями:
При передаче параметра недопустимого типа внутренние и пользовательские функции ведут себя по-разному.
Пользовательские функции выдают a TypeError, но внутренние функции ведут себя по-разному, в зависимости от нескольких условий. Во всяком случае, типичным поведением является предупреждение и возврат null. Смотрите следующий пример в PHP 7.4:
var_dump(strlen(new stdClass));
Это приведет к следующему предупреждению:
Warning: strlen() expects parameter 1 to be string, object given in /path/to/your/test.php on line 4 NULL
Если strict_types включен или информация аргумента указывает типы, поведение будет другим. В таких случаях ошибка типа обнаруживается и приводит к TypeError.
Такая ситуация может привести к ряду проблем, хорошо объясненных в разделе вопросов RFC.
Чтобы устранить эти несоответствия, этот RFC предлагает сделать API-интерфейсы для внутреннего анализа параметров всегда генерировать ThrowErrorв случае несоответствия типов параметров.
В PHP 8 приведенный выше код выдает следующую ошибку:
Fatal error: Uncaught TypeError: strlen(): Argument #1 ($str) must be of type string, object given in /path/to/your/test.php:4 Stack trace: #0 {main} thrown in /path/to/your/test.php on line 4
В PHP throw это утверждение, поэтому его нельзя использовать там, где разрешено только выражение.
Этот RFC предлагает преобразовать оператор throw в выражение, чтобы его можно было использовать в любом контексте, где выражения разрешены. Например, функции стрелок, нулевой оператор объединения, троичные операторы и операторы Элвиса и т. д.
Смотрите следующие примеры из RFC:
$callable = fn() => throw new Exception(); // $значение не может быть обнулено. $value = $nullableValue ?? throw new InvalidArgumentException(); // $ценность - это истина. $value = $falsableValue ?: throw new InvalidArgumentException();
Начало статьи: Что нового в PHP 8 (функции, улучшения и JIT-компилятор)
Продолжение: Что нового в PHP 8 (функции, улучшения и JIT-компилятор). Часть 3