Это вторая часть нашей минисерии статей «Чего ждать от PHP7». Читать часть 1

Как вы наверное уже знаете, PHP7 придет в этом году! И сейчас самое время узнать что же нового он нам принесет.

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

Новый экранирующий символ для Unicode


Добавление нового escape-символа \u позволяет нам указывать специфические unicode символы внутри PHP-строк (да-да, те самые emoji и не только).

Синтаксис выглядит так — \u{CODEPOINT}, например, зеленое сердце, , может быть выражено как PHP-строка: "\u{1F49A}".

Оператор объединения со значением NULL (Null Coalesce Operator)


Еще один новый оператор — ??. Он возвращает левый операнд, если он не имеет значение NULL; в противном случае возвращается правый операнд.

Самое главное, что не генерирует notice, если левый операнд является несуществующей переменной. В отличии от короткого тернарного оператора ?:, он работает как isset().

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

$config = $config ?? $this->config ?? static::$defaultConfig;

Привязка замыканий во время вызова


С PHP5.4 к нам пришли нововведения Closure->bindTo() и Closure::bind(), которые позволяют изменить привязку $this и области вызова, вместе, или по отдельности, создавая дубликат замыкания.

PHP7 теперь добавляет легкий способ сделать это прямо во время вызова, связывая $this и область вызова с помощью Closure->call(). Этот метод принимает объект в качестве своего первого аргумента, а затем любые другие аргументы, которые пойдут в замыкание:

class HelloWorld {
     private $greeting = "Hello";
}

$closure = function($whom) { echo $this->greeting . ' ' . $whom; }

$obj = new HelloWorld();
$closure->call($obj, 'World'); // Hello World

Группировка деклараций use


Если вам когда-либо приходилось импортировать много классов из одного и того же пространства имен, вы, наверное, были очень счастливы, когда IDE делала всю основную работу за вас. Для всех остальных, и для краткости, в PHP7 теперь есть возможность группировать декларирование операторов use. Это позволит быстрее и удобнее работать с большим количеством импортов и сделает код читаемее:

// Original
use Framework\Component\SubComponent\ClassA;
use Framework\Component\SubComponent\ClassB as ClassC;
use Framework\Component\OtherComponent\ClassD;

// With Group Use
use Framework\Component\{
     SubComponent\ClassA,
     SubComponent\ClassB as ClassC,
     OtherComponent\ClassD
};

Группировка может использоваться с константами и импортируемыми функциями, вы можете смешивать все вместе:

use Framework\Component\{
     SubComponent\ClassA,
     function OtherComponent\someFunction,
     const OtherComponent\SOME_CONSTANT
};

Улучшение генераторов


return в генераторах

В генераторах появились две очень интересные возможности. Первая — Generator Return Expressions, позволяющая возвращать значение после (успешного) завершения работы генератора.

До PHP7, если вы пытались что-нибудь вернуть в генераторе, это приводило к ошибке. Однако, теперь вы можете вызвать $generator->getReturn(), чтобы получить возвращаемое значение.

Если генератор еще не завершился или выбросил непойманное исключение, вызов $generator->getReturn() сгенерирует исключение.

Если же генератор завершен, но не объявлен return, то метод вернет NULL.

Пример:

function gen() {
    yield "Hello";
    yield " ";
    yield "World!";

    return "Goodbye Moon!";
}

$gen = gen();

foreach ($gen as $value) {
    echo $value; 
}

// Outputs "Hello" on iteration 1, " " on iterator 2, and "World!" on iteration 3

echo $gen->getReturn(); // Goodbye Moon!

Делегирование генератора

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

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

Это утверждение также справедливо при отправке данных в генератор или выбросе исключений. Они передаются в суб-структуру, как если бы это был ее непосредственный вызов.

Синтаксис такой — yield from <expression>. Посмотрим на примере:

function hello() {
     yield "Hello";
     yield " ";
     yield "World!";

     yield from goodbye();
}

function goodbye() {
     yield "Goodbye";
     yield " ";
     yield "Moon!";
}

$gen = hello();
foreach ($gen as $value) {
     echo $value;
}

При каждой итерации будет выводиться:

  1. «Hello»
  2. " "
  3. «World!»
  4. «Goodbye»
  5. " "
  6. «Moon!»

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

\EngineException


Обработка фатальный и catchable фатальных ошибок в PHP традиционна была невозможна, или по крайней мере очень сложна. Но с добавлением Engine исключений, многие из этих ошибок будут теперь выбрасывать исключение вместо самой ошибки.

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

