Разработка сайта

Мы Makklays. Мы помогаем воплотить идею.

Что нового в PHP 8 ?


Makklays - Статьи image

eye 255

Все наши сайты в Makklays мы пишем на языке програмирования PHP. Нас интересует развитие PHP и связанных с PHP технологии. Как уже всем известно, в конце 2020 года выходит новая версия php 8 и в этой связи разберем, а что нового в ней будет? И так.

Начиная с версии 7.0, большая часть ядра была переписана, что привело к увеличению производительности в 2-3 раза. Каждый новый релиз оказывает положительное влияние на производительность, и это можно наблюдать на бенчмарках. Будет ли прирост скорости в PHP 8? В PHP 8, в список ускорялок добавится JIT-компилятор, который также позволит PHP войти в новые области помимо веб-разработки (разработки сайтов). Думаю ожидать прироста скорости как в PHP 7 не будет (возможно немного).

JIT - Just In Time-компилятор - в PHP 8 появится долгоожидаемый jit компилятор. Почему долго ожидаемый? Так как разработчики уже собирались включить его в раннии версии, но появится jit только в новом PHP8. JIT реализован как часть расширения Opcache.

И кстати, меня здесь недавно спрашивали моё мнение о PEAR библиотеке (молодые разработчики уже не знаёт насколько когда-то была востребована и популярна эта библиотека в php, а некоторые её элементы используют и по сей день). Так вот, считаю её устаревшей. Так как весомая часть функций из неё уже deprecated (не поддерживается новыми версиями php - их функции были исключены из новых версий php). Чему собственно и рад. Собственными глазами вижу развитие php от одной новой версии к другой новой версии php. Но здесь есть один большой минус - нужно постоянно учуться. Изучать новые функции и новые библиотеки, которые разработчики включают и добавляют в php. А их становится всё больше и больше. Почему, собственно, PEAR библиотека (аля не apple из едемского сада) и была исключена базовой поставки php в версии 7.4.

Union Types 2.0 (Типы объединения)



PHP начинает поддерживать двое типов данных переменной одновременно.
Type или null использует специальный синтаксис "?Type"
array или traversable используя специальный тип iterable
Но произвольные типы объединений в данный момент не поддерживаются языком php. В место этого используются аннотации phpdoc как в примере ниже:
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;
}
}

Типы объединения используются в данный момент во всех позициях, где типы в настоящее время воспринимаются (сиснтаксис 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;
}
}

Тип void который ничего не возвращает, не может быть частью типа объединения.
А nullable союзы могут быть написаны с использованием |null или с использованием ? записи
public function foo(Foo|null $foo): void;
public function bar(?Bar $bar): void;


Выражение совпадения v2



Очень упростился устаревший синтаксис switch выражения. Появился новый match. match не требует break операторов, может возвращать значение, комбинирует условия, использует строгие сравнения типов и не выполняет никаких типов принуждения.
Было так:
switch (1) {
case 0:
$result = 'Foo';
break;
case 1:
$result = 'Bar';
break;
case 2:
$result = 'Baz';
break;
}
echo $result;


А стало так:
echo match (1) {
0 => 'Foo',
1 => 'Bar',
2 => 'Baz',
};

Если есть несколько условий, они могут быть разделены запятыми для выполнения одного и того же блока кода.
$result = match($input) {
0 => "Какой-то Вывод",
'1', '2', '3' => "Вывод условий 1,2,3",
};


Продвижение свойств конструктора



Упрощение синтаксиса использования свойств в конструкторе. Ранее свойства в классе приходилось прописывать несколько раз. Рассмотрим на примере класса
class Point {
public float $x;
public float $y;
public float $z;

public function __construct(
float $x = 0.0,
float $y = 0.0,
float $z = 0.0,
) {
$this->x = $x;
$this->y = $y;
$this->z = $z;
}
}

