company_banner

PHP 8 — пробуем новые возможности

Автор оригинала: Marcel Pociot
  • Перевод

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:


Это одно из крупнейших изменений в 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;
Mail.ru Group
Строим Интернет

Похожие публикации

Комментарии 50

    +7
    Времена динозавров:

    var $user;


    Далее:

    public $user;


    Далее:

    public int $user;


    Наши дни:

    public array|bool|callable|int|float|null|object|resource|string $user;
      +4
      var $user; по прежнему работает. Даже в PHP 8.
        –1
        Вот это немного напоминает нам
        Улучшение английского
        The European Commission has just announced an agreement whereby English will be the official language of the European Union rather than German, which was the other possibility.

        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.

        Из специфичного в своей простоте php, делают нечто вроде java. Типизации, либы, импорты, наследования, классы, компиляторы и т.д.
        И почти как говорилось в этом мемасике "давайте примем как стандарт пхп, улучшим его и через пять лет мы будем все писать на яве как изначально и предполагалось"
          +4
          Наши дни:
          public mixed $user;
          0
          Видео с примерами.
          beyondco.de/course/whats-new-in-php-8
            –7
            По моему мнению, в попытке реализовать различные улучшатели языка, в 8 версии они перегнули палку и очень сильно усложнили PHP, почти до полного нежелания его использовать.
              –1
              Просто взяли (очень) немного фич из нормальных языков.
              +1

              Надеюсь в php-cs уже подготовили правила для disable unions. Если необходим общий функционал — надо выносить в интерфейсы/трейты. На данный момент выглядит как шаг назад в плане типизации. Если у кто-то может рассказать, в каких случаях union будут лучше интерфейсов/трейтов — буду рад услышать в комментариях

                0
                Я думаю, что логика добаления unions такая-же, как изначально в TypeScript — возможность типизировать существующий код, например библиотеки, не ломая обратную совместимость.
                  +9
                  Если необходим общий функционал — надо выносить в интерфейсы/трейты.
                  Непонятно, причём тут трейты/интерфейсы. Как с помощью них реализовать, допустим, вот такую функцию?
                  function isPositive(int|float $value): bool {
                      return $value > 0;
                  }
                    –1

                    Объясните непрограммисту, почему вариант if (isPositive($value)) это более предпочтительная конструкция чем if ($value > 0)?

                      +1
                      Какой именно вариант использовать зависит от задачи.
                      В общем случае функции полезны тем, что позволяют переиспользовать код и делать его более читабельным.

                      isPositive это просто абстрактный пример. Причём довольно простой. Поэтому вместо него вполне можно использовать «инлайновую» проверку if ($value > 0). Хотя иногда даже простой код приходиться оборачивать в функцию чтобы реализовать интерфейс или передать в качестве функции обратного вызова в другой сервис.

                        +1

                        Представьте, что вы пишете валидатор. Именно он должен реализовывать весь функционал, а уж насколько конкретный функционал банален — дело вторичное. Важно, что у вас происходит выделение логики валидации в отдельную абстракцию.

                          0

                          Если однострочная функция возвращает bool, это не значит, что она будет использована в if. Она может быть предназначена для использования в качестве колбэка в функции сортировки или фильтрации, в т.ч. находясь при этом в другом классе или неймспейсе.

                          0
                          Если вы «играете в типизацию», то необходимость реализовывать такой код говорит о неверной архитектуре проекта. Если вам нужно проверить, положительный ли баланс на карте клиента, то очевидно это будет `isPositive(float $balance)`. И напротив, если вам нужно проверить, совершеннолетний ли человек, то это будет `isLegalAge(int $age)`. А все остальное — плохая архитектура. ИМХО.
                            0

                            А если у вас универсальный валидатор, то, видимо, надо будет как в go писать вот так:


                            isPositiveInt(int $value)
                            isPositiveFloat(float $value)


                            Да?


                            На самом деле то, о чем говорите вы, должно строиться как бизнес-логика над имеющейся абстрактной моделью. Т.е. isLegalAge(int $age) внутри должно вызывать isPositiveInt(int $value). Но на самом деле не только, так как legal age для сигарет, вступления в брак и покупки алкоголя может отличаться. Так что первым делом вы проверите общую валидность, а вторым специфическую для конкретных условий.

                              0
                              В go не силен, писал пару мелких сервисов для себя, но глубоко не погружался, не могу ответить.
                              Насчет того, что isLegalAge должен внутри вызывать isPositiveInt — это выглядит как ненужное усложнение, как по мне — это самостоятельная реализация. А если нужны именно проверки на позитивность, то пожалуй да, оно так и должно выглядеть.

                              Если мы говорим об абстрактных валидаторах, которые не знают, что в них приходит (например, а-ля валидация ларавеля), то mixed и объединения типов кажутся хорошим вариантом. Но если говорить о бизнес-логике, имхо она должна быть конкретной, и в этом случае mixed уже выглядит как костыль для ослабления для каких-то кейсов, сходу так и не придумаешь даже.
                                0
                                Если мы говорим об абстрактных валидаторах, которые не знают, что в них приходит (например, а-ля валидация ларавеля), то mixed и объединения типов кажутся хорошим вариантом. Но если говорить о бизнес-логике, имхо она должна быть конкретной

                                Ну так о чем и речь, что вы смешали в кучу бизнес-логику и абстракцию. Конечно, в большинстве случаев для бизнес-логики юнионы являются скорее ошибкой проектирования. Хотя, наверняка, и там можно найти пример, где это не так.

                              0
                              А если мне просто нужно узнать что число положительное? Например, для какого нибудь универсального валидатора или библиотеки ассёртов. Использование union типов позволяет обойтись без mixed типа. Пример.

                                0
                                В соседнем комментарии как раз ответил. Возможно, мне стоило уточнить, что мой комментарий относился к бизнес-слою приложения, а не к валидации реквестов, например. Пожалуй в валидаторах или ассертах действительно это имеет право на жизнь.
                            0

                            Практически любым инструментом можно воспользоваться неправильно. Те же трейты могут сильно захламить код, если их использовать не в дополнение, а вместо dependency injection.

                            +2

                            Не, ну материал, естественно, полезный. Но не тогда, когда уже было 1000 раз уже...


                            Каждая новая неделя и новый пост "Что нового в PHP 8". Действительно, ведь никто ещё не видел подобных постов на хабре, новинка (сарказм)!

                            –2

                            Много чего позаимствовано из C#, что не может не радовать.

                              +3
                              В параметрах списка теперь допускается опциональная висящая запятая
                              parameter list переводится как список параметров.

                              method_with_many_arguments(
                                  1,
                                  2,
                                  3,
                                  4,
                              );
                              Висячие запятые при вызове методов и функция были добавлены ещё в PHP 7.3 (RFC)
                                +2
                                В 7.3 включили только для вызова функции. В 8.0 добавляют для описании функции.
                                  0
                                  Вопрос, эта висячая запятая только для того чтоб diff был поменьше?
                                    0
                                    Для однообразия. В массивах уже давно так можно. Редактировать удобней. Можно добавлять/удалять аргументы не отвлекаясь на эту запятую.
                                      0

                                      И таки для diff тоже.

                                  +3
                                  Читал как то статью на хабре, где автору не нравится во что превращается PHP.

                                  А я скажу, что прям в точку все попадает, и «Возвращаемый тип static » и "$object::class" и "?->", да и много других вещей, так не хватало в PHP, не раз сталкивался с такой необходимостью, теперь код можно будет писать более чисто, без личшних замудренностей!
                                    0

                                    Интересно, а так прокатит?


                                    function calculateDistance(int x1, int y1, int x2, int y2){...}
                                    
                                    $c = [['x1', 1], ['x2', 2], ['y1', 3], ['y4', 4]];
                                    echo calculateDistance($c[0][0] : $c[0][1], $c[1][0] : $c[1][1] .....);

                                    Надеюсь, что нет.

                                      0
                                      Можно будет как в js, через деструктуризацию, тут подробней

                                      function calculateDistance(int x1, int y1, int x2, int y2){...}
                                      
                                      $c = [ 'x1' => 1, 'x2' => 2, 'y1' => 3, 'y2' => 4];
                                      echo calculateDistance(...$c);
                                      
                                        0

                                        Это несколько про другое всё же.

                                        0

                                        Не должно, так как именованный параметр не являет собой string. Оно бы так, как у вас, потенциально сработало, если бы в стандарте было calculateDistance('x1': 1, 'x2': 2 .....). Уже, кажется, где-то обсуждалось.

                                        0
                                        Чет не много полезного. Для себя нашел только mixed, match и именованные аргументы. Но JIT наверное порадует.
                                          0
                                          Добавлена поддержка оператора nullsafe (?->)

                                          Вот это просто огонь. Очень актуально, например, при работае с DOM-парсерами
                                            –1
                                            пока не могу придумать для чего нужны юнионы и абстрактные приватные методы в трэйтах?
                                              +1
                                              Всё это вкусовщина, лишь бы на собеседованиях не спрашивали
                                                0
                                                РНР комбайн становится всё больше и больше… Может стоит всё таки заняться его аппроксимизацией? Придумывать ради того чтобы придумывать.
                                                  0

                                                  Если не секрет, что означает сей неологизм в контексте вашей реплики: "аппроксимизация"?

                                                    0
                                                    Извиняюсь, имел ввиду аппроксимация
                                                      0
                                                      но слово получилось забавное, подходит ко многим случаям, например, «аппроксимизация медицины»
                                                  0
                                                  А можете объяснить, чем атрибуты принципиально лучше phpDoc-ов, если всё равно используется рефлексия?
                                                    0
                                                    1. Тем, что это стандартизованная часть языка.
                                                    2. Тем, что это эффективнее, так как парсер за один (с оговорками) проход собирает всю эту информацию, и нет необходимости в файловом кэше и громоздких парсерах уровнем ниже.

                                                    P.S. А что еще как не рефлексию здесь надо было использовать? Ведь рефлексия — это не что иное, как получение метаданных, связанных с объектом. Как иначе сделать? Вы, конечно, можете это скрыть за какой-нибудь языковой конструкцией, но внутри все равно будет механизм рефлексии.

                                                      0
                                                      т.е. получение через рефлексию атрибутов будет быстрее работать, чем получение phpdoc через ту же рефлексию?
                                                        0

                                                        У вас вопросы только по части синтаксиса что ли? Если так, то phpdoc — это все-таки комментарии. Этот подход с самого начала критиковали за использование комментариев не по назначению. Поэтому логично, что на смену phpdoc придет языковая конструкция.


                                                        P.S. Предвидя возражение: технически, да # — тоже комментарий, но по факту использование этого символа для комментирования не одобрялось сообществом с давних времен. Так что его переосмысление будет только на пользу.

                                                          0
                                                          спасибо
                                                          0

                                                          В целом да. Но и там и там настолько незначительные времена, что смысла сравнивать их по скорости нет.

                                                      +1
                                                      Кому интересно грубое сравнение производительности PHP7.4 и PHP8 bolknote.ru/all/php-74-vs-php-80a
                                                      PHP8 быстрее на 10% — 30%
                                                        0
                                                        оптимизирующий транслятор «Брейнфака», с загруженной в него задачей вычисления числа Пи.

                                                        О да! Прям стандартная для PHP задача.

                                                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                      Самое читаемое