Когда люди обсуждают изменения в PHP7, самое распространенное что вы слышите — это значительно улучшенный движок, который может похвастаться более быстрой скоростью выполнения и значительно меньшим объемом памяти при сравнении обычных приложений PHP, таких как Drupal, WordPress и MediaWiki.


Не поймите меня неправильно, это все конечно здорово! Мне удалось перенести несколько устаревших приложений CodeIgniter на PHP7 и достигнуть гораздо более высокой производительность с небольшими изменениями в кодовой базе. Тем не менее, PHP7 также добавляет несколько новых функций, которые могут помочь оптимизировать существующий код или повысить качество написания нового кода. Здесь я изложил несколько моих избранных фич.


Скалярный параметр и возврат заявленного типа


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


Принудительный:


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


<?php
function reverseString(String $str) : String 
{
    return strrev($str);
}
print(reverseString(1234));

Мы указываем, что параметр $str должен иметь тип String а также возвращаемое значение также должно иметь тип String. Поэтому, когда мы передаем число 1234, оно принудительно переводится в строку "1234" и переводится без ошибок.


Строгий:


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



<?PHP
declare(strict_types = 1);
function  reverseString(String  $str): String 
{
    return  strrev($str);
}
print (reverseString(1234));

Добавив единую declare инструкцию в самом начале файла, в тот же код, что и раньше, теперь мы получаем следующее сообщение об ошибке:


Fatal error: Uncaught TypeError: Argument 1 passed to reverseString() must be of the type string, integer given

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


Null Оператор ??


В отличие от некоторых языков, где вы можете использовать имя переменной в качестве выражения в выражении if и смело предполагать, что если значение не определено или пустое, то значение будет false, PHP же будет бросать ошибку о неопределенной переменной, индексе и т. д. Это делает очень многословным, обычный код, с использованием if, чем другие языки, как например, в следующем примере.


 <?php
if(!isset($_GET['key'])) {
    $key = 'default-value';
} else {
    $key = $_GET['key'];
}

Даже при использовании тернарного оператора необходима функция isset. С новым null оператором ?? вы можете существенно облегчить код:


 <?PHP
$key = $_GET['key'] ?? 'default_value';

Такое использование еще более эффективно в случаях цепочной проверки, требующих одного или несколько других операторов if.


 <?php
