PHP 8 уже на этапе release candidate, версия RC 3 вышла 29 октября, а полноценный релиз назначен на 26 ноября. Так что пора взглянуть на новые возможности, которые нас ждут в PHP 8. График релизов можно посмотреть здесь. А официальное руководство по обновлению на новую версию лежит тут.
Добавлена поддержка типов union (RFC)
Тип union принимает значения разных других типов, а не только какого-то одного.
<?php
declare(strict_types=1);
class Number {
private int|float $number;
public function setNumber(int|float $number): void {
$this->number = $number;
}
public function getNumber(): int|float {
return $this->number;
}
}
/**
* We can pass both floats or integer values
* to the number object. Try passing a string.
*/
$number = new Number();
$number->setNumber(5);
dump($number->getNumber());
$number->setNumber(11.54);
dump($number->getNumber());
exit;
Добавлены WeakMap (RFC)
Слабые карты (weak maps) позволяют создавать связи между объектами и произвольными значениями (как и SplObjectStorage
), при этом объекты, используемые в качестве ключей, не защищаются от сборщика мусора. Если сборщик уничтожает такой объект, тот просто удаляется из карты.
Это очень полезная фича. Она позволяет нам ещё меньше думать об утечках памяти в коде. Хотя для большинства PHP-разработчиков это не должно быть проблемой, но стоит обращать внимание при создании долгоиграющих процессов, например, используя ReactPHP. С WeakMaps ссылки на объекты автоматически собираются сборщиком мусора, когда объект становится недоступен.
Если вы сделаете то же самое с массивом, то ссылки на объект сохранятся, что приведёт к утечке памяти.
<?php
declare(strict_types=1);
class FooBar {
public WeakMap $cache;
public function __construct() {
$this->cache = new WeakMap();
}
public function getSomethingWithCaching(object $obj) {
return $this->cache[$obj] ??= $this->computeSomethingExpensive($obj);
}
public function computeSomethingExpensive(object $obj) {
dump("I got called");
return rand(1, 100);
}
}
$cacheObject = new stdClass;
$obj = new FooBar;
// "I got called" only will be printed once
$obj->getSomethingWithCaching($cacheObject);
$obj->getSomethingWithCaching($cacheObject);
dump(count($obj->cache));
// When unsetting our object, the WeakMap frees up memory
unset($cacheObject);
dump(count($obj->cache));
exit;
Новое исключение ValueError
В PHP 8 появился новый встроенный класс исключений
ValueError
. Он дополняет собой \Exception
. PHP бросает такое исключение каждый раз, когда вы передаёте функции значение правильного типа, однако его нельзя использовать в этой операции. Раньше в таких случаях выдавалось предупреждение. Примеры:<?php
declare(strict_types=1);
/**
* We pass an array to array_rand,
* which is of the correct type. But
* array_rand expects non-empty arrays.
*
* This throws a ValueError exception.
*/
array_rand([], 0);
/**
* The depth argument for json_decode is a
* valid integer, but it must be greater than 0
*/
json_decode('{}', true, -1);
При определении функций можно использовать вариативный аргумент
Вариативным аргументом теперь можно заменить любое количество параметров функции, если их типы совместимы. Например, следующий код некорректен:
<?php
declare(strict_types=1);
class A {
public function method(int $many, string $parameters, $here) {
}
}
class B extends A {
public function method(...$everything) {
dd($everything);
}
}
$b = new B();
$b->method('i can be overwritten!');
exit;
Возвращаемый тип static (RFC)
Возвращаемый тип static теперь можно использовать для определения ситуации, что метод возвращает класс, для которого этот метод и был вызван, даже если он был унаследован (позднее статическое связывание).
<?php
declare(strict_types=1);
class Test {
public function doWhatever(): static {
// Do whatever.
return $this;
}
}
exit;
Литерал имени класса для объекта (RFC)
Теперь можно извлекать имя класса объекта с помощью
$object::class
. Результат будет такой же, как в случае с get_class($object)
.<?php
declare(strict_types=1);
auth()->loginUsingId(1);
dump(auth()->user()::class);
// Or with a temporary variable
$user = auth()->user();
dump($user::class);
exit;
Настройки синтаксиса переменных (RFC)
New
и instanceof
теперь можно использовать с произвольными выражениями: new (выражение)(...$args)
и $obj instanceof (выражение)
.<?php
declare(strict_types=1);
class Foo {}
class Bar {}
$class = new (collect(['Foo', 'Bar'])->random());
dd($class);
exit;
Интерфейс Stringable (RFC)
В PHP 8 появился новый интерфейс
Stringable
, который добавляется автоматически, как только класс реализует метод __toString
. Вам не нужно явно реализовывать этот интерфейс.<?php
declare(strict_types=1);
class Foo {
public function __toString() {
return 'I am a class';
}
}
$obj = new Foo;
dump($obj instanceof Stringable);
exit;
Теперь трейты могут определять абстрактные приватные методы (RFC)
<?php
declare(strict_types=1);
trait MyTrait {
abstract private function neededByTheTrait(): string;
public function doSomething() {
return strlen($this->neededByTheTrait());
}
}
class TraitUser {
use MyTrait;
// This is allowed:
private function neededByTheTrait(): string { }
// This is forbidden (incorrect return type)
// private function neededByTheTrait(): stdClass { }
// This is forbidden (non-static changed to static)
// private static function neededByTheTrait(): string { }
}
exit;
throw теперь можно использовать как выражение (RFC)
Выражение
throw
теперь можно использовать там, где допускаются только выражения: в стрелочных функциях, coalesce-операторах тернарных условных операторах (ternary/elvis).<?php
declare(strict_types=1);
$callable = fn() => throw new Exception();
$nullableValue = null;
// $value is non-nullable.
$value = $nullableValue ?? throw new \InvalidArgumentException();
exit;
В параметрах списка теперь допускается опциональная висящая запятая (RFC)
По аналогии с висящей запятой в массивах, теперь можно определять её и в параметрах списка.
<?php
declare(strict_types=1);
function method_with_many_arguments(
$a,
$b,
$c,
$d,
) {
dump("this is valid syntax");
}
method_with_many_arguments(
1,
2,
3,
4,
);
exit;
Ловля исключений без сохранения в переменной (RFC)
Теперь можно писать
catch (исключение)
для ловли исключений без их сохранения в переменной.<?php
declare(strict_types=1);
$nullableValue = null;
try {
$value = $nullableValue ?? throw new \InvalidArgumentException();
} catch (\InvalidArgumentException) {
dump("Something went wrong");
}
exit;
Добавлена поддержка типа mixed (RFC)
В PHP 8 появился новый тип mixed. Он может быть эквивалентен типам array, bool, callable, int, float, null, object, resource, string.
<?php
declare(strict_types=1);
function debug_function(mixed ...$data) {
dump($data);
}
debug_function(1, 'string', []);
exit;
Добавлена поддержка атрибутов
Есть несколько предложений по внедрению атрибутов в PHP 8:
- https://wiki.php.net/rfc/attributes_v2
- https://wiki.php.net/rfc/attribute_amendments
- https://wiki.php.net/rfc/shorter_attribute_syntax
- https://wiki.php.net/rfc/shorter_attribute_syntax_change
Это одно из крупнейших изменений в PHP 8. Возможно, сначала с ними не так легко будет разобраться. Если кратко, то атрибуты позволяют добавлять метаданные в PHP-функции, параметры, классы и т.д. Эти метаданные можно потом программно извлекать. Если в PHP 7 или ниже вам потребуется парсить doclock`и, атрибуты помогут обратиться к этой информации, глубоко интегрированной в сам PHP.
Чтобы было понятнее, представим, что вашим пользователям нужно дать возможность добавлять промежуточное ПО в контроллер класса или метода с помощью использования атрибута.
<?php
declare(strict_types=1);
// First, we need to define the attribute. An Attribute itself is just a plain PHP class, that is annotated as an Attribute itself.
#[Attribute]
class ApplyMiddleware
{
public array $middleware = [];
public function __construct(...$middleware) {
$this->middleware = $middleware;
}
}
// This adds the attribute to the MyController class, with the "auth" middleware as an argument.
#[ApplyMiddleware('auth')]
class MyController
{
public function index() {}
}
// We can then retrieve all ApplyMiddleware attributes on our class using reflection
// And read the given middleware arguments.
$reflectionClass = new ReflectionClass(MyController::class);
$attributes = $reflectionClass->getAttributes(ApplyMiddleware::class);
foreach ($attributes as $attribute) {
$middlewareAttribute = $attribute->newInstance();
dump($middlewareAttribute->middleware);
}
exit;
Добавлена поддержка продвижения свойств конструктора (RFC)
Предложено добавить простой синтаксис, позволяющий комбинировать конструктор с определением свойств:
<?php
declare(strict_types=1);
class User {
public function __construct(
public int $id,
public string $name,
) {}
}
$user = new User(1, 'Marcel');
dump($user->id);
dump($user->name);
exit;
Добавлена поддержка выражения match (RFC)
Предложено добавить новое выражение
match
, которое аналогично switch
, только с более безопасной семантикой и возможностью возвращать значения.<?php
declare(strict_types=1);
echo match (1) {
0 => 'Foo',
1 => 'Bar',
2 => 'Baz',
};
exit;
Добавлена поддержка оператора nullsafe (?->) (RFC)
Когда результат левой части оператора равен null, исполнение всей цепочки останавливается, а её результат приравнивается к null. В противном случае цепочка ведёт себя как обычный оператор ->
.
<?php
declare(strict_types=1);
class User {
public function getAddress() {}
}
$user = new User();
$country = $user?->getAddress()?->country?->iso_code;
dump($country);
exit;
Добавлена поддержка именованных аргументов (RFC)
Именование позволяет передавать аргументы функции в зависимости от имени параметра, а не от его позиции. То есть значения аргументов становятся самодокументирующимися, а аргументы перестают зависеть от порядка перечисления, поэтому можно произвольно пропускать значения по умолчанию.
<?php
declare(strict_types=1);
array_fill(start_index: 0, num: 100, value: 50);
exit;
Freax
Времена динозавров:
Далее:
Далее:
Наши дни:
E_STRICT
var $user;
по прежнему работает. Даже в PHP 8.edogs
Вот это немного напоминает нам
As part of the negotiations, the British Government conceded that English spelling had some room for improvement and has accepted a 5- year phase-in plan that would become known as «Euro-English».
In the first year, «s» will replace the soft «c». Sertainly, this will make the sivil servants jump with joy. The hard «c» will be dropped in favour of «k». This should klear up konfusion, and keyboards kan have one less letter. There will be growing publik enthusiasm in the sekond year when the troublesome «ph» will be replaced with «f». This will make words like fotograf 20% shorter.
In the 3rd year, publik akseptanse of the new spelling kan be expekted to reach the stage where more komplikated changes are possible. Governments will enkourage the removal of double letters which have always ben a deterent to akurate speling. Also, al wil agre that the horibl mes of the silent «e» in the languag is disgrasful and it should go away.
By the 4th yer people wil be reseptiv to steps such as replasing «th» with «z» and «w» with «v».
During ze fifz yer, ze unesesary «o» kan be dropd from vords kontaining «ou» and after ziz fifz yer, ve vil hav a reil sensibl riten styl. Zer vil be no mor trubl or difikultis and evrivun vil find it ezi tu understand ech oza. Ze drem of a united urop vil finali kum tru.
Und efter ze fifz yer, ve vil al be speking German like zey vunted in ze forst plas.
If zis mad you smil, pleas pas on to oza pepl.
Rukis
Наши дни:
E_STRICT
Видео с примерами.
beyondco.de/course/whats-new-in-php-8
rsashka
По моему мнению, в попытке реализовать различные улучшатели языка, в 8 версии они перегнули палку и очень сильно усложнили PHP, почти до полного нежелания его использовать.
arrakisfremen
Просто взяли (очень) немного фич из нормальных языков.
leon0399
Надеюсь в php-cs уже подготовили правила для disable unions. Если необходим общий функционал — надо выносить в интерфейсы/трейты. На данный момент выглядит как шаг назад в плане типизации. Если у кто-то может рассказать, в каких случаях union будут лучше интерфейсов/трейтов — буду рад услышать в комментариях
Imenem
Я думаю, что логика добаления unions такая-же, как изначально в TypeScript — возможность типизировать существующий код, например библиотеки, не ломая обратную совместимость.
E_STRICT
Lopar
Объясните непрограммисту, почему вариант
if (isPositive($value))
это более предпочтительная конструкция чемif ($value > 0)
?E_STRICT
Какой именно вариант использовать зависит от задачи.
В общем случае функции полезны тем, что позволяют переиспользовать код и делать его более читабельным.
isPositive
это просто абстрактный пример. Причём довольно простой. Поэтому вместо него вполне можно использовать «инлайновую» проверкуif ($value > 0)
. Хотя иногда даже простой код приходиться оборачивать в функцию чтобы реализовать интерфейс или передать в качестве функции обратного вызова в другой сервис.tendium
Представьте, что вы пишете валидатор. Именно он должен реализовывать весь функционал, а уж насколько конкретный функционал банален — дело вторичное. Важно, что у вас происходит выделение логики валидации в отдельную абстракцию.
Fortistello
Если вы «играете в типизацию», то необходимость реализовывать такой код говорит о неверной архитектуре проекта. Если вам нужно проверить, положительный ли баланс на карте клиента, то очевидно это будет `isPositive(float $balance)`. И напротив, если вам нужно проверить, совершеннолетний ли человек, то это будет `isLegalAge(int $age)`. А все остальное — плохая архитектура. ИМХО.
tendium
А если у вас универсальный валидатор, то, видимо, надо будет как в go писать вот так:
isPositiveInt(int $value)
isPositiveFloat(float $value)
Да?
На самом деле то, о чем говорите вы, должно строиться как бизнес-логика над имеющейся абстрактной моделью. Т.е. isLegalAge(int $age) внутри должно вызывать isPositiveInt(int $value). Но на самом деле не только, так как legal age для сигарет, вступления в брак и покупки алкоголя может отличаться. Так что первым делом вы проверите общую валидность, а вторым специфическую для конкретных условий.
Fortistello
В go не силен, писал пару мелких сервисов для себя, но глубоко не погружался, не могу ответить.
Насчет того, что isLegalAge должен внутри вызывать isPositiveInt — это выглядит как ненужное усложнение, как по мне — это самостоятельная реализация. А если нужны именно проверки на позитивность, то пожалуй да, оно так и должно выглядеть.
Если мы говорим об абстрактных валидаторах, которые не знают, что в них приходит (например, а-ля валидация ларавеля), то mixed и объединения типов кажутся хорошим вариантом. Но если говорить о бизнес-логике, имхо она должна быть конкретной, и в этом случае mixed уже выглядит как костыль для ослабления для каких-то кейсов, сходу так и не придумаешь даже.
tendium
Ну так о чем и речь, что вы смешали в кучу бизнес-логику и абстракцию. Конечно, в большинстве случаев для бизнес-логики юнионы являются скорее ошибкой проектирования. Хотя, наверняка, и там можно найти пример, где это не так.
E_STRICT
А если мне просто нужно узнать что число положительное? Например, для какого нибудь универсального валидатора или библиотеки ассёртов. Использование union типов позволяет обойтись без mixed типа. Пример.
Fortistello
В соседнем комментарии как раз ответил. Возможно, мне стоило уточнить, что мой комментарий относился к бизнес-слою приложения, а не к валидации реквестов, например. Пожалуй в валидаторах или ассертах действительно это имеет право на жизнь.
tendium
Практически любым инструментом можно воспользоваться неправильно. Те же трейты могут сильно захламить код, если их использовать не в дополнение, а вместо dependency injection.
SerafimArts
Не, ну материал, естественно, полезный. Но не тогда, когда уже было 1000 раз уже...
Каждая новая неделя и новый пост "Что нового в PHP 8". Действительно, ведь никто ещё не видел подобных постов на хабре, новинка (сарказм)!
SerafimArts
Судя по минусу — я не прав и вы действительно не видели подобных публикаций раннее.
Да, соглашусь, возможно я погорячился, написав "каждую неделю" и у меня уже глаз замылился. В любом случае, для тех кто не видел — можете ознакомиться с материалами за последние 3-4 месяца:
AikoKirino
Много чего позаимствовано из C#, что не может не радовать.
E_STRICT
Висячие запятые при вызове методов и функция были добавлены ещё в PHP 7.3 (RFC)
miraage
В 7.3 включили только для вызова функции. В 8.0 добавляют для описании функции.
RC_Cat
Вопрос, эта висячая запятая только для того чтоб diff был поменьше?
E_STRICT
Для однообразия. В массивах уже давно так можно. Редактировать удобней. Можно добавлять/удалять аргументы не отвлекаясь на эту запятую.
tendium
И таки для diff тоже.
snegprog
Читал как то статью на хабре, где автору не нравится во что превращается PHP.
А я скажу, что прям в точку все попадает, и «Возвращаемый тип static » и "$object::class" и "?->", да и много других вещей, так не хватало в PHP, не раз сталкивался с такой необходимостью, теперь код можно будет писать более чисто, без личшних замудренностей!
rjhdby
Интересно, а так прокатит?
Надеюсь, что нет.
YmNIK_13
Можно будет как в js, через деструктуризацию, тут подробней
rjhdby
Это несколько про другое всё же.
tendium
Не должно, так как именованный параметр не являет собой string. Оно бы так, как у вас, потенциально сработало, если бы в стандарте было
calculateDistance('x1': 1, 'x2': 2 .....)
. Уже, кажется, где-то обсуждалось.Full-R
Чет не много полезного. Для себя нашел только mixed, match и именованные аргументы. Но JIT наверное порадует.
uaoleg
Вот это просто огонь. Очень актуально, например, при работае с DOM-парсерами
Giperoglif
пока не могу придумать для чего нужны юнионы и абстрактные приватные методы в трэйтах?
Divvers
Всё это вкусовщина, лишь бы на собеседованиях не спрашивали
BATPYIIIKOB
РНР комбайн становится всё больше и больше… Может стоит всё таки заняться его аппроксимизацией? Придумывать ради того чтобы придумывать.
tendium
Если не секрет, что означает сей неологизм в контексте вашей реплики: "аппроксимизация"?
BATPYIIIKOB
Извиняюсь, имел ввиду аппроксимация
bardex
но слово получилось забавное, подходит ко многим случаям, например, «аппроксимизация медицины»
bardex
А можете объяснить, чем атрибуты принципиально лучше phpDoc-ов, если всё равно используется рефлексия?
tendium
P.S. А что еще как не рефлексию здесь надо было использовать? Ведь рефлексия — это не что иное, как получение метаданных, связанных с объектом. Как иначе сделать? Вы, конечно, можете это скрыть за какой-нибудь языковой конструкцией, но внутри все равно будет механизм рефлексии.
bardex
т.е. получение через рефлексию атрибутов будет быстрее работать, чем получение phpdoc через ту же рефлексию?
tendium
У вас вопросы только по части синтаксиса что ли? Если так, то phpdoc — это все-таки комментарии. Этот подход с самого начала критиковали за использование комментариев не по назначению. Поэтому логично, что на смену phpdoc придет языковая конструкция.
P.S. Предвидя возражение: технически, да
#
— тоже комментарий, но по факту использование этого символа для комментирования не одобрялось сообществом с давних времен. Так что его переосмысление будет только на пользу.bardex
спасибо
rjhdby
В целом да. Но и там и там настолько незначительные времена, что смысла сравнивать их по скорости нет.
NTHub
Кому интересно грубое сравнение производительности PHP7.4 и PHP8 bolknote.ru/all/php-74-vs-php-80a
PHP8 быстрее на 10% — 30%
rjhdby
О да! Прям стандартная для PHP задача.