Свойства повторялись 1) в объявлении свойства, 2) в параметрах конструктора и 3) два раза в назначении свойства. Кроме того, тип свойств тоже повторяется дважды.

В php8 вместо указания свойств класса и конструктора для них в php теперь можно объединить их в одно. В результате чего, код максимально сокращается:

class Point { 
публичная функция __construct (
публичный список $ x = 0.0 ,
публичный список $ y = 0.0 ,
публичный список $ z = 0.0 ,
) { }
}


Ещё пример, вместо
class Money 
{
public Currency $currency;

public int $amount;

public function __construct(
Currency $currency,
int $amount,
) {
$this->currency = $currency;
$this->amount = $amount;
}
}

Теперь может быть
class Money 
{
public function __construct(
public Currency $currency,
public int $amount,
) {}
}


Этот урезанный код равносилен предыдущему примеру, но более лаконичен. А выбор синтаксиса был взят из родственного языка Hack.

Новый тип возврата static



Возвращение уже было возможно self, но до PHP 8 он не был допустимым типом возврата static. Учитывая динамически типизированный характер PHP, эта функция будет полезна для многих разработчиков.
class Foo
{
public function method(): static
{
return new static();
}
}


Аттрибуты



Аттрибуты нам исвестны из других языков программирования, как аннотации или декораторы, предоставляют возможность добавления метеданных в классы. Широкое использование показывает, что это очень востребованная функция в PHP сообщества. Аттрибуты представляют собой спеально отформатированный текст, заключенный в кавычки '<<' и '>>' путем повторного использования существующих токенов T_SL и T_SR.

Вот список того, где могут применяться аттрибуты:
- функции (включая замыкания и короткие замыкания)
- классы (включая анонимные классы), интерфейсы, трейты
- константы класса
- свойства класса
- методы класса
- параметры и функции

Как это выглядит в коде:
use App\Attributes\ExampleAttribute;

<>
class Foo
{
<>
public const FOO = 'foo';

<>
public $x;

<>
public function foo(<> $bar) { }
}

<>
class ExampleAttribute
{
public $value;

public function __construct($value)
{
$this->value = $value;
}
}

Обращаю внимание, что вы можете сделать собственные Attribute, а о том как сделать и как работают аттрибуты вы можете узнать прочитав в этом посте.

Новый тип mixed v2



В PHP 7 были добавлены скалярные типы. В PHP 7.1 обнуляемые типы. А в PHP 8 - типы объединения. Теперь программисты могут явно указывать типы для переменных, параметров, классов, методов и функций, значений которые возвращают методы и функции, а также свойств класса. Но в PHP не всегда поддерживаются типы, это связано с тем, что PHP – интерпретируемый язык, то есть, PHP может позволять упускать информацию о типах. Это приводит к проблеме, что её значение неоднозначно, когда отсутствует информация о типе.
- функция не возвращает ничего или null
- мы ожидаем один из нескольких типов
- мы жидаем, тип, который не может быть подсказан в PHP
Из ходя из этих причин был придуман и добавлен тип mixed. Тип mixed - это один из следующих типов:
- array
- bool
- callable
- int
- float
- null
- object
- resource
- string
Обратите внимание, что mixed может использоваться как параметр или тип свойства, а не только как тип возвращаемого значения. И ещё обратите внимание, что, mixed уже включает в себя тип null, это не может сделать его обнуляемым. Следующее вызовет ошибку:
// Fatal error: Mixed types cannot be nullable, null is already part of the mixed type.
function bar(): ?mixed {}


Throw выражения



Опреатор throw в PHP не может делать исключения в местах, где разрешены только выражения, такие как функции стрелок, оператор объединения и тернарный оператор. PHP 8 преобразовывает утверждение throw в выражение, чтобы эти случаи стали возможными.

// This was previously not possible since arrow functions only accept a single expression while throw was a statement.
$callable = fn() => throw new Exception();

