Позднее статическое связывание в PHP (Часть I)

    phpПозднее Статическое Связывание (Late Static Binding, LSB) является бурно темой обсуждений последние три года в кругах разработчиков PHP (и наконец мы его получили в PHP 5.3). Но зачем оно нужно? В данной статье, как раз и будет рассматриваться, как позднее статическое связывание может значительно упростить ваш код.

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

    С тех пор как позднее статическое связывание было объявлено как грядущая фишка, прошло два года. И вот наконец LSB стало доступно для использования в PHP 5.3. Но это событие прошло незаметно для разработчиков использующих PHP, из заметок только страничка в мануале.

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

    class Beer {
        const NAME = 'Beer!';
          public function getName() {
              return self::NAME;
        }
    }
    class Ale extends Beer {
    	const NAME = 'Ale!';
    }
    
    $beerDrink = new Beer;
    $aleDrink = new Ale;
    
    echo "Beer is: " . $beerDrink->getName() ."\n";
    echo "Ale is:  " . $aleDrink->getName()  ."\n";
    


    Этот код выдаст такой результат:

    Beer is: Beer!
    Ale is:  Beer!
    


    Класс Ale унаследовал метод getName(), но при этом self все еще указывает на класс в котором оно используется (в данном случае это класс Beer). Это осталось и в PHP 5.3, но добавилось слово static. И снова рассмотрим пример:

    class Beer {
      const NAME = 'Beer!';
      public function getName() {
    	  return self::NAME;
      }
      public function getStaticName() {
    	  return static::NAME;
      }
    }
    
    class Ale extends Beer {
      const NAME = 'Ale!';
    }
    
    $beerDrink = new Beer;
    
    $aleDrink = new Ale;
    
    echo "Beer is: " . $beerDrink->getName() ."\n";
    echo "Ale is:  " . $aleDrink->getName()  ."\n";
    
    echo "Beer is actually: " . $beerDrink->getStaticName() ."\n";
    echo "Ale is actually:  " . $aleDrink->getStaticName()  ."\n";
    


    Новое ключевое слово static указывает, что необходимо использовать константу унаследованного класса, вместо константы которая была определена в классе где объявлен метод getStaticName(). Слово static было добавлено, чтобы реализовать новый функционал, а для обратной совместимости self работает также как и в предыдущих версиях PHP.

    Внутренне, основное отличие (и, собственно, причина почему связывание назвали поздним) между этими двумя способами доступа, в том, что PHP определят значение для self::NAME во время «компиляции» (когда симовлы PHP преобразуются в машинный код, который будет обрабатываться движком Zend), а для static::NAME значение будет определено в момент запуска (в тот момент, когда машинный код будет выполнятся в движке Zend).

    Это еще один инструмент для PHP-разработчиков. Во второй части рассмотрим как его можно использовать во благо.

    UPDATE: Вторая часть статьи. Практический пример.

    VIA: Позднее статическое связывание в PHP (Часть I)

    Перевод вот этого: Late Static Binding: a practical example
    Поделиться публикацией
    Комментарии 53
      0
      быть может я недалек, но что-то мне подсказывает, что пользование такой "фишкой" только усложнит понимание и сопровождение кода
        +1
        Я всё ещё опасаюсь в очередной раз с вами спорить, но всё-таки скажу что иногда и это может пригодиться.
        P.S. Ох, чувствую, доказать трудно будет :)
          0
          Сегодня-завтра опубликую вторую часть - там будет пример использования рассмотрен.
            0
            Согласен, похоже на какой-то кошмарик. Почему нельзя просто завести виртуальную функцию, возвращать в ней что надо и переопределять ее в тех классах где это нужно?
              0
              Потому что в любая функция по умолчанию виртуальная, и при вызове будет вызываться функция родителя.
                0
                в *PHP*
                  +1
                  Значит я не допонял - ко второму куску кода отсутствует результат его вывода.
                0
                Нет не усложнит, наоборот упростит. Будет тоже самое, что и для динамических методов.
                Многие программные модели станет проще реализовывать :)
                  0
                  ну у меня слова "перегрузить", "переопределить", "перекрыть" ассоциируются с методами ...
                  с константами такие ассоциации никак в голове не укладывается. На то они и константы.

                  А своих работах я вижу, где за счет этой фишки я могу убрать 5-10 строк кода (заменить геттеры), но делать этого не стану, ибо константа должна быть константой
                    0
                    Почему обязательно константы? Вся соль в том, что непереопределенный родительский метод будет знать, что вызывают его из наследника, а не так как сейчас. Сейчас он думает, что его напрямую вызвали.
                      0
                      честно признаться не понял что вы сказали, скорее всего из-за своей недалекости, но вот последнее предложение у меня вызвало массу эмоций:

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

                      Один из канонов ООП - инкапсуляция. Ни один метод объекта никогда даже намеком не должен знать откуда его вызвали.
                        0
                        Рассмотрим пример:

                        Во втором классе Bar переопределена только статическая переменная. Указание $this в методе dyn() позволяет узнать имя класс, без него всегда вернет Foo. В статическом методе ничего подобного реализовать нелья, поэтому необходимо переопределять метод, как в классе Habr. Это вызывает неоторые неудобства. Со статической переменной думаю тоже все понятно.

                        <?
                        class Foo {
                        static $var = 'dima';

                        function dyn()
                        {
                        echo 'Dynamic: ', get_class($this), "\n";
                        }

                        static function stat($class=null)
                        {
                        if (is_null($class))
                        {
                        $class = get_class();
                        }

                        echo 'Static: ', $class, "\n";
                        }

                        static function stat_var()
                        {
                        echo 'Variable: ', self::$var, "\n";
                        }
                        }

                        class Bar extends Foo {
                        static $var;
                        }

                        class Habr extends Foo {
                        static $var;

                        static function stat($class=null)
                        {
                        parent::stat(get_class());
                        }
                        }

                        $obj = new Bar;
                        $obj->dyn();

                        Bar::stat();
                        Bar::stat_var();

                        Habr::stat();
                        ?>
                          0
                          Возможно вы правы. Наверное мне следует себя пересилить и принять наконец такие конструкции в коде как "get_class", "фишка static::CONST", и т.д.

                          но пока удается прожить без этого (возможно лишними усилиями), и я этом рад :-)
                            0
                            Это надо просто знать, не обязательно пихать куда не следует :)
                            Я привел кусок г..на, которое приходится поддерживать. А вот с выходом 5.3 может получится хотя бы запашок убрать :)
                          0
                          Он не знает, откуда он вызван. Он знает какому классу принадлежит.
                            0
                            Тьфу, ну так это я и имел в виду :)
                            0
                            >>Один из канонов ООП - инкапсуляция. Ни один метод объекта никогда даже намеком не должен знать >>откуда его вызвали.

                            к инкапсуляции это не имеет ни малейшего отношения
                      –1
                      Для товарищей, которые курят нормальные мануалы, программили на ЦПП и шарят в ООД — вряд ли.
                        0
                        Это вовсе не «фишка» — это наконец нормальная реализация, а то обычные методы в PHP всегда виртуальные а статические получается — никогда не виртуальные?
                        Теперь все работает как ожидалось, а это довольно важно в программировании.
                        0
                        В своё время мне нехватало позднего связывания статических методов. Наконец-то эту проблему решили. Только на РНР я уже не программирую :) Так что опоздали господа.
                          +10
                          Думаю, что сообщество PHP от этого не сильно пострадало.
                            0
                            Зато мне в своё время было неудобно из-за этого недосмотра в языке. Как написали ниже, трудно нормальный ActiveRecord было создать.
                          0
                          Тут разработчик наш говорит что LSB в CSV уже месяца три.
                            0
                            Там ещё много чего ;) и врядли всё попадёт в релиз 5.3 как правило из-за того что мало тест кейсов.
                            0
                            "зачем-зачем". забыли, например, про паттерн синглтон ?
                              +1
                              и при чём тут синглетон?
                                0
                                думаю имелось ввиду нормальное наследование(extends Singletone) и callStatic
                                  0
                                  да, это и имелось ввиду. не как именно для чего нужно, а как возможное применение.
                                0
                                синглтон прекрасно реализуется и без этого.
                                  0
                                  реализуется, но не прекрасно.
                                0
                                Возможность конечно клевая, НО она доступна начиная с PHP 5.3.0

                                А текущая стабильная версия 5.2.5. Когда мы этим всем поспользуемся не очень понятно...
                                  +2
                                  не хватало очень раньше позднего связывания.
                                  раньше приходилось делать через жопу protected static.
                                  кстати почему вы ничего не написали про __callStatic ?
                                    +2
                                    Нужная вещь которой давно не хватало в PHP. Без нее нормальный ORM не сделать. Неполучится красиво написать $user = Users::find($user_id), раньше этого добивались через *опу.
                                      0
                                      Поясните пожалуйста. Никогда не замечал недостатка этой фичи при реализации ORM. Может, я чего не так делаю? :)
                                        0
                                        Смотря какой ORM. ActiveRecord например до сих пор полностью нельзя реализовать на PHP, DataAccessObject тоже от чего очень страдают Doctrine ORM и Propel ORM.
                                          0
                                          Да именно об этом я и говорю.
                                            0
                                            Спасибо. К сожалению, мой скилл не позволяет пока этого понять :(
                                        0
                                        Получается, что смысл такой "фичи" в том, чтобы иногда можно было обойтись без перегрузки методов.
                                        Если разбирать чужой код, то перегрузка получается более прозрачной, чем LSB.
                                          +1
                                          Что по вашему лучше? Это?

                                          <?
                                          class DAO {
                                          static function getObjects($class_name=null)
                                          {
                                          $sql = 'SELECT FROM '.$class_name;
                                          }
                                          }

                                          class News extends DAO {
                                          static function getObjects($class_name=null)
                                          {
                                          parent::getObjects(get_class());
                                          }
                                          }

                                          class Faq extends DAO {
                                          static function getObjects($class_name=null)
                                          {
                                          parent::getObjects(get_class());
                                          }
                                          }

                                          $news = News::getObjects();
                                          $faq = Faq::getObjects();
                                          ?>


                                          Или это?

                                          <?
                                          class DAO {
                                          static function getObjects()
                                          {
                                          $sql = 'SELECT FROM '.$class_name;
                                          }
                                          }

                                          class News extends DAO {}
                                          class Faq extends DAO {}

                                          $news = News::getObjects();
                                          $faq = Faq::getObjects();
                                          ?>


                                          Неужели не прозрачно?
                                            –2
                                            лучше - третий вариант, которого вы не описали

                                            class News extends DAO {
                                            const TABLE_NAME = 'news';
                                            static function getTableName()
                                            {
                                            return self::TABLE_NAME;
                                            }
                                            static function getObjects()
                                            {
                                            parent::getTableObjects(self::getTableName());
                                            }
                                            }
                                              0
                                              ХАРДКОД!!!
                                                0
                                                простите, ну это сугубо мое имхо, ваш код - полный ацтой, DorBer - все очень правильно показал. я рад LSB, мне лично это добавит счастья, скорее бы стабильная версия
                                            +1
                                            Честно говоря, кажется костылем.
                                              0
                                              Вынужденная мера для сохранения BC
                                              +1
                                              С выходом 5.3 все начнут писать абстрактных синглтонов :)
                                                0
                                                Ой как красиво подмечено!
                                                0
                                                Три года этот баг в багтракере висит! Ужас.
                                                  0
                                                  Это не баг, это - судьба :))
                                                  Автору - полегче на поворотах: "и наконец мы его получили в PHP 5.3" - это пока ещё голубая мечта. Фиг знает когда он ещё выйдет. Я уж судорожно на php.net полез проверять :)
                                                  0
                                                  О ну наконец-то! Как я в свое время с этим натрахался.
                                                  Приходилось жутко хачить через debug_backtrace...
                                                    0
                                                    Наконец-то, давно пора, а то статические свойства и методы в PHP вообще слабо развиты. Они бы еще с функциями что-то порешали, local как в перле добавили или позволили бы объявлять, замыкания, хотя на это надеяться не стоит наверное ;)
                                                      0
                                                      Ура!! свершилось! как часто приходилось извращаться чтоб решать эти проблеммы! теперь реально многие, очень многие вещи можно будет делать по-человечески! Спасиб огромное автору - отличная новость!
                                                        0
                                                        Имхо. думаю что ввод такой константы во времена первого использования будет вызывать лёгкое раздражение при дебажинге, так как перестанешь доверять тому что константа на самом деле и не константа, что повлечёт за собой просмотр родителя каждый раз при наращивании функцивнала через длительный промежуток времени простоя кода.
                                                          0
                                                          Вообще-то, это не ООП-style.
                                                          Статические члены тем и отличаются от динамических, что принадлежат типу (классу), а не объекту. Поэтому наследование и перекрытие никак не должно на них влиять, это сродни сквозной функциональности.
                                                          Наличие же позднего связывания развяжет руки ради мнимой выгоды. "Наследование статических членов"... Не должно такого быть. Как бы красиво это не выглядело.

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

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