Когда использовать статические методы

Original author: Mathias Verraes
  • Translation
  • Tutorial
tl;dr Использовать ли статические методы? Да, когда они не зависят от внутреннего состояния объекта.

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

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

Основная проблема здесь — отсутствие совместно используемого глобального состояния. Вот пример из прошлого поста:

<?php
$time = Time::from("11:45");

В данном примере возвращаемый результат свободен от побочных эффектов и вполне предсказуем, т.к. зависит только от аргументов, подаваемых на вход. Каждый раз при вызове метода вам будет возвращен идентичный результат (объект Time со значением 11:45), вне зависимости от состояния системы, контекста или чего-либо еще.

Другой пример:

<?php
$sum = Calculator::sum(1, 2); 

И снова — результат предсказуем, Calculator::sum(1, 2); предоставляет нам сервис, не имеющий состояния, и не зависящий ни от чего, кроме аргументов. Более того, эта реализация не может быть полиморфной или иметь различные имплементации, т.к. любой результат кроме 3 будет ошибкой. Да, вы можете изменить внутреннюю реализацию метода, улучшив алгоритм сложения чисел, но это не должно никак отражаться на результате его использования.

Возьмем обратный пример, на этот раз с состоянием:

<?php
Counter::increment(1);
$count = Counter::getCount();

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

Абстракция


Возможно, вы все еще чувствуете неприятие против кода, вроде Calculator::sum($x, $y), т.к. мы не можем сымитировать или расширить его. Но не стоит забывать, что это довольно низкий уровень абстракции. Вы также не сможете сымитировать и расширить оператор + в PHP, но я не думаю, что вы когда-либо чувствовали потребность в этом. Если вам нужен более высокий уровень абстракции, то композиция — ваш верный спутник. Но хочу заметить, между Calculator::sum($x, $y) и + есть довольно интересное различие, первый может вот так, а второй нет:

<?php
$result = array_reduce([1,2,3], 'Calculator::sum', 0);
// $result = 6

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


Часть 1: Как использовать именованные конструкторы в PHP
Share post