if (isset($_GET['key']) {
    $key = $_GET['key'];
} else if(isset($_POST['key'])) {
    $key = $_POST['key'];
} else {
    $key = 'default value';
}

// Versus

$key = $_GET['key'] ?? $_POST['key'] ?? 'default value';

Маленькое дополнение: Если вы работаете с JavaScript, вы можете делать такие вещи:


const value = 0 || false || 'hello';
console.log(value); // hello

Это не будет работать в PHP, и эквивалентный код на PHP установит значение 0, поскольку новый оператор работает только с null значениями.


Групповые Use Declarations


В предыдущих версиях PHP вы могли импортировать только один элемент (класс, функцию, константу) из определенного пространства имен в одном выражении с использованием объявления use. Это часто приводило к очень повторяющемуся коду, такому, как в примере ниже..


<?php
use VendorName/LibraryName/ClasName1;
use VendorName/LibraryName/ClasName2;
use VendorName/LibraryName/ClasName3;

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


<?php
use VendorName/LibraryName/{ClasName1, ClassName2. ClassName3};

Константные массивы


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


<?php
define('COLOR_RED', '#f44141');
define('COLOR_BLUE', '#4286f4');
define('COLOR_GREEN', '#1ae01e');
define('COLOR_PURPLE', '#f309f7');
define('COLOR_ORANGE', '#ef7700');

Именованные константы, в дополнение к ранее поддерживаемым типам данных, могут быть как индексированным так и ассоциативным массивом. Это поможет более точно сгруппировать многие именованные константы, которые могут быть у вас в вашем приложении.


<?php

// В качестве ассоциативного массива

define('COLORS', [
    'red' => '#f44141',
    'blue' => '#4286f4',
    'green' => '#1ae01e',
    'purple' => '#f309f7',
    'orange' => '#ef7700',
]);
echo(COLORS['red']); // #f44141

// Как индексированный массив

define('COLORS', [
    'red',
    'blue',
    'green',
    'purple',
    'orange',
]);
echo(COLORS[0]); // 'red'

Вывод


Есть еще несколько замечательных новых функций, о которых я не сказал, таких как анонимные классы и оператор spaceship. Так что определенно проверьте документацию на PHP.net для получения дополнительной информации. Спасибо, что нашли время, чтобы прочитать все это и, пожалуйста, оставляйте любые вопросы или комментарии ниже.


спасибо berez за замечания.

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


  1. 027
    08.08.2018 20:59
    -1

    использовать имя переменной в качестве выражения в выражении if и смело предполагать, что если значение не определено или пустое, то значение будет false

    необходима функция isset

    Всегда предпочитал !empty().


    1. Denai
      08.08.2018 22:39

      image


      1. 027
        08.08.2018 22:50

        Я в курсе. К чему эта портянка? Вы не поняли, где у вас в тексте ошибка?

        смело предполагать, что если значение… пустое, то значение будет false
        функция isset

        isset() на пустую строку, false, число 0, строку '0' даст true.
        Эта функция не для проверки значения, она для проверки факта существования переменной.
        Тщательне?е надо.


        1. taliban
          09.08.2018 00:47

          Все он правильно сказал, все что вы перечислили не пустые значения


          1. 027
            09.08.2018 00:51

            «Нет пустого значения, кроме NULL, и taliban пророк его.»


            1. 027
              09.08.2018 14:57

              Собственно, NULL — это даже не пустое значение, это отсутствие значения вообще. Даже пустого.


              1. SerafimArts
                09.08.2018 16:22

                А void? =)


                1. 027
                  09.08.2018 16:24

                  var_dump'ом посмотри?те. :)


        1. Zanak
          09.08.2018 18:51

          Ой не пойму я, чего вы спорите, берем и пишем:

          $arr = ['', 0, '0', false, NULL];
          echo "Num\tEmp\tSet\tNul\tBool\n";
          for($i = 0; $i < 5; $i++) {
              echo $i, "\t", empty($arr[$i]), "\t", isset($arr[$i]), "\t", is_null($arr[$i]), "\t", boolval($arr[$i]), "\n";
          }
          

          смотрим на вывод и делаем выводы.

          ЗЫ. Если лень заморачиваться: с одной стороны, NULL означает отсутствие значения, с другой, мы можем написать $a=NULL. Это значит, что значение может быть установлено, но оно все равно будет неопределено. Даже более простой случай со строкой '0' заставляет нас помнить о контексте использования: если работаем со строкой, то это эквивалентное true значение, а если с числами, то false. В общем, нельзя расслабляться, даже в мелочах.


    1. asmdk
      09.08.2018 08:29

      Всегда предпочитал !empty().

      Не подходит, когда значение может быть 0, false и т д


      1. morozovsk
        09.08.2018 15:13

        Да, но подходит для того что описал автор:

        значение не определено или пустое
        что собственно и процитировал 027 в своём первом комментарии.
        Причём под этой фразой автор проверяет только на «определено» isset(), но не проверяет на «пустое» (перевод — правильный, в оригинале — «the value isn’t defined or is empty», значит ошибку допустил автор в коде или его описании), нужно isset($x) && $x, на что 027 и ответил, что в таких случаях предпочитает использовать !empty(), что вполне логично, так многие и делают.


        1. asmdk
          09.08.2018 15:29

          @27 вырвал фразы из разных контекстов, автор пишет:

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


          т е имеется ввиду что-то вроде такого:

          $value = isset($_GET['limit']) ? $_GET['limit'] : 10;


          конечно щависит от ситуации, иногда тебе подойдет и empty, но однозначто это сказать нельзя, поэтому они и неэквивалентны


        1. taliban
          09.08.2018 18:35

          А что значит пустое? 0 пустое значение? А если мне надо число в промежутке от -1 до 1, значит ли это что валидных значения всего два? Ведь ноль пустое значение. Да и фолс, мне нужен ответ человека на вопрос: «курит ли он», бессмысленно хранить его как текст да/нет, бул предпочтительней. К пустому значению с натяжкой можно лишь пустую строку притащить, ито даже это значение может быть вполне себе валидным и полным значением отвечающим требованию.
          Сама фраза пустое значение изначально неправильная. Эти значения не пустые, они вполне себе определенные и ими можно оперировать. Только по этому к нему придираются, а не из-за неправильного перевода или чего бы то еще.


          1. morozovsk
            09.08.2018 21:45

            В php всё просто — пустое значение или нет определяется с помощью функции empty().


      1. 027
        09.08.2018 15:14
        +1

        Не подходит для чего именно?

        isset() даст true, если переменная существует и ей не присвоено NULL.
        !empty() даст true, если переменная существует, и заодно не NULL, не нуль (целое либо дробное), не символ нуль (string '0'), не пустая строка, не пустой массив, не false.

        То есть, делает примерно то же, что и if внутри скобок. На практике такая проверка нужна часто, а empty() удобна тем, что не генерирует варнинг. И можно не заморачиваться предварительным объявлением переменной. В документации это прекрасно описано. Мне казалось, это даже коты знают, а поди ж ты. :)


        1. asmdk
          09.08.2018 16:31

          Я сейчас не об этом писал, вы вырвали две фразы из разных контекстов, про функцию isset, говорилось в контексте тренарной операции, а не if, и второй момент не подходит если я хочу чтобы значение 0 например присвоилось и не проигнорировалось и взялось дефолтное…


          1. 027
            09.08.2018 16:37

            и второй момент не подходит если я хочу чтобы значение 0 например присвоилось и не проигнорировалось и взялось дефолтное…

            Ничего не понял. Сдаюсь. :)


            1. asmdk
              09.08.2018 16:39

              $value = empty($_GET['limit']) ? $_GET['limit'] : 10;


              если $_GET['limit'] будет равно нулю, то $value будет равно 10, но я хочу, чтобы было 0, в этих случаях empty не подходит, а isset — да


    1. D01
      09.08.2018 17:09

      Аналогично. Оно хоть и работало, но спамило в логи, поэтому приходилось использовать empty и т.п.


  1. 027
    08.08.2018 21:13

    const value = 0 || false || 'hello';
    console.log(value); // hello

    Это не будет работать в PHP, и эквивалентный код на PHP установит значение 0

    Хм…
    const value = 0 || false || 'hello';
    console.log(value);
    VM114:2 hello

    onotole@home:~$ php
    <?php
    $value = 0 || false || 'hello';
    var_dump($value);    
    print_r(phpversion());
    bool(true)
    7.2.7-0ubuntu0.18.04.2

    Жабаскрипт отдает строку хелло, но это его заморочки, по булевой логике должно быть true.
    Что и делает наш любимый седьмой пых. :)


    1. asmdk
      09.08.2018 08:35

      Я думаю имелось ввиду, что оператор ?? в пхп, почти эквивалентен джсному ||, но пхп проверяет только на null, т е:

      echo 0 ?? false ?? 'hello';
      echo null ?? $null ?? 'hello';
      

      0hello

      в джс немного по другому…


    1. sectus
      09.08.2018 09:36

      Кстати.

      <?php
      $value = 0 ?: false ?: 'hello';
      var_dump($value); // string(5) "hello"
      


      3v4l.org/fFm3U


      1. asmdk
        09.08.2018 09:54

        да, но если вставить этот вариант в ваш пример то он выдаст нотис:

        echo null ?: $null ?: 'hello';


        а в джс вроде отработает как надо

        Так тчо тут использование по ситуации


  1. 027
    08.08.2018 21:28

    Константные массивы

    Еще в 5.6 появилось.


  1. Audiophile
    08.08.2018 22:41

    Статья запоздала на пару лет как минимум.


  1. AlexLeonov
    08.08.2018 23:17
    +4

    Когда люди обсуждают изменения в PHP7

    Нормальные программисты давно уже обсуждают изменения в PHP 8.
    И стараются не использовать define()

    предлагает два разных объявления типов

    Никакого «объявления типов» здесь нет. Это рантайм контроль типов.
    И не «сообщение об ошибке», а исключение.

    Безграмотная статья.


    1. taliban
      09.08.2018 00:53

      А что не так с define?


      1. sha4
        09.08.2018 00:59

        Да, что не так с define?


        1. SerafimArts
          09.08.2018 04:34

          Совершенно бессмысленная глобальщина же. Код:

          Заголовок спойлера
          define('COLOR_RED', '#f44141');
          define('COLOR_BLUE', '#4286f4');
          define('COLOR_GREEN', '#1ae01e');
          define('COLOR_PURPLE', '#f309f7');
          define('COLOR_ORANGE', '#ef7700');
          


          1. asmdk
            09.08.2018 10:20

            Стоит упоминать, что мы не всегда используем классы, или вынуждены использовать define по ряду другим причин? не вижу смысла полностью списывать его со счетов…


            1. SerafimArts
              09.08.2018 10:54

              «мы» — это кто? И почему «вынуждены»? Кто-то запрещает это делать? Я лично вообще не помню когда последний раз писал 10ти-строчные скриптики, где классы были бы просто оверинжинерингом.


          1. kaichou
            09.08.2018 10:34

            У второго варианта есть как плюсы, так и минусы.
            Ваше предложение, по сути, аналогично предложению заменить isset на empty (см. первый комментарий).


          1. istepan
            10.08.2018 07:27

            define удобен для конфигов. Например DEBUG, ENV.


      1. AlexLeonov
        09.08.2018 10:52

        define() работает в рантайме, const — конструкция этапа компиляции. Стоит ли говорить, что второе предпочтительнее?


        1. taliban
          09.08.2018 13:15
          +1

          Вы еще скажите что АОТ лучше чем JIT, только потому что одно заранее все полностью перелопатит, а второе по требованию. Или скажите что lazy loading это полная ересь, и ее использовать ни в коем случае не стоит. Стоит ли говорить что вы глупость сказали?


          1. xotey83
            09.08.2018 14:48

            Не передёргивайте пожалуйста. Определение данных и структур во время компиляции, JIT и lazy loading — это как путать тёплое с мягким. Совершенно разные технологии, предназначенные для разного и решающие разные задачи. Это во-первых.

            Во-вторых, ваш тон и тон Алекса Леонова черезчур категоричен. Пожалуй, что стоило бы для начала разобраться в чём разница между const и define(). Какие есть плюсы и минусы. Пожалуй, лучше всего ответил на этот вопрос Никита Попов (один из разработчиков ядра PHP): https://stackoverflow.com/a/3193704

            В третьих, так да, const определённо лучше, чем define хотя бы потому, что значение константы инлайнится (в пределах файла, где происходит определение константы) и лучше поддаётся оптимизации OpCache благодаря возможности константных вычислений во время компиляции, а не в рантайме, что, допустим, может привести к меньшему количеству опкодов и более эффективному использованию буфера interned strings (не знаю как правильно на русский перевести).

            В-четвёртых, в свете реализованных некоторых SSA-оптимизаций конструкция const может (именно может, но не факт) работать опять-таки благодаря константным выражениям и как следствие раскрутки и слопыванию узлов AST.

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


            1. taliban
              09.08.2018 15:09

              Я ни в коем случае не уверждал обратного, я лишь сказал что фраза «Стоит ли говорить, что второе предпочтительнее?» — глупость. Так как не всегда этап компиляции предпочтительней, перед рантаймом. Как ни крутите а код в рантайме исполняется, и пхп при этом нормально себя чувствует. По сравнению с основным кодом количество констант описаных с помошью define крайне мало, и это не затронет производительности (это ведь не основное действие в коде — установка констант?).
              А за ссылку спасибо, она явно объективней чем ответ Алекса.


    1. asmdk
      09.08.2018 10:02
      +1

      И не «сообщение об ошибке», а исключение.

      С каких пор fatal error стало исключением?
      тем более, что вы не отловите его с помощью класса \Exception, только \Error ну и \Throwable само собой…


      1. AlexLeonov
        09.08.2018 10:50

        Fatal error здесь потому, что исключение не было поймано. Вам никто не мешает его поймать.


        1. asmdk
          09.08.2018 11:15

          странные вещи вы пишите, ок вот вам примеры тогда:

          \Exception не ловится
          sandbox.onlinephpfunctions.com/code/27f7043b880d9cdcc5de067b17243972514b75e1

          \Error все ок
          sandbox.onlinephpfunctions.com/code/2b97205a8f3cb8a354abeaf9917718484e1210d9

          ну и второй момент ексепшен не прерывает выполнение, а фатал — прерывает, можете проверить сами, здесь прерывается…


          1. SerafimArts
            09.08.2018 11:32

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

            try {
                require __DIR__ . '/any-bad-code.php';
            } catch (\Error $e) {
                echo $e;
            }
            
            \var_dump('ALL OK');
            


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

            Т.е. с точки зрения именования любые Throwable/Error — это ошибки с разным уровнем «страшности», а с точки зрения семантики и поведения — это обычные исключения.


            1. asmdk
              09.08.2018 12:10

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

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

              Ну и опять же называть ошибку исключением у меня язык не повернется…


              1. AlexLeonov
                09.08.2018 12:21

                Вы меня простите, но у вас каша какая-то в голове. Ничего личного, извините.

                Итак, у нас есть интерфейс Throwable. Это общий интерфейс для всех объектов, которые можно выбросить (throw) и поймать (catch).

                Далее есть два класса, реализующих этот интерфейс: Error (для исключений системных) и Exception (для пользовательских). От первого наследуется большое количество системных исключений, вроде TypeError, от второго тоже немало, например InvalidArgumentException.

                Важно понимать, что Error — это не ошибка. Это класс исключений, который называется «Error».

                Что такое ошибки в PHP можно почитать здесь: php.net/manual/ru/errorfunc.constants.php

                Почему же мы видим всё-таки ошибку Fatal error? Потому что не поймали исключение! Обратите внимание, что любое исключение, хоть наследующееся от Error, хоть от Exception, если вы его не отловите с помощью catch, всплывет по стеку вызовов до самого верха и вызовет фатальную ошибку.

                Вот и всё.

                Резюме:
                — Error это не «ошибка», а класс исключений, называющийся «Error»
                — Любой Throwable можно поймать, в том числе и любой Error
                — Ошибки в PHP — не исключения, это совсем другой механизм
                — Фатальная oшибка неизбежно возникает, если вы упустили любой Throwable


                1. asmdk
                  09.08.2018 12:32

                  Я прекрасно вас понимаю, и тут дело только в терминологии, и с моей точки зрения, назвать это ошибкой тоже правильно, и вот например если вы откроете описание того же класса \Error, то там как раз таки пишеться про ошибки…

                  php.net/manual/ru/class.error.php


                  1. AlexLeonov
                    09.08.2018 12:37

                    Базовый класс исключений для внутренних ошибок PHP.
                    Я не вижу никакого противоречия.

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

                    Да, согласен, терминология может запутать.


          1. AlexLeonov
            09.08.2018 11:54

            Исключение — это не только Exception и его наследники, но всё, что реализует Throwable.


    1. MaximChistov
      09.08.2018 16:06

      Нормальные программисты давно уже обсуждают изменения в PHP 8.

      Не подскажете ссылку на почитать? Что-то ничего кроме статей про новый JIT не гуглится



  1. AlexLeonov
    08.08.2018 23:23
    -1

    У меня у одного складывается ощущение, что российское широкое сообщество PHP слегка более продвинутое, чем зарубежное?


    1. Vlad_fox
      09.08.2018 09:50

      если дальше русскоязычного хабра никуда не ходить, то именно такое ощущение и сложится.


  1. nefone
    10.08.2018 07:33

    в своё время мне очень помогли константы с ключами


  1. Fantyk
    10.08.2018 16:21

    Групповые Use Declarations


    Надеюсь это не получит широкого распространения, т.к. поиск по проекту без IDE станет невозможным. (А с легаси кодом бывает так, что и с IDE не удается найти использования класса без полного поиска по проекту)