Что значит слово «static» в PHP и зачем оно применяется?

Ключевое слово static имеет в PHP три различных значения. Разберем их в хронологическом порядке, как они появлялись в языке.

Значение первое — статическая локальная переменная

function foo() {

  $a = 0;

  echo $a;

  $a = $a + 1;

}

foo(); // 0

foo(); // 0

foo(); // 0

В PHP переменные локальны. Это значит, что переменная, определенная и получившая значение внутри функции (метода), существует только во время выполнения этой функции (метода). При выходе из метода локальная переменная уничтожается, а при повторном входе — создается заново. В коде выше такой локальной переменной является переменная $a — она существует только внутри функции foo() и каждый раз при вызове этой функции создается заново. Инкремент переменной в этом коде бессмысленен, поскольку на следующей же строчке кода функция закончит свою работу и значение переменной будет потеряно. Сколько бы раз мы не вызвали функцию foo(), она всегда будет выводить 0…

Однако всё меняется, если мы перед присваиванием поставим ключевое слово static:

( см. запуск: https://3v4l.org/EZMpb )

function foo() {
  static $a = 0;
  echo $a;
  $a = $a + 1;
}

foo(); // 0
foo(); // 1
foo(); // 2

Ключевое слово static, написанное перед присваиванием значения локальной переменной, приводит к следующим эффектам:

  • Присваивание выполняется только один раз, при первом вызове функции

  • Значение помеченной таким образом переменной сохраняется после окончания работы функции

  • При последующих вызовах функции вместо присваивания переменная получает сохраненное ранее значение

Такое использование слова static называется статическая локальная переменная.

Подводные камни статических переменных

Разумеется, как всегда в PHP, не обходится без «подводных камней».

Камень первый — статической переменной присваивать можно только константы или константные выражения. Вот такой код:

static $a = bar();

с неизбежностью приведет к ошибке парсера. К счастью, начиная с версии 5.6 стало допустимым присвоение не только констант, но и константных выражений (например — «1+2» или «[1, 2, 3]»), то есть таких выражений, которые не зависят от другого кода и могут быть вычислены на этапе компиляции

Камень второй — методы существуют в единственном экземпляре.

Тут всё чуть сложнее. Для понимания сути приведу код:

class A {
  public function foo() {
    static $x = 0;
    echo ++$x;
  }
}

$a1 = new A;
$a2 = new A;

$a1->foo(); // 1
$a2->foo(); // 2

$a1->foo(); // 3
$a2->foo(); // 4

Вопреки интуитивному ожиданию «разные объекты — разные методы» мы наглядно видим на этом примере, что динамические методы в PHP «не размножаются». Даже если у нас будет сто объектов этого класса, метод будет существовать лишь в одном экземпляре, просто при каждом вызове в него будет пробрасываться разный $this.

Такое поведение может быть неожиданным для неподготовленного к нему разработчика и послужить источником ошибок. Нужно заметить, что наследование класса (и метода) приводит к тому, что всё-таки создается новый метод:

class A {
  public function foo() {
    static $x = 0;
    echo ++$x;
  }
}

class B extends A {
}

$a1 = new A;
$b1 = new B;

$a1->foo(); // 1
$b1->foo(); // 1

$a1->foo(); // 2
$b1->foo(); // 2

Вывод: динамические методы в PHP существуют в контексте классов, а не объектов.

И только лишь в рантайме происходит подстановка «$this = текущий_объект»

Значение второе — статические свойства и методы классов

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

class A {
  public static $x = 'foo';
  public static function test() {
    return 42;
  }
}

echo A::$x; // 'foo'

echo A::test(); // 42

Для доступа к таким свойствам и методам используются конструкции с двойным двоеточием («Paamayim Nekudotayim»), такие как ИМЯ_КЛАССА::$имяПеременной и ИМЯ_КЛАССА:: имяМетода().

Само собой разумеется, что у статических свойств и статических методов есть свои особенности и свои «подводные камни», которые нужно знать.

Особенность первая, банальная — нет $this. Собственно это проистекает из самого определения статического метода — поскольку он связан с классом, а не объектом, в нём недоступна псевдопеременная $this, указывающая в динамических методах на текущий объект. Что совершенно логично.

Однако, нужно знать, что в отличие от других языков, PHP не определяет ситуацию «в статическом методе написано $this» на этапе парсинга или компиляции. Подобная ошибка может возникнуть только в рантайме, если вы попытаетесь выполнить код с $this внутри статического метода.

Код типа такого:

class A {
  public $id = 42;
  static public function foo() {
    echo $this->id;
  }
}

не приведет ни к каким ошибкам, до тех пор, пока вы не попытаетесь использовать метод foo() неподобающим образом:

$a = new A;

$a->foo();

(и сразу получите «Fatal error: Using $this when not in object context»)

Особенность вторая — static не аксиома!

class A {
  static public function foo() {
    echo 42;
  }
}

$a = new A;
$a->foo(); // 42

(см. https://3v4l.org/9jPUI )

Вот так, да. Статический метод, если он не содержит в коде $this, вполне можно вызывать в динамическом контексте, как метод объекта. Это не является ошибкой в PHP.

Обратное не совсем верно:

class A {
  public function foo() {
    echo 42;
  }
}

A::foo();  // Deprecated / Fatal error: Uncaught Error: Non-static method A::foo() cannot be called statically

( см. https://3v4l.org/VGdYI )

Динамический метод, не использующий $this, можно выполнять в статическом контексте. Однако вы получите предупреждение «Non-static method A::foo() should not be called statically» уровня E_STRICT. Тут решать вам — или строго следовать стандартам кода, или подавлять предупреждения.

Первое, разумеется, предпочтительнее.

И кстати, всё написанное выше относится только к методам. Использование статического свойства через «->» невозможно и ведет к фатальной ошибке.

Значение третье — позднее статическое связывание

Разработчики языка PHP не остановились на двух значениях ключевого слова «static» и в версии 5.3 добавили еще одну «фичу» языка, которая реализована тем же самым словом! Она называется «позднее статическое связывание» или LSB (Late Static Binding).

Понять суть LSB проще всего на несложных примерах:

class Model {
  public static $table = 'table';
  public static function getTable() {
    return self::$table;
  }
}

echo Model::getTable(); // 'table'

Ключевое слово self в PHP всегда значит «имя класса, где это слово написано». В данном случае self заменяется на класс Model, а self::$table — на Model::$table.

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

Немного изменим наш код:

class Model {
  public static $table = 'table';
  public static function getTable() {
    return self::$table;
  }
}

class User extends Model {
  public static $table = 'users';
}

echo User::getTable(); // 'table'

Теперь вы понимаете, почему PHP ведёт себя в этой ситуации неинтуитивно. self был связан с классом Model тогда, когда о классе User еще ничего не было известно, поэтому и указывает на Model.

Как быть?

Для решения этой дилеммы был придуман механизм связывания «позднего», на этапе рантайма. Работает он очень просто — достаточно вместо слова «self» написать «static» и связь будет установлена с тем классом, который вызывает данный код, а не с тем, где он написан:

class Model {

  public static $table = 'table';

  public static function getTable() {

    return static::$table;

  }

}

class User extends Model {

  public static $table = 'users';

}

echo User::getTable(); // 'users'

(см. https://3v4l.org/ERiqM )

Это и есть загадочное «позднее статическое связывание».

PS: Нужно отметить, что для большего удобства в PHP кроме слова «static» есть еще специальная функция get_called_class(), которая сообщит вам — в контексте какого класса в данный момент работает ваш код.


Источники :

(1) https://habr.com/ru/post/259627/

  • Я прошёл сертификацию Zend PHP
    Недавно я прошёл международную сертификацию на знание языка PHP 7.3, с присвоением звания «Zend certified engineer» / ZCE. 🔥 Это уже вторая моя сертификация, прошлая была по версии языка PHP 5.6 (когда он еще был актуален). Ниже — о всех нюансах сертификации 👇 Об экзамене Сам экзамен, по моему, был сложным : состоял из 70 […]
  • Шаблонный метод / Template Method
    Суть паттерна Проблема Решение Аналогия из жизни Структура Псевдокод Применимость Шаги реализации Преимущества и недостатки Отношения с другими паттернами Примеры реализации паттерна на PHP Особенности паттерна на PHP Концептуальный пример Пример из реальной жизни Суть паттерна Шаблонный метод — это поведенческий паттерн проектирования, который определяет скелет алгоритма, перекладывая ответственность за некоторые его шаги на подклассы. […]
  • Что такое принципы SOLID в ООП ?
    SOLID (S) Single-responsibility principle (Принцип единственной ответственности) (O) Open–closed principle (Принцип открытости-закрытости) (L) Liskov substitution principle (Принцип подстановки Лисков) (I) Interface segregation principle (Принцип разделения интерфейсов) (D) Dependency inversion principle (Принцип инверсии зависимостей) Заключение В этом тексте приводится набор принципов, которые должен знать любой разработчик, и которые следует периодически освежать в памяти. Считайте их своим […]
  • Что такое примеси / трейты (traits) в PHP ?
    Что такое трейты Пример использования Приоритет методов при работе с трейтами. Конфликты трейтов Статические методы и свойства Доступ к свойствам базового класса Константы связанные с трейтами Итоги: Что такое трейты Трейты (англ. traits) — это механизм обеспечения повторного использования кода в языках с поддержкой единого наследования. В php трейты внедрены с версии 5.4.0. Они позволяют […]
  • Что такое линтер и как его использовать
    Код нужно оформлять определённым образом, чтобы он был достаточно понятным и простым в поддержке. Специальные наборы правил — стандарты — описывают различные аспекты написания кода. Конкретно в PHP самым распространенными являются стандарты PSR (PHP Standards Recommendations или стандартные рекомендации PHP) от PHP-FIG. Рекомендация представляет из себя набор правил, призванных облегчить определённую сторону разработки (или решить […]
Tags

Нет комментариев

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.