Баба static php p. Позднее статическое связывание в PHP. Подводные камни статических переменных


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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

class Beer {
const NAME = "Beer!" ;

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 . И снова рассмотрим пример:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

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-разработчиков. Во второй части рассмотрим как его можно использовать во благо.

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

Самое важно что нужно понять - статические свойства и методы пренадлежат классам, а не объектам.

На примере станет сразу понятно. Давайте создадим обект Math (сокращённое названием математики в английском).

Статические методы PHP

Этот класс предоставляет инструменты для работы с математическими функциями без необходимости создания объекта. В классе есть конструктор, который увеличивает статическое свойство $count на единицу. Класс запоминает значение этого свойства, так как оно статическое.

Кстати, для объявления метода или свойства статическим используется слово static , а для доступа к статическому свойству используется слово self с двойным двоеточием " :: ".

Всё это лучше понять в сравнении, особенно в сравнении рабочего примера с ошибочным. Давайте немного расширим наш пример.

Статические методы PHP

В этом примере мы добавили классу обычное свойство $counter , оно также увеличивалось на единицу в конструкторе. Но обычное свойство принадлежит объекту, поэтому оно не сохраняется между вызовами объектов. При любом создании экземпляра класса (объекта) свойство будет равно нолю, в конструкторе оно будет увеличено на единицу.

Статическое свойство принадлежит классу, поэтому его значение сохраняется.

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

Попытка использовать в статическом методе переменную $this приведёт к ошибке (Fatal error: Using $this when not in object context).

Статические методы PHP

Если в этом примере убрать слово static перед именем свойства, то возникнет ошибка "Access to undeclared static property".

Статические свойства отсутствуют в объектах класса.

Статические методы PHP

Статический метод можно вызвать используя конструкцию self::метод() . Пример:

Статические методы PHP

Статическое свойство можно получить в контексте класса используя синтаксис:

echo TestClass::$age;

Причём попытка обращения к обычному свойству таким образом приведёт к ошибке: "Fatal error: Access to undeclared static property".

Статическое свойство можно менять в контексте класса используя синтаксис:

TestClass::$age += 20; // например

Ещё пример кода со статическими методами и свойствами

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

Статические методы PHP

Обратите внимание, и это важно, в этом примере мы обратились к нестатическому методу sayHi() используя синтаксис обращения к статическим элементам класса.

Резюме
  • Главное: статические свойства принадлежат классам, а не объектам.
  • Из статического метода нельзя обратиться к обычным свойствам и методам класса, $this->name не работает тут.
  • Из статического метода можно обратиться к статическим свойствам используя self::$name .
  • Статические свойства класса не доступны объектам.
  • Обычный метод может обратиться к статическому свойству используя self::$name .
  • Статическое свойство можно получить в контексте класса использую синтаксис: TestClass::$age .
  • Обычный метод можно вызвать в контексте и объекта ($object->метод()), и класса используя синтаксис TestClass::метод() .
  • При помощи синтаксиса $object::$age у меня получилось получить доступ к статическому свойству через объект.
Параллели с JavaScript

В JavaScript есть такой класс Math, содержащий очень много различных математических функций.

Для проведения математических вычислений (расчёт синуса или экспоненты) в JavaScript не нужно создавать обект класса Math, так как его методы являются статическими. До изучения PHP я никак не мог понять что это такое, и только изучив классы и объекты в PHP у меня в голове всё встало на свои полочки.

На самом деле это очень удобно, иметь прямой доступ к математическим методам класса Math, избегая создания объекта.

  • Программирование ,
  • ООП
    • Tutorial

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

