Если вы не читали мою предыдущую статью о нововведениях в php 8.2, вы можете найти ее здесь.

Команда PHP не перестает добавлять новые фичи и исправления с каждым релизом. Но одними фичами и исправлениями дело не обходится. Иногда команде приходится принимать трудное решение отказаться от чего-либо, чтобы стандартизировать язык. Мы называем это депрекацией (deprecation), а про таковые фичи иногда говорим, что они устарели. И PHP 8.2 не станет исключением. В нем есть несколько важных депрекаций.

Динамические свойства

В классах PHP можно динамически присваивать значения свойствам, которые вы не указывали во время объявления класса, тем самым добавляя их после создания класса.

class FooBar {
    public string $bar;
}

$fooBar = new FooBar();
$fooBar->foo = 'Foo';

Вы можете предотвратить такое поведение только с помощью __set() и __get().

В PHP 8.2 и более поздних версиях присвоение значений необъявленному свойству класса считается устаревшим и выдает соответствующее уведомление о депрекации при первой установке свойства в рамках работы приложения.

class FooBar {
    public string $bar;
}
$fooBar = new FooBar();
$fooBar->foo = 'Foo';

Этот код генерирует уведомление о депрекации, как показано ниже:

Deprecated: Creation of dynamic property FooBar::$foo is deprecated in ... on line ...

В PHP 9 это уже вызовет критическую ошибку.

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

Проблема заключается в том, что многие библиотеки и фреймворки полагаются на динамические свойства. Возьмем, к примеру, Laravel Eloquent ORM, который очень сильно зависит от динамических свойств для своих свойств и взаимосвязей. Таким образом, из-за этой депрекации в фреймворке Laravel придется многое переписывать.

В рамках этой депрекации есть три исключения:

1. Классы с атрибутом #[AllowDynamicProperties].

С помощью этого нового атрибута, представленного в PHP 8.2, вы можете запретить PHP генерировать уведомление о депрекации. Это поведение унаследуют также и дочерние классы.

2. stdClass и его подклассы

В stdClass атрибут #[AllowDynamicProperties] определен по умолчанию, поэтому stdClass и любой из его наследников будут разрешать динамические свойства без каких-либо дополнительных действий с вашей стороны.

3. Классы с магическими методами __get и __set

Если в классе определен магический метод __set(), эта депрекация на него не повлияет. Вы также можете добавить метод __get() для создания практически полезного класса.

class FooBar {
    public function __set(string $name, mixed $value): void {}
}
$fooBar = new FooBar();
$fooBar->foo = 'Foo';

Но установка динамических свойств в __set() прежнему не разрешена.

class FooBar {
    public function __set(string $name, mixed $value): void {
        $this->{$name} = $value;
    }
}
$fooBar = new FooBar();
$fooBar->foo = 'foo';

То, что мы делаем выше, не разрешено, потому что мы устанавливаем динамические свойства в __set(). В результате будет сгенерировано то же уведомление о депрекации:

Deprecated: Creation of dynamic property FooBar::$foo is deprecated in ... on line ...

Частично-поддерживаемые callable 

PHP 8.2 объявляет устаревшими некоторые виды callable, которые не работают в виде  $callable().

Незатронутые виды callable

$callable = 'strlen';
$callable = ['MyClass', 'myMethod'];
$callable = 'MyClass::myMethod'];
$callable = Closure::fromCallable();
$callable = [$this, 'myMethod'];
$callable = [new MyClass(), 'myMethod'];
$callable = strlen(...);

Эти виды callable будут работать без каких-либо уведомлений о депрекации.

Устаревшие виды callable

$callable = "self::method";
$callable = "parent::method";
$callable = "static::method";
$callable = ["self", "method"];
$callable = ["parent", "method"];
$callable = ["static", "method"];
$callable = ["MyClass", "MyParentClass::myMethod"];
$callable = [new MyClass(), "MyOtherClass::myMethod"];

Если вы попытаетесь использовать callable вида, подобного приведенным выше, то увидите уведомление о депрекации.

Mbstring: кодировки Base64, Uuencode, QPrint и HTML Entity

Расширение PHP Multi-Byte Strings (mbstring) добавляет функционал для работы со строками PHP, содержащими многобайтовые символы, такие как символы азиатских письменностей, эмодзи и тысячи других символов, которые не могут поместиться в один байт.

В PHP 8.2 использование расширения Mbstring для кодирования/декодирования строк в Base64, Quoted-Printable, Uuencode и HTML Entities подверглось депрекации.

