Вступ до об'єктно-орієнтованого програмування
Термін “ООП” означає об'єктно-орієнтоване програмування, що є способом організації та структурування коду. ООП дозволяє нам розглядати програму як набір об'єктів, що взаємодіють між собою, а не як послідовність команд і функцій.
В ООП “об'єкт” – це одиниця, яка містить дані та функції, що працюють з цими даними. Об'єкти створюються за “класами”, які можна розуміти як креслення або шаблони для об'єктів. Коли ми маємо клас, ми можемо створити його “екземпляр”, що є конкретним об'єктом, створеним за цим класом.
Давайте покажемо, як ми можемо створити простий клас у PHP. При визначенні класу ми використовуємо ключове слово “class”, за яким слідує назва класу, а потім фігурні дужки, що оточують функції (їх називають “методами”) та змінні класу (їх називають “властивостями” або англійською “property”):
class Автомобіль
{
function посигналити()
{
echo 'Bip bip!';
}
}
У цьому прикладі ми створили клас з назвою Автомобіль
з однією
функцією (або “методом”), названою посигналити
.
Кожен клас повинен вирішувати лише одне основне завдання. Якщо клас робить занадто багато речей, може бути доцільно розділити його на менші, спеціалізовані класи.
Класи зазвичай зберігаються в окремих файлах, щоб код був
організованим і легким для навігації. Назва файлу повинна відповідати
назві класу, тому для класу Автомобіль
назва файлу буде
Автомобіль.php
.
При іменуванні класів добре дотримуватися конвенції “PascalCase”, що означає, що кожне слово в назві починається з великої літери, і між ними немає підкреслень або інших роздільників. Методи та властивості використовують конвенцію “camelCase”, тобто починаються з малої літери.
Деякі методи в PHP мають спеціальні завдання і позначаються префіксом
__
(два підкреслення). Одним з найважливіших спеціальних методів
є “конструктор”, який позначається як __construct
. Конструктор – це
метод, який автоматично викликається, коли ви створюєте новий
екземпляр класу.
Конструктор часто використовується для встановлення початкового стану об'єкта. Наприклад, коли ви створюєте об'єкт, що представляє особу, ви можете використати конструктор для встановлення її віку, імені або інших властивостей.
Давайте покажемо, як використовувати конструктор у PHP:
class Людина
{
private $вік;
function __construct($вік)
{
$this->вік = $вік;
}
function отриматиВік()
{
return $this->вік;
}
}
$людина = new Людина(25);
echo $людина->отриматиВік(); // Виведе: 25
У цьому прикладі клас Людина
має властивість (змінну)
$вік
та конструктор, який встановлює цю властивість. Метод
отриматиВік()
потім дозволяє отримати доступ до віку особи.
Псевдозмінна $this
використовується всередині класу для
доступу до властивостей та методів об'єкта.
Ключове слово new
використовується для створення нового
екземпляра класу. У наведеному вище прикладі ми створили нову людину з
віком 25.
Ви також можете встановити значення за замовчуванням для параметрів конструктора, якщо вони не вказані під час створення об'єкта. Наприклад:
class Людина
{
private $вік;
function __construct($вік = 20)
{
$this->вік = $вік;
}
function отриматиВік()
{
return $this->вік;
}
}
$людина = new Людина; // якщо ми не передаємо жодного аргументу, дужки можна опустити
echo $людина->отриматиВік(); // Виведе: 20
У цьому прикладі, якщо ви не вкажете вік при створенні об'єкта
Людина
, буде використано значення за замовчуванням 20.
Приємно, що визначення властивості з її ініціалізацією через конструктор можна так скоротити та спростити:
class Людина
{
function __construct(
private $вік = 20,
) {
}
}
Для повноти, крім конструкторів, об'єкти можуть мати й деструктори
(метод __destruct
), які викликаються перед тим, як об'єкт буде
звільнений з пам'яті.
Простори імен
Простори імен (або “namespaces” англійською) дозволяють нам організовувати та групувати пов'язані класи, функції та константи, а також уникати конфліктів імен. Ви можете уявити їх як папки на комп'ютері, де кожна папка містить файли, що належать до певного проекту або теми.
Простори імен особливо корисні у великих проектах або коли ви використовуєте бібліотеки сторонніх розробників, де можуть виникнути конфлікти в назвах класів.
Уявіть, що у вас є клас з назвою Автомобіль
у вашому проекті, і
ви хочете розмістити його в просторі імен під назвою Транспорт
.
Ви зробите це так:
namespace Транспорт;
class Автомобіль
{
function посигналити()
{
echo 'Bip bip!';
}
}
Якщо ви хочете використати клас Автомобіль
в іншому файлі, ви
повинні вказати, з якого простору імен походить клас:
$автомобіль = new Транспорт\Автомобіль;
Для спрощення ви можете на початку файлу вказати, який клас з даного простору імен ви хочете використовувати, що дозволяє створювати екземпляри без необхідності вказувати повний шлях:
use Транспорт\Автомобіль;
$автомобіль = new Автомобіль;
Успадкування
Успадкування є інструментом об'єктно-орієнтованого програмування, який дозволяє створювати нові класи на основі вже існуючих класів, переймати їхні властивості та методи, а також розширювати або перевизначати їх за потребою. Успадкування дозволяє забезпечити повторне використання коду та ієрархію класів.
Простіше кажучи, якщо ми маємо один клас і хотіли б створити інший, похідний від нього, але з деякими змінами, ми можемо “успадкувати” новий клас від початкового класу.
У PHP успадкування реалізується за допомогою ключового слова
extends
.
Наш клас Людина
зберігає інформацію про вік. Ми можемо мати
інший клас Student
, який розширює Людину
і додає інформацію
про спеціальність.
Розглянемо приклад:
class Людина
{
private $вік;
function __construct($вік)
{
$this->вік = $вік;
}
function вивестиІнформацію()
{
echo "Вік: {$this->вік} років\n";
}
}
class Student extends Людина
{
private $спеціальність;
function __construct($вік, $спеціальність)
{
parent::__construct($вік);
$this->спеціальність = $спеціальність;
}
function вивестиІнформацію()
{
parent::вивестиІнформацію();
echo "Спеціальність: {$this->спеціальність} \n";
}
}
$student = new Student(20, 'Інформатика');
$student->вивестиІнформацію();
Як працює цей код?
- Ми використали ключове слово
extends
для розширення класуЛюдина
, що означає, що класStudent
успадкує всі методи та властивості відЛюдини
. - Ключове слово
parent::
дозволяє нам викликати методи з батьківського класу. У цьому випадку ми викликали конструктор з класуЛюдина
перед додаванням власної функціональності до класуStudent
. І аналогічно методвивестиІнформацію()
батьківського класу перед виведенням інформації про студента.
Успадкування призначене для ситуацій, коли існує відношення “є” між
класами. Наприклад, Student
є Людина
. Кішка є твариною. Це дає
нам можливість у випадках, коли в коді ми очікуємо один об'єкт (напр.,
“Людина”), використати замість нього успадкований об'єкт (напр.,
“Student”).
Важливо усвідомити, що основною метою успадкування не є запобігання дублюванню коду. Навпаки, неправильне використання успадкування може призвести до складного і важкопідтримуваного коду. Якщо відношення “є” між класами не існує, ми повинні замість успадкування розглянути композицію.
Зверніть увагу, що методи вивестиІнформацію()
в класах
Людина
та Student
виводять трохи різну інформацію. І ми
можемо додати інші класи (наприклад, Працівник
), які
надаватимуть інші реалізації цього методу. Здатність об'єктів різних
класів реагувати на один і той самий метод різними способами
називається поліморфізмом:
$люди = [
new Людина(30),
new Student(20, 'Інформатика'),
new Працівник(45, 'Директор'),
];
foreach ($люди as $людина) {
$людина->вивестиІнформацію();
}
Композиція
Композиція – це техніка, коли замість того, щоб успадковувати властивості та методи іншого класу, ми просто використовуємо його екземпляр у нашому класі. Це дозволяє нам комбінувати функціональність та властивості кількох класів без необхідності створювати складні структури успадкування.
Розглянемо приклад. Ми маємо клас Двигун
і клас
Автомобіль
. Замість того, щоб говорити “Автомобіль є Двигун”,
ми говоримо “Автомобіль має Двигун”, що є типовим відношенням
композиції.
class Двигун
{
function запустити()
{
echo 'Двигун працює.';
}
}
class Автомобіль
{
private $двигун;
function __construct()
{
$this->двигун = new Двигун;
}
function завести()
{
$this->двигун->запустити();
echo 'Автомобіль готовий до поїздки!';
}
}
$автомобіль = new Автомобіль;
$автомобіль->завести();
Тут Автомобіль
не має всіх властивостей та методів
Двигуна
, але має до нього доступ через властивість
$двигун
.
Перевагою композиції є більша гнучкість у дизайні та краща можливість модифікації в майбутньому.
Видимість
У PHP ви можете визначити “видимість” для властивостей, методів та констант класу. Видимість визначає, звідки ви можете отримати доступ до цих елементів.
- Public: Якщо елемент позначений як
public
, це означає, що до нього можна отримати доступ звідусіль, навіть поза класом. - Protected: Елемент з позначкою
protected
доступний лише в межах даного класу та всіх його нащадків (класів, що успадковують від цього класу). - Private: Якщо елемент є
private
, до нього можна отримати доступ лише зсередини класу, в якому він був визначений.
Якщо ви не вкажете видимість, PHP автоматично встановить її на
public
.
Розглянемо приклад коду:
class ПрикладВидимості
{
public $публічнаВластивість = 'Публічна';
protected $захищенаВластивість = 'Захищена';
private $приватнаВластивість = 'Приватна';
public function вивестиВластивості()
{
echo $this->публічнаВластивість; // Працює
echo $this->захищенаВластивість; // Працює
echo $this->приватнаВластивість; // Працює
}
}
$обєкт = new ПрикладВидимості;
$обєкт->вивестиВластивості();
echo $обєкт->публічнаВластивість; // Працює
// echo $обєкт->захищенаВластивість; // Викличе помилку
// echo $обєкт->приватнаВластивість; // Викличе помилку
Продовжимо з успадкуванням класу:
class НащадокКласу extends ПрикладВидимості
{
public function вивестиВластивості()
{
echo $this->публічнаВластивість; // Працює
echo $this->захищенаВластивість; // Працює
// echo $this->приватнаВластивість; // Викличе помилку
}
}
У цьому випадку метод вивестиВластивості()
в класі
НащадокКласу
може отримати доступ до публічних та захищених
властивостей, але не може отримати доступ до приватних властивостей
батьківського класу.
Дані та методи повинні бути якомога більше прихованими та доступними лише через визначений інтерфейс. Це дозволить вам змінювати внутрішню реалізацію класу, не впливаючи на решту коду.
Ключове слово final
У PHP ми можемо використовувати ключове слово final
, якщо хочемо
заборонити класу, методу або константі успадковуватися або
перевизначатися. Коли ми позначаємо клас як final
, він не може бути
розширений. Коли ми позначаємо метод як final
, він не може бути
перевизначений у класі-нащадку.
Усвідомлення того, що певний клас або метод не буде далі модифікуватися, дозволяє нам легше вносити зміни, не побоюючись можливих конфліктів. Наприклад, ми можемо додати новий метод без побоювань, що якийсь його нащадок вже має метод з такою ж назвою і сталася б колізія. Або ми можемо змінити параметри методу, оскільки знову ж таки немає ризику викликати невідповідність з перевизначеним методом у нащадку.
final class ФінальнийКлас
{
}
// Наступний код викличе помилку, оскільки ми не можемо успадковувати від фінального класу.
class НащадокФінальногоКласу extends ФінальнийКлас
{
}
У цьому прикладі спроба успадкування від фінального класу
ФінальнийКлас
викличе помилку.
Статичні властивості та методи
Коли ми говоримо в PHP про “статичні” елементи класу, ми маємо на увазі методи та властивості, які належать самому класу, а не конкретному екземпляру цього класу. Це означає, що вам не потрібно створювати екземпляр класу, щоб отримати до них доступ. Замість цього ви викликаєте їх або отримуєте до них доступ безпосередньо через назву класу.
Майте на увазі, що оскільки статичні елементи належать класу, а не
його екземплярам, ви не можете всередині статичних методів
використовувати псевдозмінну $this
.
Використання статичних властивостей призводить до незрозумілого коду, повного підводних каменів, тому ви ніколи не повинні їх використовувати, і ми навіть не будемо показувати приклад використання тут. Навпаки, статичні методи корисні. Приклад використання:
class Калькулятор
{
public static function додавання($a, $b)
{
return $a + $b;
}
public static function віднімання($a, $b)
{
return $a - $b;
}
}
// Використання статичного методу без створення екземпляра класу
echo Калькулятор::додавання(5, 3); // Результат: 8
echo Калькулятор::віднімання(5, 3); // Результат: 2
У цьому прикладі ми створили клас Калькулятор
з двома
статичними методами. Ці методи ми можемо викликати безпосередньо без
створення екземпляра класу за допомогою оператора ::
. Статичні
методи особливо корисні для операцій, які не залежать від стану
конкретного екземпляра класу.
Константи класу
У межах класів ми маємо можливість визначати константи. Константи – це значення, які ніколи не змінюються під час виконання програми. На відміну від змінних, значення константи залишається незмінним.
class Автомобіль
{
public const КількістьКоліс = 4;
public function показатиКількістьКоліс(): int
{
echo self::КількістьКоліс;
}
}
echo Автомобіль::КількістьКоліс; // Вивід: 4
У цьому прикладі ми маємо клас Автомобіль
з константою
КількістьКоліс
. Коли ми хочемо отримати доступ до константи
всередині класу, ми можемо використовувати ключове слово self
замість назви класу.
Інтерфейси об'єктів
Інтерфейси об'єктів діють як “контракти” для класів. Якщо клас має реалізувати інтерфейс, він повинен містити всі методи, які визначає цей інтерфейс. Це чудовий спосіб забезпечити, щоб певні класи дотримувалися однакового “контракту” або структури.
У PHP інтерфейс визначається ключовим словом interface
. Усі методи,
визначені в інтерфейсі, є публічними (public
). Коли клас реалізує
інтерфейс, він використовує ключове слово implements
.
interface Тварина
{
function видатиЗвук();
}
class Кішка implements Тварина
{
public function видатиЗвук()
{
echo 'Няв';
}
}
$кішка = new Кішка;
$кішка->видатиЗвук();
Якщо клас реалізує інтерфейс, але в ньому не визначені всі очікувані методи, PHP видасть помилку.
Клас може реалізувати кілька інтерфейсів одночасно, що є відмінністю від успадкування, де клас може успадковувати лише від одного класу:
interface Охоронець
{
function охоронятиБудинок();
}
class Собака implements Тварина, Охоронець
{
public function видатиЗвук()
{
echo 'Гав';
}
public function охоронятиБудинок()
{
echo 'Собака пильно охороняє будинок';
}
}
Абстрактні класи
Абстрактні класи служать базовими шаблонами для інших класів, але ви не можете створювати їхні екземпляри безпосередньо. Вони містять комбінацію повних методів та абстрактних методів, які не мають визначеного вмісту. Класи, що успадковують від абстрактних класів, повинні надати визначення для всіх абстрактних методів предка.
Для визначення абстрактного класу ми використовуємо ключове слово
abstract
.
abstract class АбстрактнийКлас
{
public function звичайнийМетод()
{
echo 'Це звичайний метод';
}
abstract public function абстрактнийМетод();
}
class Нащадок extends АбстрактнийКлас
{
public function абстрактнийМетод()
{
echo 'Це реалізація абстрактного методу';
}
}
$екземпляр = new Нащадок;
$екземпляр->звичайнийМетод();
$екземпляр->абстрактнийМетод();
У цьому прикладі ми маємо абстрактний клас з одним звичайним та одним
абстрактним методом. Потім ми маємо клас Нащадок
, який
успадковує від АбстрактнийКлас
і надає реалізацію для
абстрактного методу.
Чим насправді відрізняються інтерфейси та абстрактні класи? Абстрактні класи можуть містити як абстрактні, так і конкретні методи, тоді як інтерфейси лише визначають, які методи повинен реалізувати клас, але не надають жодної реалізації. Клас може успадковувати лише від одного абстрактного класу, але може реалізувати будь-яку кількість інтерфейсів.
Перевірка типів
У програмуванні дуже важливо мати впевненість, що дані, з якими ми працюємо, мають правильний тип. У PHP є інструменти, які нам це забезпечують. Перевірка того, чи мають дані правильний тип, називається “перевіркою типів”.
Типи, з якими ми можемо зіткнутися в PHP:
- Базові типи: Включають
int
(цілі числа),float
(дійсні числа),bool
(логічні значення),string
(рядки),array
(масиви) таnull
. - Класи: Якщо ми хочемо, щоб значення було екземпляром певного класу.
- Інтерфейси: Визначає набір методів, які клас повинен реалізувати. Значення, яке відповідає інтерфейсу, повинно мати ці методи.
- Змішані типи: Ми можемо вказати, що змінна може мати кілька дозволених типів.
- Void: Цей спеціальний тип позначає, що функція чи метод не повертає жодного значення.
Давайте покажемо, як змінити код, щоб він включав типи:
class Людина
{
private int $вік;
public function __construct(int $вік)
{
$this->вік = $вік;
}
public function вивестиВік(): void
{
echo "Цій людині {$this->вік} років.";
}
}
/**
* Функція, яка приймає об'єкт класу Людина та виводить вік людини.
*/
function вивестиВікЛюдини(Людина $людина): void
{
$людина->вивестиВік();
}
Таким чином ми забезпечили, що наш код очікує та працює з даними правильного типу, що допомагає нам запобігати потенційним помилкам.
Деякі типи неможливо записати безпосередньо в PHP. У такому випадку
вони вказуються в коментарі phpDoc, що є стандартним форматом для
документування коду PHP, який починається з /**
і закінчується
*/
. Він дозволяє додавати описи класів, методів тощо. А також
вказувати складні типи за допомогою так званих анотацій @var
,
@param
та @return
. Ці типи потім використовуються
інструментами статичного аналізу коду, але сам PHP їх не перевіряє.
class Список
{
/** @var array<Людина> запис означає, що це масив об'єктів Людина */
private array $люди = [];
public function додатиЛюдину(Людина $людина): void
{
$this->люди[] = $людина;
}
}
Порівняння та ідентичність
У PHP ви можете порівнювати об'єкти двома способами:
- Порівняння значень
==
: Перевіряє, чи об'єкти належать до одного класу і мають однакові значення у своїх властивостях. - Ідентичність
===
: Перевіряє, чи це той самий екземпляр об'єкта.
class Автомобіль
{
public string $марка;
public function __construct(string $марка)
{
$this->марка = $марка;
}
}
$автомобіль1 = new Автомобіль('Skoda');
$автомобіль2 = new Автомобіль('Skoda');
$автомобіль3 = $автомобіль1;
var_dump($автомобіль1 == $автомобіль2); // true, оскільки вони мають однакове значення
var_dump($автомобіль1 === $автомобіль2); // false, оскільки це не той самий екземпляр
var_dump($автомобіль1 === $автомобіль3); // true, оскільки $автомобіль3 є тим самим екземпляром, що й $автомобіль1
Оператор instanceof
Оператор instanceof
дозволяє визначити, чи є даний об'єкт
екземпляром певного класу, нащадком цього класу, або чи реалізує він
певний інтерфейс.
Уявімо собі, що ми маємо клас Людина
та інший клас Student
,
який є нащадком класу Людина
:
class Людина
{
private int $вік;
public function __construct(int $вік)
{
$this->вік = $вік;
}
}
class Student extends Людина
{
private string $спеціальність;
public function __construct(int $вік, string $спеціальність)
{
parent::__construct($вік);
$this->спеціальність = $спеціальність;
}
}
$студент = new Student(20, 'Інформатика');
// Перевірка, чи є $студент екземпляром класу Student
var_dump($студент instanceof Student); // Вивід: bool(true)
// Перевірка, чи є $студент екземпляром класу Людина (оскільки Student є нащадком Людини)
var_dump($студент instanceof Людина); // Вивід: bool(true)
З виводів видно, що об'єкт $студент
одночасно вважається
екземпляром обох класів – Student
і Людина
.
Fluent Interfaces
“Плавний інтерфейс” (англійською “Fluent Interface”) – це техніка в ООП, яка дозволяє ланцюжком викликати методи один за одним в одному виклику. Це часто спрощує та робить код зрозумілішим.
Ключовим елементом плавного інтерфейсу є те, що кожен метод у
ланцюжку повертає посилання на поточний об'єкт. Цього ми досягаємо тим,
що в кінці методу використовуємо return $this;
. Цей стиль
програмування часто асоціюється з методами, що називаються
“сеттерами”, які встановлюють значення властивостей об'єкта.
Покажемо, як може виглядати плавний інтерфейс на прикладі надсилання електронних листів:
public function sendMessage()
{
$email = new Email;
$email->setFrom('sender@example.com')
->setRecipient('admin@example.com')
->setMessage('Hello, this is a message.')
->send();
}
У цьому прикладі методи setFrom()
, setRecipient()
та setMessage()
служать для встановлення відповідних значень (відправника,
одержувача, вмісту повідомлення). Після встановлення кожного з цих
значень методи повертають поточний об'єкт ($email
), що дозволяє нам
викликати наступний метод у ланцюжку. Нарешті, ми викликаємо метод
send()
, який фактично надсилає електронний лист.
Завдяки плавним інтерфейсам ми можемо писати код, який є інтуїтивно зрозумілим та легко читабельним.
Копіювання за допомогою clone
У PHP ми можемо створити копію об'єкта за допомогою оператора
clone
. Таким чином ми отримаємо новий екземпляр з ідентичним
вмістом.
Якщо нам потрібно під час копіювання об'єкта змінити деякі його
властивості, ми можемо визначити в класі спеціальний метод
__clone()
. Цей метод автоматично викликається, коли об'єкт
клонується.
class Вівця
{
public string $імя;
public function __construct(string $імя)
{
$this->імя = $імя;
}
public function __clone()
{
$this->імя = 'Клон ' . $this->імя;
}
}
$оригінал = new Вівця('Dolly');
echo $оригінал->імя . "\n"; // Виведе: Dolly
$клон = clone $оригінал;
echo $клон->імя . "\n"; // Виведе: Клон Dolly
У цьому прикладі ми маємо клас Вівця
з однією властивістю
$імя
. Коли ми клонуємо екземпляр цього класу, метод __clone()
дбає про те, щоб ім'я клонованої вівці отримало префікс “Клон”.
Трейди
Трейди в PHP – це інструмент, який дозволяє спільно використовувати методи, властивості та константи між класами та запобігти дублюванню коду. Ви можете уявити їх як механізм “копіювати та вставити” (Ctrl-C та Ctrl-V), коли вміст трейту “вставляється” в класи. Це дозволяє вам повторно використовувати код без необхідності створювати складні ієрархії класів.
Давайте покажемо простий приклад, як використовувати трейти в PHP:
trait Сигналення
{
public function посигналити()
{
echo 'Bip bip!';
}
}
class Автомобіль
{
use Сигналення;
}
class Вантажівка
{
use Сигналення;
}
$автомобіль = new Автомобіль;
$автомобіль->посигналити(); // Виведе 'Bip bip!'
$вантажівка = new Вантажівка;
$вантажівка->посигналити(); // Також виведе 'Bip bip!'
У цьому прикладі ми маємо трейт під назвою Сигналення
, який
містить один метод посигналити()
. Потім ми маємо два класи:
Автомобіль
та Вантажівка
, які обидва використовують
трейт Сигналення
. Завдяки цьому обидва класи “мають” метод
посигналити()
, і ми можемо викликати його на об'єктах обох
класів.
Трейди дозволяють вам легко та ефективно спільно використовувати
код між класами. При цьому вони не входять до ієрархії успадкування,
тобто $автомобіль instanceof Сигналення
поверне false
.
Винятки
Винятки в ООП дозволяють нам елегантно обробляти помилки та неочікувані ситуації в нашому коді. Це об'єкти, які несуть інформацію про помилку або незвичайну ситуацію.
У PHP є вбудований клас Exception
, який служить основою для всіх
винятків. Він має кілька методів, які дозволяють нам отримати більше
інформації про виняток, як-от повідомлення про помилку, файл і рядок, де
сталася помилка, тощо.
Коли в коді виникає помилка, ми можемо “викинути” виняток за
допомогою ключового слова throw
.
function ділення(float $a, float $b): float
{
if ($b === 0.0) { // Порівняння з float
throw new Exception('Ділення на нуль!');
}
return $a / $b;
}
Коли функція ділення()
отримує нуль як другий аргумент, вона
викине виняток з повідомленням про помилку 'Ділення на нуль!'
.
Щоб запобігти аварійному завершенню програми під час викидання
винятку, ми перехоплюємо його в блоці try/catch
:
try {
echo ділення(10, 0);
} catch (Exception $e) {
echo 'Виняток перехоплено: '. $e->getMessage();
}
Код, який може викинути виняток, загортається в блок try
. Якщо
виняток викинуто, виконання коду переходить до блоку catch
, де ми
можемо обробити виняток (наприклад, вивести повідомлення про
помилку).
Після блоків try
та catch
ми можемо додати необов'язковий
блок finally
, який виконується завжди, незалежно від того, чи було
викинуто виняток (навіть якщо в блоці try
або catch
ми
використовуємо оператор return
, break
або continue
):
try {
echo ділення(10, 0);
} catch (Exception $e) {
echo 'Виняток перехоплено: '. $e->getMessage();
} finally {
// Код, який виконується завжди, незалежно від того, чи було викинуто виняток
}
Ми також можемо створити власні класи (ієрархію) винятків, які успадковують від класу Exception. Як приклад, уявімо собі простий банківський додаток, який дозволяє здійснювати поповнення та зняття коштів:
class БанківськийВиняток extends Exception {}
class ВинятокНедостатньоКоштів extends БанківськийВиняток {}
class ВинятокПеревищенняЛіміту extends БанківськийВиняток {}
class БанківськийРахунок
{
private int $баланс = 0;
private int $деннийЛіміт = 1000;
public function поповнити(int $сума): int
{
$this->баланс += $сума;
return $this->баланс;
}
public function зняти(int $сума): int
{
if ($сума > $this->баланс) {
throw new ВинятокНедостатньоКоштів('На рахунку недостатньо коштів.');
}
if ($сума > $this->деннийЛіміт) {
throw new ВинятокПеревищенняЛіміту('Перевищено денний ліміт зняття коштів.');
}
$this->баланс -= $сума;
return $this->баланс;
}
}
Для одного блоку try
можна вказати кілька блоків catch
,
якщо ви очікуєте різні типи винятків.
$рахунок = new БанківськийРахунок;
$рахунок->поповнити(500);
try {
$рахунок->зняти(1500);
} catch (ВинятокПеревищенняЛіміту $e) {
echo $e->getMessage();
} catch (ВинятокНедостатньоКоштів $e) {
echo $e->getMessage();
} catch (БанківськийВиняток $e) {
echo 'Під час виконання операції сталася помилка.';
}
У цьому прикладі важливо звернути увагу на порядок блоків catch
.
Оскільки всі винятки успадковують від БанківськийВиняток
, якби
цей блок був першим, у ньому були б перехоплені всі винятки, і код не
дійшов би до наступних блоків catch
. Тому важливо розміщувати
більш специфічні винятки (тобто ті, що успадковують від інших) у блоці
catch
вище за порядком, ніж їхні батьківські винятки.
Ітерація
У PHP ви можете перебирати об'єкти за допомогою циклу foreach
,
подібно до того, як ви перебираєте масиви. Щоб це працювало, об'єкт
повинен реалізувати спеціальний інтерфейс.
Перший варіант – реалізувати інтерфейс Iterator
, який має методи
current()
, що повертає поточне значення, key()
, що повертає ключ,
next()
, що переходить до наступного значення, rewind()
, що
переходить на початок, та valid()
, що перевіряє, чи ми ще не дійшли
до кінця.
Другий варіант – реалізувати інтерфейс IteratorAggregate
, який має
лише один метод getIterator()
. Він або повертає об'єкт-заступник, який
забезпечуватиме ітерацію, або може представляти генератор, що є
спеціальною функцією, в якій використовується yield
для
послідовного повернення ключів та значень:
class Людина
{
public function __construct(
public int $вік,
) {
}
}
class Список implements IteratorAggregate
{
private array $люди = [];
public function додатиЛюдину(Людина $людина): void
{
$this->люди[] = $людина;
}
public function getIterator(): Generator
{
foreach ($this->люди as $людина) {
yield $людина;
}
}
}
$список = new Список;
$список->додатиЛюдину(new Людина(30));
$список->додатиЛюдину(new Людина(25));
foreach ($список as $людина) {
echo "Вік: {$людина->вік} років \n";
}
Найкращі практики
Коли ви засвоїли основні принципи об'єктно-орієнтованого програмування, важливо зосередитися на найкращих практиках в ООП. Вони допоможуть вам писати код, який є не тільки функціональним, але й читабельним, зрозумілим та легким у підтримці.
- Розділення відповідальності (Separation of Concerns): Кожен клас повинен мати чітко визначену відповідальність і вирішувати лише одне основне завдання. Якщо клас робить занадто багато речей, може бути доцільно розділити його на менші, спеціалізовані класи.
- Інкапсуляція (Encapsulation): Дані та методи повинні бути якомога більше прихованими та доступними лише через визначений інтерфейс. Це дозволить вам змінювати внутрішню реалізацію класу, не впливаючи на решту коду.
- Впровадження залежностей (Dependency Injection): Замість того, щоб створювати залежності безпосередньо в класі, ви повинні “впроваджувати” їх ззовні. Для глибшого розуміння цього принципу рекомендуємо розділи про Dependency Injection.