    Попробуем разобрать «по косточкам» один из таких вопросов - что значит слово «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:

    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» или ""), то есть таких выражений, которые не зависят от другого кода и могут быть вычислены на этапе компиляции

    Камень второй - методы существуют в единственном экземпляре.
    Тут всё чуть сложнее. Для понимания сути приведу код:
    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();
    Вот так, да. Статический метод, если он не содержит в коде $this, вполне можно вызывать в динамическом контексте, как метод объекта. Это не является ошибкой в PHP.

    Обратное не совсем верно:
    class A { public function foo() { echo 42; } } A::foo();
    Динамический метод, не использующий $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"

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

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

    Удачных собеседований!

    Mar 23, 2010 dec5e

    В PHP 5.3 появилась такая интересная возможность, как позднее статическое связывание (late static binding). Дальше немного вольный перевод описания из официального мануала .

    Начиная с PHP 5.3.0 в языке реализована возможность, называемая поздним статическим связыванием, которая может использоваться для ссылки на вызываемый класс в контексте статического наследования.

    Эту возможность назвали «позднее статическое связывание». «Позднее связывание» говорит о том, что static:: будет разрешаться не относительно класса, где определен метод, но будет вычисляться во время выполнения. «Статическое связывание» означает, что оно может быть использовано в вызовах статических методов (но не ограничивается только ими).

    Ограничения self::

    Пример № 1: использование self::

    Пример выведет:

    Использование позднего статического связывания

    Позднее статическое связывание пытается решить это ограничение, вводя ключевое слово, ссылающееся на класс, первоначально вызванный в процессе выполнения. То есть, ключевое слово, которое позволит сослаться на B из test() в предыдущем примере. Было решено не вводить новое слово, а использовать уже зарезервированное static .

    Пример № 2: простое использование static::

    Пример выведет:

    Замечание: static:: не работает как $this для статических методов! $this-> следует правилам наследования, а static:: нет. Это различие уточняется ниже.

    Пример № 3: использование static:: в нестатическом контексте

    Пример выведет:

    Замечание: Позднее статическое связывание останавливает процесс разрешения вызова. Статические вызовы с использованием ключевых слов parent:: или self:: передают информацию о вызове дальше.

    Пример № 4: Передача и непередача вызовов

    Пример выведет

    Крайние случаи

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

    Пример № 5 Позднее статическое связывание в магических методах

    ". Я написал ответную статью, но так и не опубликовал ее. А вот недавно увидел нечто, что можно назвать «Классо-Ориентированное Программирование». Это освежило мой интерес к теме и вот результат.

    «Классо-Ориентированое Программирование» - это когда используются классы, состоящие только из статических методов и свойств, а экземпляр класса никогда не создается. В этой статье я буду говорить о том, что:

    • это не дает никаких преимуществ по сравнению с процедурным программированием
    • не стоит отказываться от объектов
    • наличие статических членов класса!= смерть тестам
    Хотя эта статья про PHP, концепции применимы и к другим языкам.
    Зависимости Обычно, код зависит от другого кода. Например:

    $foo = substr($bar, 42);
    Этот код зависит от переменной $bar и функции substr . $bar - это просто локальная переменная, определенная немного выше в этом же файле и в той же области видимости. substr - это функция ядра PHP. Здесь все просто.

    Теперь, такой пример:

    Class BloomFilter { ... public function __construct($m, $k) { ... } public static function getK($m, $n) { return ceil(($m / $n) * log(2)); } ... }
    Эта маленькая вспомогательная функция просто предоставляет обертку для конкретного алгоритма, который помогает рассчитать хорошее число для аргумета $k , используемого в конструкторе. Т.к. она должна быть вызвана до создания экземпляра класса, она должна быть статичной. Этот алгоритм не имеет внешних зависимостей и вряд ли будет заменен. Он используется так:

    $m = 10000; $n = 2000; $b = new BloomFilter($m, BloomFilter::getK($m, $n));
    Это не создает никаких дополнительных зависимостей. Класс зависит сам от себя.

  • Альтернативный конструктор. Хорошим примером является класс DateTime , встроенный в PHP. Его экземпляр можно создать двумя разными способами:

    $date = new DateTime("2012-11-04"); $date = DateTime::createFromFormat("d-m-Y", "04-11-2012");
    В обоих случая результатом будет экземпляр DateTime и в обоих случаях код привязан к классу DateTime так или иначе. Статический метод DateTime::createFromFormat - это альтернативный коструктор объекта, возвращающий тоже самое что и new DateTime , но используя дополнительную функциональность. Там, где можно написать new Class , можно написать и Class::method() . Никаких новых зависимостей при этом не возникает.

  • Остальные варианты использования статических методов влияют на связывание и могут образовывать неявные зависимости.Слово об абстракции Зачем вся эта возня с зависимостями? Возможность абстрагировать! С ростом Вашего продукта, растет его сложность. И абстракция - ключ к управлению сложностью.

    Для примера, у Вас есть класс Application , который представляет Ваше приложение. Он общается с классом User , который является предствлением пользователя. Который получает данные от Database . Классу Database нужен DatabaseDriver . DatabaseDriver нужны параметры подключения. И так далее. Если просто вызвать Application::start() статически, который вызовет User::getData() статически, который вызовет БД статически и так далее, в надежде, что каждый слой разберется со своими зависимостями, можно получить ужасный бардак, если что-то пойдет не так. Невозможно угадать, будет ли работать вызов Application::start() , потому что совсем не очевидно, как себя поведут внутренние зависимости. Еще хуже то, что единственный способ влиять на поведение Application::start() - это изменять исходный код этого класса и код классов которые он вызызвает и код классов, которые вызызвают те классы… в доме который построил Джек.

    Наиболее эффективный подход, при создании сложных приложений - это создание отдельных частей, на которые можно опираться в дальнейшем. Частей, о которых можно перестать думать, в которых можно быть уверенным. Например, при вызове статического Database::fetchAll(...) , нет никаких гарантий, что соединение с БД уже установлено или будет установлено.

    Function (Database $database) { ... }
    Если код внутри этой функции будет выполнен - это значит, что экземпляр Database был успешно передан, что значит, что экземпляр объекта Database был успешно создан. Если класс Database спроектирован верно, то можно быть уверенным, что наличие экземпляра этого класса означает возможность выполнять запросы к БД. Если экземпляра класса не будет, то тело функции не будет выполнено. Это значит, что функция не должна заботиться о состоянии БД, класс Database это сделает сам. Такой подход позволяет забыть о зависимостях и сконцентрироваться на решении задач.

    Без возможности не думать о зависимостях и зависимостях этих зависимостей, практически невозможно написать хоть сколь-нибудь сложное приложение. Database может быть маленьким классом-оберткой или гигантским многослойным монстром с кучей зависимостей, он может начаться как маленькая обертка и мутировать в гигантского монстра со временем, Вы можете унаследовать класс Database и передать в функцию потомок, это все не важно для Вашей function (Database $database) , до тех пор пока, публичный интерфейс Database не изменяется. Если Ваши классы правильно отделены от остальных частей приложения с помощью внедрения зависимостей, Вы можете тестировать каждый из них, используя заглушки вместо их зависимостей. Когда Вы протестировали класс достаточно, чтобы убедиться, что он работает как надо, Вы можете выкинуть лишнее из головы, просто зная, что для работы с БД нужно использовать экземпляр Database .

    Классо-ориентированное программирование - глупость. Учитесь использовать ООП.

    Выбор редакции
    Драйвера АТОЛ – это специальная программа, необходимая для взаимодействия кассового аппарата с компьютером. Данное программное...

    А также изменился интерфейс страницы спора. По этой причине у многих покупателей возникло много вопросов: как отрыть спор по новым...

    Немецкое слово брандмауэр (нем. Brandmauer, от Brand - пожар и Mauer - стена) плотно вошло в обиход пользователей операционной системы...

    Существует множество различных способов конвертирования различных форматов изображений. Сегодня мы с вами рассмотрим несколько из них,...
    С переходом к десятой модификации операционной системы Windows большинство пользователей очень обрадовалось тому, что в интерфейсе...
    Для некоторых людей подъем по утрам – очень сложная задача. Хорошо, когда не нужно никуда спешить, но если Вы боитесь опоздать на работу,...
    В «Инстаграме» самыми популярными объектами являются, естественно, фотографии. Конечно, снимки с комментариями будут смотреться намного...
    Аппараты на ОС Андроид отличаются возможностью изменить практически все в интерфейсе пользователя. Китайская модификация прошивки – miui,...
    В век бурно развивающихся технологий индустрия наручных часов не остаётся в стороне. В 2017 году швейцарская компания TAG Heuer...