Comments 44

    –1
    Если речь идет о PHP, то какой смысл использовать классы в качестве неймспейсов, если можно просто положить обычную функцию в неймспейс? Что за мода всюду пихать классы? Объекты нужны для связи данных и методов, которые их обрабатывают. Если метод чистый, то это обычная фукция и классы тут не нужны. Каждой задаче — свой инструмент!

      +5
      Потому-то для функций не сделали автолоадера.
        0
        но можно же так, если сильно хочется))
          +1
          похоже парсер зажевал и не смог отредактировать=(
          <?php
          
          namespace LongLongNamespace\Math {
              function Sum($a, $b)
              {
                  return $a+$b;
              }
          }
          
          namespace {
              use LongLongNamespace\Math as Math;
              echo Math\Sum(1, 2);
          }
          
          //3
          
            0
            Видимо, использование классов вместо неймспейсов считается меньшим злом, чем приведённый вами извращения )
              0
              Да нет, просто большинство просто не знают что так можно делать :)
                –1
                слава бгу и так говнокода полно
                  –1
                  Использовать функции — говнокод? Странное заявление как минимум, от человека, который ежедневно использует функции.
                    0
                    В каком месте я утверждал, что использование фукнции — говнокод?
                      0
                      Ветка про использование функций в неймспейсах, и код про использование функции вы назвали извращением и на фразу «так не делают потому что не знают» вы ответили «слава богу, говнокода итак хватает». Вывод сам собой пришел.
                        0
                        А подветка про то, как извратиться, чтобы не использовать статические классы.
                          0
                          статические классы это пережиток прошлого.
                          в java использование статических классов оправдано т.к. нельзя объявлять функции вне класса.
                          в php это не нужно т.к. добавили неймспейсы.

          0
          для любителей автолоада тоже есть выход
          `
          final class my_function1 {
          static function run($a) {
          return "а я люблю собирать функции в классы ";
          }
          }
          final class my_function2 {
          static function run($a) {
          return " и обмазыватся автолоадом";
          }
          }


          echo my_function1::run(). my_function2::run()

          `
          да еще для любителе попрятать "всякие приватные механики" будет радость
            0
            В этом случае лучше использовать __invoke(), тогда получится просто echo my_function1(). Хотя тоже очень на любителя…
              0
              всегда думал что это работает только для объектов а не для классов
              и вправда не работает — так бы давно заиспользовал

              Warning: The magic method __invoke() must have public visibility and cannot be static in [...][...] on line 4

              Fatal error: Uncaught Error: Call to undefined function my_function1() in [...][...]:9
              Stack trace:
              0 {main}
              thrown in [...][...] on line 9

                0
                Не нужно метод декларировать статическим — будет работать :)
                  0
                  С каких это пор?
                  Метод __invoke будет вызван, когда Вы обращаетесь к объекту как к функции
                  Если Ваша "логика" верна, я бы не смог создать ф-цию test, а ниже класс test!

            0
            Для функций не нужен автолоад. Просто прописываем в composer.json:
            "autoload": {
                "files": ["src/functions.php"],
            }

            а дальше opcache просто закэширует это дело.
            0
            В классе могут содержаться всякие приватные механики, которые не светят наружу в автокомплите IDE;
              0
              <?php
              
              namespace LongLongNamespace\Math {
                  function Sum($a, $b)
                  {
                      function PrivateSum($a, $b) 
                      {
                          return $a + $b;
                      }
                      return PrivateSum($a, $b);
                  }
              }
              
              namespace {
                  use LongLongNamespace\Math as Math;
                  echo Math\Sum(1, 2);
              }
              
              //3
              


              вот вам приватная механика
                0
                namespace {
                use LongLongNamespace\Math as Math;
                echo Math\Sum(1, 2);
                echo Math\Sum(1, 2);
                }
                вот и закончилась приватная математика Cannot redeclare LongLongNamespace\Math\PrivateSum()
                  0
                  Ну там ещё можно попытаться использовать вариант с присвоением функции переменной:
                  $privateSum = function() {
                      // магия здесь
                  }

                  Но это не решает проблемы, представленной мной ниже.
                  0
                  А если этот приватный метод потребуется в нескольких публичных?
                    0
                    а что если мне после рефакторинга нужно вынести этот 1 метод в другой класс? тогда мне будет не доступен больше тот приватный метод.
                    оформить в виде функции и не трахать людям мозг — это же так просто

                      0
                      // Довольно обидно, что в read-only нельзя минусить
                      Речь идёт про сокрытие функций внутри функции. Вопрос был поставлен, как обеспечить приватную логику, а я уточнил, что как быть, если эта приватная логика нужна в нескольких публичных методах. Твой комментарий не уместен.
                        –1
                        ты гониш — прочитай свой комент сначала

                        А если этот приватный метод потребуется в нескольких публичных

                        какое скрытие функции в другой функции? и как ты собрался скрывать функцию в функции если тебе надо ее использовать в двух методах?
                          0
                          Нет-нет. Я веду мысль к тому, что как только появляется приватная логика, то реализация на функциях начинает сливать. А если появляется необходимость вынести часть общей логики в приватную часть — то вообще не подходит.

                          Скажем, мы берём методы sum и sub (сложить и вычесть), но хотим складывать в какой-то важной уберфункции, которая параллельно будет что-то и логировать. Такой функцией может стать, например, приватная функция sumInternal с аргументами $a, $b, $operation (плюс, минус, умножение, etc.), а функции sum и sub будут просто вызывать её с правильными аргументами. С функциями так не выйдет, а вот с классом — без проблем.

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

                            0
                            и смысл прятания в чем? только в эмуляции области видимости?

                            • вот у нас функция sumInternal она работает с такими аргументами
                            • вот функции sum и sub более высокого уровня — которые используют функцию sumInternal

                            и если все три функции видны и находятся в своем неймпейсе — ничего страшного нет.
                              0
                              Хорошо. Мне лень продолжать спор.
                                0
                                Лень? Тут либо приведите аргумент, либо обосритесь.
                  0
                  те. автокомплит IDE задает правила написания кода теперь? пишетм код не для решения задачи — а чтобы в IDE красиво и удобно было — просто офигенно.

                    +1
                    Все нормальные IDE разрабатываются под нормальный код и весь нормальный код нормально поддерживается нормальными IDE.
                    Если код не может пройти статический анализ, это верный признак говнокодного говнокодища.
                      –2
                      статический аналих в PHP, лол
                    0
                    Это не тот кейс, который обсуждается в статье. В статье речь идет о чистой функции — функции, которая не зависит от окружения. Зависимость от приватных полей класса — это тоже зависимость и для этого нужно использовать классы, тут я с вами согласен. Но для примера из статьи — нет.
                    0
                    просто они пишут для устаревших версий PHP где нет namespace
                    а все потому что годами пилили в стиле ООП фреймворк. а за это время PHP развился
                    а кода много переписать не реально (Yii, Symphony и подобные треш)
                    0
                    Я для себя сформировал такое правило — если функция чистая (https://ru.wikipedia.org/wiki/%D0%A7%D0%B8%D1%81%D1%82%D0%BE%D1%82%D0%B0_%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%B8) — то её можно делать статической. Правда даже в этом случае есть проблемы — тяжело уследить когда метод перестал быть чистым и пора отрефакторить его и его использование
                      –1
                      Большое количество статики за пределами utils — не слишком хороший признак как мне кажется, особенно если эта статика не является creator\фабричным методом и тем более — мутирует состояние передаваемых аргументов.
                      А для алгоритмов более актуальным было бы применение стратегии на мой взгляд.
                        0
                        Наличие utils — не слишком хороший признак сам по себе :-)
                        +1
                        Статические методы хороши, когда возвращают экземпляр другого класса с привязкой к текущему (позднее статическое наследование).
                          –1
                          да удобно. но мне всеже больше нравится использовать внешний построитель объекта, который все сделает и без статики.
                          заодно избавит объект бизнес логики от лишних методов и костылей, когда конструкторы наследником принимают разное количество аргументов
                          0
                          Мне кажеться логичным использование статического метода, который возвращает имя таблицы в классе, реализующем интерфейс ActiveRecord.
                            0
                            и каким образом вы обяжете класс возвращать имя таблицы? как будет выглядеть ваш статический метод?
                              0
                              в интерфейсе ActiveRecord объявлю метод tableName() и наследуемый клас его реализует:
                              public static function tableName()
                              {
                                   return 'my_table_name';
                              }
                                0
                                Да, действительно. Что-то я никогда не сталкивался со статическими методами в интерфейсе))
                                Жаль не могу вам + поставить.

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