Php навіщо використовують статичні методи. Готуємось до співбесіди з PHP: ключове слово «static. Слово про абстракцію

Дуже важливою особливістю ООПє наявність статичних властивостей та методів. Головне, що необхідно одразу зрозуміти, що такі властивості та методи належать не об'єкту, а класу. Це потрібно зрозуміти з самого початку, а от застосування статичних властивостей та методів у PHPя розгляну у цій статті.

Найкласичніший приклад - це клас, який відповідає за математичні функції. Якщо хтось знає Java, то він знає, що там є клас MathJavaScriptтакий клас теж є), що містить безліч математичних функцій. І там методи статичні. Це означає, що для того, щоб порахувати синус або експоненту, не потрібно створювати об'єкт даного класу, що дуже зручно.

Давайте ми з Вами напишемо зменшену копію цього класу, але тільки для PHP:

class Math (
private static $count = 0;
public function __construct() (
self::$count++;
}
public static function calcSin($x) (
return sin($x);
}
public static function calcSQRT($x) (
return sqrt($x);
}
public static function getCount() (
return self::$count;
}
}
echo Math::calcSin(1);
echo "
";
echo Math::calcSQRT(9);
echo "
";
$math = new Math();
$math_2 = новий Math();
echo Math::getCount();
?>

У цьому коді я показав використання статичних методів та властивостей. Зверніть увагу, що я реалізував класичний спосіб лічильника об'єктів. Це вийшло лише завдяки тому, що поле countє статичним, і вона має те саме значення всім об'єктів.

Ще один популярний приклад використання статичних методів та властивостей– це ведення лога. Усі записи додаються через статичні методи. Також дуже часто роблять клас, що складається з безлічі налаштувань, і там також усі поля статичні. Як бачите, прикладів використання статичних методів та властивостей у PHPта інших мовах більш ніж достатньо, тому вміти з ними працювати треба обов'язково.

Починаючи з версії PHP 5.3.0 з'явилася особливість, звана пізніше статичне зв'язування, яка може бути використана для того, щоб отримати посилання на клас, що викликається в контексті статичного спадкування.

Якщо точніше говорити, пізніше статичне зв'язування зберігає ім'я класу вказаного в останньому "не перенаправленому виклику". У разі статичних викликів це явно вказаний клас (зазвичай ліворуч від оператора :: ); у разі нестатичних викликів це клас об'єкта. "Переспрямований виклик" - це статичний виклик, що починається з self::, parent::, static::, або, якщо рухатися вгору по ієрархії класів, forward_static_call(). Функція get_called_class()може бути використана щоб отримати рядок з ім'ям викликаного класу, та static::представляє її сферу дії.

Сама назва "пізнє статичне зв'язування" відображає в собі внутрішню реалізацію цієї особливості. "Пізнє зв'язування" відображає той факт, що звернення через static::не будуть обчислюватися по відношенню до класу, в якому метод, що викликається, визначений, а обчислюватимуться на основі інформації в ході виконання. Також ця особливість була названа "статичне зв'язування" тому, що вона може бути використана (але не обов'язково) у статичних методах.

Обмеження self::

Приклад #1 Використання self::

class A (
echo __CLASS__ ;
}
public static function
test () (
self::who();
}
}

class B extends A (
public static function who () (
echo __CLASS__ ;
}
}

B:: test ();
?>

Використання пізнього статичного зв'язування

Пізніше статичне зв'язування намагається усунути це обмеження, надаючи ключове слово, яке посилається на клас, викликаний безпосередньо в ході виконання. Простіше кажучи, ключове слово, яке дозволить вам посилатися на Bз test()у попередньому прикладі. Вирішили не вводити нове ключове слово, а використовувати static, яка вже зарезервована.

Приклад #2 Просте використання static::

class A (
public static function who () (
echo __CLASS__ ;
}
public static function
test () (
static:: who(); // Тут діє пізнє статичне зв'язування
}
}

