Класи та об’єкти в PHP
Сьогодні неможливо уявити процес створення сучасних Web-додатків без розуміння принципів об’єктно-орієнтованого програмування (ООП). Популярні фреймворки побудовані за об’єктно-орієнтованими принципами, використовують компоненти та визначені класи, простори імен та успадкування. Тому ігнорувати або не знати принципи ООП не може дозволити собі жоден розробник.

Об’єктно-орієнтоване програмування поряд з визначеними типами (такими як integer, boolean, double, string) дозволяє формувати власні абстрактні типи змінних - класи. Екземпляри класів чи змінні їх типу називаються об’єктами. Створюючи власні типи даних, можна оперувати не машинними термінами (змінна), а об’єктами реального світу, піднімаючись цим на новий абстрактний рівень.
Не можна виконувати спільні операції з машинами та деревами, проте низькорівневий код запросто дозволить зробити таку логічну помилку, тоді як при використанні абстрактних типів даних ця операція стає неможливою або принаймні дуже складною. Абстрактні типи даних створені для реалізації можливості вводити у програму змінні з бажаними властивостями. Клас описує склад об’єкта - змінні та функції, які обробляють і визначають поведінку об’єкта.
У тілі класу може бути оголошено змінні, які називаються змінними класу. Наприклад, для позначення точки координат можна створити клас Map, який містить дві координати $х і $у:
Як і звичайна змінна, об’єкт існує до кінця часу виконання скрипта або поки не буде явно знищений за допомогою конструкції Unset. Використання конструкції Unset може бути корисним, якщо об’єкт займає великий обсяг оперативної пам’яті, і його слід звільнити для недопущення переповнення.
В цьому випадку замість того, щоб кожен раз передавати об’єкт, як це відбувається у випадку звичайних змінних, передається посилання на об’єкт. Це дозволяє значно прискорити виконання скриптів, особливо, коли об’єкт потрібно передати через межу області видимості.
Наведемо приклад використання закритих методів класу:
Для виклику статичного методу необхідно перед ім’ям класу необхідно додати оператор дозволу області видимості «::»:
Зазвичай в об’єктно-орієнтованих мовах програмування явний виклик конструктора взагалі не допускається, оскільки це суперечить принципу інкапсуляції, однак у РНР конструктор можна викликати не лише у самому класі, але й із зовнішнього коду. Але слід уникати маніпулювання конструктором напряму. Якщо одні й тіж самі дії можуть виконуватися як конструктором, і будь-яким іншим методом, краще визначити окремий метод до виконання цього набору дій.
Метод __get() призначений для реалізації читання властивості, приймає єдиний параметр, який є ключем. Метод __set() дозволяє присвоїти властивості нове значення і приймає два параметри, перший із яких є ключем, а другий - значенням властивості.
Наведемо приклад, у якому з допомогою методу __set() об’єкту присвоюються нові властивості, які поміщаються у масив $this->arr, а перевантажений метод __get() дозволяє витягти їх із масиву.
За допомогою спеціального методу __call() можна емулювати методи зі змінною кількістю параметрів. Для прикладу створимо клас MinMax, який надає користувачеві два методи: min() і max(), що приймають довільну кількість числових параметрів та визначають мінімальне та максимальне значення відповідно.
За аналогією зі спеціальним методом __call() існує статичний варіант __callStatic(), що дозволяє динамічно створювати статичні методи. Наведемо приклад реалізації класу MinMax, в якому методи min() і max() визначаються як статичні:
Суть спадкування полягає у можливості створення нового класу на основі вже існуючого, автоматично включивши до нового класу змінні та методи старого. При цьому "старий" клас називається базовим, а новостворений клас - похідним. Під час оголошення похідного класу необхідно вказати ім’я базового класу за допомогою ключового слова extends. Наведемо приклад, у якому буде створено клас Base і клас Derived, який успадковуватиме перший клас.
Іноді зручно, щоб змінна або метод базового класу залишаючись закритими для зовнішнього коду, були відкриті для похідного класу. У цьому випадку вдаються до специфікатора доступу protected. Забезпечені ним компоненти класи називають захищеними.
#самовчитель_php

