![image](https://habrastorage.org/files/d83/5da/237/d835da237d3846eb9c41ea3e0ea29e40.jpg)
Попробуем разобрать «по косточкам» один из таких вопросов — что значит слово «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» или "[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();
Вот так, да. Статический метод, если он не содержит в коде $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(), которая сообщит вам — в контексте какого класса в данный момент работает ваш код.
Удачных собеседований!
Список полезных ссылок на мануал:
Комментарии (67)
AlexLeonov Автор
05.06.2015 15:15однако, один вопрос в статье упущен (или мне показалось?)
Ну всего в одну статью не впихнёшь, я бы хотел серию сделать с подзаголовком «Готовимся к собеседованию». Часть ваших вопросов интересна, часть — не имеет смысла в PHP )))
stavinsky
05.06.2015 15:30+4Ох как мне не нравятся подобные статьи, точнее их последствия. Мы один раз долго не могли понять почему серия из 5ти соискателей отвечает на одинаковый вопрос слово в слово. И вроде бы правильно отвечает, а если копнуть глубже 4 из 5 не смогли развить тему и чуть дальше. После поиска в гугле нашли подобную статью и ответ на наш вопрос в ней. Долго смеялись и просили на следующих собеседованиях отвечать другими терминами.
P.S. Статья хорошая, не подумайте. Автору спасибоAlexLeonov Автор
05.06.2015 15:50А вы копайте, копайте глубже ))) Не копая не поймешь — цитирует собеседуемый статью, или действительно понимает смысл произносимых слов.
plus
06.06.2015 08:34+2А откуда бы вы хотели чтоб человек черпал информацию в том числе по static в php? Я думаю это не проблема конкретно этой статьи, а вообще образования, конечно кто-то просто зазубрит что-то, что от него требует, а кто-то прочитает официальный мануал, прочитает несколько таких статей, попробует что-то сделать сам и усвоит знания. И я думаю 1 из 5 не так уж плохо, вот серьёзно :)
stavinsky
08.06.2015 11:31Я согласен ) Вопрос лишь в том что 4 из 5 перед собеседованием вместо теории и мануалов читают мануал как пройти собеседование по PHP.
Vizakenjack
06.06.2015 10:52А вы на собеседованиях ставите условие, чтобы ответ был более-менее уникальным? Тогда уж лучше тестовое задание давать, вместо этой теории.
stavinsky
08.06.2015 11:30Нет просто если мы слышим что человек цитирует подобную статью мы просим перефразировать ответ. Задание к сожалению меньше выявляет проблем. Лучше комплекс.
guyfawkes
05.06.2015 20:31+5Скажите, а вы в своем «обыденном» коде как часто используете статические локальные переменные?
maximw
05.06.2015 22:34Бывает использую в рекурсивных функциях, например, для обработки древовидных структур, когда нужна общая переменная в процессе обработки. Ее можено либо передавать как аргумент, либо сделать глобальной, либо статической. Мне больше нравится последнее.
guyfawkes
06.06.2015 11:51Ну в принципе ее можно сделать членом класса еще :)
maximw
06.06.2015 14:34Конечно, я просто думал, что вы речь ведете только про static относительно функций, а не методов классов и объектов.
dvapelnik
12.06.2015 09:04нельзя, если древовидная структура — это скомпонированное древовидное множество из одной иерархии класса, но в таком случае лучше проходить каким-то посетителем
Rathil
06.06.2015 02:01+1Мы тоже не часто используем их, но есть места, где они спасают. Плохо только одно, когда их начинает писать тот, кто не понимает как они работают.
AlexLeonov Автор
06.06.2015 12:42+1Не часто. Ровно также, как нечастно использую, скажем, ссылки. Или интерфейс ArrayAccess.
Но это не значит, что я имею право не знать о такой возможности.
annenkov
06.06.2015 13:03Вообще эта вещь очень удобна для кэширования результатов выполнение функции, поэтому раньше когда программировал функционально частенько применял, с переходом на ООП в методах применяю редко по причине описанной в статье — легко выстрелить в ногу.
XanderBass
05.06.2015 22:48+4Прошу прощения, не хочу показаться занудой, но зачем в коде:
function foo() {
static $a = 0;
echo $a;
$a = $a + 1;
}
echo foo(); // 0
echo foo(); // 1
echo foo(); // 2
echo перед вызовом функции foo?stychos
06.06.2015 04:23Можно написать и echo(foo()), просто echo (она же print) — языковая конструкция, а не функция.
romy4
08.06.2015 15:42-1Результат тот же, но echo — не print хотя бы по тому, что echo — конструкция языка, а print — функция, хоть внутри php source и имеют вызов одного функционала добавления строки в буффер вывода. Всё же они работают немного по-разному.
Например: сделайте
echo (echo "string");
и
echo (print "string");
maximw
08.06.2015 15:55print тоже языковая конструкция, а не функция.
Отличия
— print всегда вернет 1, а еcho ничего не возвращает.
— print допускает только один параметр, а echo несколько через запятуюromy4
08.06.2015 16:50+1echo не просто не возвращает (функция, не возвращающяя значения в php при присвоении даст null), выражение вызывает fatal error при попытке присвоить результат echo.
Rathil
06.06.2015 01:56В PHP переменные локальны. Это значит, что переменная, определенная и получившая значение внутри функции (метода), существует только во время выполнения этой функции (метода). При выходе из метода локальная переменная уничтожается, а при повторном входе — создается заново. В коде выше такой локальной переменной является переменная $a — она существует только внутри функции foo() и каждый раз при вызове этой функции создается заново
В целом да, но не всегда :)
НО, конкретно в том примере, что приведен выше — да
Razor
06.06.2015 10:59+1И кстати, всё написанное выше относится только к свойствам. Использование статического свойства через "->" невозможно и ведет к фатальной ошибке.
В первом предложении не должно быть «к методам» вместо «свойствам»?
NekR
06.06.2015 11:28+1Вот я мимо проходил, статью не читал, но может надо готовиться к программированию на PHP, а не к собеседованию?
guyfawkes
06.06.2015 11:53В вопросах часто звучат нюансы, которые либо знаешь, потому что читал/сам ошибался и искал фикс/экспериментировал, либо нет, так как не приходилось использовать, и касается вопросов не только по PHP.
NekR
06.06.2015 12:06-1Абсолютно согласен, однако то что такие вопросы задают не значит что это хорошо/правильно. Сам на собеседования задавал такие вопросы, мне задавали их на собеседованиях. Когда подрос понял что это глупо. Но могу быть не прав, если у кого то есть веские аргументы в пользу такого подхода.
AlexLeonov Автор
06.06.2015 12:40Когда подрос понял что это глупо
Не подскажете — что глупого в вопросах на знание языка, задаваемых кандидату на позицию разработчика на этом языке?NekR
06.06.2015 12:56+2Я говорю не о знании языка, а конкретно о глупых вопросах именно на собеседовании, как их назвали в комментарии выше — «нюансах». Конечно нужно знать язык, в этом суть, но очень часто спрашивают о ситуациях которые встречаются ну очень редко или вообще могу не встретится человеку. Понятно что их тоже хорошо бы знать и они могут пригодиться, но если человек знает хорошо свою работу и вообще способный, то когда встретится тот самый нюанс, я думаю, он сможет с ним справиться. На крайний случай, если нет, то для этого и нужна команда и вообще все люди/программисты продолжают дальше учиться, даже когда уже знают достаточно.
Ещё раз повторю, статью я не читал, меня взволновал заголовок и в следствии чего последовал вопрос. На счёт «нюансов» отвечаю так как про них заговорили в ответе к моему комментарию. Если же статья не про нюансы, а про другое, тогда мой первый вопрос на счёт заголовка до сих пор в силе.AlexLeonov Автор
07.06.2015 11:50Если же статья не про нюансы, а про другое, тогда мой первый вопрос на счёт заголовка до сих пор в силе.
Статья, с вашего позволения, не про «нюансы», к которым вы испытываете столь сложные чувства, а про систематизацию. Про собрать в одном месте всё, что касается одного ключевого слова языка и систематизировать эти знания.NekR
07.06.2015 17:24+1Это здорово, но позвольте, вы так и не ответили тогда на вопрос «почему для собеседования?». Если это общие знания которые нужно знать любому разработчику что бы хорошо программировать на языке, почему не назвать статью «Изучаем <язык>: <тема статьи>»? Лично в моём понимании, если статья называется «для собеседования», то это значит что там говорится про то, что с очень маленькой вероятностью может пригодиться в реальной работе. Выглядит так, что контент этой статьи нужен будет людям только что бы пройти собеседование, а дальше могут забыть о нём и не пользоваться.
> а про систематизацию. Про собрать в одном месте всё, что касается одного ключевого слова языка и систематизировать эти знания.
Вы молодец что делаете это и к статье самой или к вам у меня претензий быть не может. Моё желание лишь понять, чем вы руководствовались когда называли это «для собеседования».AlexLeonov Автор
07.06.2015 17:29+1Это здорово, но позвольте, вы так и не ответили тогда на вопрос «почему для собеседования?»
Извините, не увидел вопроса.
Потому что именно собеседование для многих заменяет экзамен на профессионализм. Рядовой разработчик на PHP зачастую только на собеседовании, к стыду своему, понимает, что он как тот Джон Сноу — имеет в портфолио десяток проектов, кучу фреймворков и паттернов, но ничего не знает…
guyfawkes
06.06.2015 16:57Честно говоря, я и сам отношусь к подобным вопросам скорее негативно. Но это не отменяет того факта, что они просто есть.
hospect
06.06.2015 13:29Интересно, только я не знал первый вариант использования «static» (создание статической локальной переменной)? Наверное, минус постоянной ОО разработки — с некоторыми нюансами языка, проявляющимися при процедурном программировании, просто никогда не сталкиваешься.
fear86
06.06.2015 20:56Некоторые вещи про php лучше не знать. Например то что есть трюк вызывать protected методы не только из вашего класса )
hospect
07.06.2015 00:12Имеется ввиду из классов, не наследующих данный? Если так, то заинтриговали :) Поделитесь ссылкой?
fear86
07.06.2015 13:55Если мне не изменяет память то достаточно что бы у классов был общий родитель. Но не обязательно наследовать нужный класс.
to0n1
08.06.2015 23:40+1Я думаю fear86 говорит о следующем кейсе
код<?php class firstClass { /** @var string */ protected $message; public function __construct($message) { $this->message = $message; } } class secondClass extends firstClass { /** @var firstClass */ protected $firstClass; public function __construct(firstClass $firstClass) { $this->firstClass = $firstClass; } public function accessProtected() { return $this->firstClass->message; } } $a = new firstClass('Hello protected'); $b = new secondClass($a); var_dump($b->accessProtected());
fear86
09.06.2015 00:08Да, я имел ввиду вызов из других обьектов. И я ошибся с общим родителем, класс надо наследовать, или хотя бы наследовать тот класс в иерархии, который содержит нужный нам метод/свойство.
Вот так тоже можно:
class a { protected function test() { return 'protected'; } } class e extends a { } class c extends a { public function __call($name, $args) { return call_user_func_array(array(array_shift($args), $name), $args); } } $e = new e(); $c = new c(); echo $c->test($e);
annenkov
07.06.2015 01:19Тоже интересно, может все таки имелось ввиду вызывать не только из вашего объекта — php.net/manual/ru/language.oop5.visibility.php#language.oop5.visibility-other-objects, рефлекшены уж наверное тут не подразумевались.
TimID
07.06.2015 01:09Ну скажите как интересно, описать что означает static в типовом ООП, причём так, будто какие-то неожиданные свойства открылись. А реально неожиданное — это только «позднее связывание» с заменой $self на static. Правда практическое применение придумать что-то не выходит у меня.
Автору спасибо — «размял» немного мозг, но лучше было бы написать, что такое static и почему от него ожидают именно того поведения, которое приведено в статье.TimID
07.06.2015 01:13P.S. На всякий случай: ключевое слово статик вовсе не связано с «Значение помеченной таким образом переменной сохраняется после окончания работы функции» — это следствие.
AlexLeonov Автор
07.06.2015 11:52+1А реально неожиданное — это только «позднее связывание» с заменой $self на static.
Эта возможность появилась лет пять назад, в 5.3. Она является основной многих современных фреймворков, например, можно сказать что Yii 2 обязан своему появлению переходу именно на 5.3 А для вас это вдруг неожиданность.
Поэтому я и пишу такие статьи. Чтобы «неожиданностей» в мануале по языку, за знание которого вам платят деньги, было поменьше.TimID
07.06.2015 23:46«Неожиданное» в смысле «классических» ООП языков. Ну никак из смысла static не вытекает возможность сделать ссылку на класс-потомок сделать доступной в родительском классе. Обычно просто используются виртуальные методы и т.п.
AlexLeonov Автор
08.06.2015 13:53Скорее просто неудачный выбор ключевого слова.
Согласитесь, что например children::method() или current::method() было бы гораздо проще запоминать и объяснять начинающим, нежели static::method()dvapelnik
13.06.2015 13:45именно
static
и ведет себя так, как должен вести себя в классических ОО-языках, для меня было большей неожиданностью поведениеself
когда я узнал о позднем связывании. потому следует понимать когда нужно использоватьself
, а когдаstatic
. как по мне, то использованиеstatic
более прозрачно, а вотself
использовать уже когда это дейсвительно нужно оправдано
karser
08.06.2015 16:21Странно, столько мнений, но никто не сказал, что не стоит увлекаться статикой. В любой книге про ООП написано, что она портит архитектуру — делает код нетестируемым и ломким, а классы тесно связаными друг с другом. Мне кажется именно об этом стоит говорить на собеседовании.
AlexLeonov Автор
08.06.2015 17:09+1Дело в том, что это мнение про «не стоит увлекаться» само по себе ничего не значит, если вы не способны это мнение обосновать.
Что такое «не стоит увлекаться»? Почему? Только потому, что «в любой книге написано»? В чём проблема тестирования такого кода? Как эти проблемы решать?
Видите, сколько сразу вопросов, которые вам зададут на приличном собеседовании?
P.S. Мнение «статика — всегда плохо» как минимум спорно. Есть ORM где, например, статические методы класса сущности работают с таблицей сущностей, динамические — с конкретной записью в этой таблице. Это тоже плохо? Почему?karser
11.06.2015 20:14Статика, глобальные переменные это кошмар при написании unit тестов.
Тесты должны исполняться независимо друг от друга.
Чтобы быть уверенным, что никто до вас не вызывал функцию foo, вам придется перезапускать php процесс перед выполнением каждого теста.
function foo() { static $a = 0; echo $a; $a = $a + 1; } foo(); // 0 foo(); // 1 foo(); // 2
AlexLeonov Автор
11.06.2015 22:32Во-первых вы немного путаете. Предыдущий юзер имел в виду, под «статикой» статические методы классов все-таки.
Во-вторых да, тесты должны выполняться независимо. Но с другой стороны архитектура может потребовать от вас некую «функцию с внутренним состоянием», чем бы это ни было, пусть даже это будет объект, который банально считает число вызовов своего определенного метода. И как быть? Как бы вы решили эту дилемму?
Lure_of_Chaos
Все становится просто супер интуитивно понятным, если понять, что значит static:
— привязка к «объекту» класса, а его не его реализующего объекта, т.е. для всех объектов класса
— привязка происходит в момент загрузки класса, не раньше («загружен, но не вызван») и не позже («выполнился коллбек-конструктор класса»)
все относится как к переменным, так и методам объекта.
однако, один вопрос в статье упущен (или мне показалось?): наследование статических методов и полей.
1. можно ли вызывать такие методы из потомка?
2. можно ли перекрывать такие методы, и как добраться до метода суперкласса?
3. время жизни статических полей?
4. доступны ли во время выполнения конструктора\деструктора?
5. что, если выполним два одновременных запроса к пхп, увидим ли мы одинаковые значение статических полей, или разные?
6. то же, что и 5?только в разное время?
7. что, если обращаемся к статическому методу по нуллевой ссыле? (типичный вопрос для джава-юниора)
bolk
1) да, через через self/static
2) да, да, через parent
3) = времени жизни скрипта
4) да
5) разные.
6) разные.
7) в таком смысле в PHP ссылок нет
Ugputu
почему 5 и 6 разные?
hell0w0rd
Потому что bolk не прав. Зависит от того, что значит «запроса к пхп». Изначально все зависит от реализации, может быть в статических полях константы — и мы всегда увидим одинаковые значения. Вероятно вопрос в том, сохраняются ли значения между разными запросами вида: запрос — php отработал — ответ, тогда нет, не сохранятся. А если php запущен, как демон, то сохранятся.
Так что вопрос идиотский и ответ на него тоже не понятный.
bolk
Ответ на вопрос всегда даётся в общем случае. В общем случае там разные значения. Я примерно понимаю о чём спрашивает Lure_of_Chaos, потому что я не припомню больше языков в вебе, которые работали бы как ПХП — умирали между запросами. А у него вопрос явно относится к разделяемым между различными обработчиками значениям.
Да, есть попытки привинчивания к PHP «честного» FastCGI или WSGI, но в данном случае постоянность значений не обеспечивается свойством статичных переменных, поэтому и тут мой ответ — «разные».
Ugputu
Я может глупость скажу, может чего-то не знаю. Но вроде Perl, Ruby, Python, PHP умирают между запросами, если они выполняются в обычном контексте веб-сервера.
bolk
Про Руби не знаю, а у Перла есть «честный» FastCGI, «Пайтон» же чаще всего пускают через WSGI.
Ugputu
что значит «в статических полях константы»?
velezh
Это будут разные поля, не одно и то же. А значение может быть какое угодно, хотите будет разное, хотите одинаковое.
nikita2206
2) — через имя класса, из которого хотите вытащить статический метод. parent:: работает только в контексте вызова метода объекта