Новое расширение PHP позволяет добавлять произвольные методы к скалярам

    На Github появилось новое экспериментальное расширение PHP “scalar_objects”, с помощью которого можно добавлять любые методы к числам, строкам, массивам.
    Выглядит это примерно так:

    $result = $string->replace('shit', 'candy')->remove(',')->toUpper()->split(" ")->sort();
    

    Симпатично, не правда ли?
    Инструкции по установке можно найти по ссылке выше, требуется PHP 5.4.
    Имейте ввиду, что расширение находится в очень ранней альфа-версии, первый коммит датирован 24 января.
    Радует, что автор расширения — контрибьютор PHP Никита Попов: есть вероятность, что подобное API когда-нибудь добавят в ядро интерпретатора.
    Дальше я просто покажу как можно использовать эти новые возможности в разработке.

    Допустим, у нас есть вот такая строка:

    $string = 'lemon, orange, shit, banana, apple';
    

    Задача:
    • заменить shit на candy;
    • убрать запятые;
    • перевести строку в верхний регистр;
    • разделить слова и поместить их в массив;
    • отсортировать этот массив по алфавиту.

    Обычно это делается так:

    $string = str_replace('shit', 'candy', $string);
    $string = str_replace(',', '', $string);
    $string = strtoupper($string);
    $array = explode(' ', $string);
    sort($array);
    

    С помощью данного расширения задача решается в одну строчку:

    $result = $string->replace('shit', 'candy')->remove(',')->toUpper()->split(" ")->sort();
    

    Все понятно и красиво, только как такое провернуть?
    1. Устанавливаем расширение scalar_objects;
    2. Создадим классы-хендлеры для строк и массивов с нужными нам публичными методами:

    class StringHandler {
    
        public function replace($from, $to) {
            return str_replace($from, $to, $this);
        }
    
        public function split($separator, $limit = PHP_INT_MAX) {
            return explode($separator, $this, $limit);
        }
    
        public function toUpper() {
            return strtoupper($this);
        }
    
        public function remove($what)
        {
        	return $this->replace($what, '');
        }
    }
    
    class ArrayHandler
    {
        public function sort($flags = SORT_REGULAR)
        {
            sort($this, $flags);
            return $this;
        }
    
        public function count()
        {
            return count($this);
        }
    }
    

    3. Привяжем методы для строк и массивов к соответствующим классам:

    register_primitive_type_handler('string', 'StringHandler');
    register_primitive_type_handler('array', 'ArrayHandler');
    

    После этого у строковых переменных появились методы replace, split, remove и так далее, у массивов — метод count(). Естественно, таких полезных методов можно добавить сколько угодно. Обратите внимание, что все они — публичные, а в качестве обрабатываемой строки(массива, числа) выступает $this. Подобным образом обработчики можно присвоить любому скалярному типу.
    Также следует отметить, что конструкции вида:

    "foobar"->trim();
    

    вызывают Parse error, то есть методы можно вызывать пока только у переменных.
    Также в репозитории есть пример реализации удобного API «запросов к строкам»:

    // содержит ли строка любую из указанных подстрок?
    $str->contains(str\anyOf(['foo', 'bar', 'hello', 'world']));
    
    // не заканчивается ли строка любым из этих значений?
    $str->endsWith(str\noneOf(['.c', '.ho', '.lo']));
    

    Очень наглядно и гораздо проще для запоминания, в отличии от strspn, strcspn, strpbrk.

    Хочется верить, что разработчики PHP включат подобный функционал в следующие версии. Чуточку такого синтаксического сахара сделает разработку намного приятней, а разработчиков — счастливее.

    Ссылка на проект: https://github.com/nikic/scalar_objects/
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 68

      +13
      В других языках программирование подобное реализовано из за того что переменная уже объект, в PHP нет. Зачем городить костыли?
        +24
        Для удобства, видимо. А то с ума можно сойти от этих str* функций.
          +6
          Действительно, str_* функции раздражают и провоцируют новичков писать велосипеды.
            0
            Или хотя бы написать ООП-обертку. Честно, все API пхп, калом мамонта лежащее с версии 4, повергает в уныние.
              +1
              Меня периодически посещают такие мысли. А вообще по хорошему стоило бы форкнуть PHP и избавиться от этого кала :)
                0
                Полностью поддерживаю! Всякие array_* str_* это просто грустно!
            0
            Постепенно двигаться в ту же сторону.
              +2
              ruby-like php? было бы симпатиШно.
              • UFO just landed and posted this here
              +1
              Хм, впринципе все предельно ясно, скрыть за фасадом кучу str_* и все, а дальше уже будет «небольшая» свобода действий, хотя конечно я сомневаюсь что это расширение будет кто-то использовать сильно очень)
                0
                А я бы уже с радостью использовал. Но, немного подожду, пока станет более ясным будущее этого расширения (будет ли включено в сам РНР/будет как сторонний модуль/будет заброшен)
                  +1
                  Автор пишет, что
                  Once the APIs are figured out it will be proposed for inclusion into PHP.

                  Зная консервативность разработчиков PHP, говорить об реальном использовании этого расширения еще очень рано.
                  Кроме того, API будет еще много раз меняться.

                  Но главное — чтобы не произошло того, что каждый будет писать свои обертки-велосипеды.
                    0
                    На самом деле вполне вероятно что проскочит, им обычно главное чтобы BC break-ов не было :)
                      0
                      Разве в случае геттеров-сеттеров были?
                        0
                        Да вот нет. Но по ним большинство проминусовавших просто отмолчались. Мне, правда, тоже не очень нравилась такая реализация, с автоматически создаваемыми полями, которые доступны исключительно из их аксессоров, с областью видимости тоже много вопросов было. Про оверрайдинг вообще вопрос еще — нужен ли он, особенно вопрос — нужно ли изменять область видимости при этом.
                +1
                Javascript?
                  0
                  Ага, есть риск начать холивар на тему «что есть объект и не объект в Javascript»
                    0
                    Зачем нужен холивар? В стандарте же описано всё.
                +1
                не понимаю смысла в таких велосипедах.

                ну ок, сэкономили вы 4 строки (хотя можно объединить 2 str_replace в один), зато потратили N минут времени программиста, который после вас будет читать этот код. а если использовать такие конструкции повсеместно в проекте — время только растет, т.к. новичок не может использовать стандартные функции, которые он знает (пацаны не поймут) — приходиться постоянно рыться в библиотеке, пока не запомнишь «новый стандарт».

                плюс ко всему, по своему опыту могу сказать, что ситуации, когда нужно выполнить 3 и более манипуляций со строками в одном месте очень и очень редки. а если нет, то обычно они однотипны и выносятся в одно место.
                  +12
                  смысл в большой выразительности языка. Это по сути Extension Methods из С# для скалярных функций
                  дело ведь не сокращении количества символов, а в однотипности кода.
                  к тому же, если это станет часть ядра (очень надеюсь) то появиться поддержка в иде и проблем с пониманием не будет
                    +2
                    новичок не может использовать стандартные функции, которые он знает (пацаны не поймут) — приходиться постоянно рыться в библиотеке, пока не запомнишь «новый стандарт»

                    И потом еще удивляются стереотипам про тупых пехепешников. «А мы naming conventions не используем, а то новичок не сможет называть переменные, как привык.»
                    +1
                    Отличная новость. Если правильно сформировать API, это будет вещью.

                    Скажем прямо, вау!
                      +2
                      Естественно, писать в каждом проекте разные реализации одного и тоже — зло. Как и попыткы вместить вызов десятка методов в одну строку.
                      Совсем другое дело, если все это включат в ядро PHP и появится поддержка этого API в IDE с автодополнением.
                      Вводишь
                      $str->
                      и видишь все доступные методы.
                      А сейчас «пацаны» все равно не помнят все эти strnatcasecmp, strrchr и потому пишут всякие непотребства.
                        +2
                        Это не «экспериментальное расширение»! Это proof of concept, это значит что он не предназначен для использования, и вряд ли будет. Если это будет сделано в ядре, ObjectHandler-ы не будут юзерлэндовыми, они будут заданы в ядре же. Я не думаю, что Никита это делал, чтобы этим пользовались, но да, он не раз говорил, что он хочет сделать работу со скалярными типами как с объектами.

                        Тем, кто говорит «В других языках программирование подобное реализовано из за того что переменная уже объект» — объекты влекут за собой соответствующий оверхед, поэтому нужно настолько, насколько это возможно, держаться со скалярами.
                        Это уже не говоря о том, что пхп мульти-парадигменный.
                          0
                          Я не большой специалист, но мне кажется, что основной части оверхеда можно избежать, если оборачивать скалярное значение в объект непосредственно перед вызовом метода или доступом к свойству — так же, как это сделано в Javascript.
                            0
                            А какой в этом смысл? На каждый вызов метода, в таком случае, нужно создавать объект под этот скаляр? Это ведь тоже время. К тому же тогда нарушается целосность — могут быть два объекта, ссылающиеся на один скаляр.
                              0
                              Не хочу выступать в роли испорченного телефона, погуглите на тему primitive data type wrappers. Вот, например, статья, которая рассказывает, что это такое.
                                0
                                Ок, скажу по-другому — основной оверхед тогда будет не по памяти, а по времени. Серьезно, в этом нет никакого практического смысла, если можно сделать так, как сделано в этом проекте.
                          +1
                          Еще бы добавили перезагрузку операций для объектов.

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

                             $a = 1;
                             $a->plus(2);
                          
                            //и
                          
                            $a = "1";
                            $a->plus(2);
                          
                            //по разному работает.
                          
                          
                            0
                            > А для вышеупомянутого, скорее всего сначала надо реализовать более менее жесткую типизацию, а то потом будем гадать, почему:

                            на коде
                              $a = "1";
                              $a->plus(2);
                            

                            будет фатал, ибо будет использован StringHandler, в котором нет метода plus()
                              0
                              Это просто пример, можно пофантозировать на тему других примеров, например:
                               class StringHandler {
                                    public function each($func) {
                                         for($i = 0; $i < strlen($this); $i++) {
                                               $func(substr($this, $i; 1));
                                         }
                                         return $this;
                                    }
                              }  
                              
                              class IntegerHandler {
                                    public function each($func) {
                                         for($i = 0; $i < $this; $i++) {
                                               $func($i);
                                         }
                                         return $this;
                                    }
                              }
                              
                                0
                                И это, кстати, вполне себе проблема. А ((int)$a)->plus(2) уже не сильно элегантно выглядит.
                                0
                                Я за специальный оператор создания объектов:
                                $str = #String('bob')->capitalize();
                                // Вместо
                                $str = (new String('bob'))->capitalize();
                                
                                  +2
                                  Этот символ зарезервирован. А ну-ка, давайте поиграем в игру «найди незарезервированный в PHP символ!» В internals mail-листе тут недавно так на тему аннотаций развлекались, так и не нашли :(
                                    –3
                                    Хмм, для каких целей? Что-то я ничего не нагуглил по этому поводу.
                                      +3
                                      Для целей #комментарий до конца строки
                                        0
                                        А, ну да. Что-то затупил)
                                      +4
                                      € и £
                                        0
                                        Тильда (~), ну еще можно пройтись по таким символам как многоточие (…), длинное тире (—), и прочие (©,®,™,№) :)
                                          0
                                          Давайте искать эти символы на клавиатуре в стандартной раскладке. Мы же программировать собираемся.
                                            0
                                            Тильда занята, это побитовое отрицание если не ошибаюсь.
                                        +1
                                        А почему не $str = String::new('bob')->capitalize();?
                                        0
                                        Для этого лучше использовать нормальное, правильное расширение Operator
                                        А операции над скалярами оставить в покое ;)
                                        <?php
                                        class foo {
                                            private $value;
                                        
                                            function __is_greater($val) {
                                                return $this->value > $val;
                                            }
                                        
                                            function __is_greater_or_equal($val) {
                                                return $this->value >= $val;
                                            }
                                        
                                            function __construct($init) {
                                                $this->value = $init;
                                            }
                                        }
                                        
                                        $c = new foo(5);
                                        
                                        var_dump($c > 5);
                                        var_dump($c > 4);
                                        var_dump($c >= 5);
                                        var_dump($c >= 6);
                                        /**--EXPECT--
                                        bool(false)
                                        bool(true)
                                        bool(true)
                                        bool(false)
                                        **/
                                        
                                          0
                                          Что касается типизации, то PHP уже имеет оную в виде SPL Type Handling реализации посредствам PECL либы.
                                          На данный момент имеются:
                                          SplInt, SplFloat, SplEnum, SplBool и SplString.

                                          Не скажу, что это ох как красиво и нативно, на как есть, так есть. Может, кому-то будет интересно.

                                          Пример:

                                          $string = new SplString(«Testing»);

                                          try {
                                          $string = array();
                                          } catch (UnexpectedValueException $uve) {
                                          echo $uve->getMessage(). PHP_EOL;
                                          }

                                          var_dump($string);
                                          echo $string; // Выведет «Testing»

                                          P.S: использовать тэги и линки карма не позволяет, звиняйте.
                                          +5
                                          В одну строчку тоже можно:

                                          sort(explode(' ', strtoupper(str_replace(array('shit', ','), array('candy', ''), $string))));
                                          
                                          // Против
                                          
                                          $result = $string->replace('shit', 'candy')->remove(',')->toUpper()->split(" ")->sort();
                                          


                                          но второй вариант и правда выглядит гораздо интереснее и читабельнее.
                                            +3
                                            Нельзя так, sort принимает по ссылке.
                                              0
                                              Да, согласен. Тут вышел промах. Нужна еще одна строка.
                                              +1
                                              Как раз, потому что мы читаем слева на право и сверху внизу. А вложенность функций заставляет читать задом на перёд, кроме того, это увеличивает использование памяти.
                                                0
                                                Всмысле человеческой памяти? Интерпретатор то и так и так одинаково съест.
                                                  0
                                                  Именно оперативной памяти. Как минимум в некоторых ситуациях на PHP 5.2 потребление увеличивалось вдвое из-за вложенности функций.
                                                    0
                                                    Думаю вы говорите про стек вызовов, его, действительно, растить — память не жалеть. Но в приведенных примерах call stack вовсе не растет.
                                                      0
                                                      Проверил, действительно так, потребление растёт минимально, значит у меня в той задаче это была не первопричина. Мне самому странным это показалось.
                                              0
                                              class String
                                              {
                                                ...
                                                function toUpper()
                                                {
                                                   $this->value = strtoupper($this->value);
                                                   return $this;
                                                }
                                              }
                                              
                                                –2
                                                Строки иммутабельны :) Нельзя так.
                                                  0
                                                  Не совсем Вас понял. Можно :)
                                                    0
                                                    Я вообще не совсем понял к чему вы этот пример привели, но суть в том, что на уровне движка строки нельзя изменять. При каждой конкатенации на самом деле создается новая строка (читай масив каров). Поэтому давать подобное апи в юзерлэнд — вводить в заблуждение юзеров.
                                                    Помимо этого, такая реализация предполагает, что скаляр уже вовсе и не скаляр, а объект.

                                                    Еще по поводу иммутабельности погуглите string interning.
                                                +5
                                                Str('hello, world')->replace(...)->…
                                                Arr(range(10))->…

                                                Можно сделать и сейчас. И тип явно видно, и parse error не будет.
                                                  0
                                                  Масса удовольствия и матов при переезде на новый сервак, или когда ты отдаешь заказчику проект, а поддержкой и настройкой сервера будет заниматься другой разработчик!
                                                    +1
                                                    Такие вещи описываются в требованиях. Тоже мне проблема.
                                                    0
                                                    $result = $string->replace('shit', 'candy')->remove(',')->toUpper()->split(" ")->sort();
                                                    


                                                    Интересно, конечно, но я обычно в таких случаях делаю так:

                                                    $result = sort(explode(' ', strtoupper(str_replace(',', '', str_replace('shit', 'candy', $string)))));
                                                    
                                                      0
                                                      Сорри, прочитал комменты выше и увидел что далеко не один я такой находчивый.
                                                        0
                                                        И таки да, находчивость до добра не доведет — в $result будет лежать boolean ;)
                                                          0
                                                          true, если точнее, ибо фэйла сортировки сложно ожидать при работе со строками
                                                        +4
                                                        Вы правда так и делаете? И что же вы надеетесь получить в $result?
                                                        0
                                                        Меня не оставляет надежда, что в конторе php сделают (если сделают на уровне движка php) по-умному и не станут все скаляры создавать классами по-умолчанию, а предоставят выбор программисту создавать ли их классами или нет (как в java). Чтоб не снижать скорость работы сайта.
                                                          +3
                                                          Полез в чужой код, а там 13 не 13 а объект, который эхается как 42?
                                                            0
                                                            В коде скаляр остается скаляром
                                                            0
                                                            >С помощью данного расширения задача решается в одну строчку:
                                                            >$result = $string->replace('shit', 'candy')->remove(',')->toUpper()->split(" ")->sort();
                                                            >Все понятно и красиво, только как такое провернуть?
                                                            >1. Устанавливаем расширение scalar_objects;
                                                            >2. Создадим классы-хендлеры для строк и массивов с нужными нам публичными методами:

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

                                                            Лучше б переработали наконец в PHP стандартные API и привели в человеческий вид, чем костыли городить.

                                                            Only users with full accounts can post comments. Log in, please.