PHP-Дайджест № 194 (1 – 14 декабря 2020)


    Свежая подборка со ссылками на новости и материалы. В выпуске: Enum в PHP 8.1, удаление Serializable и ограничение $GLOBALS, а также другие новости из PHP Internals, PhpStorm 2020.3, Symfony UX, порция полезных инструментов, видео, и первый PHP Дайджест Стрим.

    Приятного чтения!



    Новости и релизы


    • habr PhpStorm 2020.3: PHP 8, атрибуты, PHPStan и Psalm, Xdebug 3, Tailwind CSS и совместная разработка.
    • JetBrains Qodana EAP — Новый продукт от JetBrains для запуска PhpStorm в CI.
    • Slack куплен за $27 млрд — Примечательно, то что бекенд Слэка изначально написан на PHP, а позже расширен на Hack.
    • WordPress 5.6 — C бета-поддержкой PHP 8.
    • Статистика версий PHP – 2020.2 — Традиционная подборка статистики на основе данных, которые Composer отправляет при подключении к packagist.org.
      PHP 7.4: 42.61% (+22.55)
      PHP 7.3: 27.05% (-3.00)
      PHP 7.2: 15.28% (-12.21)
      PHP 7.1: 7.45% (-4.1)
      PHP 5.6: 2.71% (-2.28)
      PHP 7.0: 2.70% (-1.30)

    PHP Internals


    • [RFC] Deprecate passing null to non-nullable arguments of internal functions — В текущих версиях PHP стандартные функции без ошибок принимают null в качестве аргумента даже, когда параметр не nullable. Например, str_contains("", null)

      Проблема в том, что если объявить пользовательскую функцию с такой сигнатурой, то там аргумент null вызовет ошибку.

      str_contains("", null);
      // > No errors
      
      function _str_contains ( string $haystack , string $needle ) : bool
      {
          return true;
      }
      
      _str_contains("", null);
      // > Fatal error: Uncaught TypeError: Argument 2 passed to _str_contains() must be of the type string, null given
      

      Предлагается поэтапно исправить это несоответствие и в 8.1 бросать deprecation notice.

      Ну а пока можно добавить declare(strict_types=1); и проблема решена.
    • [RFC] Restrict $GLOBALS usage — Сейчас, чтобы $GLOBALS вел себя как массив, приходится поддерживать еще один специальный тип INDIRECT и предоставлять прямую ссылку на внутреннюю таблицу символов PHP. Поддержка этого влияет на производительность всех операций с массивами в PHP.

      В этом RFC Никита Попов предлагает ограничить использование $GLOBALS.

      Продолжат работать чтение, запись, isset и unset:

      $GLOBALS['x'] = 1;
      
      echo $GLOBALS['x']
      
      isset($GLOBALS['x']);
      unset($GLOBALS['x']);

      И он не будет содержать рекурсивную ссылку на самого себя.

      А вот попытка изменить саму переменную $GLOBALS вызовет ошибку:

      $GLOBALS = [];
      $GLOBALS =& $x;
      $x =& $GLOBALS;
      unset($GLOBALS);

      Также ошибка будет, если передать $GLOBALS по ссылке в функцию:

      asort($GLOBALS);
      // > Compile-time error

      Забавный факт: именно об этом говорил Дмитрий Стогов на стриме отвечая на вопрос: «что стоило бы убрать в следующих версиях PHP».
    • [RFC] Phasing out Serializable — В 7.4 был введен новый механизм сериализации: наряду со старым интерфейсом Serialiazable были добавлены магические методы __serialize() и __unserialize().

      Предлагается постепенно полностью избавиться от Serializable. В PHP 8.1 его использование будет генерировать deprecation notice, а в PHP 9.0 — compile-time error.
    • [RFC] Enumerations — Ilija Tovilo и Larry Garfield провели исследования enum во всех языках и представили RFC, который вдохновлен Swift, Rust, и Kotlin.

      Enum это по сути класс, а его возможные значения — это объекты. Это означает, что, на вопрос «как бы вели себя Enums в ситуации X» можно ответить «так же, как и любой другой объект». Но есть несколько исключений.

      Описывается с помощью ключевых слов enum и case:

      enum Suit {
        case Hearts;
        case Diamonds;
        case Clubs;
        case Spades;
      }

      Переменным можно присваивать одно из значений:

      $val = Suit::Diamonds;
      
      function pick_a_card(Suit $suit) { ... }
      
      pick_a_card($val);        // OK
      pick_a_card(Suit::Clubs); // OK
      pick_a_card('Spades');    // TypeError

      Enum ведут себя как синглтон-объекты:

      $a = Suit::Spades;
      $b = Suit::Spades;
      
      $a === $b; // true
      
      $a instanceof Suit;         // true
      $a instanceof Suit::Spades; // true

      Есть возможность объявить скалярные Enum. Их можно прозрачно использовать в контексте, где ожидаются скалярные значения:

      enum Suit: string {
        case Hearts = 'H';
        case Diamonds = 'D';
        case Clubs = 'C';
        case Spades = 'S';
      }
      
      echo "I hope I draw a " . Suit::Spades;
      // prints "I hope I draw a S".

      Enum может иметь методы, в том числе статические. Пример использования метода для вывода списка опций чекбокса:

      enum UserStatus: string {
        case Pending = 'pending';
        case Active = 'active';
        case Suspended = 'suspended';
        case CanceledByUser = 'canceled';
      
        public function label(): string {
          return match($this) {
            UserStatus::Pending => 'Pending',
            UserStatus::Active => 'Active',
            UserStatus::Suspended => 'Suspended',
            UserStatus::CanceledByUser => 'Canceled by user',
          };
        }
      }
      
      foreach (UserStatus::cases() as $key => $val) {
        printf('<option value="%s">%s</option>\n', $key, $val->label());
      }

      Из открытых пока еще вопросов остаются:

      • Могут ли иметь свои методы значения case?
      • Можно ли использовать конкретное значение в качестве тайпхинта? function stuff(Suit::Heart|Suit:Diamond $card) { ... }
      • Разрешена ли сериализация Enum?
      • < Должно ли слово enum быть полностью ключевым? Тогда сломается весь код, который использует имя класса Enum. Или стоит сделать контекстное ключевое слово вот так: enum class UserStatus {...}

    • [RFC] Algebraic Data Types — Предложение про Enum является первым шагом большого плана по реализации алгебраического типа в PHP. В качестве *возможных* дальнейших шагов рассматриваются tagged unions и pattern matching.
    • [RFC] Direct execution opcode file without php source code file — Предлагается сделать возможным сохранять бинарный файл опкеша и запускать его уже без исходника. По сути, что-то очень похожее на питоновские файлы .pyc / .pyo.

      Формат опкода в PHP нестабилен и несовместим от версии к версии. Чтобы решить эту проблему, предлагается скомпилированный таким образом файл помечать в начале меткой <?phpo%php_version_id%.

      Такой файл можно будет подключать через стандартные функции include(), include_once(), require(), require_once().
    • [RFC] Wall-Clock Time Based Execution Timeout — PHP измеряет всякого рода таймауты используя процессорное время. Это значит время затраченной в sleep() или сетевых запросах не учитывается. Например, это влияет на использование ini-настройки max_execution_time.

      В данном RFC всего лишь предлагается ввести новую установку max_execution_wall_time, которая бы учитывала реальное время вместо процессорного.

    Инструменты


    • itsgoingd/clockwork v5.0 — Отладочная панель и расширение для Chrome, которое добавляет вкладку в dev tools для отладки PHP-приложений. Подробный разбор инструмента смотрите в video Пятиминутке PHP.
    • nicofff/LazyIter — Библиотека для работы с массивами в виде цепочек вызовов. Вдохновленная итератором из Rust. Пост про строгую типизацию в поддержку.
    • veewee/composer-run-parallel — Позволяет параллельно запускать команды из секции «scripts» в composer.json.
    • clue/phar-composer — Берет проект с composer.json и создает исполняемый архив phar со всеми зависимостями.
    • clue/commander — Для быстрого создания консольных команд.
    • quasilyte/phpgrep 1.0 — Инструмент для поиска по PHP-коду – как grep, только с «пониманием» синтаксиса PHP.

    Symfony



    Laravel



    Yii



    Async PHP


    • clue/reactphp-zenity — Обертка над Zenity для создания GUI приложений на PHP. Работает из коробки на Ubuntu.

    Материалы для обучения



    Аудио/Видео



    Занимательное




    Сегодня первый раз проведу стрим по PHP дайджесту. Все новости и ссылки из выпуска + больше деталей, обзор присланного, интересное но не вошедшее в выпуск, и конкурс со слониками.
    Начало в 20:00 Москва, Минск / 19:00 Киев.



    Если вы заметили ошибку или неточность — сообщите, пожалуйста, в личку хабра или телеграм.

    Больше новостей и комментариев в Telegram-канале PHP Digest.

    Прислать ссылку
    Поиск ссылок по всем дайджестам
    Предыдущий выпуск: PHP-Дайджест № 193

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

      0
      [RFC] Direct execution opcode file without php source code file

      Ещё в архивчик запаковать… или phar адаптировать, чтоб php-fpm с ним работать мог.

        +5
        [RFC] Direct execution opcode file without php source code file
        Надеюсь это никогда не примут. Сейчас под контролем разработчика 100% PHP кода в проекте, можно отдебажить любую точку в коде, любой класс, любой метод. А затянуть себе опкод файл — это затянуть кота в мешке, если я правильно понимаю идею. Я уже прям вижу как во всякие вордпрессы юзеры будут сами себе ставить кучу малвари. А ещё проекты, вроде «нам надо поправить вот тут две формочки». Берёшь такой проект, а там один php файл и всё остальное в опкодах лежит, делай что хочешь. Бррр.
          +1

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


          Будет намного проще отдать клиенту "код" без костылей в виде обфускации (всё равно можно развернуть обратно). Текущие решения вроде Ioncube не работают с современными версиями php (7.4 / 8.0).


          Тем более, в мире C++ (и не только) и скомпилированных библиотек с этим нет никаких проблем: библиотека поставляется вместе с заголовочником, схема отлаженная. Возможно, в php появится похожая возможность.


          во всякие вордпрессы юзеры будут сами себе ставить кучу малвари
          Это происходит и сейчас, некоторые разработчики скачивают "крякнутые" плагины/темы и т.п. и устанавливают их, даже не открывая сорцы.
            +4
            Будет намного проще отдать клиенту «код» без костылей в виде обфускации (всё равно можно развернуть обратно). Текущие решения вроде Ioncube не работают с современными версиями php (7.4 / 8.0).
            Те же сишные бинарники ломают во всю, думаете сорцы на PHP опкодах не научатся ломать?
              +1

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


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

                +1
                Это может быть нужно авторам платных коробок.
                Для inhouse-разработки смысла в пред-компиляции немного, усложнится SDLC.
                Разве что поверх PHP появится аналог Typescript.
                  0
                  Разве что поверх PHP появится аналог Typescript.

                  Интересная мысль. Правада, скорее не не аналог Typescript, а аналог Scala, Kotlin, JPython и т. п.

                    0

                    Всё верно.


                    Я рассуждал с мыслью о платных коробках. Когда при продаже ПО клиенту можно не переживать, что через несколько месяцев он будет выложен на какой-нибудь warez-помойке в комплекте с парочкой троянов.


                    Иногда условия бизнеса не позволяют создать какое-то SAAS-решение, клиент борется за то, чтобы софт был на его сервере и под его контролем.

                    0
                    то пользы от этого будет точно больше, чем проблем

                    Прощай использование __DIR__, __FILE__ и прочих… Совсем нет проблем, ага, учитывая что сейчас сложно найти код не использующий эти константы (например require __DIR__ .'/vendor/autoload.php')


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

                      0

                      Почему прощай-то? Просто компилировать нужно будет сразу в продакшен-путях: chroot, docker и ко.

                        0

                        В bcompiler решали подобные проблемы добавлением заголовков в бинарные файлы. В этих заголовках и были прописаны пути до файлов и прочая метаинформация.
                        Насчёт __FILE__ и __DIR__ не помню, но ошибки точно вываливались с теми путями, которые были на машине разработчика в момент компиляции :)


                        Кроме того, нет особого смысла байткодить все файлы. Всякие штуки вроде require __DIR__ .'/vendor/autoload.php' можно было бы и вынести

                    0
                    Текущие решения вроде Ioncube не работают с современными версиями php (7.4 / 8.0).

                    C PHP 8 — еще нет, с 7.4 — уже какое-то заметное время да. Я по работе занимаюсь в том числе тестированием совместимости продукта с IonCube, нахожусь в прямом контакте с их технарями, и они адекватно и достаточно оперативно реагируют на проблемы в рантайме… если отправить им PoC на косяк декодера, фикс иногда прилетает через 24 часа, и через 48 уходит в релиз. Нормальные ребята, в общем.
                    +3
                    Можно еще называть такие файлы SomeClass.min.php.
                      0
                      Такие штуки уже существуют от сторонних разработчиков.
                    +1
                    itsgoingd/clockwork v5.0 — Отладочная панель и расширение для Chrome

                    Не только для Chrome!
                      +1
                      Добавлена поддержка PHP 8 в Yii 1

                      Респект таким ребятам!
                        0
                        Жаль только, что пока космические корабли бороздят просторы вселенной, самые востребованные части php — PDO и mysqli, остаются кучей мусора, нарушают и SOLID, и стандарты языка.
                        PDOStatement не расширить. Убогий синтаксис плейсхолдеров. Коллекции — руками.
                        А внедрение значения в поля объекта без вызова конструктора при fetch в объект — это ж совершенно непредсказуемое поведение.

                        SPL types так и остался экспериментальным. SplBool — шикарная же идея, чтобы писать if ($object).

                        Зачем алгебра в языке для middleware? Зачем делать из скриптового языка компилируемый? Работа с СУБД нужна.
                          +1

                          Разве юзерленд либы не решают все проблемы с СУБД?

                            0
                            Это ж классика афоризма :)
                            Любую проблему можно решить введением дополнительного уровня абстракции кроме проблемы слишком большого количества уровней абстракции.

                            Когда нужен тюнинг запросов, например, ничего никто не решает. Абстракции не бесплатные.

                            PDO предоставляет PDOStatement для запросов, без вариантов. Не переопределить, не расширить.
                            Почему PDOStatement делает и присваивание параметров, и выполнение запроса, и формирование результата, что за нарушение SRP?
                            Результат fetchAll() — массив объектов или массивов, без вариантов. А когда нет записей — пустой массив, без вариантов. И зачем он такой, fetchAll() с массивом объектов — что за смесь бульдога с носорогом?
                              0

                              А чем плох массив объектов? Какие альтернативы?

                                0

                                Коллекции объектов же а-ля Doctrine или Eloquent Collection. Возможно виртуальные на итераторах/генераторах. А может что-то из мира асинхронщины с колбэками )

                                  0

                                  Ааа, понял теперь о чем речь. Ну, то есть по факту о том, что нет коллекций из коробки?
                                  Просто в чем проблема делать new ArrayCollection(PDOStatement::fetchAll)?


                                  То, что здесь хорошо было бы дженерики — это понятно. Непонятно в чем проблема использования коллекций Doctrine/Eloquent.

                                    0

                                    Это моё предположение, что имел в виду Grikdotnet я точно не знаю.

                                      0
                                      по факту о том, что нет коллекций из коробки?

                                      Тем, что нет ничего. Тупая процедурщина, как в php3. Как 20 лет назад написали fetch в массив — с тем php и похоронят.
                                      Дженерики — то отдельная тема. Хардкодить этого монстра PDOStatement — ошибка дизайна. Надо инъектить в PDO наследника PDOStatement, который бы позволял переопределить fetch.

                                      Полиморфизм? Inversion of Control? Liskov substitution? Нет, не слышали!
                                        0

                                        del

                                          0
                                          del (не в ту ветку).
                                          +1
                                          Надо инъектить в PDO наследника PDOStatement, который бы позволял переопределить fetch.

                                          А что мешает так сделать и переопределить fetch как надо?
                                            0
                                            наверное, то, что PDO генерит PDOStatement внутри, и расширить его невозможно
                                              +1
                                              Возможно. Смотрете аттрибут PDO::ATTR_STATEMENT_CLASS
                                              www.php.net/manual/ru/pdo.setattribute.php
                                                0
                                                Спасибо! Мое утверждение о невозможности подменить PDOStatement ложное, пропустил эту константу :)

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

                                                  В подавляющем большинстве современного ПО PDO сидит где-то внутри ORM (Doctrine/Cycle/Eloquent/etc) и использовать его напрямую зачастую даже вредно (в рамках DM паттерна с наличием Identity Map, например).


                                                  Так что выбирая между PDO vs Generics (ну вдруг так случится), то какими бы не были убедительными аргументы в пользу PDO — юзкейсов с дженериками на порядки больше, т.к. это конструкция языка, которая "протекает" и в бизнес-логику, которую невозможно покрыть библиотечным функционалом, а не низкоуровневый драйвер работы с БД, который, при работе через фреймы, изучается лишь "для галочки" и никогда вообще не используется.

                                                    0
                                                    PDO сидит где-то внутри ORM (Doctrine/Cycle/Eloquent/etc)

                                                    юзкейсов с дженериками на порядки больше

                                                    И чуть ли не в первую очередь дженерики нужны как раз для ORM

                                                      0
                                                      да, но orm имеет ограниченную сферу применения — в проектах, где нужна оптимизация, пишут зпросы на sql, и ручной мапинг на сущности
                                                        0

                                                        Я такой подход называю "ad hoc ORM" в отличии от universal ORM библиотек типа Doctrine, но дженерики и там, и там нужны над методами, возвращающими коллекции/массивы

                                      –1
                                      чем плох массив объектов?

                                      PDOStatement просят fetch в объект, а оно возвращает массив. Это ошибка дизайна.
                                      Массив — это процедурное программирование.
                                      Логично вернуть специальный объект типа PdoResultSet, расширяющий SPLFixedArray. Или как в fetchObject, в параметре принимать пользовательский тип для коллекции.
                                    0
                                    а mysqli — это вообще песня!
                                    метод bindParam() — передача констант по ссылке, ниче так для php8? :)
                                    асинхронный режим запросов, который нельзя подцепить к libevent-циклу — зачем он, вообще?
                                    0

                                    Имхо не нужна, а вредна. Должны быть низкоуровневые функции, а уже в библиотеках делать абстракции с разными подходами и как угодно ломая или не ломая совместимость.


                                    На мой взгляд не хватает дженериков, параллелизма в ядре как в Go, и улучшения application server до уровня продакшена.

                                      0
                                      Должны быть низкоуровневые функции

                                      Как я понимаю, речь о том, что они не очень удобные и, как бы это, правильные что ли. И абстракции не избавляют нас от трейсов с вызовами этих низкоуровневых функций.

                                        0
                                        дженерики Никита пытался сделать очень упорно, но не выходит для динамического языка, нормально работает только без проверки в runtime — слишком дорого

                                        а так-то Феррара их proof of concept написал лет под 10 назад, только тормозят жутко
                                          0
                                          Параллелизм в виде корутин есть в свуле, но у него очень ограниченная сфера применения. Надо помнить, что 36% интернета — wordpress.
                                            0

                                            Разве корутины в свуле параллельно выполняются?

                                              0
                                              создавать треды свуле, конечно, не умеет, но логика исполнения распараллеливается, и несколько запросов обработать в неблокирующем режиме можно
                                        0
                                        за Laravel отдельное спасибо
                                          +1
                                          Руководство по разработке расширений для PHP от Zend

                                          Ох ты ж, какая годнота!

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

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