Эти исключения являются \EngineException объектами, и в отличии от всех пользовательских исключений, они не наследуются от базового класса \Exception. Это сделано специально, чтобы существующий код, который ловит класс \Exception не отлавливал и фатальные ошибки, изменяя свое поведение. Таким образом сохраняется обратная совместимость.

В будущем, если вы хотите поймать как традиционные исключения, так и engine исключения, вам нужно будет отлавливать их новый общий родительский класс \BaseException.

Кроме того, ошибки парсинга в выполняемом функцией eval() коде теперь будут выбрасывать \ParseException, а несоответствие типов приведет к \TypeException.

Пример:

try {
    nonExistentFunction();
} catch (\EngineException $e) {
     var_dump($e);
}

object(EngineException)#1 (7) {
  ["message":protected]=>
  string(32) "Call to undefined function nonExistantFunction()"
  ["string":"BaseException":private]=>
  string(0) ""
  ["code":protected]=>
  int(1)
  ["file":protected]=>
  string(17) "engine-exceptions.php"
  ["line":protected]=>
  int(1)
  ["trace":"BaseException":private]=>
  array(0) {
  }
  ["previous":"BaseException":private]=>
  NULL
}

Скоро!


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

И вы можете помочь сделать еще лучше.

Проверь свой код

Возьмите PHP7 vagrant box от Расмуса и запустите ваши тесты или проверьте по своему чек-листу ваше приложение. Сообщите о багах в проект, повторяйте регулярно :)

Помоги GoPHP7-ext

Одним из основных препятствий для PHP7 является большое количество работы по обновлению всех расширений для работы с новым Zend Engine 3.

Если вы используете расширение, которое не слишком популярно и известно, и вам не нравится уровень его поддержки, или же у вас есть свои собственные расширения — посмотрите на проект GoPHP7-ext и примите участие, перенеся свои расширения на новый движок.

Документация

Каждая новая фича PHP7 имеет свой RFC. Все они могут быть найдены в вики PHP.net и являются хорошей отправной точкой для написания новой документации. Вы можете сделать это онлайн в GUI среде, в том числе и закоммитить (если у вас есть карма) или отправить патч на проверку.

Заключение


РНР 7 будет великим!

Протестируйте ваши приложения. Помогите перенести расширения.