class B extends A (
public static function who () (
echo __CLASS__ ;
}
}

B:: test ();
?>

Результат виконання цього прикладу:

Зауваження:

У нестатичному контексті викликаним класом буде той, до якого належить екземпляр об'єкта. Оскільки $this->буде намагатися викликати закриті методи з тієї ж області дії, використання static::може дати різні результати. Інша відмінність у тому, що static::може посилатися лише на статичні поля класу.

Приклад #3 Використання static::у нестатичному контексті

class A (
private function foo () (
echo "success!\n" ;
}
public function test () (
$this -> foo();
static:: foo();
}
}

class B extends A (
/* foo() буде скопійований В, отже його область дії за попереднім А,
і виклик буде успішним*/
}

class C extends A (
private function foo () (
/* вихідний метод замінено; область дії нового методу С*/
}
}

$ b = New B ();
$b -> test();
$c = new C();
$c -> test(); //не вірно
?>

Результат виконання цього прикладу:

success! success! success! Детальний error: Call до private method C::foo() from context "A" in /tmp/test.php on line 9

Зауваження:

Роздільна область пізнього статичного зв'язування буде фіксована статичним викликом, що її обчислює. З іншого боку, статичні виклики з використанням таких директив як parent::або self::перенаправляють інформацію дзвінка.

Приклад #4 Перенаправлені та не перенаправлені дзвінки

У PHP є можливість визначити метод як статичний. Статичний метод немає доступу до властивостей об'єкта. Такі методи можуть бути викликані лише у контексті класу, але з контексті об'єкта.

Найважливіше що треба зрозуміти – статичні властивості та методи належать класам, а не об'єктам.

На прикладі стане одразу зрозуміло. Давайте створимо об'єкт Math (скорочене назвою математики в англійській).

Статичні методи PHP
$math_1 = new Math(); $math_2 = new Math(); $math_3 = new Math(); $math_4 = new Math(); echo "Створено об'єктів: " . Math::getCount(); ?>

Цей клас надає інструменти для роботи з математичними функціями без створення об'єкта. У класі є конструктор, який підвищує статичну властивість $count на одиницю. Клас запам'ятовує значення цієї властивості, оскільки вона статична.

До речі, для оголошення методу чи властивості статичним використовується слово static, а для доступу до статичної властивості використовується слово self з подвійною двокрапкою "::".

Все це краще зрозуміти порівняно, особливо порівняно робочого прикладу з помилковим. Давайте трохи розширимо наш приклад.

Статичні методи PHP counter++; ) public static function calcSin($x) ( return sin($x); ) public static function calcSQRT($x) ( return sqrt($x); ) public static function getCount() ( return self::$count; ) public function getCounter() ( return $this->counter; ) ) echo Math::calcSin(1); echo "
"; echo Math::calcSQRT(9); echo "
$math_1 = новий Math(); $math_2 = новий Math(); $math_3 = новий Math(); $math_4 = новий Math(); echo "Створено об'єктів: " . Math::getCount();
"; echo "Створено об'єктів: " . $math_4->getCounter(); ?>