Об’єктно-орієнтоване програмування поряд з визначеними типами (такими як integer, boolean, double, string) дозволяє формувати власні абстрактні типи змінних - класи. Екземпляри класів чи змінні їх типу називаються об’єктами. Створюючи власні типи даних, можна оперувати не машинними термінами (змінна), а об’єктами реального світу, піднімаючись цим на новий абстрактний рівень.
Не можна виконувати спільні операції з машинами та деревами, проте низькорівневий код запросто дозволить зробити таку логічну помилку, тоді як при використанні абстрактних типів даних ця операція стає неможливою або принаймні дуже складною. Абстрактні типи даних створені для реалізації можливості вводити у програму змінні з бажаними властивостями. Клас описує склад об’єкта - змінні та функції, які обробляють і визначають поведінку об’єкта.
Створення класу
У мові програмування PHP клас оголошується за допомогою ключового слова class, після якого йдуть унікальне ім’я класу та тіло класу у фігурних дужках. Назва не залежить від регістру і може містити будь-яку кількість цифр, букв та символів підкреслення, однак не може починатися з цифри. Стандарт кодування PSR вимагає, щоб ім’я класу має бути оголошено з використанням так званої СаmеlСаse: кожне слово починається з великої літери, між словами немає роздільників. Докладніше можна дізнатися зі статті Стандарти, стилі та правила оформлення коду PHP.<?php
class MyClass
{
// Змінні класу
}РНР дозволяє включати скрипти в документ за допомогою тегів <?php і ?>. Один документ може містити велику кількість включень цих тегів, однак клас повинен оголошуватися в одному нерозривному блоці <?php і ?>. Не вдасться також механічно розбити клас за допомогою інструкції Include або Require.У тілі класу може бути оголошено змінні, які називаються змінними класу. Наприклад, для позначення точки координат можна створити клас Map, який містить дві координати $х і $у:
<?php
class Map
{
public $x;
public $y;
}У скрипті можна лише один раз визначити клас. Спроба повторного визначення класу призведе до генерації повідомлення про помилку "Fatal error: Cannot declare class Map, because the name is already in use". Цю помилку досить легко діагностувати, тобто обидва класи знаходяться в тому самому файлі. Однак, на практиці під кожен клас виділяється окремий файл, а у великому проєкті, що складається з десятків класів, використання інструкцій підключення файлів може призвести до ситуації, коли той самий файл включається повторно, що призведе також до помилки. Ситуація може ускладнюватися тим, що всередині файлів можуть бути розташовані інші конструкції, що включають інші файли. Щоб вирішити проблему з повторним визначенням класів, необхідно використовувати Include_once або Require_once. Їхня відмінність від оригінальних інструкцій полягає в тому, що вони включають файл лише один раз, спроби повторного увімкнення файлу ігноруються.Створення об’єкту
Після створення класу можна переходити до створення об’єкта цього класу шляхом оголошення за допомогою ключового слова new, за яким слідує ім’я класу. Стандарт кодування PSR рекомендує зберігати код класу та використовує його код у різних файлах. Щоб скористатися класом, файл, що містить його визначення, слід підключити його за допомогою інструкції require_once:<?php
require_once 'map.php';
$map = new Map;
$map->x = 1;
$map->y = 2;
echo $map->x; // 1Об’єкт $map класу Map створюється ключовим словом new. Для звернення до змінних об’єктів $х і $у, слід скористатися спеціальним оператором «->». На відміну від стандартних змінних РНР символ $ після -> при зверненні до змінних об’єкта не вказується. Після ініціалізації об’єкта створюється стандартна змінна $map зі специфічними якостями. Як і будь-якій іншій змінній, об’єкту можна надавати нове значення.<?php
require_once 'map.php';
$map = new Map; // object
$map = 'str'; // string(3)У цьому прикладі об’єкту $map надається рядкове значення, і він стає змінною рядкового типу.Як і звичайна змінна, об’єкт існує до кінця часу виконання скрипта або поки не буде явно знищений за допомогою конструкції Unset. Використання конструкції Unset може бути корисним, якщо об’єкт займає великий обсяг оперативної пам’яті, і його слід звільнити для недопущення переповнення.
Специфікатори доступу
Змінні класу оголошуються за допомогою специфікаторів public, private або protected, які вказують на доступ до змінних об’єктів ззовні. Відкриті члени класу оголошуються специфікатором доступу public і доступні як усередині класу, так і ззовні. Закриті методи та члени класу оголошуються за допомогою специфікатора private та доступні лише у межах класу. Специфікатор protected використовується при успадкуванні (розглянемо далі).<?php
class PrivateMap
{
public $x;
private $y;
}
// Оголошуємо об’єкт класу PrivateMap
$map = new PrivateMap;
// Присвоюємо значення змінним об’єкта
$map->x = 1;
$map->y = 2; // Fatal error: Uncaught Error: Cannot access private property PrivateMapУ цьому прикладі клас містить відкриту змінну $х і закриту $у. Звернення до закритого члена класу $у завершиться помилкою "Fatal error: Uncaught Error: Cannot access private property PrivateMap".Статичні змінні класу
Кожен об’єкт має власний набір змінних, незалежних від інших об’єктів. Але можна створити статичні змінні на рівні класу, які оголошуються за допомогою ключового слова static. Особливістю таких змінних є можливість їх ініціалізації у класі при оголошенні:<?php
class MyStatic
{
public static $staticvar = 50;
}Звертатися до таких змінних можна без створення об’єктів за допомогою оператора доступу видимості «::»:echo MyStatic::$staticvar; // 50Посилання на змінні
Якщо надавати змінним однакові значення за допомогою оператора, виходять дві незалежні змінні:<?php
$first = $second = 1;
$first = 2;
echo $second; // 1Працюючи з об’єктами ситуація зовсім інша. Оператор присвоєння = не призводить до створення нової копії об’єкта: і старий, і новий об’єкт вказують на ту саму область пам’яті:<?php
require_once 'map.php';
$first = new Map;
$first->x = 3;
$first->y = 3;
$second = $first;
$second->x = 5;
$second->y = 5;
echo "x: {$first->x}, y: {$first->y}";Присвоєння нових значень змінним одного об’єкта призводить до того, що ці значення отримує і другий об’єкт. Тобто змінні $first і $second посилаються на той самий об’єкт.В цьому випадку замість того, щоб кожен раз передавати об’єкт, як це відбувається у випадку звичайних змінних, передається посилання на об’єкт. Це дозволяє значно прискорити виконання скриптів, особливо, коли об’єкт потрібно передати через межу області видимості.
Клонування об’єктів
Якщо потрібно створити копію поточного об’єкта, використовується спеціальна операція - клонування. Вона виконується за допомогою ключового слова clone, яке розташовується безпосередньо перед об’єктом клонування:<?php
require_once 'map.php';
$first = new Map;
$first->x = 3;
$first->y = 3;
$second = clone $first;
$second->x = 5;
$second->y = 5;
echo "x: {$first->x}, y: {$first->y}"; // x: 3, y: 3Методи
Найбільш корисною властивістю класів є можливість оголошення всередині них функцій, які можуть виконувати операції над даними об’єктом, викликати інші функції об’єкта. Такі функції називають методами.Визначення методу
Крім змінних у класи можна включати методи, які є звичайними функціями РНР. Наведемо приклад класу Hello, до складу якого входить метод Greet, який повертає повідомлення «Hello, world!»:<?php
class Hello
{
public function greet()
{
return 'Hello, world!';
}
}Звернутися до методу можна, аналогічно змінним, за допомогою оператора ->:$obj = new Hello;
echo $obj->greet(); // Hello, world!Звернення до змінних об’єктів
Для того, щоб звернутися до змінних або інших методів усередині методу, використовується спеціальна змінна $this, після якої слідують оператор -> і назва змінної або класу. Для оголошення закритих змінних, доступних тільки всередині класу або об’єкта, використовується специфікатор доступу Private, а для відкритих - специфікатор доступу Public. Специфікатори можуть використовуватись і щодо методів.Наведемо приклад використання закритих методів класу:
<?php
class Sum
{
private $x;
public function setX($x)
{
$this->x = $x;
}
public function getX()
{
return $this->x;
}
public function result()
{
return $this->getX() + 2;
}
}
$sum = new Sum;
$sum->setX(2);
echo $sum->result(); // 4Оскільки змінна $х доступна лише всередині методів класу Sum, то звернутися безпосередньо через об’єкт echo $sum->x вже не вийде, і користувач побачить помилку: Fatal error: Uncaught Error: Cannot access private property Sum. Для цього можна використовувати метод-аксесор, який встановлює нове значення змінної $х:void setX(int $x)Крім вище описаного методу, у прикладі використовується додатковий метод Result, який додає до значення змінної $x значення 2.Статичні методи
До поточного моменту використовувалися методи об’єкта, які можна викликати лише після того, як об’єкт класу створювався за допомогою конструкції New. Навіть коли методи викликають інші методи за допомогою $this->, вони все одно звертаються до поточного об’єкта і не можуть бути викликані раніше, ніж після його створення. Однак, РНР надає спосіб використання методу без об’єктів. Для цього метод слід оголосити статичним за допомогою ключового слова Static:<?php
class Greet
{
public static function hello()
{
return 'Hello, world!';
}
}РНР допускає будь-який порядок проходження специфікаторів доступу та ключового слова Static перед ім’ям функції. Однак, відповідно до вимог PSR, ключове слово Static завжди розташовується після специфікаторів доступу.Для виклику статичного методу необхідно перед ім’ям класу необхідно додати оператор дозволу області видимості «::»:
echo Greet::hello(); // Hello, world!Ключове слово self
Всередині методу класу, щоб послатися на статичну змінну цього класу, не обов’язково використовувати ім’я класу перед оператором дозволу області видимості. Замість нього може використовуватись ключове слово self:<?php
class Greet
{
public static function hello()
{
return 'Hello, world!';
}
public static function result()
{
return self::hello();
}
}
echo Greet::result(); // Hello, world!Варіант із Self дозволяє перейменовувати клас без численних виправлень і, як правило, коротший у написанні.Спеціальні методи
Клас може містити ряд спеціальних методів, що викликаються неявно при створенні, видаленні об’єкта, при зверненні до його змінних та методів. Існує можливість розробнику перевизначити ці методи і цим втрутитися у життєвий цикл об’єкта. Такі спеціальні методи у співтоваристві називають магічними методами, а щоб відрізнити їх від інших методів, їхня назва передується двома символами підкреслення, наприклад, __constructor().Конструктор __construct()
При створенні об’єкта до виклику інших нестатичних методів класу можна автоматично виконати спеціальний метод класу під назвою конструктор, який використовується головним чином для ініціалізації об’єкта, забезпечуючи узгоджений стан об’єкта перед початком роботи. Для оголошення конструктора класа необхідно створити метод з ім’ям «__construct()». Наведемо приклад, де при оголошеннях класу Constructor, що містить конструктор, буде виведено повідомлення "Виклик конструктора" та ініціалізується закритий член $var.<?php
class Constructor
{
private $var;
public function __construct()
{
echo 'Виклик конструктора';
$this->var = 5;
}
}
$obj = new Constructor;
print_r($obj);Результатом роботи скрипта є наступні рядки:Виклик конструктора
Constructor Object
(
[var:Constructor:private] => 5
)Конструктор виконується автоматично під час виконання оператора New. Це дозволяє розробнику класу бути впевненим, що змінні класу отримають коректну ініціалізацію.Зазвичай в об’єктно-орієнтованих мовах програмування явний виклик конструктора взагалі не допускається, оскільки це суперечить принципу інкапсуляції, однак у РНР конструктор можна викликати не лише у самому класі, але й із зовнішнього коду. Але слід уникати маніпулювання конструктором напряму. Якщо одні й тіж самі дії можуть виконуватися як конструктором, і будь-яким іншим методом, краще визначити окремий метод до виконання цього набору дій.
Параметри конструктора
При оголошенні об’єкта в круглих дужках після імені класу можна вказати параметри, які може приймати конструктор так само, як будь-який інший метод. Для прикладу продемонструємо клас Map, який має закриту змінну $х і конструктор, який ініціалізує закритий член класу:<?php
class Map
{
private $x;
public function __construct($x)
{
$this->x = $x;
}
public function getX()
{
return $this->x;
}
}
$obj = new Map(5);
echo $obj->getX();Важливо, що якщо не вказати аргумент при оголошенні об’єкта, то буде помилка "Fatal error: Uncaught ArgumentCountError: Too few arguments to function Map::__construct()". Однак, можна використовувати необов’язкові параметри. У наступному прикладі закрита змінна $х отримує значення 0, якщо значення не встановлюється через параметри конструктора.public function __construct($x = 0)
{
$this->x = $x;
}Деструктоp __destruct()
Деструктор – це спеціальний метод класу, який автоматично виконується у момент знищення об’єкта. Цей метод викликається завжди останнім і використовується головним чином для коректного звільнення зарезервованих конструктором ресурсів. Для оголошення деструктора у класі необхідно створити метод з ім’ям «__destruct()»:<?php
class Destructor
{
public function __destruct()
{
echo 'Виклик деструктора';
}
}
$obj = new Destructor();Як можна бачити, деструктор виконується в останню чергу і знищує об’єкт при завершенні скрипту. Насправді деструктор використовується досить рідко, навіть у ситуаціях, де міг бути корисним. Справа в тому, що деструктор викликається лише у тому випадку, коли об’єкт штатно збирається збирачем сміття. Ця подія може статися далеко не відразу або взагалі не наступити, якщо виникає помилка, виняткова ситуація або скрипт завершується ззовні.Методи-аксесори __set() та __get()
Використання переважно закритих змінних, доступ яких здійснюється через відкриті методи, дозволяє приховати внутрішню реалізацію класу. PHP надає можливість використовувати спеціальні методи «__set()» і «__get()» - аксесори, звернення до яких виглядає так само, як до відкритих змінних класу.Метод __get() призначений для реалізації читання властивості, приймає єдиний параметр, який є ключем. Метод __set() дозволяє присвоїти властивості нове значення і приймає два параметри, перший із яких є ключем, а другий - значенням властивості.
Наведемо приклад, у якому з допомогою методу __set() об’єкту присвоюються нові властивості, які поміщаються у масив $this->arr, а перевантажений метод __get() дозволяє витягти їх із масиву.
<?php
class Accessor
{
private $arr = [];
public function __get($key)
{
return $this->arr[$key];
}
public function __set($key, $value)
{
$this->arr[$key] = $value;
}
}
$obj = new Accessor();
$obj->name = 'Hello, world!';
$obj->color = 'Red';
echo $obj->name;
echo "<pre>";
print_r($obj);
echo "</pre>";Результатом роботи скрипта є наступні рядки:Hello, world!
Accessor Object
(
[arr:Accessor:private] => Array
(
[name] => Hello, world!
[color] => Red
)
)Клас Accessor перехоплює всі звернення до властивостей об’єкта та створює відповідний елемент у закритому масиві $arr. Будь-яка спроба привласнити властивості значення призводить до створення нового елемента закритого масиву $arr.Динамічні методи
Спеціальний метод «__call()» призначений для створення динамічних методів: якщо в класі не перевантажено метод __call(), звернення до неіснуючого методу не призведе до помилки, а передасть керування методом __call(). Як перший параметр метод __call() приймає ім’я викликаного методу, а як другий - масив, елементами якого є параметри, передані при виклику.За допомогою спеціального методу __call() можна емулювати методи зі змінною кількістю параметрів. Для прикладу створимо клас MinMax, який надає користувачеві два методи: min() і max(), що приймають довільну кількість числових параметрів та визначають мінімальне та максимальне значення відповідно.
<?php
class MinMax
{
public function __call($method, $arg)
{
if (!is_array($arg)) {
return false;
}
$value = $arg[0];
if ($method == 'min') {
for ($i = 0; $i < count($arg); $i++)
{
if ($arg[$i] < $value) {
$value = $arg[$i];
}
}
}
if ($method == 'max') {
for ($i = 0; $i < count($arg); $i++) {
if ($arg[$i] > $value) {
$value = $arg[$i];
}
}
}
return $value;
}
}
$obj = new MinMax();
echo $obj->min(3, 7, 1, 2, 9, 5); // 1
echo '<br />';
echo $obj->max(3, 7, 1, 2, 9, 5); // 9Залежно від методу - min() або max() - використовуються два різні алгоритми для пошуку результату. Крім того, для класу MinMax допустимий виклик методу з довільним ім’ям, і, якщо воно відмінне від min або max, буде повертатися перший аргумент. Незалежно від імені методу, якщо не передано жодного аргументу, повертається значення False.За аналогією зі спеціальним методом __call() існує статичний варіант __callStatic(), що дозволяє динамічно створювати статичні методи. Наведемо приклад реалізації класу MinMax, в якому методи min() і max() визначаються як статичні:
<?php
class MinMax
{
public static function __callStatic($method, $arg)
{
if (!is_array($arg)) {
return false;
}
$value = $arg[0];
if ($method == 'min') {
for ($i = 0; $i < count($arg); $i++)
{
if ($arg[$i] < $value) {
$value = $arg[$i];
}
}
}
if ($method == 'max') {
for ($i = 0; $i < count($arg); $i++) {
if ($arg[$i] > $value) {
$value = $arg[$i];
}
}
}
return $value;
}
}
$obj = new MinMax();
echo $obj::min(3, 7, 1, 2, 9, 5); // 1
echo '<br />';
echo $obj::max(3, 7, 1, 2, 9, 5); // 9Інтерполяція об’єкту
Спеціальний метод __toString() дозволяє інтерполувати (підставляти) об’єкт у рядок. Аналогічно змінним, для підстановки значень яких необхідно укласти рядок у подвійні лапки, такої ж поведінки можна домогтися і від об’єкта, якщо реалізувати у його класі метод __toString(), який перетворює об’єкт у рядок. Слід звернути увагу. що метод __toString() виводить результат з допомогою конструкції return, а не echo.public function __toString() {
return "({$this->x})";
}Слід зазначити, що виклик об’єкта в рядковому контексті можливий, тільки якщо його клас містить реалізацію методу __toString, інакше спроба використовувати об’єкт у рядку буде закінчуватися помилкою "Recoverable fatal error: Object of class Point could not be converted to string".Успадкування
Використання повторного коду є однією з головних цілей об’єктно-орієнтованого підходу та один із механізмів реалізації полягає у використанні успадкування.Суть спадкування полягає у можливості створення нового класу на основі вже існуючого, автоматично включивши до нового класу змінні та методи старого. При цьому "старий" клас називається базовим, а новостворений клас - похідним. Під час оголошення похідного класу необхідно вказати ім’я базового класу за допомогою ключового слова extends. Наведемо приклад, у якому буде створено клас Base і клас Derived, який успадковуватиме перший клас.
<?php
class Base
{
public $first;
public function printFirst()
{
echo $this->first;
}
}
class Derived extends Base
{
public $second;
public function printSecond()
{
echo $this->second;
}
}
$obj = new Derived;
$obj->first = 100;
$obj->second = 200;
$obj->printFirst(); // 100
$obj->printSecond(); // 200У базовому класі Base містяться змінна $first та метод printFirst. Від класу Base успадковується клас Derived, що містить змінну $second та метод printSecond. Похідний клас, у свою чергу, може виступати як базовий для інших класів. В результаті можна отримати розгалужену ієрархію класів, розширюючи функціональність без повторного створення змінних та методів, використовуючи наявні з існуючих класів.Специфікатори доступу та успадкування
Всі змінні та методи зі специфікаторами доступу public і private при успадкування поводяться по відношенню до похідного класу так само, як і зовнішній код щодо об’єкта. Це означає, що з похідного класу доступні всі змінні методи, оголошені зі специфікатором доступу public, і не доступні компоненти, оголошені як private.Іноді зручно, щоб змінна або метод базового класу залишаючись закритими для зовнішнього коду, були відкриті для похідного класу. У цьому випадку вдаються до специфікатора доступу protected. Забезпечені ним компоненти класи називають захищеними.
Перевантаження методів
У похідному класі можна створити метод з такою самою назвою, що й у базовому класі, який замінить метод базового класу під час виклику. Така процедура називається перевантаження методів. Однак у рамках похідного класу залишається можливість викликати метод базового класу, звернувшись щодо нього з допомогою префікса parent::. За допомогою навантаження методу можна розширити метод базового класу, так і повністю замінити його вже новим.Пізнє статичне зв’язування
При успадкування класів слід приділяти особливу увагу статичним методам. Ключове слово self дозволяє посилатися на статичний метод і завжди посилається на поточний клас, його не можна звернутися до статичним методам базового класу. РНР надає спеціальне ключове слово static, яке можна використовувати замість self і звертатися до статичним методам базового класу.#самовчитель_php
