Nette Documentation Preview

syntax
Вступ до об'єктно-орієнтованого програмування
*********************************************

.[perex]
Термін "ООП" розшифровується як об'єктно-орієнтоване програмування - спосіб організації та структурування коду. ООП дозволяє розглядати програму як набір об'єктів, які взаємодіють між собою, а не як послідовність команд і функцій.

В ООП "об'єкт" - це одиниця, яка містить дані та функції, що оперують цими даними. Об'єкти створюються на основі "класів", які можна розуміти як схеми або шаблони для об'єктів. Маючи клас, ми можемо створити його "екземпляр", тобто конкретний об'єкт, створений на основі цього класу.

Давайте розглянемо, як можна створити простий клас в PHP. При визначенні класу ми використовуємо ключове слово "class", за яким слідує ім'я класу, а потім фігурні дужки, в які вкладаються функції класу (звані "методами") і змінні класу (звані "властивостями" або "атрибутами"):

```php
class Car
{
	function honk()
	{
		echo 'Beep beep!';
	}
}
```

У цьому прикладі ми створили клас з іменем `Car` з однією функцією (або "методом") `honk`.

Кожен клас повинен вирішувати лише одне основне завдання. Якщо клас виконує занадто багато завдань, може бути доцільно розділити його на менші, спеціалізовані класи.

Класи зазвичай зберігаються в окремих файлах, щоб упорядкувати код і полегшити навігацію по ньому. Ім'я файлу має відповідати імені класу, тому для класу `Car` ім'я файлу буде `Car.php`.

Називаючи класи, варто дотримуватися конвенції "PascalCase", тобто кожне слово в назві починається з великої літери, без підкреслень або інших роздільників. Методи та властивості слідують конвенції "camelCase", тобто починаються з малої літери.

Деякі методи в PHP відіграють особливу роль і мають префікс `__` (два підкреслення). Одним з найважливіших спеціальних методів є "конструктор", позначений як `__construct`. Конструктор - це метод, який автоматично викликається при створенні нового екземпляра класу.

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

Давайте подивимося, як використовувати конструктор в PHP:

```php
class Person
{
	private $age;

	function __construct($age)
	{
		$this->age = $age;
	}

	function howOldAreYou()
	{
		return $this->age;
	}
}

$person = new Person(25);
echo $person->howOldAreYou(); // Outputs: 25
```

У цьому прикладі клас `Person` має властивість (змінну) `$age` і конструктор, який встановлює цю властивість. Метод `howOldAreYou()` надає доступ до віку людини.

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

Ключове слово `new` використовується для створення нового екземпляра класу. У наведеному вище прикладі ми створили нову особу віком 25 років.

Ви також можете встановити значення за замовчуванням для параметрів конструктора, якщо вони не вказані при створенні об'єкта. Наприклад:

```php
class Person
{
	private $age;

	function __construct($age = 20)
	{
		$this->age = $age;
	}

	function howOldAreYou()
	{
		return $this->age;
	}
}

$person = new Person;  // if no argument is passed, parentheses can be omitted
echo $person->howOldAreYou(); // Outputs: 20
```

У цьому прикладі, якщо не вказати вік при створенні об'єкта `Person`, буде використано значення за замовчуванням 20.

Приємно те, що визначення властивості з її ініціалізацією через конструктор можна скоротити і спростити таким чином:

```php
class Person
{
	function __construct(
		private $age = 20,
	) {
	}
}
```

Для повноти, окрім конструкторів, об'єкти можуть мати деструктори (метод `__destruct`), які викликаються перед звільненням об'єкта з пам'яті.