У цьому прикладі ми додали класу звичайну властивість $counter, вона також збільшувалася на одиницю в конструкторі. Але звичайна властивість належить об'єкту, тому вона зберігається між викликами об'єктів. При будь-якому створенні екземпляра класу (об'єкта) властивість дорівнюватиме нулю, в конструкторі воно буде збільшено на одиницю.

Статична властивість належить класу, тому його значення зберігається.

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

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

Статичні методи PHP age. "old."; // Це помилка "Using $this when not in object context". )) $TestClass = new TestClass(); $TestClass->sayHello(); ?>

До речі, без рядка:

$TestClass->sayHello();

помилки не буде, але як тільки ви в коді спробуєте запустити статичний метод зі змінною $this, ви відразу отримаєте повідомлення про помилку.

Якщо в цьому прикладі забрати слово static, то помилки не буде.

Якщо зі статичного методу звернутися до властивості об'єкта, це призведе до помилки. Можна звертатись лише до статичних властивостей за допомогою конструкції self::$age . Зверніть увагу, що є знак $ перед ім'ям змінної, на відміну конструкції $this->age .

Статичні методи PHP sayHello(); ?>

Якщо у цьому прикладі прибрати слово static перед ім'ям властивості, виникне помилка "Access to undeclared static property".

Статичні властивості відсутні в об'єктах класу.

Статичні методи PHP "; print_r($TestClass); echo ""; ?>

Статичний метод можна викликати, використовуючи конструкцію self::метод() . Приклад:

Статичні методи PHP printHello(); ?>

Статичну властивість можна отримати в контексті класу, використовуючи синтаксис:

echo TestClass::$age;

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

Статичну властивість можна змінювати в контексті класу, використовуючи синтаксис:

TestClass::$age += 20; // Наприклад

Ще приклад коду зі статичними методами та властивостями

У цьому вся прикладі ще прості варіанти використання статичних методів і властивостей. Чим більше простого коду ви зрозумієте, краще запам'ятайте матеріал.

Статичні методи PHP ".TestClass::$age; // echo TestClass::$txt; // Помилка: Fatal error: Access для undeclared static property. echo "
"; TestClass::sayHi(); echo "
"; TestClass::sayHello(); // А ось так мені вдалося отримати доступ до статичної змінної через об'єкт ($obj::$age)... echo "
$obj = new TestClass; echo "Отримуємо поступ до статичної змінної через об'єкт: " . $obj::$age; ?>

Зверніть увагу, і це важливо, у цьому прикладі ми звернулися до нестатичного методу sayHi(), використовуючи синтаксис звернення до статичних елементів класу.

Резюме

  • Головне: статичні властивості належать класам, а чи не об'єктам.
  • Зі статичного методу не можна звернутися до звичайних властивостей і методів класу, $this->name не працює тут.
  • Зі статичного методу можна звернутися до статичних властивостей використовуючи self::$name .
  • Статичні характеристики класу не доступні об'єктам.
  • Звичайний метод може звернутися до статичної властивості, використовуючи self::$name .
  • Статичну властивість можна отримати в контексті класу, використовуючи синтаксис: TestClass::$age .
  • Звичайний метод можна викликати в контексті об'єкта ($object->метод()), і класу використовуючи синтаксис TestClass::метод() .
  • За допомогою синтаксису $object::$age мені вдалося отримати доступ до статичної властивості через об'єкт.

Паралелі з JavaScript

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

Для проведення математичних обчислень (розрахунок синуса або експоненти) JavaScript не потрібно створювати об'єкт класу Math, так як його методи є статичними. До вивчення PHP я ніяк не міг зрозуміти що це таке, і тільки вивчивши класи та об'єкти в PHP, у мене в голові все стало на свої полички.

Насправді це дуже зручно, мати прямий доступ до математичних методів класу Math, уникаючи створення об'єкта.

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

«Класо-орієнтоване програмування» - це коли використовуються класи, що складаються тільки зі статичних методів і властивостей, а екземпляр класу ніколи не створюється. У цій статті я говоритиму про те, що:

  • це не дає жодних переваг у порівнянні з процедурним програмуванням
  • не варто відмовлятися від об'єктів
  • наявність статичних членів класу! = смерть тестам
Хоча ця стаття про 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 = новий 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.

    Класоорієнтоване програмування - дурість. Вчитеся використовувати ООП.

    • 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, написане перед наданням значення локальної змінної, призводить до наступних ефектів:

    1. Привласнення виконується лише один раз, при першому виклику функції
    2. Значення позначеної таким чином змінної зберігається після закінчення функції
    3. При наступних дзвінках функції замість присвоєння змінна отримує збережене раніше значення
    Таке використання слова 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(), яка повідомить вам - у контексті якого класу в даний момент працює ваш код.

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