P.S. вы уже пробовали PHP7? Как вы относитесь к нововведениям? Есть ли что-то, с чем не согласны? Когда вы планируете перейти на новую версию? Напишите свои мысли по этому поводу в комментариях.

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


  1. evnuh
    18.05.2015 16:06
    -10

    Зачем перед именем класса всегда писать \?


    1. iGusev Автор
      18.05.2015 16:09
      +8

      namespace A\B\C;
      class Exception extends \Exception {}
      
      $a = new Exception('hi'); // $a - это объект класса A\B\C\Exception
      $b = new \Exception('hi'); // $b - это объект класса Exception
      
      $c = new ArrayObject; // фатальная ошибка, класс A\B\C\ArrayObject не найден
      

      Использование пространств имен: переход к глобальной функции/константе


  1. andrewnester
    18.05.2015 16:08
    +6

    лично очень жду именнованные параметры в методах/функциях, надеялся, что, быть может, придут к консенсусу до PHP 7, но к сожалению нет :(


  1. mark_ablov
    18.05.2015 16:15
    +5

    ?? джва десять лет ждал :)


    1. maximw
      18.05.2015 16:38
      -5

      По-мне так просто сахарок. Ничего принципиально нового не вносит. Тренарник + isset() или empty() вполне себе годный для этих целей. Разве что цепочки не так красиво делаются, но цепочки это уже совсем экзотика.


      1. AHDPEu
        18.05.2015 16:54
        +1

        Для красоты всегда хватало

        function is(){
          foreach(func_get_args() as $v){
            if(!is_null($v)){
              return $v;
            }
          }
        }
        
        $value = is(null,null,false,true,1);
        


        1. bolk
          20.05.2015 09:24

          Дело в isset, а не в проверке на null.


          1. AHDPEu
            09.06.2015 11:55

            function is(&$args) {
                foreach (func_get_args() as $v) {
                    if (!is_null($v)) {
                        return $v;
                    }
                }
            }
            
            $arr = array('a' => 1, 'b' => false);
            
            var_dump(is($arr['c'], $arr['b'], $arr['a']));
            

            Работает как isset


            1. bolk
              09.06.2015 12:01
              +1

              Ага, почти:

              <?php
              ini_set('display_errors', true);
              error_reporting(-1);
              
              function is(&$args) {
                  foreach (func_get_args() as $v) {
                      if (!is_null($v)) {
                          return $v;
                      }
                  }
              }
              
              $arr = array('a' => 1, 'b' => false);
              
              var_dump(is($arr['a'], $arr['b'], $arr['c']));


              PHP Notice:  Undefined index: c in test.php on line 15
              
              Notice: Undefined index: c in test.php on line 15
              int(1)
              


              1. AHDPEu
                09.06.2015 14:52

                У меня 5.4.32 версия под винду. Уведомление о несуществующим индексе не выскакивает. Разбираться, почему так происходит, совсем не хочется.
                Проверил на убунте, так же версия из 5.4 ветки, уведомление есть.
                Печаль, что тут сказать. А счастье было близко.


                1. iGusev Автор
                  09.06.2015 14:59

                  Разбираться, почему так происходит, совсем не хочется.

                  Ответ содержится прямо в вопросе:
                  версия под винду

                  Используйте идентичное окружение и будет вам счастье.


                  1. AHDPEu
                    09.06.2015 15:26

                    Используйте идентичное окружение и будет вам счастье.

                    Так исторически сложилось — сервера с виндой, Internet Explorer, бюрократия…

                    А убунта личная.


                    1. REZ1DENT3
                      11.06.2015 10:07

                      <?php
                      
                      function is() {
                          foreach (func_get_args() as $v) {
                              if (!is_null($v)) {
                                  return $v;
                              }
                          }
                      }
                      
                      $arr = array('a' => 1, 'b' => false);
                      
                      var_dump(is($arr['a'], $arr['b'], $arr['c']));
                      


                      дерзай

                      PS сделать надо «default» return значение


                      1. AHDPEu
                        11.06.2015 14:04

                        Если принять тот факт, что первый параметр это «default» значение, то и эта задача решается.

                        function is($default = null, &$args1 = null, &$args2 = null, &$args3 = null) {
                            for ($i = 1; func_num_args() > $i; $i++) {
                                $value = func_get_arg($i);
                                if (!is_null($value)) {
                                    return $value;
                                }
                            }
                            return $default;
                        }
                        
                        $arr = array();
                        $return = is(false, $arr['a'], $arr['b'], $arr['c']);
                        var_dump($return);
                        


                        1. Fesor
                          11.06.2015 14:15

                          Чуть чуть PHP5.6

                          function is($default = null, ...$args) {
                              foreach($args as $arg) {
                                   if (!is_null($arg)) {
                                       return $arg;
                                   }
                              }
                          
                              return $default;
                          }
                          
                          $arr = array();
                          $return = is(false, $arr['a'], $arr['b'], $arr['c']);
                          var_dump($return);
                          


                          1. AHDPEu
                            11.06.2015 14:28

                            для 5.6 я так же описывал вариант, несколькими комментариями ниже.
                            Нужно добавить передачу параметров по ссылке. Иначе будут выскакивать уведомление о несуществующем индексе.

                            function is($default = null, &...$args) {
                              ...
                            }
                            
                            


                            Магия…


                1. bolk
                  09.06.2015 15:07
                  +1

                  А вы запустите мой-то пример вместо своего. У вас там первый и единственный параметр по ссылке передаётся, а вы именно туда отсутствующий подставили, вот и вся разгадка.


                  1. AHDPEu
                    09.06.2015 15:23

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

                     function is(&$args1=null, &$args2=null, &$args3=null, &$args4=null, ...) {
                    


                    Ещё вариант для версии 5.6
                    function is(&...$args) {
                    


      1. mark_ablov
        18.05.2015 17:23
        +7

        Да это-то понятно, но тем не менее писать

        $blablabla ?? $default ?? 'None'

        приятнее чем
        isset($blablabla) ? $blablabla : (isset($default) ? $default : 'None')


        1. symbix
          18.05.2015 19:45
          -10

          Если приходится часто писать isset, это что-то не то с архитектурой.


          1. mark_ablov
            18.05.2015 19:51
            +1

            Ну как сказать, внешние данные же принимать как-то нужно.
            В том числе и опциональные. И не всегда можно обойтись указанием $default в $request->param().
            + данные моделей тоже могут быть необязательными.


            1. symbix
              18.05.2015 21:39
              -5

              $request->get('key', 'default')

              Для «не всегда» — проверка на value lambda внутри обработчика default (как в Laravel):
              $request->get('key', function() {
              return result_of_some_computations;
              });


              1. mark_ablov
                18.05.2015 21:43
                +3

                Да, признаю, куда удобнее лепить многословное php-замыкание (небось еще и замкнуть внешние переменные придётся через use), чем написать простой one-liner.


                1. symbix
                  18.05.2015 22:48
                  -4

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


                  1. mark_ablov
                    19.05.2015 04:16
                    +4

                    Каким образом isset() нарушает принципы инкапсуляции?

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


                    1. symbix
                      19.05.2015 16:53

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


                      1. sferrka
                        20.05.2015 09:26
                        +2

                        Не все параметры, обрабатываемые в контроллере, являются полями модели.


                        1. symbix
                          20.05.2015 18:15

                          В контроллере вообще не должны обрабатываться поля модели.

                          Касаемо же request-объекта — если нужно что-то сложнее, чем присвоение значения по умолчанию, это признак протекания бизнес-логики в контроллер.


                          1. sferrka
                            20.05.2015 19:08

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


                            1. symbix
                              20.05.2015 19:44

                              Какой дернуть метод — это уровень роутера.

                              Какую модель — мм, не могу придумать случая, который не сводится к передаче поля запроса в фабрику. Если вдруг что-то будет зависеть от наличия поля — мм, окей, это будет if ($request->has('foo')), куда тут всунуть оператор ??, все равно непонятно.


                              1. sferrka
                                20.05.2015 20:05

                                Какой дернуть метод — это уровень роутера.

                                Я про метод модели.
                                Если вдруг что-то будет зависеть от наличия поля — мм, окей, это будет if ($request->has('foo')), куда тут всунуть оператор ??, все равно непонятно.

                                Входящее поле не обязано быть таким простым, это может быть JSON, XML, массив и т.д. Его нужно будет распарсить в контроллере, выяснить нужные детали, а потом может и модель вызывать не придется, какой-нибудь редирект получится. И ваш пример с «has» ничем по сути не отличается от isset, вы ведь говорили именно о том, что не нужно проверять наличие существования полей в контроллере.


                                1. symbix
                                  20.05.2015 20:47

                                  Я говорил именно про isset, намекая на две плохие практики — непосредственную работу с superglobals и anemic models. Касаемо JSON etc — _иногда_ это действительно надо, когда есть какой-то особый случай и такая необходимость возникает в конкретном методе 1 раз. В общем же случае — это признак «толстого контроллера» и звоночек, говорящий о необходимости декомпозиции: JSON принципиально ничем не отличается от form-urlencoded, и точно так же может преобразовываться в request и подлежать маршрутизации.


                              1. Fesor
                                20.05.2015 23:27

                                это уровень роутера

                                Есть даже такой уровень? Мне казалось это все уровень фронт-контроллера.


                                1. symbix
                                  21.05.2015 00:34

                                  Фронт-контроллер — это понятие довольно абстрактное. Много что туда относят: единая точка входа, подключение composer autoloader, инициализация DI, сборка реквеста, подключение какой-нибудь дебаг-консоли в отладочном режиме… Я предпочитаю не использовать этот термин вообще, он слишком широк.


                                  1. Fesor
                                    21.05.2015 00:44

                                    ну как, подключение composer autoload я лично считаю процессом бутстрапинга, и это еще не фронт контроллер. Фронт контроллер в моем понимании это вот такой вот простой интерфейс (будем уже писать в терминах PHP7):

                                    interface Controller {
                                        public function handle(Request $request): Response;
                                    }
                                    


                                    а уж что там внутри особо разницы не имеет, маршрутизаторы ли там, иссеты и ифы со свитчами…


                                    1. symbix
                                      21.05.2015 02:10

                                      В том-то и проблема с этим термином. Авторы Symfony, например, считают иначе:

                                      symfony.com/doc/current/cookbook/configuration/front_controllers_and_kernel.html#the-front-controller


  1. KlonD90
    18.05.2015 21:26
    +1

    Есть уже гид как старые extensions переписывать чтобы в PHP7 работало? Типа XSLCache того же.



  1. Blumfontein
    18.05.2015 21:41

    Со всем согласен, но BaseException — это какой-то отвратительный костыль


    1. iGusev Автор
      18.05.2015 22:15
      +3

      Вынужденный. Слишком уж много людей ленятся наследоваться от \Exception и отлавливать именно свои исключения :(


      1. symbix
        19.05.2015 01:00

        Можно было бы отнаследоваться от ErrorException. Его в 99% случаев используют по делу.

        А код, смешивающий «свои» обработчики исключений с «олдскульной» обработкой engine errors, все равно ж сломается, не?


        1. iGusev Автор
          19.05.2015 01:07

          ErrorException extends Exception {
          

          Документация

          Тут скорее полезно именно добавление абстрактного \BaseException, после которого уже не будет возникать подобных случаев

          BaseException (abstract)
           +- EngineException
           +- ParseException
           +- Exception
               +- ErrorException
               +- RuntimeException
                   +- ...
               +- ...
          


          1. symbix
            19.05.2015 02:13

            А как введение еще одного базового класса поможет остаться работоспособным коду вида

            $f = fopen(...);
            if (!$f) {
            $error = error_get_last();
            // handle error…
            }

            ?

            Никак же, улетит исключение еще из fopen.

            Другое дело, что, наверное, с этой точки зрения необработанное исключение лучше, чем catch чего-то «не того». Но кода, обрабатывающего php-ошибки «по-старинке», не заворачивающего их через set_error_handler, и при этом в принципе использующего исключения, я вообще не встречал на практике. Так что тут лечится какой-то невообразимый сферический говнокод в вакууме, а нормально написанный код, обрабатывающий ErrorException, придется переписывать.


  1. itcoder
    18.05.2015 22:22
    +2

    Да, php был и остается живее всех живых, если еще после выхода PHP 7 проекты с веток 5.4 — 5.6 удастся почти безболезненно и быстро перевести, то цены не будет. В случае с python конечно грустно получилось, люди так до конца все и не перешли на 3 версию из за медленного перехода пакетов и библиотек.


    1. Casus
      19.05.2015 13:45
      -6

      Посмотрите на коммент ниже от Arilas

      У PHP нет будущего. Принятие рфц, в основном базируется на совместимости, и не способно, исправить архитектурные провалы языка. Дело становится совсем худо, если посмотреть с каким трудом принимаются необходимые для развития языка рфц. Для того чтоб принялись рфц, авторам приходится их извращать до неузнаваемости, а после внедрения подобных фич, приходишь ко мнению, что лучшеб вообще не делали.

      В общем, не вырастит этот язык до энтерпрайза, особенно с нынешними конкурентами. Нет, нет, писать большие проекты можно, но это как в м$ пейнте рисовать анимацию.


      1. itcoder
        19.05.2015 14:37
        +1

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


        1. Casus
          19.05.2015 17:19
          -3

          <irony> Простите был не прав, рфц принимаются на ура и не доводят контрибютеров до желания уйти! Реализация примесей — безупречна. А ооп, джависты обзавидуются! А скорость, еслиб не си, всё писали бы на php!!! синтаксис — эталон, который стоит в университетах подавать как учебный материал. </irony>

          С точки зрения бизнеса у php есть право на жизнь, пока… Но сегодня написать «hello world» на play2, проще чем на symfony2. И не надо говорить про порок вхождения, я не о домашних сайтах на коленке.


          1. bolk
            20.05.2015 09:26
            +2

            «Порок вхождения»! Боже… это шедевр!


            1. Casus
              20.05.2015 11:09

              Рад, что и вам понравилось, но лавры не мои.


          1. Fesor
            20.05.2015 10:40

            контрибютеров до желания уйти

            Вы про драму со статическим тайп хинтингом?


            1. Casus
              20.05.2015 11:07
              -2

              В целом да, но ведь это уже не первый громкий уход.


  1. Arilas
    18.05.2015 23:32
    +2

    Я уже даже доклад про «особенности» PHP 7 делал, очень много кривых и недоделанных возможностей:
    1. Нет nullable, если что-то может вернуть null, тип указать нельзя (привет find по id)
    2. declare(strict_types=1); — вот этот вот костыль просто говорит, что входить может int, выйдет string (оно если что отконвертирует), без него Type Hints — это конвертеры типов (написано string, передает объект, оно дергает __toString()).

    Несколько примеров, и комменты снизу кода:

    class Foo {
        public static function test() : self {
            return new Foo;
        }
    }
    class Bar extends Foo {
        public static function test() : parent {
            return new Bar;
        }
        public function action() : int {
            return 5;
        }
    }
    

    Как вам parent?
    namespace Bar;
    interface Foo {}
    interface FooManager {
    	public function bind():Foo;
    }
    class FooImpl implements Foo {}
    class FooManagerImpl implements FooManager {
    	public function bind():FooImpl {
    		return new FooImpl();
    	}
    }
    //Fatal error: Declaration of Bar\FooManagerImpl::bind() must be compatible with Bar\FooManager::bind(): Bar\Foo in *** on line 14
    

    Нет переопределения return'а, в других языках есть
    function foo(string $a):bool {
        return $a + 1;
    }
    var_dump(foo(5));//bool(true)
    

    А почему бы и нет?
    declare(strict_types=1);
    function foo($a):float {
        return $a + 1;
    }
    var_dump(foo("asdf5"));//float(1)
    

    Тоже нормально
    declare(strict_types=1);
    function foobar(float $abc): int {
        return ceil($abc + 1);
    }
    foobar(123.0);
    

    А тут ошибка, ceil возвращает float (хотя строку в предыдущем примере отконвертировало)


    1. iGusev Автор
      19.05.2015 15:08

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


    1. symbix
      19.05.2015 23:18

      strict_types — это вынужденный костыль для BC. Отсутствие nullable return types — да, серьезный косяк. С parent странно, конечно; видимо, из-за проблем с производительностью не проверяется цепочка наследования; хотя я бы тут использовал интерфейс. С floor/ceil/round проблема в том, что на 32-битных системах невозможно в общем случае сконвертировать в int без потерь, полагаю.


    1. bolk
      20.05.2015 09:33

      Ну слушайте, чего вы хотите от альфы-то? Про такие вещи пишите в bugs.php.net, сделаете язык лучше.


      1. Arilas
        20.05.2015 10:49
        +1

        Это не баги, это то, что вошло в PHP 7 и не будет изменено(новые RFC не принимаются в PHP 7).
        1. По поводу Nullable — уже есть RFC, который висит очень давно, и даже не рассматривался (хотя ссылки на него есть в Return Type Hints)
        2. По поводу переопределения типа возврата, в изначальном RFC по Return Type Hints это было, и даже изначально его таким приняли, потом вспыло про BC и про перекрестную имплементацию, в итоге убрали.
        3. По поводу Scalar Type Hints, изначальный RFC был сделан очень интересно, там даже было оптимизировано выделение памяти для типов (по примеру статического анализатора типов в strict mode у hack)
        4. По поводу нового оператора ??, зачем-то изобрели новый костыль, могли бы заюзать то, как это допустим в том же js: var name = obj.name || «Name»
        5. По поводу нового оператора сравнения <=>, я не совсем понял зачем он (зачем сравнивать массивы и объекты?) а для чисел получать результат сравнения в виде -1 либо 0 (если равны) либо 1 (если больше) тоже не совсем понятно зачем.

        Вообще на PHP 7 я возлагал большие надежды, сейчас смотрю на HACK от HHVM, там есть очень много плюшек и нормальные Type Hint's(даже для массивов, Векторов, Мап, Callable и т.д.). А их статический анализатор — это просто сказка, он находит 90% багов прямо во время сохранения файлов.


        1. bolk
          20.05.2015 11:27
          +1

          4. По поводу нового оператора ??, зачем-то изобрели новый костыль, могли бы заюзать то, как это допустим в том же js: var name = obj.name || «Name»
          Во-первых, зачем использовать то, как в JS, если это не JS?

          Во-вторых, $var || $smth имеет тип возвращаемый тип bool, менять это — ломать обратную совместимость.

          В-третьих, «||» в JS и «??» в PHP ни разу не эквивалентны. $obj->name()['abc'] ?? $smth сработает в PHP, но не свалится в JS, если $obj не определён.


          1. Arilas
            20.05.2015 11:44

            $obj->name()['abc'] ?? $smth

            Оно разве отловит Exception о том, что мы пытаемся вызвать метод у null? Это Recoverable Exception, но все же в определении ?? не указано, что он Exception'ы отлавливает, если бы он так себя вел, то помойму использовать его вообще нельзя (если нужно наружу выкинуть Exception, то что тогда?)

            Проверил:

            <?php
            $a = $obj->name()['abs'] ?? 'asfd';
            var_dump($a);
            

            Вывод:
            Fatal error: Call to a member function name() on null in *** on line 2
            

            То-есть он аналогичен js по поведению:
            var a = obj.some() || 5
            

            Uncaught ReferenceError: obj is not defined
            


            Большинство php разработчиков пишет часто на js, сейчас такая специфика, очень много client-side кода, и разное поведение одного и того же функционала часто приводит к банальным ошибкам (в большом проекте всегда есть коммит, который фиксит strpos с === либо сравнение с -1 вместо false во многих случаях)


            1. bolk
              20.05.2015 12:15

              Да, действительно, тут я сглупил. isset поведёт себя так же. Скорее надо проверять что-то вроде

              $obj->name->value ?? 'asdf';


              1. Arilas
                20.05.2015 12:40

                Да, в данном случае сработало, но помойму это костыль, сразу через два null'а идти, напрямую дергать поля, а не методы, если у кого-то такое будет в коде — нужно по рукам сразу же :)


                1. bolk
                  20.05.2015 12:53

                  Во всех языках это встречается. В JS — вообще постоянно.


    1. Fesor
      20.05.2015 10:46

      Нет nullable

      Ну тут да, это проблема. К сожалению в момент накала страстей, когда принимали статический тайп хинтинг, разработчица которая ввела эти RFC просто скипнула, и из всех ее RFC (в числе которых был и нуллабл) сделали по сути только тайп хинтинг для скаляров. Думаю к версии 7.1 сделают.

      оно если что отконвертирует

      при strict_types=1 оно кинет ошибку, если вы что-то не то куда-то передали.

      Как вам parent?

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


      1. Fesor
        20.05.2015 11:00

        А почему бы и нет?

        А что в этом примере плохого? Вы передали значение, которое нельзя без потери данных привести к флоту. В результате там 0.

        А если так:
        declare(strict_types=1);
        function foo(float $a):float {
            return $a + 1;
        }
        
        var_dump(foo("asdf5"));
        //Fatal error: Argument 1 passed to foo() must be of the type float, string given
        


        или так:

        declare(strict_types=1);
        function foo(float $a):float {
            return $a + 1;
        }
        
        var_dump(foo("asdf5"));
        //Fatal error: Argument 1 passed to foo() must be of the type float, string given
        // к слову раньше там писало про catchable error....
        


        1. Arilas
          20.05.2015 11:18

          Если сравнить strict types с тем же HACK, то он обозначает, что в файле должны быть указаны все типы, для всех входящих/выходящих переменных, в PHP 7 strict_types просто отключает некоторые конвертирования типов (то-есть если указан int, а передается string, он не будет строку в int перегонять).

          Я знаю как сделать чтобы код, который я скинул работал, проблема в другом, что все равно RFC очень поверхностные. Что дает strict mode в HACK:
          1. Полный статический анализ кода, и типов каждой переменной (в hh_client можно узнать тип каждой переменной, указав номер строки и символа, также можно получить автодополнение в любой редактор для любой части кода)
          2. Из-за того, что типы известны, есть экономия памяти, потому что не нужно делать структуру как в PHP для хранения значения
          3. Исходя из пункта выше, JIT некоторые части кода после прогрева переводит в нативный код (если помните HPHPc, то он компилил тогда весь проект, здесь, только если может, и может отдельные части)

          Что дает strict_types в PHP 7:
          1. Дополнительные проверки в runtime для того, чтобы проверить, что переменная действительно нужного типа
          2. Нельзя статически анализировать(нет полной картины типов, из-за отсутствия Nullable)


          1. Fesor
            20.05.2015 13:46
            +1

            Исходя из пункта выше, JIT некоторые части кода после прогрева переводит в нативный код


            JIT все и всегда переводит в нативный код как бэ, просто со временем оптимизирующий компилятор делает все еще лучше, а профит в производительности достигается за счет того что скаляры не оборачиваются в обертку, то есть за счет анбоксинга. Посмотрите на JS, там вообще все типы динамические и оптимизации носят спекулятивный характер. То есть оптимизатор на основе статистики просто говорит что 99% что там инт, если вдруг чего откатывайся.

            В PHP7 нет JIT, а к 8-ой версии в принципе не проблема это сделать. Ну и да, судя по тому что от билда к билду поведение того же тайп хинтинга меняется, можно судить что это еще не стабильная реализация. И да, если вы считаете что-то багом, надо бежать в багтрекер а не пилить RFC.


  1. REZ1DENT3
    18.05.2015 23:42

    За все время использования PHP в своих проектах, так и не придумал задачи с использованием генераторов… Пользуется, кто ими и для какой цели?


    1. Arilas
      18.05.2015 23:47
      -1

      Как альтернатива lock'ам в cron job'ах, из PDOStatement каждый раз выбираем только одну вещь(ей сразу ставим статус, чтобы она не подходила под условия выборки), и если запрос достаточно большой, то это дает профит(несколько cron job одновременно в несколько процессов могут спокойно несколько часов работать, что дает хороший профит).


    1. KlonD90
      19.05.2015 00:14

      Ну самая стандартная задача генерировать id внутри транзакции еще какой-то дополнительный.
      Lazy evaluation/Stream function типа чтения из файлов и т.д. Ну вообщем-то не сказать, чтоб обширное поле деятельности было, но вполне себе имеет право на жизнь.


    1. iGusev Автор
      19.05.2015 00:22
      +1

      Есть хорошая статья на эту тему: Генераторы в действии


    1. kwolfy
      19.05.2015 10:30
      +2

      Например, если нам нужно перевести массив в вид [userId => user]. Преимущество — не расходуется память на лишнюю переменную.

      К тому же, разница на лицо

      function users()
      {
          foreach($users as $user)
              yield $user['id'] => $user;
      }
      

      function users()
      {
          $_users = [];
      
          foreach($users as $user)
              $_users[$user['id']] = $user;
      
          return $_users;
      }
      


    1. bolk
      20.05.2015 09:28

      Вы посмотрите языки, где они есть давно, это лучший способ их понят. Мне вспоминается хорошая статья Вани Сагалаева, где он рассказывал как он применил генераторы для конкретной задачи (язык там — «Пайтон», но кажется это не мешает восприятию): softwaremaniacs.org/blog/2010/09/18/ijson


      1. isagalaev
        21.05.2015 03:09

        А вот я сейчас прямо пишу статью, где парсер ijson на Rust реализую, и там будет как раз очень показательный пример полезности `yield`. Завтра, надеюсь, допишу.


    1. Fesor
      20.05.2015 10:43

      Самый распространенный пример использования генераторов — реализация xrange для экономии памяти на больших размерах массивов.

      А еще есть такие клевые штуки как корутины.


      1. bolk
        20.05.2015 11:33

        Самый распространенный пример использования генераторов — реализация xrange для экономии памяти на больших размерах массивов.
        xrange с лёгкостью заменяется циклом. Это где же это самый распространённый пример?


        1. Fesor
          20.05.2015 11:46
          +1

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

          А ну и еще стрим-парсеры удобно делать.


          1. bolk
            20.05.2015 15:53

            Ну, короткого синтаксиса (генераторный выражений из «Пайтона») мне в ПХП не хватает, но в основном — годно. Для интереса запилил пайтонячий itertools на генераторах ПХП, получилось вполне съедобно.


    1. AmdY
      11.06.2015 22:32

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


  1. boombick
    19.05.2015 06:01
    +2

    Заключение особо порадовало :) PHP — уже велик! (ударение поставьте сами :))


    1. mapron
      19.05.2015 13:48
      +1

      С ударениями там 4 варианта)


  1. Mercury13
    20.05.2015 12:32

    Круто! — уходит функция at(), которую я вечно пишу где-то

    function at($array, $index, $default)
    {
        return isset($array[$index]) ? $array[$index] : $default;
    }
    
    ...
    
    $name = at($_POST, 'name', '');
    // Стало
    $name = $_POST['name'] ?? '';
    


    1. Arilas
      20.05.2015 12:42
      -2

      Давно уже можно так:

      <?php
      $a = $_POST['a'] ?: 'asdf';
      var_dump($a);
      

      И без всякого ??


      1. Mercury13
        20.05.2015 13:00

        Надо будет проверить, насколько работает «Элвис». Он в PHP относительно недавно, а т.к. я не профессионал, за этим не слежу.
        1) Как я понял, он работает по вычислимости в true, т.е. дока скрывает, что будет, когда такой переменной вообще нет.
        2) По той же причине для строки '0', скорее всего, вернёт значение по умолчанию.


      1. bolk
        20.05.2015 15:54

        $a ?: $b это аналог $a? $a: $b, а $a ?? $b — isset($a)? $a: $b

        совершенно разные вещи.