Простори імен .[#toc-namespaces]
--------------------------------

Простори імен дозволяють нам організовувати і групувати пов'язані класи, функції і константи, уникаючи конфліктів імен. Ви можете уявити їх як папки на комп'ютері, де кожна папка містить файли, пов'язані з певним проектом або темою.

Простори імен особливо корисні у великих проектах або при використанні сторонніх бібліотек, де можуть виникати конфлікти імен класів.

Уявіть, що у вашому проекті є клас з іменем `Car`, і ви хочете розмістити його у просторі імен `Transport`. Ви можете зробити це таким чином:

```php
namespace Transport;

class Car
{
	function honk()
	{
		echo 'Beep beep!';
	}
}
```

Якщо ви хочете використати клас `Car` в іншому файлі, вам потрібно вказати, з якого простору імен походить клас:

```php
$car = new Transport\Car;
```

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

```php
use Transport\Car;

$car = new Car;
```


Спадщина .[#toc-inheritance]
----------------------------

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

Простіше кажучи, якщо ми маємо один клас і хочемо створити інший, похідний від нього, але з деякими змінами, ми можемо "успадкувати" новий клас від початкового.

У PHP успадкування реалізується за допомогою ключового слова `extends`.

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

Давайте розглянемо приклад:

```php
class Person
{
	private $age;

	function __construct($age)
	{
		$this->age = $age;
	}

	function printInformation()
	{
		echo "Age: {$this->age} years\n";
	}
}

class Student extends Person
{
	private $fieldOfStudy;

	function __construct($age, $fieldOfStudy)
	{
		parent::__construct($age);
		$this->fieldOfStudy = $fieldOfStudy;
	}

	function printInformation()
	{
		parent::printInformation();
		echo "Field of study: {$this->fieldOfStudy} \n";
	}
}

$student = new Student(20, 'Computer Science');
$student->printInformation();
```

Як працює цей код?

- Ми використали ключове слово `extends` для розширення класу `Person`, тобто клас `Student` успадковує всі методи та властивості від `Person`.

- Ключове слово `parent::` дозволяє нам викликати методи з батьківського класу. У цьому випадку ми викликали конструктор з класу `Person` перед тим, як додати власну функціональність до класу `Student`. І аналогічно, метод предка `printInformation()` перед виведенням інформації про студента.

Спадкування призначене для ситуацій, коли між класами існує відношення "є". Наприклад, `Student` є `Person`. Кіт - це тварина. Це дозволяє нам у випадках, коли ми очікуємо один об'єкт (наприклад, "Людина") в коді, використовувати замість нього похідний об'єкт (наприклад, "Студент").

Важливо розуміти, що основною метою успадкування **не є** запобігання дублюванню коду. Навпаки, зловживання успадкуванням може призвести до створення складного і важкого для супроводу коду. Якщо між класами немає відношення "є", то замість успадкування слід розглядати композицію.

Зверніть увагу, що методи `printInformation()` у класах `Person` та `Student` виводять дещо різну інформацію. І ми можемо додати інші класи (наприклад, `Employee`), які будуть надавати інші реалізації цього методу. Здатність об'єктів різних класів по-різному реагувати на один і той самий метод називається поліморфізмом:

```php
$people = [
	new Person(30),
	new Student(20, 'Computer Science'),
	new Employee(45, 'Director'),
];

foreach ($people as $person) {
	$person->printInformation();
}
```


Композиція .[#toc-composition]
------------------------------

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

Наприклад, у нас є клас `Engine` і клас `Car`. Замість того, щоб сказати "Автомобіль - це двигун", ми скажемо "Автомобіль має двигун", що є типовим відношенням композиції.

```php
class Engine
{
	function start()
	{
		echo 'Engine is running.';
	}
}

class Car
{
	private $engine;

	function __construct()
	{
		$this->engine = new Engine;
	}

	function start()
	{
		$this->engine->start();
		echo 'The car is ready to drive!';
	}
}

$car = new Car;
$car->start();
```

Тут `Car` не має всіх властивостей і методів класу `Engine`, але має доступ до нього через властивість `$engine`.

Перевагою композиції є більша гнучкість дизайну та краща адаптивність до майбутніх змін.


Видимість .[#toc-visibility]
----------------------------

У PHP ви можете визначити "видимість" для властивостей, методів і констант класу. Видимість визначає, де ви можете отримати доступ до цих елементів.

1. **Публічний:** Якщо елемент позначено як `public`, це означає, що ви можете отримати до нього доступ з будь-якого місця, навіть за межами класу.

2. **Protected:** Елемент, позначений як `protected`, доступний тільки в межах класу та всіх його нащадків (класів, що успадковуються від нього).

3. **Приватний:** Якщо елемент позначено як `private`, ви можете отримати доступ до нього лише з класу, в якому його було визначено.

Якщо ви не вкажете видимість, PHP автоматично встановить її на `public`.

Давайте розглянемо приклад коду:

```php
class VisibilityExample
{
	public $publicProperty = 'Public';
	protected $protectedProperty = 'Protected';
	private $privateProperty = 'Private';

	public function printProperties()
	{
		echo $this->publicProperty;     // Works
		echo $this->protectedProperty;  // Works
		echo $this->privateProperty;    // Works
	}
}

$object = new VisibilityExample;
$object->printProperties();
echo $object->publicProperty;        // Works
// echo $object->protectedProperty;   // Throws an error
// echo $object->privateProperty;     // Throws an error
```

Продовження успадкування класів:

```php
class ChildClass extends VisibilityExample
{
	public function printProperties()
	{
		echo $this->publicProperty;     // Works
		echo $this->protectedProperty;  // Works
		// echo $this->privateProperty;   // Throws an error
	}
}
```

У цьому випадку метод `printProperties()` у класі `ChildClass` може отримати доступ до загальнодоступних та захищених властивостей, але не може отримати доступ до приватних властивостей батьківського класу.

Дані та методи повинні бути максимально прихованими і доступними лише через визначений інтерфейс. Це дозволяє змінювати внутрішню реалізацію класу, не впливаючи на решту коду.


Заключне ключове слово .[#toc-final-keyword]
--------------------------------------------

У PHP ми можемо використовувати ключове слово `final`, якщо ми хочемо запобігти успадкуванню або перевизначенню класу, методу або константи. Коли клас позначено як `final`, він не може бути розширений. Коли метод позначено як `final`, його не можна перевизначити в підкласі.

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

```php
final class FinalClass
{
}

// The following code will throw an error because we cannot inherit from a final class.
class ChildOfFinalClass extends FinalClass
{
}
```

У цьому прикладі спроба успадкувати від кінцевого класу `FinalClass` призведе до помилки.


Статичні властивості та методи .[#toc-static-properties-and-methods]
--------------------------------------------------------------------

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

Майте на увазі, що оскільки статичні елементи належать класу, а не його екземплярам, ви не можете використовувати псевдо-змінну `$this` всередині статичних методів.

Використання статичних властивостей призводить до [заплутаного коду, повного підводних каменів |dependency-injection:global-state], тому ви ніколи не повинні використовувати їх, і ми не будемо показувати приклад тут. З іншого боку, статичні методи корисні. Ось приклад:

```php
class Calculator
{
	public static function add($a, $b)
	{
		return $a + $b;
	}

	public static function subtract($a, $b)
	{
		return $a - $b;
	}
}

// Using the static method without creating an instance of the class
echo Calculator::add(5, 3); // Output: 8
echo Calculator::subtract(5, 3); // Output: 2
```

У цьому прикладі ми створили клас `Calculator` з двома статичними методами. Ми можемо викликати ці методи безпосередньо, не створюючи екземпляр класу за допомогою оператора `::`. Статичні методи особливо корисні для операцій, які не залежать від стану конкретного екземпляра класу.


Константи класу .[#toc-class-constants]
---------------------------------------

У класах ми маємо можливість визначати константи. Константи - це значення, які ніколи не змінюються під час виконання програми. На відміну від змінних, значення константи залишається незмінним.

```php
class Car
{
	public const NumberOfWheels = 4;

	public function displayNumberOfWheels(): int
	{
		echo self::NumberOfWheels;
	}
}

echo Car::NumberOfWheels;  // Output: 4
```

У цьому прикладі ми маємо клас `Car` з константою `NumberOfWheels`. При зверненні до константи всередині класу ми можемо використовувати ключове слово `self` замість імені класу.


Інтерфейси об'єктів .[#toc-object-interfaces]
---------------------------------------------

Об'єктні інтерфейси діють як "контракти" для класів. Якщо клас реалізує об'єктний інтерфейс, він повинен містити всі методи, які визначає інтерфейс. Це чудовий спосіб гарантувати, що певні класи дотримуються одного "контракту" або структури.

У PHP інтерфейси визначаються за допомогою ключового слова `interface`. Всі методи, визначені в інтерфейсі, є загальнодоступними (`public`). Коли клас реалізує інтерфейс, він використовує ключове слово `implements`.

```php
interface Animal
{
	function makeSound();
}

class Cat implements Animal
{
	public function makeSound()
	{
		echo 'Meow';
	}
}

$cat = new Cat;
$cat->makeSound();
```

Якщо клас реалізує інтерфейс, але не всі очікувані методи визначені, PHP видасть помилку.

Клас може реалізовувати декілька інтерфейсів одночасно, що відрізняється від успадкування, де клас може успадковувати тільки від одного класу:

```php
interface Guardian
{
	function guardHouse();
}

class Dog implements Animal, Guardian
{
	public function makeSound()
	{
		echo 'Bark';
	}

	public function guardHouse()
	{
		echo 'Dog diligently guards the house';
	}
}
```


Абстрактні класи .[#toc-abstract-classes]
-----------------------------------------

Абстрактні класи слугують базовими шаблонами для інших класів, але ви не можете створювати їхні екземпляри безпосередньо. Вони містять суміш повних методів та абстрактних методів, які не мають визначеного змісту. Класи, які успадковують абстрактні класи, повинні надавати визначення для всіх абстрактних методів батьківського класу.

Ми використовуємо ключове слово `abstract` для визначення абстрактного класу.

```php
abstract class AbstractClass
{
	public function regularMethod()
	{
		echo 'This is a regular method';
	}

	abstract public function abstractMethod();
}

class Child extends AbstractClass
{
	public function abstractMethod()
	{
		echo 'This is the implementation of the abstract method';
	}
}

$instance = new Child;
$instance->regularMethod();
$instance->abstractMethod();
```

У цьому прикладі ми маємо абстрактний клас з одним звичайним і одним абстрактним методом. Потім у нас є клас `Child`, який успадковується від `AbstractClass` і надає реалізацію для абстрактного методу.

Чим відрізняються інтерфейси від абстрактних класів? Абстрактні класи можуть містити як абстрактні, так і конкретні методи, в той час як інтерфейси лише визначають, які методи повинен реалізовувати клас, але не надають їх реалізації. Клас може успадковувати лише один абстрактний клас, але може реалізовувати будь-яку кількість інтерфейсів.


Перевірка типів .[#toc-type-checking]
-------------------------------------

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

Типи, з якими ми можемо зіткнутися в PHP:

1. **Базові типи**: До них відносяться `int` (цілі числа), `float` (числа з плаваючою комою), `bool` (логічні значення), `string` (рядки), `array` (масиви) і `null`.
2. **Класи**: Коли ми хочемо, щоб значення було екземпляром певного класу.
3. **Інтерфейси**: Визначає набір методів, які клас повинен реалізувати. Значення, яке відповідає інтерфейсу, повинно мати ці методи.
4. **Змішані типи**: Ми можемо вказати, що змінна може мати декілька дозволених типів.
5. **Немає значення**: Цей спеціальний тип вказує на те, що функція або метод не повертає жодного значення.

Давайте подивимося, як модифікувати код для включення типів:

```php
class Person
{
	private int $age;

	public function __construct(int $age)
	{
		$this->age = $age;
	}

	public function printAge(): void
	{
		echo "This person is {$this->age} years old.";
	}
}

/**
 * A function that accepts a Person object and prints the person's age.
 */
function printPersonAge(Person $person): void
{
	$person->printAge();
}
```

Таким чином, ми гарантуємо, що наш код очікує і працює з даними правильного типу, що допоможе нам запобігти потенційним помилкам.

Деякі типи не можуть бути написані безпосередньо в PHP. В такому випадку вони перераховуються в коментарі phpDoc, який є стандартним форматом для документування PHP-коду, починаючи з `/**` і закінчуючи `*/`. Він дозволяє додавати описи класів, методів і так далі. А також перераховувати складні типи за допомогою так званих анотацій `@var`, `@param` і `@return`. Ці типи потім використовуються інструментами статичного аналізу коду, але не перевіряються самим PHP.

```php
class Registry
{
	/** @var array<Person>  indicates that it's an array of Person objects */
	private array $persons = [];

	public function addPerson(Person $person): void
	{
		$this->persons[] = $person;
	}
}
```


Порівняння та ідентифікація .[#toc-comparison-and-identity]
-----------------------------------------------------------

У PHP ви можете порівнювати об'єкти двома способами:

1. Порівняння значень `==`: Перевіряє, чи об'єкти належать до одного класу і мають однакові значення у своїх властивостях.
2. Ідентифікація `===`: Перевіряє, чи це той самий екземпляр об'єкта.

```php
class Car
{
	public string $brand;

	public function __construct(string $brand)
	{
		$this->brand = $brand;
	}
}

$car1 = new Car('Skoda');
$car2 = new Car('Skoda');
$car3 = $car1;

var_dump($car1 == $car2);   // true, because they have the same value
var_dump($car1 === $car2);  // false, because they are not the same instance
var_dump($car1 === $car3);  // true, because $car3 is the same instance as $car1
```


Оператор `instanceof` .[#toc-the-instanceof-operator]
-----------------------------------------------------

Оператор `instanceof` дозволяє визначити, чи є даний об'єкт екземпляром певного класу, нащадком цього класу або чи реалізує він певний інтерфейс.

Уявімо, що у нас є клас `Person` і ще один клас `Student`, який є нащадком класу `Person`:

```php
class Person
{
	private int $age;

	public function __construct(int $age)
	{
		$this->age = $age;
	}
}

class Student extends Person
{
	private string $major;

	public function __construct(int $age, string $major)
	{
		parent::__construct($age);
		$this->major = $major;
	}
}

$student = new Student(20, 'Computer Science');

// Check if $student is an instance of the Student class
var_dump($student instanceof Student);  // Output: bool(true)

// Check if $student is an instance of the Person class (because Student is a descendant of Person)
var_dump($student instanceof Person);   // Output: bool(true)
```

З результатів видно, що об'єкт `$student` вважається екземпляром класів `Student` та `Person`.


Зручні інтерфейси .[#toc-fluent-interfaces]
-------------------------------------------

"Вільний інтерфейс" - це техніка в ООП, яка дозволяє об'єднувати методи в ланцюжок в одному виклику. Це часто спрощує і робить код зрозумілішим.

Ключовим елементом вільного інтерфейсу є те, що кожен метод у ланцюжку повертає посилання на поточний об'єкт. Це досягається за рахунок використання `return $this;` в кінці методу. Цей стиль програмування часто асоціюється з методами, що називаються "сеттерами", які встановлюють значення властивостей об'єкта.

Давайте подивимося, як може виглядати зручний інтерфейс для надсилання електронних листів:

```php
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` .[#toc-copying-with-clone]
----------------------------------------------------------

У PHP ми можемо створити копію об'єкта за допомогою оператора `clone`. Таким чином, ми отримуємо новий екземпляр з ідентичним вмістом.

Якщо при копіюванні об'єкта нам потрібно змінити деякі його властивості, ми можемо визначити в класі спеціальний метод `__clone()`. Цей метод автоматично викликається при клонуванні об'єкта.

```php
class Sheep
{
	public string $name;

	public function __construct(string $name)
	{
		$this->name = $name;
	}

	public function __clone()
	{
		$this->name = 'Clone of ' . $this->name;
	}
}

$original = new Sheep('Dolly');
echo $original->name . "\n";  // Outputs: Dolly

$clone = clone $original;
echo $clone->name . "\n";     // Outputs: Clone of Dolly
```

У нашому прикладі є клас `Sheep` з однією властивістю `$name`. Коли ми клонуємо екземпляр цього класу, метод `__clone()` гарантує, що ім'я клонованої вівці отримає префікс "Clone of".


Властивості .[#toc-traits]
--------------------------

Риси в PHP - це інструмент, який дозволяє обмінюватися методами, властивостями і константами між класами і запобігає дублюванню коду. Ви можете думати про них як про механізм "копіювання і вставки" (Ctrl-C і Ctrl-V), де вміст трейту "вставляється" в класи. Це дозволяє повторно використовувати код без необхідності створювати складні ієрархії класів.

Давайте розглянемо простий приклад використання трейтів в PHP:

```php
trait Honking
{
	public function honk()
	{
		echo 'Beep beep!';
	}
}

class Car
{
	use Honking;
}

class Truck
{
	use Honking;
}

$car = new Car;
$car->honk(); // Outputs 'Beep beep!'

$truck = new Truck;
$truck->honk(); // Also outputs 'Beep beep!'
```

У цьому прикладі у нас є трейт з іменем `Honking`, який містить один метод `honk()`. Потім у нас є два класи: `Car` і `Truck`, обидва з яких використовують трейта `Honking`. У результаті обидва класи "мають" метод `honk()`, і ми можемо викликати його в об'єктах обох класів.

Трейти дозволяють легко і ефективно обмінюватися кодом між класами. Вони не входять в ієрархію успадкування, тобто `$car instanceof Honking` поверне `false`.


Винятки
-------

Винятки в ООП дозволяють нам витончено обробляти помилки та непередбачувані ситуації в нашому коді. Це об'єкти, які несуть інформацію про помилку або нестандартну ситуацію.

У PHP є вбудований клас `Exception`, який служить основою для всіх винятків. Він має кілька методів, які дозволяють отримати більше інформації про виняток, наприклад, повідомлення про помилку, файл і рядок, де сталася помилка, тощо.

Коли в коді виникає помилка, ми можемо "згенерувати" виключення за допомогою ключового слова `throw`.

```php
function division(float $a, float $b): float
{
	if ($b === 0) {
		throw new Exception('Division by zero!');
	}
	return $a / $b;
}
```

Коли функція `division()` отримує другим аргументом null, вона генерує виключення з повідомленням про помилку `'Division by zero!'`. Щоб запобігти аварійному завершенню роботи програми при виникненні виключення, ми перехоплюємо його у блоці `try/catch`:

```php
try {
	echo division(10, 0);
} catch (Exception $e) {
	echo 'Exception caught: '. $e->getMessage();
}
```

Код, що може згенерувати виключення, обгорнутий в блок `try`. Якщо виняток згенеровано, виконання коду переходить до блоку `catch`, де ми можемо обробити виняток (наприклад, написати повідомлення про помилку).

Після блоків `try` і `catch` можна додати необов'язковий блок `finally`, який завжди виконується незалежно від того, чи було згенеровано виключення чи ні (навіть якщо ми використовуємо `return`, `break` або `continue` в блоці `try` або `catch` ):

```php
try {
	echo division(10, 0);
} catch (Exception $e) {
	echo 'Exception caught: '. $e->getMessage();
} finally {
	// Code that is always executed whether the exception has been thrown or not
}
```

Ми також можемо створювати власні класи винятків (ієрархію), які успадковуються від класу Exception. Як приклад, розглянемо простий банківський додаток, який дозволяє вносити та знімати кошти:

```php
class BankingException extends Exception {}
class InsufficientFundsException extends BankingException {}
class ExceededLimitException extends BankingException {}

class BankAccount
{
	private int $balance = 0;
	private int $dailyLimit = 1000;

	public function deposit(int $amount): int
	{
		$this->balance += $amount;
		return $this->balance;
	}

	public function withdraw(int $amount): int
	{
		if ($amount > $this->balance) {
			throw new InsufficientFundsException('Not enough funds in the account.');
		}

		if ($amount > $this->dailyLimit) {
			throw new ExceededLimitException('Daily withdrawal limit exceeded.');
		}

		$this->balance -= $amount;
		return $this->balance;
	}
}
```

Для одного блоку `try` можна вказати кілька блоків `catch`, якщо ви очікуєте різні типи виключень.

```php
$account = new BankAccount;
$account->deposit(500);

try {
	$account->withdraw(1500);
} catch (ExceededLimitException $e) {
	echo $e->getMessage();
} catch (InsufficientFundsException $e) {
	echo $e->getMessage();
} catch (BankingException $e) {
	echo 'An error occurred during the operation.';
}
```

У цьому прикладі важливо звернути увагу на порядок розташування блоків `catch`. Оскільки всі винятки успадковуються від `BankingException`, якби цей блок був першим, то всі винятки були б перехоплені в ньому без того, щоб код дійшов до наступних блоків `catch`. Тому важливо, щоб більш специфічні винятки (тобто ті, що успадковуються від інших) були вище в порядку блоків `catch`, ніж їхні батьківські винятки.


Ітерації .[#toc-iterations]
---------------------------

У PHP ви можете циклічно перебирати об'єкти за допомогою циклу `foreach`, подібно до того, як ви перебираєте масив. Щоб це працювало, об'єкт повинен реалізовувати спеціальний інтерфейс.

Перший варіант - реалізувати інтерфейс `Iterator`, який має методи `current()`, що повертає поточне значення, `key()`, що повертає ключ, `next()`, що переходить до наступного значення, `rewind()`, що переходить на початок, і `valid()`, що перевіряє, чи ми ще не дійшли до кінця.

Інший варіант - реалізувати інтерфейс `IteratorAggregate`, який має лише один метод `getIterator()`. Він або повертає об'єкт-заповнювач, який забезпечить обхід, або може бути генератором, тобто спеціальною функцією, яка використовує `yield` для послідовного повернення ключів і значень:

```php
class Person
{
	public function __construct(
		public int $age,
	) {
	}
}

class Registry implements IteratorAggregate
{
	private array $people = [];

	public function addPerson(Person $person): void
	{
		$this->people[] = $person;
	}

	public function getIterator(): Generator
	{
		foreach ($this->people as $person) {
			yield $person;
		}
	}
}

$list = new Registry;
$list->addPerson(new Person(30));
$list->addPerson(new Person(25));

foreach ($list as $person) {
	echo "Age: {$person->age} years\n";
}
```


Кращі практики .[#toc-best-practices]
-------------------------------------

Після того, як ви засвоїли основні принципи об'єктно-орієнтованого програмування, дуже важливо зосередитися на найкращих практиках ООП. Вони допоможуть вам писати код, який буде не лише функціональним, але й читабельним, зрозумілим та легко підтримуваним.

1) **Розподіл обов'язків: Кожен клас повинен мати чітко визначену відповідальність і вирішувати лише одну основну задачу. Якщо клас робить занадто багато речей, може бути доцільно розділити його на менші, спеціалізовані класи.
2) **Інкапсуляція**: Дані та методи повинні бути максимально приховані і доступні лише через визначений інтерфейс. Це дозволяє змінювати внутрішню реалізацію класу, не впливаючи на решту коду.
3) **Ін'єкція залежностей**: Замість того, щоб створювати залежності безпосередньо всередині класу, ви повинні "впорскувати" їх ззовні. Для більш глибокого розуміння цього принципу ми рекомендуємо прочитати [главу про |dependency-injection:introduction] ін'єкцію залежностей.

Вступ до об'єктно-орієнтованого програмування

Термін „ООП“ розшифровується як об'єктно-орієнтоване програмування – спосіб організації та структурування коду. ООП дозволяє розглядати програму як набір об'єктів, які взаємодіють між собою, а не як послідовність команд і функцій.

В ООП „об'єкт“ – це одиниця, яка містить дані та функції, що оперують цими даними. Об'єкти створюються на основі „класів“, які можна розуміти як схеми або шаблони для об'єктів. Маючи клас, ми можемо створити його „екземпляр“, тобто конкретний об'єкт, створений на основі цього класу.

Давайте розглянемо, як можна створити простий клас в PHP. При визначенні класу ми використовуємо ключове слово „class“, за яким слідує ім'я класу, а потім фігурні дужки, в які вкладаються функції класу (звані „методами“) і змінні класу (звані „властивостями“ або „атрибутами“):

class Car
{
	function honk()
	{
		echo 'Beep beep!';
	}
}

У цьому прикладі ми створили клас з іменем Car з однією функцією (або „методом“) honk.

Кожен клас повинен вирішувати лише одне основне завдання. Якщо клас виконує занадто багато завдань, може бути доцільно розділити його на менші, спеціалізовані класи.

Класи зазвичай зберігаються в окремих файлах, щоб упорядкувати код і полегшити навігацію по ньому. Ім'я файлу має відповідати імені класу, тому для класу Car ім'я файлу буде Car.php.

Називаючи класи, варто дотримуватися конвенції „PascalCase“, тобто кожне слово в назві починається з великої літери, без підкреслень або інших роздільників. Методи та властивості слідують конвенції „camelCase“, тобто починаються з малої літери.

Деякі методи в PHP відіграють особливу роль і мають префікс __ (два підкреслення). Одним з найважливіших спеціальних методів є „конструктор“, позначений як __construct. Конструктор – це метод, який автоматично викликається при створенні нового екземпляра класу.

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

Давайте подивимося, як використовувати конструктор в PHP:

class Person
{
	private $age;

	function __construct($age)
	{
		$this->age = $age;
	}

	function howOldAreYou()
	{
		return $this->age;
	}
}

$person = new Person(25);
echo $person->howOldAreYou(); // Outputs: 25

У цьому прикладі клас Person має властивість (змінну) $age і конструктор, який встановлює цю властивість. Метод howOldAreYou() надає доступ до віку людини.

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

Ключове слово new використовується для створення нового екземпляра класу. У наведеному вище прикладі ми створили нову особу віком 25 років.

Ви також можете встановити значення за замовчуванням для параметрів конструктора, якщо вони не вказані при створенні об'єкта. Наприклад:

class Person
{
	private $age;

	function __construct($age = 20)
	{
		$this->age = $age;
	}

	function howOldAreYou()
	{
		return $this->age;
	}
}

$person = new Person;  // if no argument is passed, parentheses can be omitted
echo $person->howOldAreYou(); // Outputs: 20

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

Приємно те, що визначення властивості з її ініціалізацією через конструктор можна скоротити і спростити таким чином:

class Person
{
	function __construct(
		private $age = 20,
	) {
	}
}

Для повноти, окрім конструкторів, об'єкти можуть мати деструктори (метод __destruct), які викликаються перед звільненням об'єкта з пам'яті.

Простори імен

Простори імен дозволяють нам організовувати і групувати пов'язані класи, функції і константи, уникаючи конфліктів імен. Ви можете уявити їх як папки на комп'ютері, де кожна папка містить файли, пов'язані з певним проектом або темою.

Простори імен особливо корисні у великих проектах або при використанні сторонніх бібліотек, де можуть виникати конфлікти імен класів.

Уявіть, що у вашому проекті є клас з іменем Car, і ви хочете розмістити його у просторі імен Transport. Ви можете зробити це таким чином:

namespace Transport;

class Car
{
	function honk()
	{
		echo 'Beep beep!';
	}
}

Якщо ви хочете використати клас Car в іншому файлі, вам потрібно вказати, з якого простору імен походить клас:

$car = new Transport\Car;

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

use Transport\Car;

$car = new Car;

Спадщина

Спадкування – це інструмент об'єктно-орієнтованого програмування, який дозволяє створювати нові класи на основі існуючих, успадковувати їх властивості та методи, а також розширювати або перевизначати їх за потреби. Спадкування забезпечує повторне використання коду та ієрархію класів.

Простіше кажучи, якщо ми маємо один клас і хочемо створити інший, похідний від нього, але з деякими змінами, ми можемо „успадкувати“ новий клас від початкового.

У PHP успадкування реалізується за допомогою ключового слова extends.

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

Давайте розглянемо приклад:

class Person
{
	private $age;

	function __construct($age)
	{
		$this->age = $age;
	}

	function printInformation()
	{
		echo "Age: {$this->age} years\n";
	}
}

class Student extends Person
{
	private $fieldOfStudy;

	function __construct($age, $fieldOfStudy)
	{
		parent::__construct($age);
		$this->fieldOfStudy = $fieldOfStudy;
	}

	function printInformation()
	{
		parent::printInformation();
		echo "Field of study: {$this->fieldOfStudy} \n";
	}
}

$student = new Student(20, 'Computer Science');
$student->printInformation();

Як працює цей код?

  • Ми використали ключове слово extends для розширення класу Person, тобто клас Student успадковує всі методи та властивості від Person.
  • Ключове слово parent:: дозволяє нам викликати методи з батьківського класу. У цьому випадку ми викликали конструктор з класу Person перед тим, як додати власну функціональність до класу Student. І аналогічно, метод предка printInformation() перед виведенням інформації про студента.

Спадкування призначене для ситуацій, коли між класами існує відношення „є“. Наприклад, Student є Person. Кіт – це тварина. Це дозволяє нам у випадках, коли ми очікуємо один об'єкт (наприклад, „Людина“) в коді, використовувати замість нього похідний об'єкт (наприклад, „Студент“).

Важливо розуміти, що основною метою успадкування не є запобігання дублюванню коду. Навпаки, зловживання успадкуванням може призвести до створення складного і важкого для супроводу коду. Якщо між класами немає відношення „є“, то замість успадкування слід розглядати композицію.

Зверніть увагу, що методи printInformation() у класах Person та Student виводять дещо різну інформацію. І ми можемо додати інші класи (наприклад, Employee), які будуть надавати інші реалізації цього методу. Здатність об'єктів різних класів по-різному реагувати на один і той самий метод називається поліморфізмом:

$people = [
	new Person(30),
	new Student(20, 'Computer Science'),
	new Employee(45, 'Director'),
];

foreach ($people as $person) {
	$person->printInformation();
}

Композиція

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

Наприклад, у нас є клас Engine і клас Car. Замість того, щоб сказати „Автомобіль – це двигун“, ми скажемо „Автомобіль має двигун“, що є типовим відношенням композиції.

class Engine
{
	function start()
	{
		echo 'Engine is running.';
	}
}

class Car
{
	private $engine;

	function __construct()
	{
		$this->engine = new Engine;
	}

	function start()
	{
		$this->engine->start();
		echo 'The car is ready to drive!';
	}
}

$car = new Car;
$car->start();

Тут Car не має всіх властивостей і методів класу Engine, але має доступ до нього через властивість $engine.

Перевагою композиції є більша гнучкість дизайну та краща адаптивність до майбутніх змін.

Видимість

У PHP ви можете визначити „видимість“ для властивостей, методів і констант класу. Видимість визначає, де ви можете отримати доступ до цих елементів.

  1. Публічний: Якщо елемент позначено як public, це означає, що ви можете отримати до нього доступ з будь-якого місця, навіть за межами класу.
  2. Protected: Елемент, позначений як protected, доступний тільки в межах класу та всіх його нащадків (класів, що успадковуються від нього).
  3. Приватний: Якщо елемент позначено як private, ви можете отримати доступ до нього лише з класу, в якому його було визначено.

Якщо ви не вкажете видимість, PHP автоматично встановить її на public.

Давайте розглянемо приклад коду:

class VisibilityExample
{
	public $publicProperty = 'Public';
	protected $protectedProperty = 'Protected';
	private $privateProperty = 'Private';

	public function printProperties()
	{
		echo $this->publicProperty;     // Works
		echo $this->protectedProperty;  // Works
		echo $this->privateProperty;    // Works
	}
}

$object = new VisibilityExample;
$object->printProperties();
echo $object->publicProperty;        // Works
// echo $object->protectedProperty;   // Throws an error
// echo $object->privateProperty;     // Throws an error

Продовження успадкування класів:

class ChildClass extends VisibilityExample
{
	public function printProperties()
	{
		echo $this->publicProperty;     // Works
		echo $this->protectedProperty;  // Works
		// echo $this->privateProperty;   // Throws an error
	}
}

У цьому випадку метод printProperties() у класі ChildClass може отримати доступ до загальнодоступних та захищених властивостей, але не може отримати доступ до приватних властивостей батьківського класу.

Дані та методи повинні бути максимально прихованими і доступними лише через визначений інтерфейс. Це дозволяє змінювати внутрішню реалізацію класу, не впливаючи на решту коду.

Заключне ключове слово

У PHP ми можемо використовувати ключове слово final, якщо ми хочемо запобігти успадкуванню або перевизначенню класу, методу або константи. Коли клас позначено як final, він не може бути розширений. Коли метод позначено як final, його не можна перевизначити в підкласі.

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

final class FinalClass
{
}

// The following code will throw an error because we cannot inherit from a final class.
class ChildOfFinalClass extends FinalClass
{
}

У цьому прикладі спроба успадкувати від кінцевого класу FinalClass призведе до помилки.

Статичні властивості та методи

Коли ми говоримо про „статичні“ елементи класу в PHP, ми маємо на увазі методи і властивості, які належать самому класу, а не конкретному екземпляру класу. Це означає, що вам не потрібно створювати екземпляр класу, щоб отримати до них доступ. Замість цього ви викликаєте або отримуєте доступ до них безпосередньо через ім'я класу.

Майте на увазі, що оскільки статичні елементи належать класу, а не його екземплярам, ви не можете використовувати псевдо-змінну $this всередині статичних методів.

Використання статичних властивостей призводить до заплутаного коду, повного підводних каменів, тому ви ніколи не повинні використовувати їх, і ми не будемо показувати приклад тут. З іншого боку, статичні методи корисні. Ось приклад:

class Calculator
{
	public static function add($a, $b)
	{
		return $a + $b;
	}

	public static function subtract($a, $b)
	{
		return $a - $b;
	}
}

// Using the static method without creating an instance of the class
echo Calculator::add(5, 3); // Output: 8
echo Calculator::subtract(5, 3); // Output: 2

У цьому прикладі ми створили клас Calculator з двома статичними методами. Ми можемо викликати ці методи безпосередньо, не створюючи екземпляр класу за допомогою оператора ::. Статичні методи особливо корисні для операцій, які не залежать від стану конкретного екземпляра класу.

Константи класу

У класах ми маємо можливість визначати константи. Константи – це значення, які ніколи не змінюються під час виконання програми. На відміну від змінних, значення константи залишається незмінним.

class Car
{
	public const NumberOfWheels = 4;

	public function displayNumberOfWheels(): int
	{
		echo self::NumberOfWheels;
	}
}

echo Car::NumberOfWheels;  // Output: 4

У цьому прикладі ми маємо клас Car з константою NumberOfWheels. При зверненні до константи всередині класу ми можемо використовувати ключове слово self замість імені класу.

Інтерфейси об'єктів

Об'єктні інтерфейси діють як „контракти“ для класів. Якщо клас реалізує об'єктний інтерфейс, він повинен містити всі методи, які визначає інтерфейс. Це чудовий спосіб гарантувати, що певні класи дотримуються одного „контракту“ або структури.

У PHP інтерфейси визначаються за допомогою ключового слова interface. Всі методи, визначені в інтерфейсі, є загальнодоступними (public). Коли клас реалізує інтерфейс, він використовує ключове слово implements.

interface Animal
{
	function makeSound();
}

class Cat implements Animal
{
	public function makeSound()
	{
		echo 'Meow';
	}
}

$cat = new Cat;
$cat->makeSound();

Якщо клас реалізує інтерфейс, але не всі очікувані методи визначені, PHP видасть помилку.

Клас може реалізовувати декілька інтерфейсів одночасно, що відрізняється від успадкування, де клас може успадковувати тільки від одного класу:

interface Guardian
{
	function guardHouse();
}

class Dog implements Animal, Guardian
{
	public function makeSound()
	{
		echo 'Bark';
	}

	public function guardHouse()
	{
		echo 'Dog diligently guards the house';
	}
}

Абстрактні класи

Абстрактні класи слугують базовими шаблонами для інших класів, але ви не можете створювати їхні екземпляри безпосередньо. Вони містять суміш повних методів та абстрактних методів, які не мають визначеного змісту. Класи, які успадковують абстрактні класи, повинні надавати визначення для всіх абстрактних методів батьківського класу.

Ми використовуємо ключове слово abstract для визначення абстрактного класу.

abstract class AbstractClass
{
	public function regularMethod()
	{
		echo 'This is a regular method';
	}

	abstract public function abstractMethod();
}

class Child extends AbstractClass
{
	public function abstractMethod()
	{
		echo 'This is the implementation of the abstract method';
	}
}

$instance = new Child;
$instance->regularMethod();
$instance->abstractMethod();

У цьому прикладі ми маємо абстрактний клас з одним звичайним і одним абстрактним методом. Потім у нас є клас Child, який успадковується від AbstractClass і надає реалізацію для абстрактного методу.

Чим відрізняються інтерфейси від абстрактних класів? Абстрактні класи можуть містити як абстрактні, так і конкретні методи, в той час як інтерфейси лише визначають, які методи повинен реалізовувати клас, але не надають їх реалізації. Клас може успадковувати лише один абстрактний клас, але може реалізовувати будь-яку кількість інтерфейсів.

Перевірка типів

У програмуванні дуже важливо переконатися, що дані, з якими ми працюємо, мають правильний тип. У PHP є інструменти, які забезпечують таку впевненість. Перевірка того, що дані мають правильний тип, називається „перевіркою типу“.

Типи, з якими ми можемо зіткнутися в PHP:

  1. Базові типи: До них відносяться int (цілі числа), float (числа з плаваючою комою), bool (логічні значення), string (рядки), array (масиви) і null.
  2. Класи: Коли ми хочемо, щоб значення було екземпляром певного класу.
  3. Інтерфейси: Визначає набір методів, які клас повинен реалізувати. Значення, яке відповідає інтерфейсу, повинно мати ці методи.
  4. Змішані типи: Ми можемо вказати, що змінна може мати декілька дозволених типів.
  5. Немає значення: Цей спеціальний тип вказує на те, що функція або метод не повертає жодного значення.

Давайте подивимося, як модифікувати код для включення типів:

class Person
{
	private int $age;

	public function __construct(int $age)
	{
		$this->age = $age;
	}

	public function printAge(): void
	{
		echo "This person is {$this->age} years old.";
	}
}

/**
 * A function that accepts a Person object and prints the person's age.
 */
function printPersonAge(Person $person): void
{
	$person->printAge();
}

Таким чином, ми гарантуємо, що наш код очікує і працює з даними правильного типу, що допоможе нам запобігти потенційним помилкам.

Деякі типи не можуть бути написані безпосередньо в PHP. В такому випадку вони перераховуються в коментарі phpDoc, який є стандартним форматом для документування PHP-коду, починаючи з /** і закінчуючи */. Він дозволяє додавати описи класів, методів і так далі. А також перераховувати складні типи за допомогою так званих анотацій @var, @param і @return. Ці типи потім використовуються інструментами статичного аналізу коду, але не перевіряються самим PHP.

class Registry
{
	/** @var array<Person>  indicates that it's an array of Person objects */
	private array $persons = [];

	public function addPerson(Person $person): void
	{
		$this->persons[] = $person;
	}
}

Порівняння та ідентифікація

У PHP ви можете порівнювати об'єкти двома способами:

  1. Порівняння значень ==: Перевіряє, чи об'єкти належать до одного класу і мають однакові значення у своїх властивостях.
  2. Ідентифікація ===: Перевіряє, чи це той самий екземпляр об'єкта.
class Car
{
	public string $brand;

	public function __construct(string $brand)
	{
		$this->brand = $brand;
	}
}

$car1 = new Car('Skoda');
$car2 = new Car('Skoda');
$car3 = $car1;

var_dump($car1 == $car2);   // true, because they have the same value
var_dump($car1 === $car2);  // false, because they are not the same instance
var_dump($car1 === $car3);  // true, because $car3 is the same instance as $car1

Оператор instanceof

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

Уявімо, що у нас є клас Person і ще один клас Student, який є нащадком класу Person:

class Person
{
	private int $age;

	public function __construct(int $age)
	{
		$this->age = $age;
	}
}

class Student extends Person
{
	private string $major;

	public function __construct(int $age, string $major)
	{
		parent::__construct($age);
		$this->major = $major;
	}
}

$student = new Student(20, 'Computer Science');

// Check if $student is an instance of the Student class
var_dump($student instanceof Student);  // Output: bool(true)

// Check if $student is an instance of the Person class (because Student is a descendant of Person)
var_dump($student instanceof Person);   // Output: bool(true)

З результатів видно, що об'єкт $student вважається екземпляром класів Student та Person.

Зручні інтерфейси

„Вільний інтерфейс“ – це техніка в ООП, яка дозволяє об'єднувати методи в ланцюжок в одному виклику. Це часто спрощує і робить код зрозумілішим.

Ключовим елементом вільного інтерфейсу є те, що кожен метод у ланцюжку повертає посилання на поточний об'єкт. Це досягається за рахунок використання 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 Sheep
{
	public string $name;

	public function __construct(string $name)
	{
		$this->name = $name;
	}

	public function __clone()
	{
		$this->name = 'Clone of ' . $this->name;
	}
}

$original = new Sheep('Dolly');
echo $original->name . "\n";  // Outputs: Dolly

$clone = clone $original;
echo $clone->name . "\n";     // Outputs: Clone of Dolly

У нашому прикладі є клас Sheep з однією властивістю $name. Коли ми клонуємо екземпляр цього класу, метод __clone() гарантує, що ім'я клонованої вівці отримає префікс „Clone of“.

Властивості

Риси в PHP – це інструмент, який дозволяє обмінюватися методами, властивостями і константами між класами і запобігає дублюванню коду. Ви можете думати про них як про механізм „копіювання і вставки“ (Ctrl-C і Ctrl-V), де вміст трейту „вставляється“ в класи. Це дозволяє повторно використовувати код без необхідності створювати складні ієрархії класів.

Давайте розглянемо простий приклад використання трейтів в PHP:

trait Honking
{
	public function honk()
	{
		echo 'Beep beep!';
	}
}

class Car
{
	use Honking;
}

class Truck
{
	use Honking;
}

$car = new Car;
$car->honk(); // Outputs 'Beep beep!'

$truck = new Truck;
$truck->honk(); // Also outputs 'Beep beep!'

У цьому прикладі у нас є трейт з іменем Honking, який містить один метод honk(). Потім у нас є два класи: Car і Truck, обидва з яких використовують трейта Honking. У результаті обидва класи „мають“ метод honk(), і ми можемо викликати його в об'єктах обох класів.

Трейти дозволяють легко і ефективно обмінюватися кодом між класами. Вони не входять в ієрархію успадкування, тобто $car instanceof Honking поверне false.

Винятки

Винятки в ООП дозволяють нам витончено обробляти помилки та непередбачувані ситуації в нашому коді. Це об'єкти, які несуть інформацію про помилку або нестандартну ситуацію.

У PHP є вбудований клас Exception, який служить основою для всіх винятків. Він має кілька методів, які дозволяють отримати більше інформації про виняток, наприклад, повідомлення про помилку, файл і рядок, де сталася помилка, тощо.

Коли в коді виникає помилка, ми можемо „згенерувати“ виключення за допомогою ключового слова throw.

function division(float $a, float $b): float
{
	if ($b === 0) {
		throw new Exception('Division by zero!');
	}
	return $a / $b;
}

Коли функція division() отримує другим аргументом null, вона генерує виключення з повідомленням про помилку 'Division by zero!'. Щоб запобігти аварійному завершенню роботи програми при виникненні виключення, ми перехоплюємо його у блоці try/catch:

try {
	echo division(10, 0);
} catch (Exception $e) {
	echo 'Exception caught: '. $e->getMessage();
}

Код, що може згенерувати виключення, обгорнутий в блок try. Якщо виняток згенеровано, виконання коду переходить до блоку catch, де ми можемо обробити виняток (наприклад, написати повідомлення про помилку).

Після блоків try і catch можна додати необов'язковий блок finally, який завжди виконується незалежно від того, чи було згенеровано виключення чи ні (навіть якщо ми використовуємо return, break або continue в блоці try або catch ):

try {
	echo division(10, 0);
} catch (Exception $e) {
	echo 'Exception caught: '. $e->getMessage();
} finally {
	// Code that is always executed whether the exception has been thrown or not
}

Ми також можемо створювати власні класи винятків (ієрархію), які успадковуються від класу Exception. Як приклад, розглянемо простий банківський додаток, який дозволяє вносити та знімати кошти:

class BankingException extends Exception {}
class InsufficientFundsException extends BankingException {}
class ExceededLimitException extends BankingException {}

class BankAccount
{
	private int $balance = 0;
	private int $dailyLimit = 1000;

	public function deposit(int $amount): int
	{
		$this->balance += $amount;
		return $this->balance;
	}

	public function withdraw(int $amount): int
	{
		if ($amount > $this->balance) {
			throw new InsufficientFundsException('Not enough funds in the account.');
		}

		if ($amount > $this->dailyLimit) {
			throw new ExceededLimitException('Daily withdrawal limit exceeded.');
		}

		$this->balance -= $amount;
		return $this->balance;
	}
}

Для одного блоку try можна вказати кілька блоків catch, якщо ви очікуєте різні типи виключень.

$account = new BankAccount;
$account->deposit(500);

try {
	$account->withdraw(1500);
} catch (ExceededLimitException $e) {
	echo $e->getMessage();
} catch (InsufficientFundsException $e) {
	echo $e->getMessage();
} catch (BankingException $e) {
	echo 'An error occurred during the operation.';
}

У цьому прикладі важливо звернути увагу на порядок розташування блоків catch. Оскільки всі винятки успадковуються від BankingException, якби цей блок був першим, то всі винятки були б перехоплені в ньому без того, щоб код дійшов до наступних блоків catch. Тому важливо, щоб більш специфічні винятки (тобто ті, що успадковуються від інших) були вище в порядку блоків catch, ніж їхні батьківські винятки.

Ітерації

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

Перший варіант – реалізувати інтерфейс Iterator, який має методи current(), що повертає поточне значення, key(), що повертає ключ, next(), що переходить до наступного значення, rewind(), що переходить на початок, і valid(), що перевіряє, чи ми ще не дійшли до кінця.

Інший варіант – реалізувати інтерфейс IteratorAggregate, який має лише один метод getIterator(). Він або повертає об'єкт-заповнювач, який забезпечить обхід, або може бути генератором, тобто спеціальною функцією, яка використовує yield для послідовного повернення ключів і значень:

class Person
{
	public function __construct(
		public int $age,
	) {
	}
}

class Registry implements IteratorAggregate
{
	private array $people = [];

	public function addPerson(Person $person): void
	{
		$this->people[] = $person;
	}

	public function getIterator(): Generator
	{
		foreach ($this->people as $person) {
			yield $person;
		}
	}
}

$list = new Registry;
$list->addPerson(new Person(30));
$list->addPerson(new Person(25));

foreach ($list as $person) {
	echo "Age: {$person->age} years\n";
}

Кращі практики

Після того, як ви засвоїли основні принципи об'єктно-орієнтованого програмування, дуже важливо зосередитися на найкращих практиках ООП. Вони допоможуть вам писати код, який буде не лише функціональним, але й читабельним, зрозумілим та легко підтримуваним.

  1. **Розподіл обов'язків: Кожен клас повинен мати чітко визначену відповідальність і вирішувати лише одну основну задачу. Якщо клас робить занадто багато речей, може бути доцільно розділити його на менші, спеціалізовані класи.
  2. Інкапсуляція: Дані та методи повинні бути максимально приховані і доступні лише через визначений інтерфейс. Це дозволяє змінювати внутрішню реалізацію класу, не впливаючи на решту коду.
  3. Ін'єкція залежностей: Замість того, щоб створювати залежності безпосередньо всередині класу, ви повинні „впорскувати“ їх ззовні. Для більш глибокого розуміння цього принципу ми рекомендуємо прочитати главу про ін'єкцію залежностей.