// $value is non-nullable.
$value = $nullableValue ?? throw new InvalidArgumentException();

// $value is truthy.
$value = $falsableValue ?: throw new InvalidArgumentException();

// $value is only set if the array is not empty.
$value = !empty($array)
? reset($array)
: throw new InvalidArgumentException();


Есть и другие места, где он может быть использован, которые являются более спорными. Эти случаи разрешены в PHP 8
$condition && throw new Exception();
$condition || throw new Exception();
$condition and throw new Exception();
$condition or throw new Exception();


Наследование приватных методов



Раньше PHP использовал одинаковые методы проверки наследования для публичных, защищенных и приватных методов. То есть к private методам приенялась проверка как и protected и public. А private методы не будут доступны дочерним классам.
В PHP 8 эта проверка больше не выполняется для приватных методов. Кроме того, использование final private function также не имеет смысла, так как степерь это вызовет ошибку:

Warning: Private methods cannot be final as they are never overridden by other classes


Weak maps - Слабые карты



WeakMap появился в PHP 7.4 в виде первой реализации, а в PHP 8 WeakMap расширяет свою реализацию тем, что слабые карты содержат ссылки на объекты, которые не препятствуют сборке мусора этими объектами.

Например ORM, они часто реализуют кэши, содержащие ссылки на классы сущностей для улучшения производительности отношений между сущностями. Эти объекты не могут быть "очищены от мусора", до тех пор, пока в кэше есть ссылка на них.

Большая плюшка слабых карт – возможность использования более ресурсо-дружественных способов работы с этими объектами. Вот как выглядят слабые карты:

class Foo 
{
private WeakMap $cache;

public function getSomethingWithCaching(object $obj): object
{
return $this->cache[$obj]
??= $this->computeSomethingExpensive($obj);
}
}


Использование ::class на объектах



Небольшая, но полезная новая функция. Теперь в PHP 8 можно использовать ::class на объектах, результат будет идентичен get_class():
// раньше
$bar = new Foo();
echo Foo::class;
// или
echo get_class($bar);
// 'Foo'

//теперь можно будет так
$foo = new Foo();
echo $foo::class;
//'Foo'


Неподхваченные уловы Exception



Раннее каждый раз, когда вы использовали исключение до версии PHP 8, вам нужно было указывать переменную не зависимо использовали вы её или нет. Теперь вы можете просто пропустить эту переменную.
Было так:
try {
// Что-то идет не так
} catch (MySpecialException $exception) {
Log::error("Что-то пошло не так");
}


А теперь стало так:
try {
// Что-то идет не так
} catch (MySpecialException) {
Log::error("Что-то пошло не так");
}


Обратите внимание, что обязательно нужно указать тип, вы не можете использовать пустой catch. Для того чтобы перехватить все исключения и ошибки, вы можете использовать их Throwable как тип перехвата.

Завершающая запятая в списках параметров



Теперь можно оставить запятую при перечислении через запятую параметров в конце и ошибки не будет)
Можно делать следующее:
public function(
string $parameterA,
int $parameterB,
Foo $objectfoo,
) {
// …
}


Сделать DateTime объекты из интерфейса

Можно сделать DateTime объект из DateTimeImmutable объекта, используя DateTime::createFromImmutable($immutableDateTime), но наоборот было сложно. Добавление DateTime::createFromInterface() и DatetimeImmutable::createFromInterface() теперь разрешает сконвертировать DateTime и DateTimeImmutable объекты друг в друга.

DateTime::createFromInterface(DateTimeInterface $other);
DateTimeImmutable::createFromInterface(DateTimeInterface $other);


Новый Stringable интерфейс



Новый интерфейс Stringable автоматически добавляется в классы. Он реализует магический метод _toString() и нет необходимости реализовывать его вручную.
Это преследует две цели:
- разрешить использовать, string|Stringable чтобы выразить string|object-with-__toString()
- предоставить прямой путь обновления с PHP 7 к PHP 8