Это касается следующих кодировок. Сокращенные названия кодировок нечувствительны к регистру.

BASE64
UUENCODE
HTML-ENTITIES
html (alias of HTML-ENTITIES)
Quoted-Printable
qprint (alias of Quoted-Printable)

Причина этого в том, что ядро ​​PHP уже предоставляет альтернативу для этих функций. Например:

mb_convert_encoding('test', 'base64'));

Вы можете сделать тоже самое, используя base64_encode('test').

Здесь вы можете узнать об этом больше

Интерполяция строк вида ${var}

В PHP можно заменить переменную в строковом литерале с помощью двойных кавычек и heredoc-синтаксиса.

$name = 'PHP';
echo "Hello $name"; // Hello PHP

Для лучшей читабельности вы также можете выделить переменную фигурными скобками ({}). PHP также поддерживает знак доллара за пределами фигурных скобок.

echo "Hello ${name}";

Но в php 8.2 это наконец устарело.

Deprecated: Using ${var} in strings is deprecated, use {$var} instead in ... on line ...

Недавно прошел открытый мастер-класс курса "PHP Developer. Professional" на тему «Разработка одностраничного приложения на PHP с помощью Symfony и Vue.js». Приглашаем всех желающих посмотреть его в записи.

Комментарии (43)


  1. Rsa97
    21.07.2022 16:47

    Запрет динамических свойств легко обходится.

    class FooBar {
        private array $properties = [];
        public function __set(string $name, mixed $value): void
        {
            $this->properties[$name] = $value;
        }
        public function __get(string $name): mixed
        {
            return $this->properties[$name] ?? null;
        }
    }
    


    1. Djeux
      21.07.2022 16:59

      В статье об этом и упоминается. __get() / __set() никто не отменял


      1. Rsa97
        21.07.2022 17:28

        В статье сказано, что напрямую устанавливать динамические свойства будет запрещено.


        1. arkamax
          21.07.2022 21:38
          +1

          В вашем примере выше динамические свойства никто и не устанавливает - идет работа с предопределенным массивом. Или я вас не так понял?


        1. arokettu
          21.07.2022 21:40

          Так это и не напрямую

          class FooBar {
              private array $properties = [];
              public function __set(string $name, mixed $value): void
              {
                  $this->properties[$name] = $value;
              }
              public function __get(string $name): mixed
              {
                  return $this->properties[$name] ?? null;
              }
              public function __isset(string $name): bool
              {
                  return array_key_exists($name, $this->properties);
              }
          }
          
          $foo = new FooBar();
          $foo->bar = 123;
          
          var_dump(isset($foo->bar)); // true
          var_dump(property_exists($foo, 'bar')); // false


  1. psycho-coder
    21.07.2022 18:17
    +8

    Мы называем это депрекацией (deprecation), а про таковые фичи иногда говорим, что они устарели

    translate.google.com/?sl=en&tl=ru&text=Deprecated&op=translate
    Почему иногда? Иногда устарели, а иногда deprecated? Что мешает всегда называть «устаревшей», понты?
    Простите, но тупой транслит уже порядком надоел.


    1. antonkrechetov
      21.07.2022 20:02

      Например, потому что слово deprecated имеет в английском языке не такой смысл, как слово «устаревший» в русском. Проще говоря, формально перевод из гугл-транслейта неверный. По смыслу ближе что-то вроде «неодобряемый».


      1. psycho-coder
        21.07.2022 20:16
        +3

        В каком словаре найти правильный смысл? К тому-же, везде, где мне встречалось depricated переводили и использовали как устаревший, и ни у кого вопросов не возникало. И только в этом блоге, везде «деприкация»


        1. antonkrechetov
          22.07.2022 11:13
          +1

          В каком словаре найти правильный смысл?
          В любом толковом, например:
          dictionary.cambridge.org/us/dictionary/english/deprecated
          www.dictionary.com/browse/deprecated


          1. a-tk
            22.07.2022 12:08

            https://context.reverso.net/перевод/английский-русский/deprecated
            Вот неплохой вариант ещё для перевода.


          1. psycho-coder
            22.07.2022 12:50

            Хорошие варианты. Если учесть, что depricated методы выводят из эксплуатации со временем (через релиз обычно или как заведено в команде), то «устаревший» имеет вполне устоявшийся (add: и достаточно однозначный) смысл, имхо.


            1. FanatPHP
              22.07.2022 13:10
              +1

              Только deprecated.
              Это тем более важно, что есть ещё depreciated.
              Английская языка ошень трудный!


              1. psycho-coder
                22.07.2022 13:16

                Верно, опечатался. У второго варианта, совсем другой смысл


                1. FanatPHP
                  22.07.2022 13:23

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


                  1. psycho-coder
                    22.07.2022 13:51

                    Контест все-таки играет роль.
                    Ведь если код используют, то он ценность имеет, а в случае устаревания, он остается, но в другой форме.
                    А осуждаемый — тут на ум приходит только rm /usr /lib в bubmlebee и еще похожая ситуция была емнип. Откуда появилась куча мемов с Ди Каприо, вида
                    — Как ты почистил диск?
                    — Я установил/обновил bumblebee


            1. a-tk
              22.07.2022 13:20
              +1

              Кстати, есть хорошие по смыслу понятия, описывающие стадии deprecated: подлежащий списанию и списанный. Но вряд ли они активно войдут в обиход.


              1. psycho-coder
                22.07.2022 13:38

                Списанный — это уже удаленный функционал по сути)


                1. FanatPHP
                  22.07.2022 14:06

                  Об этом и речь.


            1. antonkrechetov
              22.07.2022 20:31
              +2

              add: и достаточно однозначный
              В том то и дело, что не совсем. Из заголовка вроде «устаревшие вещи в таком-то языке» без контекста непонятно, о чем пойдет речь: о deprecated или о том, например, что автор лично считает устаревшим (outdated по-английски). С++, ага.


      1. AlexKornilov
        22.07.2022 14:35

        В словарь давно заглядывали?


  1. Jsty
    21.07.2022 18:26
    +2

     Возьмем, к примеру, Laravel Elquent ORM, который очень сильно зависит от динамических свойств для своих свойств и взаимосвязей

    ...

    Классы с магическими методами __get и __set

    Не должно на него повлиять.

    https://github.com/illuminate/database/blob/master/Eloquent/Model.php#L2075


    1. tukreb
      21.07.2022 22:00

      1. Jsty
        21.07.2022 22:08

        Согласен, здесь повлияет.


        1. Djeux
          21.07.2022 22:17
          +1

          Нет, не влияет. Там есть проверка на существование метода. Все ORM-ки подвида ActiveRecord работают с массивом под капотом, а не просто несуществующие проперти проставляют.


          1. tukreb
            21.07.2022 22:33

            Да, вы правы, к сожаленью комментарии нельзя редактировать, когда они на проверки у модератора. Я поспешил с первым комментом.


  1. kamitora
    21.07.2022 23:10

    И при этом в 8.0 они ввели динамические свойства в конструкторе. Промоуцию, простите. Здесь играем, здесь не играем.


    1. a-tk
      22.07.2022 08:23
      +1

      Они не динамические. Просто объявляются совместно с конструктором.


      1. kamitora
        22.07.2022 16:33

        Ну скажем и не совсем статические

        > Constructor Property Promotion is not internally handled as an Abstract Syntax Tree transformation. This means if you inspect the AST (with an extension such as php-ast), you will still see the code as written with property visibility modifiers right in the constructor.

        https://php.watch/versions/8.0/constructor-property-promotion


        1. a-tk
          22.07.2022 16:39

          Они всё равно становятся полями объектов, притом до того, как выполнение дойдёт до тела конструктора.

          Оттуда же, следующий абзац:

          Reflection API, it will seamlessly return information about both standard and constructor-promoted properties all the same.

          И отличие только здесь:

          In Reflection API, ReflectionProperty and ReflectionParameter classes will have a new method isPromoted to determine if the parameter/property was declared in a constructor.


          1. kamitora
            22.07.2022 16:53

            Вы правы, и все же Reflection API не бесплатно:

            https://gist.github.com/mindplay-dk/3359812

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


            1. a-tk
              22.07.2022 22:34

              В других вещах такие вещи есть и ничего, хорошо себя чувствуют. Такие вещи очень классно для TDO заходят.


              1. kamitora
                23.07.2022 00:24
                +1

                Такие вещи дают возможность реализовывать неявное поведение, что вкупе с депрекейшенами динамических пропертей напоминает шизофрению. И я с вами согласен - для DTO это отличная штука. Но вот ведь в чем загвоздка, там где есть смысл рассуждать о DTO и о паттернах вообще, экономия на спичках в виде переноса объявления свойства в конструктор, не имеет смысла. А вот в крудописании крудов на скорость, которые послезавтра выкинут на свалку - очень годная фича.

                И проблема мне видится в том, что PHP оказался в тупике. Он не может попрощаться с обратной совместимостью, пофиксив действительно проблемные вещи, но и признать что стагнирует тоже не может. В итоге имеем такой синтаксический сахар для промуошена с одной стороны и депрекейшен динамических свойств с другой(при фактической возможности обходить эти ворнинги и делать то же самое). Развитие вроде и есть, но вектор его непонятен.


    1. FanatPHP
      22.07.2022 09:56
      +1

      При чем здесь это? Объявление свойств в конструкторе — это всего лишь синтаксический сахар, чтобы не писать одно и то же 3 раза.


      1. kamitora
        22.07.2022 16:45
        +1

        Лично для меня это будет вырождаться в одну и ту же проблему: я не смогу взглянуть в начало класса и посмотреть какие свойства там есть. Мне нужно будет искать конструктор и смотреть, что запромоутилось оттуда. Конструктор не всегд пишут как первый метод. Вы можете возразить, что в случае иерархии все равно нужна IDE, чтобы смотреть что откуда пришло, а она и промоушены корректно отобразит. Только вот это означает, что простого синтаксического разбора чтобы понять, какие есть свойства уже недостаточно. Нужно делать рефлексию, что тяжелее.

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


        1. FanatPHP
          22.07.2022 16:59
          +1

          У каждого инструмента свой область применения. У этого-это дтошки, где кроме этих свойств и нет ничего.
          В остальных случаях пишем как обычно.


          1. kamitora
            23.07.2022 00:30

            Соглашусь с вами. И даже предположу, что у динамических свойств тоже была своя область применения. Но увы, задепрекейтили. Неисповедимы пути трактовки применимости.


            1. FanatPHP
              23.07.2022 07:59
              +2

              Вы смешиваете мух с котлетами. Ещё волшебные кавычки пожалейте. Или там результат сравнения 0 == "абв".


              Выпиливание из пхп детских болезней не имеет никакого отношения к объявлению свойств в конструкторе.
              У динамических свойств не было никакой "области применения", кроме говнокода. Поэтому они и выпиливаются. Всё просто как 5 копеек, а вы разводите какую-то философию на пустом месте.


              1. a-tk
                23.07.2022 13:47

                На самом деле они не выпиливаются, а отсекается один из вариантов их реализации в угоду второму, правильному. За которые надо будет заплатить от полутора до трёх строк кода.

                Ну а если в проекте они активно используются, то трейты спешат на помощь.


              1. kamitora
                23.07.2022 16:26

                > Вы смешиваете мух с котлетами. Ещё волшебные кавычки пожалейте. Или там

                > результат сравнения 0 == "абв".

                Нет же. Динамические свойства, промоушен, волшебные кавычки - это способы сделать неочевидное поведение. А то что начиная с версии 8.0 починили сравнения числа со строкой - это очевидное поведение.

                > Выпиливание из пхп детских болезней не имеет никакого отношения к

                > объявлению свойств в конструкторе.

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

                > У динамических свойств не было никакой "области применения", кроме

                > говнокода. Поэтому они и выпиливаются.

                В этом я с вами согласен. Но создатели Eloquent с вами несогласны, и те кто им пользуется также. Наверняка есть создатели всяких десериализаторов, которые тоже посчитали так.

                > Всё просто как 5 копеек, а вы разводите

                > какую-то философию на пустом месте.

                Да, для меня все просто, у PHP нет вменяемой стратегии развития. А это, без всякой философии, если судить по текущим подвижкам приведет к куче синтаксического сахара и оставлению детских болезней, типа стандартной библиотеки, однопоточного ядра, как есть.


        1. a-tk
          22.07.2022 22:35

          Обычно такой конструктор будет на первом месте вместо полей, так что вряд ли проблема сильно что-то отстрелит.


          1. kamitora
            23.07.2022 00:09

            В небольших и молодых классах - да. А бывает что конструктор много лет спустя добавляют как самый последний метод, когда внезапно чего-то не хватает в стандартном конструкторе. Ну и выше я писал про промоушен из явно вызванного родительского конструктора в дочернем конструкторе.


            1. a-tk
              23.07.2022 13:48

              В любом случае не стоит проблемы стиля называть проблемами языка.


    1. Saboteur_x86
      22.07.2022 14:36

      Так в конструкторе это ж другое вроде. В конструкторе мы при проектировании класса указываем какие свойства будем создавать, указывая соответственно их типы и модификаторы доступа. Это ж не та ситуация, когда из клиентского кода пихают что попало в объект используя ->