Готуємося до співбесіди по PHP: ключове слово «static»

Не секрет, що на співбесідах люблять задавати каверзні питання. Не завжди адекватні, не завжди мають відношення до реальності, але факт залишається фактом — задають. Звичайно, питання питання ворожнечу, і іноді, на перший погляд здається вам безглуздим, насправді спрямований на перевірку того, наскільки добре ви знаєте мову, на якому пишете.

image

Спробуємо розібрати по кісточках» одне з таких питань — що значить слово «static» в PHP і навіщо воно застосовується?

Ключове слово static має в PHP три різних значення. Розберемо їх в хронологічному порядку, як вони з'являлися в мові.

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

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

echo foo(); // 0
echo foo(); // 0
echo foo(); // 0


В PHP локальні змінні. Це означає, що змінна, визначена й одержала значення всередині функції (методу), існує тільки під час виконання цієї функції (методу). При виході з методу локальна змінна знищується, а при повторному вході — створюється заново. У коді вище такої локальної змінної є змінна $a — вона існує тільки всередині функції foo() і кожен раз при виклику цієї функції створюється заново. Інкремент змінної в цьому коді безглуздий, оскільки на наступному рядку коду функція закінчить свою роботу і значення змінної буде втрачено. Скільки б разів ми не викликали функцію foo(), вона завжди буде повертати 0…

Проте все змінюється, якщо ми перед присвоюванням поставимо ключове слово static:

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

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


Ключове слово static, написаний перед присвоюванням значення локальної змінної, приводить до наступних ефектів:
  1. Присвоювання виконується тільки один раз, при першому виклику функції
  2. Значення позначеної таким чином змінної зберігається після закінчення роботи функції
  3. При наступних викликах функції замість присвоювання мінлива отримує збережений раніше значення
Таке використання слова static називається локальна статична змінна.

Підводні камені статичних змінних
Зрозуміло, як завжди в PHP, не обходиться без «підводних каменів».

Камінь перший — статичної змінної привласнювати можна лише константи або константные вираження. Ось такий код:
static $a = bar();

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

Камінь другої — методи існують в єдиному екземплярі.
Тут все трохи складніше. Для розуміння суті наведу код:
class A {
public function foo() {
static $x = 0;
echo ++$x;
}
}

$a1 = A new;
$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 = A new;
$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;
public static function foo() {
echo $this->id;
}
}

не призведе ні до яких помилок, до тих пір, поки ви не спробуєте використовувати метод foo() неналежним чином:
$a = new A;
$a->foo();
(і відразу отримаєте «Fatal error: Using $this when not in object context»)

друга Особливість — static не аксіома!
class A {
public static function foo() {
echo 42;
}
}

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

Ось так, так. Статичний метод, якщо він не містить в коді $this, цілком можна викликати у динамічному контексті, як метод об'єкта. Це не є помилкою в PHP.

Зворотне не зовсім вірно:
class A {
public function foo() {
echo 42;
}
}

A::foo();

Динамічний метод, який не використовує $this, можна виконувати в статичному контексті. Однак ви отримаєте попередження «Non-static A method::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(), яка повідомить вам — в контексті якого класу в даний момент працює ваш код.

Вдалих співбесід!

Список корисних посилань на мануал:

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.