class Foo
{
public function __toString(): string
{
return 'foo';
}
}

function bar(Stringable $stringable) { /* … */ }

bar(new Foo());
bar('abc');


Новая функция str_contains()



Данная функция пришла в помощь известной функции strpos(). str_contains() проверяет, содержится ли строка в другой строке, и возвращает логическое значение (true/false) независимо от того, была ли найдена строка.

Было так:
if (strpos('string with lots of words', 'words') !== false) { /* … */ }


Стало так:
if (str_contains('string with lots of words', 'words')) { /* … */ }


Новые функции str_starts_with() и str_ends_with()



str_starts_with() проверяет, начинается ли строка с другой строки, и возвращает логическое значение (true/false).

str_ends_with() логично проверяет, заканчивается ли строка другой строкой, и возвращает логическое значение (true/false).

str_starts_with('haystack', 'hay'); // true
str_ends_with('haystack', 'stack'); // true


Ранее эта функциональность достигалась использованием существующих функций: substr, strpos/strrpos, strncmp или substr_compare (в сочетании с strlen). Что интересно, функциональность str_starts_with и str_ends_with настолько необходима, что ее поддерживают многие основные PHP-фреймворки, включая Symfony, Laravel, Yii, FuelPHP и Phalcon.

Новая функция fdiv



Используюя функцию fdiv мы больше не получаем ошибок при делении на 0. А получаем INF, -INF или NAN, в зависимости от случая. Функция fdiv делает подобное типа функций fmod и intdiv.

Новая функцияp get_resource_id()



Ресурсы - это специальные переменные в PHP, ссылающиеся на внешние ресурсы. Например, соединение MySQL, или дескриптор файла.

Каждому из этих ресурсов присваивается идентификатор, хотя ранее единственным способом узнать, что это идентификатор, было преобразование ресурса в int:

$resourceId = (int) $resource;


PHP 8 добавляет функцию get_resource_id(), делая эту операцию более очевидной и безопасной для типов:

$resourceId = get_resource_id($resource);


Оператор @ больше не "останавливает" фатальные ошибки



Вполне возможно, что это изменение может выявить ошибки, которые снова были скрыты до PHP 8. Обязательно установите display_errors=Off на своих производственных серверах!

Уровень сообщений об ошибках по умолчанию



Теперь по умолчанию в error_reporting уровень ошибок будет установлен в E_ALL вместо текущего E_ALL & ~ E_NOTICE & ~ E_STRICT & ~ E_DEPRECATED. Это означает, что могут появиться много ошибок, которые ранее незаметно игнорировались, хотя, возможно, уже существовали до PHP 8.

Абстрактные методы улучшения трейтов



Трейты могут определять абстрактные методы, которые должны быть реализованы классами, использующими их. Однако есть предостережение: до PHP 8 сигнатура этих реализаций методов не проверялась. Следующее было действительным:

trait Test {
abstract public function test(int $input): int;
}

class UsesTrait
{
use Test;

public function test($input)
{
return $input;
}
}


PHP 8 будет выполнять правильную проверку подписи метода при использовании свйоства и реализации его абстрактных методов. Это означает, что вам нужно написать это:

class UsesTrait
{
use Test;

public function test(int $input): int
{
return $input;
}
}


Приоритет при конкатенации

Хотя это изменение уже устарело в PHP 7.4, теперь это изменение вступает в силу. Если бы вы написали что-то вроде этого:

echo "sum: " . $a + $b;


PHP ранее интерпретировал бы это так:
echo ("sum: " . $a) + $b;


PHP 8 сделает так, чтобы он интерпретировался так:

echo "sum: " . ($a + $b);


Более строгие проверки типов для арифметических и побитовых операторов

До PHP 8 можно было применять арифметические или побитовые операторы к массивам, ресурсам или объектам. Это больше не возможно и выдаст TypeError:

[] % [42];
$object + 4;