Об'єктно-орієнтоване програмування (ООП) у PHP

Привіт:) У цьому гайді ви ознайомитеся з основами об'єктно-орієнтованого програмування PHP. Почнемо з понять "клас" та "об'єкт", а далі ви дізнаєтеся про основні принципи та концепції ООП та навчитеся швидко та легко створювати корисні програми на PHP.

Зміст

Що таке об'єктно-орієнтоване програмування (ООП)?

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

У процедурному програмуванні ми створюємо структури даних — числа, рядки, масиви, а потім опрацьовуємо ці структури спеціальними функціями, які маніпулюють цими даними.

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

  • Об'єктно-орієнтоване програмування має декілька переваг перед процедурним програмуванням: ООП швидше і простіше у виконанні — дозволяє мислити категоріями повсякденних об'єктів. Наприклад, такі реальні поняття як Person, Car або Animal розглядаються як об'єкти. Це багато в чому спрощує задачу, коли ви тільки починаєте проектувати свою програму, оскільки призначення кожного об'єкта, як і мета відносин між об'єктами, будуть логічно зрозумілі.
  • Легше писати модульні програми. ООП передбачає написання модулів. Модульність спрощує обслуговування, зміну та відлагодження коду. Модульна структура дозволяє вносити незалежні зміни до різних частин програми, зводячи до мінімуму ризик помилок програмування.
  • ООП дозволяє створювати повністю повторно використовувані додатки з меншою кількістю коду та коротшим часом розробки. Згодом ви навіть можете створити цілу бібліотеку таких модулів, які надалі зможете використовувати в багатьох додатках. За допомогою ООП стає легше писати такий код, так як структури даних та функції інкапсулюються в єдиний об'єкт, який можна використовувати будь-яку кількість разів.
до змісту ↑

Об'єктно-орієнтовані концепції

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

  • Клас — це тип даних, що визначається програмістом, який включає як локальні функції, так і локальні дані. Ви можете думати про клас як шаблон для створення безлічі екземплярів одного і того ж виду (або класу) об'єкта.
  • Об'єкт — окремий екземпляр структури даних, визначеної класом. Ви визначаєте клас один раз, а потім створюєте велику кількість об'єктів, які йому належать. Об'єкти також відомі як екземпляри.
  • Змінна-член — це змінні, визначені всередині класу. Ці дані будуть невидимі для зовнішнього класу, і до них можна отримати доступ через функції-члени. Ці змінні називаються атрибутом об'єкта після створення об'єкта.
  • Функція-член — це функція, визначена всередині класу та використовується для доступу до даних об'єкта.
  • Наслідування — коли клас визначається шляхом наслідування існуючої функції батьківського класу, це називається наслідуванням (успадковуванням). Тут дочірній клас успадковує всі або декілька функцій-членів та змінних батьківського класу.
  • Батьківський клас — клас, успадкований від іншого класу. Це також називається базовим класом чи суперкласом.
  • Дочірній клас — клас, успадкований від іншого класу. Це також називається підкласом чи похідним класом.
  • Поліморфізм — це об'єктно-орієнтована концепція, в якій та сама функція може використовуватися для різних цілей. Наприклад, ім'я функції залишиться незмінним, але вона приймає іншу кількість аргументів і може виконувати різні завдання.
  • Перевантаження — тип поліморфізму, у якому деякі чи всі оператори мають різні реалізації залежно від типів їх аргументів. Так само функції можуть бути перевантажені іншою реалізацією.
  • Абстракція даних — будь-яке представлення даних, у якому деталі реалізації приховані (абстраговані).
  • Інкапсуляція — відноситься до концепції, при якій ми інкапсулюємо всі дані та функції-члени разом для формування об'єкта.
  • Конструктор — відноситься до особливого типу функції, яка буде викликатися автоматично при формуванні об'єкта з класу.
  • Деструктор — відноситься до особливого типу функції, яка буде викликатися автоматично щоразу, коли об'єкт видаляється або виходить за межі області видимості.

Отже, клас — це шаблон для об'єктів, а об'єкт — це екземпляр класу. Коли створюються окремі об'єкти, вони успадковують усі властивості та поведінку класу, але кожен об'єкт матиме різні значення властивостей.

до змісту ↑

Класи та об'єкти в PHP

Клас — це шаблон для об'єктів, а об'єкт – це екземпляр класу.

Уявіть, що ми маємо клас Car. Автомобіль може мати такі властивості, як назва, колір, диски і т.д. Ми можемо визначити змінні класу, наприклад $name, $color і $disk для збереження значень цих властивостей. Якщо ми створимо окремі об'єкти (BMW, Mercedes Benz і т.д.), то вони будуть успадковувати всі властивості та поведінку класу Car, але кожен об'єкт матиме різні значення властивостей.

до змісту ↑

Що таке клас PHP

Клас визначається за допомогою ключового слова class, за яким слідує ім'я класу та пара фігурних дужок ({}). Всі його властивості та методи вкладені у фігурні дужки:

Class Car {
 
}

Ключове слово class використовується для визначення класу PHP. Нижче наведено правила створення класу в PHP:

  • пишемо назву класу з великої літери;
  • якщо ім'я класу містить більше одного слова, ми пишемо кожне слово з великої літери. Це відомий стиль "верблюда" (CamelCase). Наприклад, JapaneseCars, AmericanIdol, EuropeTour та інших;
  • ім'я класу не може бути зарезервованим словом PHP;
  • ім'я класу не може містити пробілів.
до змісту ↑

Як додати властивості класу

Ми викликаємо властивості всередині класу. Властивості можуть приймати такі значення, як рядки, цілі числа та логічні значення (true/false), як будь-які інші змінні. Додамо кілька властивостей до класу Car:

class Car {
  public $name;
  public $color = 'green';
  public $hasSunRoof = true;
}

Правила створення властивостей класу:

  • використовуємо ключове слово public перед властивістю класу;
  • відповідно до правил імен, ім'я властивості починається з малої літери;
  • якщо ім'я містить більше одного слова, то всі слова, крім першого, починаються з великої літери. Наприклад, $color або $hasSunRoof;
  • властивість може мати значення за замовчуванням. Наприклад, $color = 'green'. Ми також можемо створити властивість без значення за замовчуванням. Наприклад, $name. Оголосимо клас з ім'ям Car, який складатиметься з двох властивостей ($name та $color) та двох методів set_name() та get_name() для встановлення та отримання властивості $name:
class Car {
  // Властивості
  public $name;
  public $color;

  // Методи
  function set_name($name) {
    $this->name = $name;
  }
  function get_name() {
    return $this->name;
  }
}

Цей код нічого не виведе, оскільки ми створили клас, але не створили поки що жодного об'єкта.

У класі всі змінні називаються властивостями, а функції методами.
до змісту ↑

Що таке об'єкт PHP

Об'єкт — це екземпляр класу. З класу ми можемо створити стільки об'єктів, скільки може знадобитися для проекту. Кожен об'єкт має всі властивості та методи, визначені у класі, але у них будуть різні значення властивостей. Для оголошення об'єкта необхідно використати ключове слово new. Далі створимо із класу Car два об'єкти - $bmw і $mercedes.

$bmw = new Car();
$mercedes = new Car();

Процес створення об'єкта також відомий як створення екземпляра.

до змісту ↑

Для чого потрібні об'єкти

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

З одного і того ж класу Car створено 3 окремі об'єкти з назвами: Mercedes, BMW та Audi. Хоча всі об'єкти були створені з одного і того ж класу і, отже, мають методи та властивості класу, вони все ж таки різні. Це не тільки тому, що вони мають різні назви, але й тому, що їхнім властивостям можуть бути присвоєні різні значення. Наприклад, на зображенні вище вони відрізняються властивістю кольору: Mercedes зелений, Bmw синій, а Ауді помаранчевий.

Клас містить методи та властивості, загальні для всіх створених із нього об'єктів.

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

до змісту ↑

Як отримати властивості об'єкта

Створивши об'єкт, ми можемо отримати його властивості. Наприклад:

echo $bmw->color;
echo $mercedes->color;

Коли об'єкт створено, ми можемо отримати доступ до властивостей класу за допомогою оператора (->). Важливо, що назва властивості не починається зі знака $. Тільки ім'я об'єкта починається із символу $.

class Car { // створюємо клас Car
  // Властивості
  public $name;
  public $color = 'green';
  public $hasSunRoof = true;
}

// Створення 2-х об'єктів із класу Car
$bmw = new Car();
$mercedes = new Car();

echo $bmw->color;
echo "<br>";
echo $mercedes->color;

Результат виконання коду

green
green

Властивість color була встановлена в класі за замовчуванням green, тому всі об'єкти його успадкували.

до змісту ↑

Як встановити властивості об'єкту

Щоб встановити властивість об'єкту, ми використовуємо аналогічний підхід.

Наприклад, встановимо синій колір об'єкту bmw:

$bmw->color = 'blue';

Аналогічним способом встановимо значення властивості $name для обох об'єктів:

$bmw->name = "BMW";
$mercedes->name = "Mercedes Benz";

Встановивши значення властивості, ми можемо отримати її значення. Щоб отримати колір об'єкта $bmw, ми використовуємо наступний рядок коду:

echo $bmw->color;

Приклад:

class Car {
  public $name;
  public $color = 'green';
  public $hasSunRoof = true;
}

$bmw = new Car();
$bmw->name = "BMW";
$bmw->color = 'blue';

$mercedes = new Car();
$mercedes->name = "Mercedes Benz"; 

echo $bmw->name . ": ";
echo $bmw->color;
echo "<br>";
echo $mercedes->name . ": ";
echo $mercedes->color;

Результат:

BMW: blue
Mercedes Benz: green
до змісту ↑

Як додати методи до класу

Класи можуть містити різні функції. Функція усередині класу називається методом. Тут ми додаємо до класу метод hello() з префіксом public:

class Car {
  public $comp;
  public $color = 'green';
  public $hasSunRoof = true;
 
  public function hello() {
    return "beep";
  }
}

Правила створення методів

  • прописуємо ключове слово public перед методом;
  • відповідно до правил імен, ім'я методу починається з малої літери;
  • якщо ім'я містить більше одного слова, всі слова, крім першого, починаються з великої літери. Наприклад, helloWorld() або getMethodClasses().

Ми можемо підходити до методів так само, як і до властивостей:

class Car {
 
  // Властивості
  public $name; 
  public $color = 'green';
  public $hasSunRoof = true;
 
   // Метод, який виводить слово "beep"
  public function hello() {
    return "beep";
  }
}
 
// Створити екземпляр класу
$bmw = new Car ();
$mercedes = new Car ();
 
// Отримати значення
echo $bmw->color; // green
echo "<br>";
echo $mercedes->color; // green
echo "<br>";
 
// Встановити значення
$bmw->color = 'blue';
$bmw->name = "BMW";
$mercedes->name = "Mercedes Benz";
 
// Отримати значення
echo $bmw->color; // blue
echo "<br>";
echo $mercedes->color; // green
echo "<br>";
echo $bmw->name; // BMW
echo "<br>";
echo $mercedes->name; // Mercedes Benz
echo "<br>";
 
// Використовуємо метод, щоб отримати звуковий сигнал
echo $bmw->hello(); // beep
echo "<br>";
echo $mercedes->hello(); // beep

У наведеному нижче прикладі створимо два методи set_name() і get_name() для встановлення та отримання властивості $name для об'єктів $bmw і $mercedes:

class Car {
  // Властивості
  public $name;
  public $color;

  // Методи
  function set_name($name) {
    $this->name = $name;
  }
  function get_name() {
    return $this->name;
  }
}

$bmw = new Car();
$mercedes = new Car();
$bmw->set_name("BMW");
$mercedes->set_name("Mercedes Benz");

echo $bmw->get_name();
echo "<br>";
echo $mercedes->get_name();

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

BMW
Mercedes Benz

Для встановлення та отримання властивості $color створених об'єктів додаємо до класу Car ще два методи:

class Car {
  // Властивості
  public $name;
  public $color;

  // Методи
  function set_name($name) {
    $this->name = $name;
  }
  function get_name() {
    return $this->name;
  }
  function set_color($color) {
    $this->color = $color;
  }
  function get_color() {
    return $this->color;
  }
}

$bmw = new Car();
$bmw->set_name("BMW");
$bmw->set_color('blue');
echo "Назва: " . $bmw->get_name();
echo "<br>";
echo "Колір: " . $bmw->get_color();

Результат:

Назва: BMW
Колір: blue
до змісту ↑

Ключове слово $this у PHP

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

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

$this->propertyName;
$this->methodName();
Тільки ключове слово $this починається зі знака $, а імена властивостей та методів ні.

Ключове слово $this відноситься до поточного об'єкта і доступне лише всередині методів.

У наступному прикладі створено клас Car та об'єкт $bmw.

class Car {
  public $name;
} 
$bmw = new Car();

Перед нами постає завдання змінити властивість $name об'єкта. Зробити це можна двома способами.

  1. Зміна властивості всередині класу шляхом додавання методу set_name() та використовуючи ключове слово $this:
class Car {
  public $name;
  
  // Метод
  function set_name($name) {
    $this->name = $name;
  }  
}

$bmw = new Car();
$bmw->set_name("BMW");
echo $bmw->name; // BMW

Результат:

BMW
  1. Зміна властивості поза класом шляхом прямої зміни значення $name:
class Car {
  public $name;  
}
$bmw = new Car();
$bmw->name = "BMW";
echo $bmw->name; // BMW

Результат:

BMW

Ще приклад:

class petrov {
	
	public $misha = '3';
	
	public function misha1() {
		return '1';
	}
	
	public function misha2() {
		$misha = '4';
		
		echo $this->misha1(); // 1
		echo misha1(); // 2
		echo $this->misha; // 3
		echo $misha; // 4
	}
	
}

$petrov = new petrov();
$petrov->misha2();

function misha1() {
	return '2';
}

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

до змісту ↑

Чи є об'єкт екземпляром класу

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

class Car {
  public $name;  
}
$bmw = new Car();

var_dump($bmw instanceof Car);

Результат:

bool(true)

Оператор instanceof не генерує жодних помилок, якщо змінна, що перевіряється, не є об'єктом. І тут він просто поверне FALSE.

Ланцюжки методів та властивостей у PHP

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

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

Щоб досягти нашої мети, ми помістимо в наш клас Car публічну властивість $tank (бак), яка представляє кількість літрів палива у баку автомобіля.

class Car {
  public $tank;
}

Ми також повинні додати два методи до нашого класу Car:

  1. fill() — додає літри палива у бак машини;
  2. ride() — обчислює скільки палива споживає автомобіль, проїжджаючи певну відстань, а потім віднімає його з бака. У нашому прикладі припустимо, що автомобіль споживає 1 літр палива кожні 10 кілометрів.
class Car {
  public $tank;

  // Додати літри палива в бак, коли ми заправляємось
  public function fill($float) {
    $this->tank += $float;
  }
  
  // Відняти літри палива, поки автомобіль рухається
  public function ride($float) {
    $kms = $float;
    $litrs = $kms/10;
    $this->tank -= $litrs;
  }
}

Щоб наш код виглядав елегантно, ми об'єднаємо методи та властивості у ланцюжок. Зверніть увагу на стрілки у коді.

$tank = $car->fill(10)->ride(40)->tank;

Щоб ми могли виконати ланцюжок, методи повинні повертати об'єкт, і оскільки ми знаходимося всередині класу, методи повинні повертати ключове слово $this.

У наведеному нижче коді ми бачимо, як кожен метод повертає ключове слово $this, щоб дозволити ланцюжок:

class Car {
  public $tank;
  
  // Додати літри палива в бак, коли ми заправляємось
  public function fill($float) {
    $this->tank += $float;
 
    return $this;
  }
  
  // Відняти літри палива, поки автомобіль рухається
  public function ride($float) {
    $kms = $float;
    $litrs = $kms/10;
    $this->tank -= ($litrs);
 
    return $this;
  }
}

Тепер ми можемо створити об'єкт із класу Car з ім'ям $bmw і дізнатися кількість літрів палива, що залишилася в баку нашої машини після того, як ми залили бак 10 літрів палива і проїхали 40 км.

// Створити новий об'єкт із класу Car
$bmw = new Car();
 
// Додати 10 л палива, проїхати 40 км і отримати кількість літрів у баці
$tank = $bmw->fill(10)->ride(40)->tank;
 
// Вивести результати на екрані
echo "Залишок літрів палива у баці: " . $tank;

Повний код:

class Car {
  public $tank;
  
  // Додати літри палива в бак, коли ми заправляємось
  public function fill($float) {
    $this->tank += $float;
 
    return $this;
  }
  
  // Відняти літри палива, поки автомобіль рухається
  public function ride($float) {
    $kms = $float;
    $litrs = $kms/10;
    $this->tank -= ($litrs);
 
    return $this;
  }
}

// Створити новий об'єкт із класу Car
$bmw = new Car();
 
// Додати 10 л палива, проїхати 40 км і отримати кількість літрів у баці
$tank = $bmw->fill(10)->ride(40)->tank;
 
// Вивести результати на екрані
echo "Залишок літрів палива у баці: " . $tank;

Результат:

Залишок літрів палива у баці: 6
до змісту ↑

Конструктор PHP

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

Що таке конструктор PHP

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

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

__construct();

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

Конструктор дозволяє нам відразу ініціалізувати властивості об'єкта при його створенні. Якщо ми створюємо функцію __construct(), то PHP автоматично викликатиме цю функцію, коли ми створюватимемо об'єкт із класу. Зазвичай ми використовуємо конструктор для встановлення значення властивості.

У нашому прикладі нам потрібно встановити значення властивості $model відразу після створення об'єкта, а тому додаємо конструктор усередині класу, який встановлює значення $model:

class Car{
  private $model;
 
  // Метод конструктор
  public function __construct($model) {
    $this->model = $model;
  }
}

$car1 = new Car(); // Fatal error: Uncaught ArgumentCountError!!!

В результаті роботи програми отримаємо помилку:

Fatal error: Uncaught ArgumentCountError

Чому виконання коду було зупинене та видане повідомлення про помилку?

Щоб використовувати конструктор, ми повинні передати аргумент класу зі значенням властивості (у нашому випадку для $model), як тільки ми створимо об'єкт. Але якщо ми спробуємо створити новий об'єкт, не привласнюючи значення конструктору, то буде виникати помилка.

$car1 = new Car(); // Fatal error

Щоб уникнути такої помилки, ми повинні присвоїти конструктору значення:

$car1 = new Car("Mercedes");

Тепер додамо метод getCarModel(), щоб відобразити значення властивості $model автомобіля із щойно створеного об'єкта:

class Car {
  private $model;
  
  // Конструктор
  public function __construct ($model) {
    $this->model = $model;
  }
   
  public function getCarModel() {
    return 'Модель автомобіля: ' . $this->model;
  }
}
  
// Передаємо значення змінної після створення об'єкту
$car1 = new Car("Mercedes");
  
echo $car1->getCarModel();

Результат:

Модель автомобіля: Mercedes

Використання конструктора позбавляє нас необхідності створення та виклику методу setCarModel(), що скорочує обсяг коду.

до змісту ↑

PHP-конструктор з декількома параметрами

У конструктор можна передавати відразу декілька параметрів:

class Car {
  public $model;
  public $color;
  
  // Конструктор
  function __construct ($model, $color) {
    $this->model = $model;
    $this->color = $color;
  }
   
  function getCarModel() {
    return 'Модель автомобіля: ' . $this->model;
  }
  function getCarColor() {
    return 'Колір автомобіля: ' . $this->color;
  }
}
  
$car1 = new Car("Mercedes", "green");
  
echo $car1->getCarModel();
echo "<br>";
echo $car1->getCarColor();

Результат:

Модель автомобіля: Mercedes
Колір автомобіля: green
до змісту ↑

Як правильно написати метод конструктора

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

Якщо ми використовуємо null як значення за замовчуванням, ми можемо використовувати умову, щоб оцінити, чи було передане значення, а потім, якщо значення передане, присвоїти значення властивості.

У наведеному прикладі ми присвоюємо значення null властивості $model і, лише якщо значення передається конструктору, ми присвоюємо це значення властивості. В іншому випадку властивість $model за замовчуванням має значення рядка N/A:

class Car {
  // Властивість $model має значення за замовчуванням "N/A"
  private $model = "N/A";
  
 // Нам не потрібно присвоювати значення властивості $model 
 // оскільки вона вже має значення за замовчуванням
  public function __construct($model = null) {
    // Тільки у випадку передачі значення model воно буде присвоєне
    if($model) { 
      $this->model = $model;
    }
  }
   
  public function getCarModel() {
    return 'Модель автомобіля: ' . $this -> model;
  }
}
  
// Створюємо новий об'єкт Car, не передаючи значення model
$car1 = new Car();
  
echo $car1->getCarModel();

Результат:

Модель автомобіля: N/A

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

З іншого боку, давайте подивимося, що станеться, коли ми визначимо model після створення об'єкта. У наступному прикладі ми надаємо значення "Merceds" властивості $model відразу при створенні об'єкта $car1:

class Car {
  private $model = "";
  
  public function __construct($model = null) {
    if($model) { 
      $this->model = $model;
    }
  }
   
  public function getCarModel() {
    return 'Модель автомобіля: ' . $this->model;
  }
}
  
$car1 = new Car('Mercedes');
  
echo $car1->getCarModel();

Результат:

Модель автомобіля: Mercedes
до змісту ↑

Магічні константи

Крім магічних методів мова PHP пропонує декілька магічних констант. Вони записуються великими літерами з префіксом і суфіксом із двома підкресленнями

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

class Car {
  private $model = '';
  public function __construct($model = null) {
    if($model) { 
      $this->model = $model;
    }
  }
   
  public function getCarModel() {
    return "Ім'я класу: " . __CLASS__ . " модель: " . $this->model;
  }
}
  
$car1 = new Car('Mercedes');  
echo $car1->getCarModel();

Результат:

Ім'я класу: Car модель: Mercedes

Інші магічні константи:

  • __LINE__ — отримати номер рядка, у якому використовується константа;
  • __FILE__ — отримати повний шлях або ім'я файлу, в якому використовується константа;
  • __METHOD__ — отримати ім'я методу, в якому використовується константа.
до змісту ↑

Деструктор PHP

Деструктор класу PHP об'єктно-орієнтованого програмування — це спеціальна функція, що належить до групи магічних методів, яка автоматично викликається при видаленні об'єкта. Коли звільняється останнє посилання об'єкт, перед вивільненням пам'яті, займаної цим об'єктом, викликається метод __destruct(), який не приймає ніяких параметрів.

Під час створення об'єкта може бути викликана функція-конструктор __construct() для ініціалізації змінних-членів. Функція-деструктор __destruct() виконує зворотну місію — під час видалення об'єкта вона звільняє пам'ять та ресурси сервера. Деструктор виконує важливу функцію, тому що на сервері одночасно працюють десятки програм різних окремих сайтів, а ресурси будь-якого сервера обмежені.

Оголошення функції-деструктора починається з двох знаків підкреслення (__) та ключового слова destruct:

__destruct();

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

У наведеному нижче прикладі клас Test виводить повідомлення при створенні екземпляра об'єкта та друкує інше повідомлення при його видаленні:

class Test {
  public $name;

  // Конструктор
  function __construct($name) {
    // присвоює значення аргументу властивості $name
    $this->name = $name;

    echo 'Привіт, '. $this->name. '<br>';
  }

   // Деструктор
  function __destruct() {
    echo 'Прощавай, '. $this->name;
  }
}

$user = new Test("Майк");

Результат:

Привіт, Майк
Прощавай, Майк

У наступному прикладі деструктор очищує декілька властивостей:

class Fruit {
  public $name;
  public $color;

  function __construct($name, $color) {
    $this->name = $name;
    $this->color = $color;
  }
  function __destruct() {
    echo "Назва фрукту {$this->name} та його колір {$this->color}.";
  }
}

$apple = new Fruit("апельсин", "оранжевий");

Результат:

Назва фрукту апельсин та його колір оранжевий

У наведених вище прикладах метод __destruct() буде викликатись наприкінці виконання скрипта.

Метод __desctructor() буде викликатись лише тоді, коли поточний скрипт PHP буде повністю виконаний, досягнувши його останнього рядка або вийшовши із нього за допомогою функцій exit() або die().
до змісту ↑

Модифікатори доступу PHP

До цього моменту ми явно оголошували всі властивості, як public (загальнодоступні). І такий тип доступу заданий за замовчанням для всіх методів. Далі розглянемо, як обмежити внутрішній доступ до наших класів за допомогою модифікаторів доступу private, protected та public.

За допомогою спеціальних модифікаторів можна задати область видимості для властивостей та методів класу. У PHP є три такі модифікатори:

  • public — властивість або метод доступні звідусіль. Це значення за замовчуванням;
  • protected — до властивості або методу можна отримати доступ усередині класу та класів, похідних від цього класу;
  • private — властивість або метод можуть бути доступні ТІЛЬКИ всередині класу.

У наступному прикладі додамо 3 різні модифікатори доступу до трьох властивостей класу Car. Тут, якщо ми спробуємо встановити властивість name, це працюватиме нормально (оскільки властивість name є загальнодоступною). Однак якщо ми спробуємо встановити властивість color або disk, це призведе до фатальної помилки (оскільки ці властивості є відповідно protected (захищеною) та private (приватною):

class Car {
  public $name;
  protected $color;
  private $disk;
}

$bmv = new Car();

$bmv->name = 'BMV'; // OK
$bmv->color = 'Yellow'; // ERROR
$bmv->disk = 'Stamped'; // ERROR

Результат:

Fatal error: Uncaught Error: Cannot access protected property Car::$color
до змісту ↑

Як отримати доступ до властивості private

Видно, що ми не маємо доступу до private (приватних) властивостей ззовні класу, але нам все одно потрібно якимось чином встановити та отримати їх значення. Для взаємодії з приватними властивостями ми використовуємо публічні (public) методи, оскільки вони можуть взаємодіяти як із кодом поза області дії класу, так і з кодом усередині класу. Публічні методи, які можуть взаємодіяти таким чином, зазвичай поділяються на два типи:

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

У наступному прикладі ми зможемо отримати та встановити значення приватної властивості $carModel за допомогою методів setter та getter. Для цього використаємо метод setModel(), щоб встановити значення моделі автомобіля, та метод getModel(), щоб отримати значення властивості:

class Car {
 
  // модифікатор private забороняє доступ до методу за межами класу
  private $model;
 
  // модифікатор публічного доступу дозволяє доступ до методу зовні класу
  public function setModel($model) {
    $this->model = $model;
  }
  
  public function getModel() {
    return "Модель автомобіля: " . $this->model;
  }
}
 
$mercedes = new Car();

// Встановити модель автомобіля
$mercedes->setModel("Mercedes Benz");

// Отримати модель автомобіля
echo $mercedes->getModel();

Результат:

Модель автомобіля: Mercedes Benz
до змісту ↑

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

У наступному прикладі перевіримо, що тільки певні моделі автомобілів можуть прокласти собі дорогу і бути призначеними приватним властивостям $model шляхом визначення дозволених альтернатив для моделей у публічному методі setModel(). Для цього всередині методу setModel() визначаємо масив дозволених моделей автомобілів і перевіряємо, що ці моделі присвоєні властивості $model.

class Car {
 
  // модифікатор private забороняє доступ до методу за межами класу
  private $model;
 
   // модифікатор публічного доступу дозволяє доступ до методу зовні класу
  public function setModel($model) {
    // встановлюємо, що тільки визначені моделі автомобілів можуть бути назначені властивості $carModel
    $allowedModels = array("Mercedes benz","BMW");
 
    if(in_array($model,$allowedModels)) {
      $this->model = $model;
    } else {
      $this->model = "нема у нашому списку моделей!";
    }
  }
  
  public function getModel() {
    return "Марка автомобіля:  " . $this->model;
  }
}
 
$mercedes = new Car();
$opel = new Car();

// Встановлює модель автомобіля
$mercedes->setModel("Mercedes benz");
$opel->setModel("Opel");

// Отримує модель автомобіля
echo $mercedes->getModel();
echo "<br>";
echo $opel->getModel();

Результат:

Марка автомобіля:  Mercedes benz
Марка автомобіля:  нема у нашому списку моделей!
до змісту ↑

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

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

Що таке наслідування

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

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

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

до змісту ↑

Як наслідувати від іншого класу

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

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

class ParentClass {

}

class ChildClass extends ParentClass {

}

У прикладі нижче клас SportsCar наслідує клас Car, тому має доступ до всіх методів і властивостей Car, які не є приватними. Це дозволяє нам писати загальнодоступні методи setModel() і hello() лише раз у батьківському класі, а потім використовувати ці методи як у батьківському, так і в дочірньому класах:

// Батьківський клас
class Car {
  // Приватна властивість всередині класу
  private $model;
 
  // Публічний метод встановлення моделі
  public function setModel($model) {
    $this->model = $model;
  }
 
  public function hello() {
    return "Біп! Я " . $this->model . "<br>";
  }
}
 
// Дочірній клас наслідує код батьківського класу
class SportsCar extends Car {
  // Немає коду в дочірньому класі
}
 
// Створюємо екземпляр з дочірнього класу
$sportsCar1 = new SportsCar();
  
// Встановлюємо значення властивості класу, використовуючи метод,
// який мі створили у батьківському
$sportsCar1->setModel('Mercedes Benz');
  
// Використовуємо другий метод, який дочірній клас успадкував від батьківського
echo $sportsCar1->hello();

Результат:

Біп! Я Mercedes Benz
до змісту ↑

Власні методи та властивості дочірнього класу

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

У прикладі нижче додамо в дочірній клас деякий власний код, додавши властивість $style, а також метод driveItWithStyle():

class Car {  
  // Приватну властивість або метод може використовувати лише батько
  private $model;  
  // Публічні методи та властивості можуть використовуватися як батьківським, так і дочірнім класами
  public function setModel($model) {
    $this->model = $model;
  }   
  public function getModel() {
    return $this->model;
  }
}
  
// Дочірній клас може використовувати код, який він успадкував від батьківського класу, 
// а також може мати власний код
class SportsCar extends Car { 
  private $style = 'швидкий та надійний'; 
  public function driveItWithStyle() {
    return 'Автомобіль '  . $this->getModel() . ' ' . $this->style . '';
  }
}
 
// Створити екземпляр із дочірнього класу
$sportsCar1 = new SportsCar();
   
// Використовуємо метод, який дочірній клас успадкував від батьківського класу
$sportsCar1->setModel('Ferrari');
  
// Використовуємо метод, який був доданий у дочірній клас
echo $sportsCar1->driveItWithStyle();

Результат:

Автомобіль Ferrari швидкий та надійний
до змісту ↑

Наслідування та модифікатор захищеного доступу

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

У цьому розділі дізнаємося про третій модифікатор — protected, який дозволяє використовувати код як усередині класу, так і з його дочірніх класів.

Що може статися, коли ми спробуємо викликати приватний метод чи властивість ззовні класу?

Наступний приклад демонструє, що може статися, якщо ми оголошуємо властивість $model у батьківському елементі приватним, але намагаємося отримати до нього доступ з його дочірнього класу:

class Car {
  // Властивість $model є приватною, а тому до неї можна отримати доступ 
  // тільки всередині классу
  private $model;  
  // Публічний метод
  public function setModel($model) {
    $this->model = $model;
  }
}
   
// Дочірній клас
class SportsCar extends Car {
  // Намагаємось отримати доступ до приватної властивості model
  public function hello() {
    return "Біп! Я <i>" . $this->model . "</i><br />";
  }
}
   
// Створюємо екземпляр із дочірнього класу
$sportsCar1 = new SportsCar();
  
// Встановлюємо ім'я моделі класу
$sportsCar1->setModel('Mercedes Benz');
   
// Отримуємо значення властивості model 
echo $sportsCar1->hello();

Результат:

Warning: Undefined property: SportsCar::$model in /home/user/scripts/code.php on line 15
Біп! Я

Ми не вивели значення моделі автомобіля $model, тому що метод hello() у дочірньому класі намагається отримати доступ до приватної властивості $model, що належить батьківському класу.

Ми можемо вирішити цю проблему, оголосивши властивість $model у батьківському класі як захищену protected, а не приватну, тому що коли ми оголошуємо властивість або метод як захищені, ми можемо звертатися до них як із батьківського, так і з дочірнього класів:

class Car {
  // Властивість $model теперь защищена, тому до неї можна отримати доступ
  // із класу та його дочірніх класів
  protected $model;
  
  public function setModel($model) {
    $this->model = $model;
  }
}
  
class SportsCar extends Car {
  // Тепер є доступ до захищеної властивості, яка належить батьку
  public function hello() {
    return "Біп! Я <i>" . $this->model . "</i><br />";
  }
}
  
$sportsCar1 = new SportsCar();
  
$sportsCar1->setModel('Mercedes Benz');
  
echo $sportsCar1->hello();

Результат:

Біп! Я Mercedes Benz

Це також відноситься і до методів:

class Car {
  public $name;
  public $color;
  public function __construct($name, $color) {
    $this->name = $name;
    $this->color = $color;
  }
  protected function intro() {
    echo "Авто {$this->name} має колір: {$this->color}.";
  }
}
  
class SportsCar extends Car {  
  public function message() {
    echo "Який колір у авто? " . "<br>";
    // Доступ до захищеного методу, який належить батькові
    $this->intro();
  }
}
  
$sportsCar1 = new SportsCar('Mercedes Benz', 'Чорний');  
$sportsCar1->message();

Результат:

Який колір у авто? 
Авто Mercedes Benz має колір: Чорний.

Публічний метод message() дочірнього класу SportsCar має доступ до захищеного методу intro() батьківського класу.

до змісту ↑

Перевизначення батьківських властивостей та методів

Так само, як дочірній клас може мати власні властивості та методи, він може перевизначати властивості та методи батьківського класу. Коли ми перевизначаємо властивості та методи класу, ми переписуємо метод або властивість (з використанням того ж імені), які існують у батьківському елементі, у дочірньому, але надаємо їм інше значення або код.

Подивіться приклад нижче. Методи __construct() та intro() у дочірньому класі (SportsCar) перевизначать методи __construct() та intro() у батьківському класі (Car):

class Car {
  public $name;
  public $color;
  public function __construct($name, $color) {
    $this->name = $name;
    $this->color = $color;
  }
   public function intro() {
    echo "Авто {$this->name} має колір: {$this->color}.";
  }
}
  
class SportsCar extends Car { 
  public $weight;
  public function __construct($name, $color, $weight) {
    $this->name = $name;
    $this->color = $color;
    $this->weight = $weight;
  }
  public function intro() {
    echo "Авто {$this->name} має колір {$this->color}, а його вага {$this->weight} кг.";
  }
}
  
$sportsCar1 = new SportsCar('Mercedes Benz', 'Чорний', 1800);  
$sportsCar1 -> intro();

Результат:

Авто Mercedes Benz має колір Чорний, а його вага 1800 кг.
до змісту ↑

Ключове слово final

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

final class Car {
  // Якийсь код
}

// Призведе до помилки
class SportsCar extends Car {
  // Якийсь код
}

Результат:

Fatal error: Class SportsCar cannot extend final class Car in /home/user/scripts/code.php on line 6

У наступному прикладі final використовується для запобігання перевизначення методу:

class Car {
  final public function intro() {
	// Якийсь код
  }
}

class SportsCar extends Car {
  public function intro() { // Призведе до помилки
    // Якийсь код
  }  
}

Результат:

Fatal error: Cannot override final method Car::intro() in /home/user/scripts/code.php on line 8

Спроба перевизначити батьківський метод intro(), захищений ключовим словом final, спричинила помилку.

до змісту ↑

Константи класів PHP

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

Що таке константа класу PHP

Константи класу PHP можуть бути оголошені у межах одного класу. При оголошенні чи зверненні до констант, на відміну від змінних, до них не застосовується символ $. Область видимості констант за замовчуванням public, хоча у визначенні можуть бути використані й інші модифікатори.

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

Значенням константи — це незмінний вираз, а не змінна, властивість або виклик функції.

Константи класу чутливі до регістру. Зазвичай ім'я константи вказується у верхньому регістрі.

Щоб створити константу, її потрібно оголосити через ключове слово const і обов'язково одразу ж задати її значення:

class Test {
  // Задаємо константу
  const CONSTANT = 'значення константи';
}
до змісту ↑

Як отримати доступ до константи класу

Ми можемо отримати доступ до константи ззовні класу, використовуючи ім'я класу, за яким слідує оператор дозволу області видимості (::), за яким вже йде ім'я константи:

class Greet { 
  
    // Оголошення константи класу
    const LEAV_MESS = "Welcome to Uraine!"; 
} 
  
// Доступ до константи класу
echo Greet::LEAV_MESS; 

Результат:

Welcome to Uraine!

Також можемо отримати доступ до константи зсередини класу, використовуючи ключове слово self, за яким слідує оператор дозволу області видимості (::), за яким вже ім'я константи:

class Greet { 

  const LEAV_MESS = "Welcome to Ukraine"; 
  public function greeting() {
    echo self::LEAV_MESS;
  }
}

$greet = new Greet();
$greet->greeting();

Результат:

Welcome to Uraine!
до змісту ↑

Константа класу як вираз

У цьому прикладі константі класу надається вираз:

const X = 22;
const Y = 7;
class Square {
   
   const PI = X/Y;
   var $side = 5;
   
   function area() {
      $area = $this->side**2*self::PI;
      return $area;
   }
}

$s1 = new Square();
echo "Число Pi = ". Square::PI . "<br>";
echo "Площа = " . $s1->area();

Результат:

Число Pi = 3.1428571428571
Площа = 78.571428571429

Підказка типів даних у PHP

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

Як зробити підказку типу масиву

Коли ми хочемо змусити функцію отримувати лише аргументи типів масиву, ми можемо помістити ключове слово array перед назвою аргументу з наступним синтаксисом:

function functionName (array $argumentName) {
  // Код
}

У наступному прикладі функція calcNumKm() обчислює кількість кілометрів, яку автомобіль може проїхати з повним баком бензину, залежно від об'єму бака, а також кількість кілометрів на літр. Ця функція приймає як аргумент лише масив, як ми бачимо з того факту, що імені аргументу передує ключове слово array:

// Функція може отримати лише масив як аргумент
function calcNumKm(array $models) {
  foreach($models as $item) {
    echo $carModel = $item[0];
    echo " : ";
    echo $numberOfKm = $item[1] * $item[2];
    echo "<br>";
   }
}

Тепер спробуємо передати функції аргумент, який не є масивом, щоб подивитися, що може статися у такому випадку:

function calcNumKm(array $models) {
  foreach($models as $item) {
    echo $carModel = $item[0];
    echo " : ";
    echo $numberOfKm = $item[1] * $item[2];
    echo "<br>";
   }
}

calcNumKm("Toyota");

Результат:

Fatal error: Uncaught TypeError: calcNumKm():
Argument #1 ($models) must be of type array, string given, called in /home/user/scripts/code.php on line 10

Із опису помилки розуміємо, що функція очікувала на змінну типу масиву, а не рядок.

Перепишемо код і передамо функції масив з очікуваними елементами, включаючи назви моделей, об'єм резервуарів та кілометри на літр:

function calcNumKm(array $models) {
  foreach($models as $item) {
    echo $carModel = $item[0];
    echo " : ";
    echo $numberOfKm = $item[1] * $item[2];
    echo "<br>";
   }
}

$models = array(
  array('Toyota', 12, 44),
  array('BMW', 13, 41)
);
 
calcNumKm($models);

Результат:

Toyota : 528
BMW : 533<br>

Тепер код працює, тому що ми передали функції масив, який вона очікувала отримати.

до змісту ↑

Як зробити підказку за типом об'єкта

Підказка типу також може бути використана, щоб змусити функцію отримати аргумент типу Object. І тому ми поміщаємо ім'я класу перед ім'ям аргументу у функції.

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

class Car {
  protected $driver;
  
  // Конструктор може отримувати лише об'єкти із класу Driver як аргументи
  public function __construct(Driver $driver)
  {
    $this->driver = $driver;
  }
}
 
class Driver {
	
}
 
$driver1 = new Driver();
$car1    = new Car($driver1);
до змісту ↑

Чи підтримує PHP вказівку на основні типи даних

У той час як PHP5 не дозволяє вказувати типи основних типів даних (цілі числа, числа з плаваючою комою, рядки і логічні значення), PHP7 підтримує підказки скалярних типів.

PHP5 не підтримує вказівки типів для основних типів даних, таких як цілі, логічні значення або рядки. Отже, коли ми повинні перевірити, чи належить аргумент базовому типу даних, ми можемо використовувати одну з функцій сімейства PHP is_.

Наприклад:

  • is_bool — чи є змінна логічного типу (true або false);
  • is_int — чи є змінна цілим числом;
  • is_float — чи є змінна float (3.14, 1.2e3 або 3E-10);
  • is_null — чи є змінна із значенням NULL;
  • is_string — чи є змінна рядком.

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

до змісту ↑

Підказка типів даних для інтерфейсів у PHP

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

Чому підказки типів для об'єктів може бути недостатньо

Уявімо ситуацію: менеджер компанії з оренди автомобілів, яка орендує тільки BMW, найняв програміста, щоб він написав програму, яка розраховує ціну за повний бак бензину для кожного автомобіля, яким володіє компанія.

Відповідно до цих вимог програміст пише клас Bmw, який містить код завдання. Цей клас містить дані про BMW, включаючи номерний знак, модель автомобіля та, що найважливіше, метод, який обчислює об'єм паливного бака. Функція, що обчислює об'єм, називається calcTankVolume. Цей метод розраховує об'єм паливного бака шляхом множення площі основи (квадрату довжини ребер основи) на висоту. Висота, а також довжина ребер основи та номерний знак, вводяться у клас через конструктор:

class Bmw {
 
  protected $model;
  protected $rib;
  protected $height;
   
  // Властивості вводяться у клас через конструктор
  public function __construct($model, $rib, $height) {
    $this->model = $model;
    $this->rib = $rib;
    $this->height = $height;
  }
 
  // Розрахувати об'єм баку для прямокутних резервуарів
  public function calcTankVolume() {
    return $this->rib * $this->rib * $this->height;
  } 
}

Далі програміст пише функцію calcTankPrice() розрахунку ціни за повний бак бензину шляхом множення об'єму баку (літрів) на ціну за літр. Однак, оскільки він не хоче, щоб функція отримувала будь-які аргументи, крім тих, що належать класу Bmw, він використовує підказку:

// Підказка типу гарантує, що функція отримає лише об'єкти Bmw у якості аргументів
public function calcTankPrice(Bmw $bmw, $pricePerLitr) {
  return $bmw->calcTankVolume() * $pricePerLitr . "$";
}

Тепер він може легко порахувати, скільки коштує повний бак бензину для BMW. Наприклад, для автомобіля з номерним знаком 777 довжина ребра 3.5 дм на висоту 5 дм та використання бензину за ціною $1 за літр:

class Bmw {
 
  protected $model;
  protected $rib;
  protected $height;
   
  // Властивості вводяться у клас через конструктор
  public function __construct($model, $rib, $height) {
    $this->model = $model;
    $this->rib = $rib;
    $this->height = $height;
  }
 
  // Розрахувати об'єм баку для прямокутних резервуарів
  public function calcTankVolume() {
    return $this->rib * $this->rib * $this->height;
  }
  
  // Підказка типу гарантує, що функція отримає лише об'єкти Bmw у якості аргументів
  public function calcTankPrice(Bmw $bmw, $pricePerLitr) {
    return $bmw->calcTankVolume() * $pricePerLitr . "$";
  }

}

$bmw1 = new Bmw('777', 3.5, 5); 
echo $bmw1->calcTankPrice($bmw1, 1);

Результат:

61.25$
до змісту ↑

Як робити підказки типів для інтерфейсів

Через короткий час менеджер вирішує ввести у свій парк автомобілів новий Mercedes, але проблема в тому, що функція calcTankPrice() може виконувати обчислення лише для BMW. Виявляється, якщо у BMW бензобак прямокутної форми, то Mercedes — бензобак циліндричної форми. Отже, наш програміст змушений написати інший клас, який зможе впоратися із цим новим завданням. З цією метою він пише наступний клас з ім'ям Mercedes, який може обчислювати об'єм для баків циліндричної форми:

class Mercedes {
  protected $model;
  protected $radius;
  protected $height;
   
  public function __construct($model, $radius, $height)
  {
    $this->model = $model;
    $this->radius = $radius;
    $this->height = $height;
  }
   
  public function calcTankVolume() {
    return $this->radius * $this->radius * pi() * $this->height;
  }
}

Коли клас Mercedes був готовий, програміст спробував порахувати вартість повного баку бензину для Mercedes:

class Mercedes {
  protected $model;
  protected $radius;
  protected $height;
   
  public function __construct($model, $radius, $height)
  {
    $this->model = $model;
    $this->radius = $radius;
    $this->height = $height;
  }
   
  public function calcTankVolume() {
    return $this->radius * $this->radius * pi() * $this->height;
  }
  
  public function calcTankPrice(Bmw $bmw, $pricePerLitr) {
    return $bmw->calcTankVolume() * $pricePerLitr . "$";
  }
  
}

$mercedes1 = new Mercedes('555', 1.8, 7); 
echo $mercedes1->calcTankPrice($mercedes1, 1);

Однак результат був неочікуваним, і виникла помилка:

Fatal error: Uncaught TypeError: Mercedes::calcTankPrice(): Argument #1 ($bmw) must be of type Bmw, Mercedes given

Це повідомлення про помилку є результатом того, що у функцію не було передано правильний об'єкт, оскільки він намагався передати об'єкт Mercedes у функцію calcTankVolume(), тоді як функція може приймати лише об'єкти, що належать класу Bmw.

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

Отже, спочатку він створив абстрактний клас з ім'ям Car, від якого можуть успадковуватися як BMW, так і Mercedes (і будь-яка інша модель автомобіля):

abstract class Car {
  
  protected $model;
  protected $height;
     
  abstract public function calcTankVolume();

}

Потім він реорганізував класи BMW та Mercedes, щоб вони успадкували від класу Car:

class Bmw extends Car {
 
  protected $rib;
  
  public function __construct($model, $rib, $height) {
    $this->model = $model;
    $this->rib = $rib;
    $this->height = $height;
  }
   
  // Розрахунок об'єму прямокутного баку
  public function calcTankVolume() {
    return $this->rib * $this->rib * $this->height;
  } 
}
 
class Mercedes extends Car {
  protected $radius;
   
  public function __construct($model, $radius, $height) {
    $this->model = $model;
    $this->radius = $radius;
    $this->height = $height;
  }
 
  
  // Розрахунок об'єму циліндрів
  public function calcTankVolume() {
    return $this->radius * $this->radius * pi() * $this->height;
  }
}

Оскільки обидва класи успадковуються від того самого інтерфейсу, можна зручно ввести підказку функції calcTankPrice() з інтерфейсом Car, щоб функція могла отримати будь-який об'єкт, якщо він належить цьому інтерфейсу:

// Підказка типу гарантує, що функція отримає лише об'єкти, 
// які належать інтерфейсу Car
function calcTankPrice(Car $car, $pricePerLitr) {
  echo $car->calcTankVolume() * $pricePerLitr . "$";
}

Тепер якщо ми спробуємо використати функцію calcTankPrice() як для об'єктів BMW, так і для Mercedes:

$bmw1 = new Bmw('777', 3.5, 5); 
echo calcTankPrice($bmw1, 1);
 
$mercedes1 = new Mercedes('555', 1.8, 7); 
echo calcTankPrice($mercedes1, 1);

Результат:

61.25$
71.251321383417$

Повний код:

abstract class Car {
  protected $model;
  protected $height;
     
  abstract public function calcTankVolume();
  
  public function calcTankPrice(Car $car, $pricePerLitr) {
    echo $car->calcTankVolume() * $pricePerLitr . "$";
  }
}

class Bmw extends Car {
 
  protected $rib;
  
  public function __construct($model, $rib, $height) {
    $this->model = $model;
    $this->rib = $rib;
    $this->height = $height;
  }
   
 public function calcTankVolume() {
    return $this->rib * $this->rib * $this->height;
  } 
}
 
class Mercedes extends Car {
  protected $radius;
   
  public function __construct($model, $radius, $height) {
    $this->model = $model;
    $this->radius = $radius;
    $this->height = $height;
  }
  
  public function calcTankVolume() {
    return $this->radius * $this->radius * pi() * $this->height;
  }
}

$bmw1 = new Bmw('777', 3.5, 5); 
echo $bmw1->calcTankPrice($bmw1, 1);

echo "<br>"; 

$mercedes1 = new Mercedes('555', 1.8, 7); 
echo $mercedes1->calcTankPrice($mercedes1, 1);

Результат:

61.25$
71.251321383417$
до змісту ↑

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

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

Що таке абстрактні класи та методи у PHP

Абстрактні класи — це класи, в яких хоча б один метод є абстрактним. Методи, оголошені абстрактними, несуть, по суті, лише описовий сенс (мають лише ім'я та аргументи) і не мають тіла. Отже, ми не можемо створювати об'єкти з абстрактних класів. Натомість нам потрібно створити дочірні класи, які додають код до тіла методів і використовують ці дочірні класи для створення об'єктів.

до змісту ↑

Оголошення абстрактних класів

Щоб оголосити абстрактний клас, ми повинні використовувати ключове слово abstract перед ім'ям класу:

abstract class ParentClass { 

  /* ... */ 

}

Оголошення абстрактних методів

Коли ви додаєте ключове слово abstract до оголошення методу, він стає абстрактним методом. І пам'ятайте, абстрактні методи не мають тіла. Тому фігурні дужки {} не використовуються.

abstract class ParentClass {

  abstract public function myMethod1();
  abstract protected function myMethod2($name, $age);
  abstract protected function myMethod3() : int;

}

Правила абстракції

Коли дочірній клас успадковується від абстрактного класу, застосовуються такі правила:

  • Дочірній клас повинен перевизначити (повторно оголосити) усі абстрактні методи;
  • Кількість обов'язкових аргументів для методів має бути такою ж, як у абстрактного методу.

Наприклад, у наведеному вище прикладі метод myMethod2 має два аргументи: $name та $age. У метода myMethod2 у дочірньому класі мають бути ті ж самі аргументи:

public function myMethod2($name, $age) {

   //...

}
  • Дочірній клас може мати аргументи зі значеннями за умовчанням, якщо в абстрактному класі вони не визначені (наприклад, $country = 'Ukraine'):
public function myMethod2($name, $age, $country = 'Ukraine') {

  //...

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

Наприклад, myMethod3 в абстрактному класі вище вказує на int. Тому дочірній метод підказує те саме:

public function myMethod3() : int {

  //...

}
  • Видимість (модифікатор доступу) дочірнього методу має бути такою ж, як у батьківського, або менш обмеженою.
Видимість абстрактного методуВидимість дочірнього методу
publicpublic
protectedprotected або public, але не private
  • Об'єкти не можна створювати із абстрактних класів.
до змісту ↑

Неабстрактні методи в абстрактному класі

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

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

Розширимо наведений вище приклад і включимо в наш клас неабстрактний метод myMethod2:

abstract class ParentClass {
  
  abstract public function myMethod1();
  
  public function myMethod2() {
    echo "Hello, World!";
  }

}
У цьому основна відмінність абстрактних класів від інтерфейсів. Абстрактні класи можуть мати реальні методи, а інтерфейси можуть мати лише оголошення методів.
до змісту ↑

Приклад абстрактного класу в PHP ООП

Батьківський абстрактний клас:

abstract class Person {
  
  public $name;
  
  public function __construct($name) {
    $this->name = $name;
  }
  
  abstract public function greet() : string;

}

У батьківському класі оголошено метод __construct та властивість $name. Отже, дочірній клас автоматично їх отримає. Але greet() — це абстрактний метод, який має бути визначений у всіх дочірніх класах, і вони мають повертати рядок.

до змісту ↑

Як створити дочірні класи із абстрактного класу

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

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

Створимо дочірні класи і визначимо абстрактний метод, успадкований від батька, greet():

class Programmer extends Person {

  public function greet() : string {
    return "Привіт, Світ! Я - " . $this->name;
  }

}

class Student extends Person {

  public function greet() : string {
    return "Добридень! Я - " . $this->name;
  }

}

class Teacher extends Person {

  public function greet() : string {
    return "Доброго дня, студенти! Я - " . $this->name;
  }

}

Тепер можемо створювати об'єкти із дочірніх класів:

$programmer = new Programmer('Василь');
echo $programmer->greet();

$student = new Student('Максим');
echo $student->greet();

$teacher = new Teacher('Валентина Федорівна');
echo $teacher->greet();

Повний код розглянутого прикладу абстрактного класу:

abstract class Person {
  
  public $name;
  
  public function __construct($name) {
    $this -> name = $name;
  }
  
  abstract public function greet() : string;
  
}


class Programmer extends Person {
	
  public function greet() : string {
    return "Привіт, Світ! Я - " . $this->name;
  }
  
}

class Student extends Person {
	
  public function greet() : string {
    return "Добридень! Я - " . $this->name;
  }
  
}

class Teacher extends Person {
	
  public function greet() : string {
    return "Доброго дня, студенти! Я - " . $this->name;
  }
  
}


$programmer = new Programmer('Василь');
echo $programmer->greet();

$student = new Student('Максим');
echo $student->greet();

$teacher = new Teacher('Валентина Федорівна');
echo $teacher->greet();

Результат:

Привіт, Світ! Я - Василь
Добридень! Я - Максим
Доброго дня, студенти! Я - Валентина Федорівна
до змісту ↑

Інтерфейси у PHP

Інтерфейси схожі на абстрактні класи і дозволяють створювати слабопов'язані програми.

Що таке інтерфейс

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

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

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

Інтерфейси можуть містити методи та/або константи, але не атрибути. Інтерфейсні константи мають ті ж обмеження, як і константи класу. Методи інтерфейсу неявно абстрактні. Інтерфейси визначаються за допомогою ключового слова interface, за яким слідує ім'я інтерфейсу:

Interface MyFirstInterface {
    const BAR = 'BAR';

    public function doSomething($param1, $param2);
}

Всі методи в інтерфейсі є абстрактними, тому вони не можуть бути реалізовані в коді, і ключове слово abstract не потрібне.

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

interface InterfaceName {
  public function someMethod1();
  public function someMethod2($name, $color);
  public function someMethod3() : string;
}
Інтерфейси не винні оголошувати конструктори чи деструктори, оскільки це деталі реалізації лише на рівні класу.
до змісту ↑

Як реалізувати інтерфейс

Щоб реалізувати інтерфейс, необхідно створити клас за допомогою ключового слова implements.

interface interfaceName { 
  // абстрактні методи
}
 
class Child implements interfaceName {
  // визначає методи інтерфейсу та може мати власний код
}

У наведеному нижче прикладі створимо інтерфейс для класів, що керують автомобілями, який передає всі свої методи setModel() та getModel() класам, що реалізують інтерфейс:

interface Car { 
  public function setModel($name);
  
  public function getModel();
}

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

Класи, що реалізують інтерфейси, повинні визначати всі методи, які вони успадковують від інтерфейсів, включаючи всі параметри. У наступному прикладі в конкретному класі з ім'ям miniCar додамо код до всіх абстрактних методів:

interface Car { 
  public function setModel($name);  
  public function getModel();
}

class miniCar implements Car {
  private $model; 
   
  public function setModel($name) { 
    $this->model = $name; 
  }  
  public function getModel() {
    return $this->model; 
  }
}

$car = new miniCar();
$car->setModel('Mercedes Benz');
echo $car->getModel();

Результат:

Mercedes Benz
до змісту ↑

Реалізація декількох інтерфейсів

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

У наступному прикладі клас MyClass реалізує два інтерфейси, розділених комами в оголошенні. При цьому ми повинні оголосити всі абстрактні методи всередині створеного класу:

Interface MyInterface1 {
  public function myMethod1();
}
Interface MyInterface2 {
  public function myMethod2();
}
class MyClass implements MyInterface1, MyInterface2 {
  public function myMethod1() {
    echo "Hello, ";
  }
  public function myMethod2() {
    echo "World!";
  }
}
$obj = new MyClass();
$obj->myMethod1();
$obj->myMethod2();

Результат:

Hello, World!
до змісту ↑

Як розширити клас та реалізувати інтерфейс

Клас може розширювати клас, а також реалізовувати один або кілька інтерфейсів:

Interface MyInterface {
  public function write();
}
class ParentClass {
  public $name;
  public function __construct($name) {
    $this->name = $name;
  }
}
class ChildClass extends ParentClass  implements MyInterface {
  function write() {
    echo $this->name;
  }
}
$child = new ChildClass('Mykhaylo Petrov');
$child->write();

Результат:

Mykhaylo Petrov
Ви можете розширити лише один клас, в той же час реалізувати декілька інтерфейсів.
до змісту ↑

Розширення інтерфейсів

Інтерфейси також можуть розширювати інтерфейси:

Interface MyInterface1 {
  public function myMethod1();
}

Interface MyInterface2 extends MyInterface1 {
  public function myMethod2();
}

class MyClass1 implements MyInterface1 {
  // Потрібен лише цей метод
  public function myMethod1() {}
}

class MyClass2 implements MyInterface2 {
  // Повинні бути оголошені як myMethod1, так і myMethod2
  public function myMethod1() {}
  public function myMethod2() {}
}

Різниця між абстрактним класом та інтерфейсом

ІнтерфейсАбстрактний клас
код- абстрактні методи
- константи
- абстрактні методи
- константи
- конкретні методи
- конкретні змінні
модифікатори доступу- публічні- публічні
- захищені
кількість батьківодин і той же клас може реалізовувати більше 1 інтерфейсудочірній клас може успадковувати лише від 1 абстрактного класу
до змісту ↑

Поліморфізм в ООП на PHP

У цьому уроці познайомимося із терміном "поліморфізм" (грец. "Багато форм") — угоду про імена, яка може допомогти писати набагато послідовніший і простіший у використанні код. Відповідно до принципу поліморфізму, методи в різних класах, які роблять схожі речі, повинні мати одне й те ж ім'я.

Що таке поліморфізм

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

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

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

Наприклад, ми можемо викликати метод, який обчислює площу, — calcArea(), і вирішити, що ми поміщаємо в кожен клас, який представляє фігуру, метод із цим ім'ям, який обчислює площу відповідно до форми. Тепер, коли б ми не захотіли обчислити площу для різних фігур, викликатимемо метод з ім'ям calcArea(), не приділяючи занадто багато уваги технічним деталям того, як фактично обчислити площу для різних фігур. Єдине, що нам потрібно знати, це ім'я методу, що обчислює площу.

до змісту ↑

Як реалізувати принцип поліморфізму

Для цього можемо вибирати між абстрактними класами та інтерфейсами.

У наведеному нижче прикладі інтерфейс з ім'ям Shape фіксує всі класи, що його реалізують, для визначення абстрактного методу з ім'ям calcArea():

interface Shape {
  public function calcArea();
}

Відповідно клас Circle реалізує інтерфейс, поміщаючи у метод calcArea() формулу, яка обчислює площу кругів:

class Circle implements Shape {
  private $radius;
   
  public function __construct($radius) {
    $this->radius = $radius;
  }
  
  // Обчислення площі круга
  public function calcArea() {
    return $this->radius * $this->radius * pi();
  }
}

Клас Rectangle також реалізує інтерфейс Shape, але визначає метод calcArea() з формулою обчислення, яка підходить для прямокутників:

class Rectangle implements Shape {
  private $width;
  private $height;
   
  public function __construct($width, $height) {
    $this->width = $width;
    $this->height = $height;
  }
  
  // Обчислення площі прямокутників   
  public function calcArea() {
    return $this->width * $this->height;
  }
}

Тепер ми можемо створювати об'єкти із конкретних класів:

$circ = new Circle(3);
$rect = new Rectangle(3,4);

Можемо бути впевнені, що всі об'єкти обчислюють площу за допомогою методу calcArea(), будь то прямокутний об'єкт або круговий об'єкт (або будь-яка інша форма), якщо вони реалізують інтерфейс Shape. Тепер можемо використовувати методи calcArea() для обчислення площі фігур:

interface Shape {
  public function calcArea();
}

class Circle implements Shape {
  private $radius;
   
  public function __construct($radius) {
    $this->radius = $radius;
  }
  
  // Обчислення площі круга
  public function calcArea() {
    return $this->radius * $this->radius * pi();
  }
}

class Rectangle implements Shape {
  private $width;
  private $height;
   
  public function __construct($width, $height) {
    $this->width = $width;
    $this->height = $height;
  }
  
  // Обчислення площі прямокутників   
  public function calcArea() {
    return $this->width * $this->height;
  }
}

$circ = new Circle(3);
$rect = new Rectangle(3,4);

echo $circ->calcArea();
echo $rect->calcArea();

Результат:

28.274333882308
12
до змісту ↑

Трейти у PHP

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

до змісту ↑

Що таке трейти

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

Синтаксис трейту такий самий як і клас, за винятком того, що ім'я трейту потрібно оголошувати за допомогою ключового слова trait:

trait TraitName {
  // якийсь код...
}

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

Саме підключення здійснюється за допомогою ключового слова use, після якого через пробіл вказується ім'я трейту, що підключається. Ця команда пишеться на початку класу:

class MyClass {
  use TraitName;
}

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

trait Reader {
  public function add($var1,$var2) {
     return $var1 + $var2;
  }
}

class File {
  use Reader; // підключаємо трейт
  
  public function calculate($var1,$var2) {
     echo "Результат суми: " . $this->add($var1,$var2) . "\n";     
  }
}

$o = new File();
$o->calculate(5,3);

Результат:

Результат суми: 8

Тут ми оголошуємо один трейт Reader. Потім створюємо клас File. Клас використовує трейт і всі методи, оголошені в трейті, будуть доступні в класі. При цьому ми звертатимемося до методів самого класу.

Якщо іншим класам необхідно використовувати функцію add(), просто використовуйте у цих класах трейт Reader. Це зменшує дублювання коду, тому що немає необхідності повторно оголошувати той самий метод знову і знову.

По суті, трейт — це просто спосіб скопіювати та вставити код під час виконання.

Якщо ви знаєте англійську, то розумієте, що trait означає саме те, що написано в назві — це безкласовий пакет методів та властивостей, які ви приєднуєте до існуючих класів за допомогою ключового слова use.

до змісту ↑

Використання декількох трейтів

Щоб продемонструвати переваги трейтів, зробимо ще один трейт Writer та ще один клас File2. Перший клас File використовує трейт Reader, а другий клас File2 використовує трейти Reader і Writer (декілька трейтів розділяються комами):

trait Reader{
  public function add($var1,$var2) {
     return $var1 + $var2;
  }
}

trait Writer {
  public function multiplication($var1,$var2) {
     return $var1 * $var2;
  }
}

class File {
  use Reader;
  public function calculate($var1,$var2) {
     echo "Результат суми: " . $this->add($var1,$var2) . "\n";
  }
}

class File2 {
  use Reader;
  use Writer; // альтернатива запису: use Reader, Writer;
  public function calculate($var1,$var2) {
     echo "Результат суми: " . $this->add($var1,$var2) . "\n";
     echo "Результат множення: " . $this->multiplication($var1,$var2);
  }
}

$o = new File();
$o->calculate(5,3);

$o2 = new File2();
$o2->calculate(6,4);

Результат:

Результат суми: 8
Результат суми: 10
Результат множення: 24
до змісту ↑

Чим трейти відрізняються від інтерфейсів

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

Інтерфейс — це контракт, в якому говориться, що "цей об'єкт може робити це", тоді як трейт дає об'єкту можливість робити це.

Іншими словами, якщо код ООП стосується планування та проектування, то інтерфейс — це план, а об'єкт — повністю побудований будинок. Тим часом, трейти — це просто спосіб допомогти збудувати будинок, спроектований за планом (інтерфейсом).

Інтерфейси — це специфікації, які можна перевірити використовуючи оператор instanceof (чи є поточний об'єкт екземпляром зазначеного класу).

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

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

interface Person {
    public function greet();
    public function eat($food);
}

trait EatingTrait {
    public function eat($food) {
        $this->putInMouth($food);
    }

    private function putInMouth($food) {
        // Перетравлюйте смачну їжу
    }
}

class NicePerson implements Person {
    use EatingTrait;

    public function greet() {
        echo 'Привіт!';
    }
}

class MeanPerson implements Person {
    use EatingTrait;

    public function greet() {
        echo 'У нас був веселий вечір!';
    }
}

$niceperson = new NicePerson;
if ($niceperson instanceOf Person) {  
    $niceperson->greet();  
}  

echo "<br>";

$meanperson = new MeanPerson;
if ($meanperson instanceOf Person) {  
    $meanperson->greet();  
}  

Результат:

Привіт!
У нас був веселий вечір!

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

до змісту ↑

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

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

Розглянемо наступний сценарій:

class BaseClass {
  function SomeMethod() { 
    echo "BaseClass"; 
  }
}

interface IBase {
  function SomeMethod();
}

trait myTrait {
  function SomeMethod() { 
  	echo "myTrait"; 
  }
}

class MyClass extends BaseClass implements IBase {
  use myTrait;

  function SomeMethod() { 
    echo "MyClass"; 
  }
}

$myclass = new MyClass; 
$myclass->SomeMethod();

Результат:

MyClass

При створенні екземпляру MyClass, описаного вище, відбувається наступне:

  1. Інтерфейс IBase включає абстрактний метод без параметрів під назвою SomeMethod(), який повинен бути реалізований у спадковому класі.
  2. Базовий клас BaseClass надає реалізацію цього методу SomeMethod().
  3. У трейту myTrait також є функція SomeMethod(), що викликається без параметрів. Вона має пріоритет над BaseClass-версіями.
  4. Клас MyClass надає власну версію методу SomeMethod(), яка має пріоритет над трейт-версіями.

Інтерфейс, за замовчуванням, не може надати реалізацію тіла методу, тоді як трейт може.

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

В одному класі можна використовувати декілька інтерфейсів, а також декілька трейтів.

до змісту ↑

Статичні методи у PHP

У деяких випадках краще підходити до методів та властивостей класу без необхідності створювати об'єкт поза класом. Це може бути досягнуто шляхом визначення методів та властивостей класу як статичних. Ми вже розглянули три модифікатори доступу: public, protected та private. Цей розділ присвячений четвертому модифікатору static, який дозволяє отримати доступ до властивостей та методів класів без необхідності створювати об'єкти поза класами.

до змісту ↑

Статичні методи

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

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

Щоб додати статичний метод до класу, потрібно після модифікатора доступу (тобто після public, private чи protected) написати ключове слово static:

class ClassName {
  public static function staticMethod() {
    // Якийсь код...
  }
}
до змісту ↑

Доступ до статичних методів

При виклику статичного методу використовується ім'я класу та оператор дозволу області видимості (::), замість операції доступу ->, так як статичний метод відноситься до всього класу, а не до конкретного об'єкту цього класу:

ClassName::staticMethod();

Розглянемо приклад:

class Car {
  static function getColor() {
   return "blue";
  }
}
echo (Car::getColor());

Результат:

blue

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

Так само можна викликати статичні методи з інших класів. Для цього видимість статичного методу має бути публічною (public):

class Car {
  public static function getColor() {
   return "blue";
  }
}

class SomeOtherClass {
  public function message() {
    echo (Car::getColor());
  }
}

$someotherclass = new SomeOtherClass();
$someotherclass->message();

Результат:

blue

У класу можуть бути як статичні, так і нестатичні методи. Так само, як ми використовували ключове слово $this для доступу до власних властивостей і методів класу зсередини класу, ми використовуємо зарезервоване ключове слово self і подвійну двокрапку (::), щоб отримати доступ до статичного методу з методу того ж класу:

class MyClass {
  public static function myStaticMethod() {
    echo "Hello, World!";
  }
  public function __construct() {
    self::myStaticMethod();
  }
}

new MyClass();

Результат:

Hello, World!

Щоб отримати доступ до статичного методу із дочірнього класу, використовуйте ключове слово parent всередині дочірнього класу:

class domain {
  protected static function getWebsiteName() {
    return "google.com";
  }
}

class domain2 extends domain {
  public $websiteName;
  public function __construct() {
    $this->websiteName = parent::getWebsiteName();
  }
}

$domain2 = new domain2;
echo $domain2->websiteName;

Результат:

google.com
Статичний метод, до якого потрібно отримати доступ із дочірнього класу, повинен мати модифікатор видимості public або protected.
до змісту ↑

Відмінності self і $this

Початківці часто плутають self і $this. Давайте прояснимо концепції.

$thisself
Представляє екземпляр класу чи об'єктуПредставляє клас
Завжди починається зі знака долара ($)Ніколи не починається зі знака долара ($)
За ним слідує оператор ->За ним слідує оператор ::
Ім'я властивості після оператора -> не має знака долара ($). Наприклад, $this->propertyІм'я властивості після оператора :: завжди має знак долара ($).
до змісту ↑

Статичні властивості у PHP

Доступ до статичних властивостей, як і статичних методів, не передбачає створення об'єкта.

Статичні властивості

Доступ до статичних властивостей здійснюється аналогічно статичним методам. Застосовуються ті ж правила видимості.

Статичні властивості оголошуються із застосуванням ключового слова static:

class ClassName {
	
  public static $staticProp = 'Hello, World!';
  
}

Для доступу до статичної властивості використовується ім'я класу та подвійна двокрапка (::), за якими слідує ім'я властивості:

ClassName::$staticProp;

Розглянемо на прикладі як отримати доступ до статичної властивості:

class ailera {
  public static $value = 3.5;
}

// Отримати статичну властивість
echo ailera::$value;

Результат:

3.5

Тут ми оголошуємо статичну властивість: $value. Потім її викликаємо, використовуючи ім'я класу, подвійну двокрапку (::) та ім'я властивості (без попереднього створення класу).

У класу можуть бути як статичні, так і нестатичні властивості. Аналогічно, як ми використовували ключове слово $this для доступу до власних властивостей класу зсередини класу, ми використовуємо зарезервоване ключове слово self і подвійну двокрапку (::), щоб отримати доступ до статичної властивості з методу того ж класу:

class ailera {
  public static $value = 3.5;
  public function staticValue() {
    return self::$value;
  }
}

$ailera = new ailera();
echo $ailera->staticValue();

Результат:

3.5

Щоб отримати доступ до статичної властивості з дочірнього класу, використовуйте ключове слово parent всередині дочірнього класу:

class ailera {
  public static $value = 3.5;
}

class docheclass extends ailera {
  public function mathStatic() {
    return parent::$value;
  }
}

// Отримати значення статичної властивості напряму через дочірній клас
echo docheclass::$value;
echo "<br>";

// або отримати значення статичної властивості за допомогою методу mathStatic()
$docheclass = new docheclass();
echo $docheclass->mathStatic();

Результат:

3.5
3.5
до змісту ↑

Коли використовувати static

Використання статичних властивостей та методів вважається поганою практикою. Однак у деяких випадках буває вигідно використовувати властивість або метод без створення об'єкту. У наступному прикладі будемо використовувати статичні властивості як лічильники, оскільки вони можуть зберігати останнє надане їм значення. Наприклад, метод addToCars() додає 1 до властивості $numberOfCars кожного разу під час виклику методу:

class Utilis {
  // Утримує кількість машин
  static public $numberOfCars = 0;
 
  // Додаємо 1 до кількості автомобілів за кожного виклику методу
  static public function addToCars() {
    self::$numberOfCars++;
  }
}
 
echo Utilis::$numberOfCars;
echo "<br>"; 

Utilis::addToCars();
echo Utilis::$numberOfCars;
echo "<br>"; 

Utilis::addToCars();
echo Utilis::$numberOfCars;
echo "<br>"; 

Utilis::addToCars();
echo Utilis::$numberOfCars;

Результат:

0
1
2
3

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

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

до змісту ↑

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

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

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

Простори імен — це кваліфікатори, які вирішують дві різні проблеми:

  1. Дозволяють використовувати те саме ім'я для більш ніж одного класу та запобігти конфлікту імен між вашим кодом і стороннім.
  2. Простори імен можуть зробити код більш організованим, групуючи класи, які працюють разом для виконання задачі.

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

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

до змісту ↑

Простори імен та файлова структура

Концепція визначення та виклику просторів імен аналогічна звичайній файловій структурі у наших операційних системах.

Уявіть, що простір імен — це теки, а файли — класи:

  • У теці /base/math може бути декілька файлів: numbers.php, calcul.php тощо. Але не може бути двох файли з однаковим ім'ям.
  • Для доступу до numbers.php із файлу calcul.php ви можете безпосередньо звернутися до numbers.php.
  • У теці /base/mechanic може бути декілька файлів: physics.php, electro.php і т.д. І тут же у вас може бути ще один файл numbers.php, навіть якщо файл із цим ім'ям уже існує у теці /base/math.
  • Всередині теки /mechanic для доступу до numbers.php ви будете посилатися /base/mechanic/numbers.php.
  • Всередині теки /mechanic, якщо вам потрібно було послатися на файл numbers.php, який знаходиться у /base/math, потрібно буде використати /base/math/numbers.php.

Так само працюють простори імен.

до змісту ↑

Оголошення простору імен

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

namespace MyApp;
Декларація namespace повинна прописуватися у першому рядку сценарію відразу після <?php (за винятком ключового слова declare). Інакше ваш код буде недійсним.

Якщо додамо оголошення простору імен на початок файлу PHP, всі класи, функції та константи будуть елементами цього простору імен. Створимо файл з ім'ям Math.php та додамо наступний код:

namespace Math;

function add($a, $b) {
  return $a + $b;
}
const PI = 3.14;

class Geometry {
  static function getCircleArea($radius) {
    return PI * $radius ** 2;
  }
}
  • Спочатку оголошуємо простір імен — namespace Math;. Тепер, усі класи, інтерфейси, константи та функції будуть елементами цього простору імен.
  • Потім оголошуємо функцію add().
  • Потім константу класу PI.
  • Потім клас Geometry.
до змісту ↑

Використання просторів імен

Створимо ще один файл з ім'ям usage.php та отримаємо доступ до елементів зазначеного вище простору імен Math (функцій, констант та класів).

// включає файл Math.php
// Як ніби тут був написаний весь код Math.php
include_once 'Math.php';

echo Math\add(4,7); // 11
echo "<br>";

echo Math\PI; // 3.14
echo "<br>";

echo Math\Geometry::getCircleArea(5); // 78.5
Символ \ використовується для переходу на рівень нижче в просторах імен.

Коли одночасно використовуються декілька класів з того ж простору імен, простіше використовувати ключове слово namespace, щоб не прописувати щоразу кваліфікатор Math\.

У наступному прикладі матимемо доступ до елементів зазначеного вище простору імен Math без кваліфікатора Math\:

namespace Math;
include_once 'Math.php';

echo add(3,9); // 12
echo "<br>";

echo PI; // 3.14
echo "<br>";

echo Geometry::getCircleArea(10); // 314.15
до змісту ↑

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

В операційній системі теки всередині можуть містити інші теки. Аналогічно простори імен можуть містити інші простори імен. Вони називаються підпросторами імен.

namespace Math\Geometry;

// ...ваші класи Geometry

У наступному прикладі створимо простір імен з ім'ям Math, а також додамо до нього підпростір імен та класи:

  • Створимо основний простір імен: Math.
  • Додамо один підпростір імен: Math\Geometry (для обробки геометрії).
  • Створимо два класи: Math\Constants (для збереження часто використовуваних констант) і Math\Geometry\Circle (для обчислення діаметра, площі, довжини кола).

Остаточна структура проекту:

/src
  /Math
    /Geometry
      Circle.php
    Constants.php

Math/Constants.php

namespace Math;

class Constants {
  const PI = 3.14159;
}

Math/Geometry/Circle.php

namespace Math\Geometry;

class Circle {  
  public $radius;
  public function __construct($radius) {
    $this->radius = $radius;
  }
  public function getDiameter() {
    return $this->radius * 2;
  }
  public function getArea() {
    // (pi)(r^2)
    return \Math\Constants::PI * $this->radius ** 2; 
  }
  public function getCircumference() {
    // 2(pi)(r)
    return 2 * \Math\Constants::PI * $this-> radius;
  }
}

У файлі Circle.php ми прописали \Math\Constants::PI з посиланням на константу у файлі Constants.php. Це тому, що сценарій PHP завжди виконується відносно поточного простору імен. Коли простір імен починається зі зворотної косої риски (\), шлях до імені елемента буде обчислюватися щодо глобального простору імен.

  • Якби ми просто прописали Constants::PI у файлі Circle.php, то посилання було б таким \Math\Geometry\Constants::PI.
  • Якби ми використовували Math\Constants::PI (без зворотної косої риски) у файлі Circle.php, то отримали б посилання \Math\Geometry\Math\Constants::PI.

Тепер використовуємо наші класи у файлі index.php, який має знаходитись у кореневій теці вашого проекту:

include_once 'src/Math/Constants.php';
include_once 'src/Math/Geometry/Circle.php';

$circle = new Math\Geometry\Circle(5);

echo $circle->getDiameter();    // 10
echo "<br>";

echo $circle->getArea();      // 78.5
echo "<br>";

echo $circle->getCircumference(); // 31.4

У прикладі вище ми спочатку включили файли класів Constants.php і Circle.php. Потім використали клас Math\Geometry\Circle та його методи.

Важливо відзначити, що index.php автоматично перебуватиме у глобальному просторі імен, тому що ми не вказали для нього жодного з просторів імен.

до змісту ↑

Імпортування просторів імен

Якщо в проекті передбачена ієрархія просторів імен, ми можемо імпортувати потрібний простір імен у той чи інший файл, використовуючи ключове слово use.

Якщо додати наступний код у index.php, клас Math\Geometry\Circle буде імпортований до поточної області:

use Math\Geometry\Circle;

$circle = new Circle(5);  // клас Circle тепер знаходиться у цьому просторі імен

echo $circle->getDiameter();    // 10
echo "<br>";

echo $circle->getArea();      // 78.5
echo "<br>";

echo $circle->getCircumference(); // 31.4

Можливо імпортувати не тільки простори імен, але і класи:

use Math\Geometry; // імпорт простору імен
use Math\Geometry\Circle; // імпорт класу
до змісту ↑

Псевдонім простору імен

Може бути корисно присвоїти простору імен або класу псевдонім:

  • якщо вже є клас з таким самим ім'ям, як у імпортованого класу;
  • коли потрібно більш зручне (або просте) ім'я для класу, що імпортується.

Таке присвоєння здійснюється за допомогою ключового слова use:

use Math\Geometry\Circle as Circ;

$circle = new Circ(5);

У прикладі вище ми надали псевдонім Circ класу Circle.

Аналогічно можна присвоїти псевдонім простору імен.

до змісту ↑

Об'єкти, що ітеруються (Iterables), в PHP

Починаючи з версії 7.1, PHP надає новий псевдотип, що називається таким що ітерується. Він приймає будь-який об'єкт (наприклад, масив), що реалізує інтерфейс Traversable. Цей тип використовує конструкцію foreach або функцію генератора, яка видає одне значення за один раз.

Що таке об'єкт, що ітерується

Таким, що ітерується, є будь-яке значення, яке можна перебрати у циклі foreach().

Псевдо-тип iterable був введений в PHP 7.1, і він може бути використаний як тип даних для аргументів функції або у якості типу функції що повертається.

Якщо значення не є масивом або екземпляром Traversable, буде видано помилку TypeError.

Використання ітерацій

У якості типу даних аргументу функції або типу функції що повертається може бути використане ключове слово iterable:

function printIterable(iterable $myIterable) { // аргумент функції що ітерується
  foreach($myIterable as $item) {
    echo $item . "<br>";
  }
}

$arr = ["Об'єкти", "що ітеруються", "PHP"];
printIterable($arr);

Результат:

Об'єкти
що ітеруються
PHP

Iterable також може використовуватися як тип що повертається, щоб вказати, що функція поверне ітеративне значення:

function getIterable():iterable {
  return ["Об'єкти", "що ітеруються", "PHP"];
}

$myIterable = getIterable();

foreach($myIterable as $item) {
  echo $item . "<br>";
}

Результат:

Об'єкти
що ітеруються
PHP
до змісту ↑

Створення ітерацій

Масиви

Всі масиви є такими що ітеруються, тому будь-який масив можна використовувати як аргумент функції, для якої потрібна ітерація.

Ітератори

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

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

Ітератор повинен мати такі методи:

МетодОпис
current()Повертає елемент, на який вказує вказівник. Це може бути будь-який тип даних.
key()Повертає ключ, пов'язаний із поточним елементом у списку. Це може бути лише ціле число, число з плаваючою комою, логічне значення або рядок.
next()Переміщує вказівник на наступний елемент у списку.
rewind()Переміщує вказівник на перший елемент у списку.
valid()Якщо внутрішній вказівник не вказує на жоден елемент (наприклад, якщо next() був викликаний в кінці списку), він повинен повернути false. У будь-якому іншому випадку повертає true.

Найпростіший приклад реалізації інтерфейсу Iterator, який демонструє, у якому порядку викликаються методи, коли використовується з ітератором оператор foreach:

class myIterator implements Iterator {
    private $position = 0;
    private $array = array(
        "firstelement",
        "secondelement",
        "lastelement",
    );

    public function __construct() {
        $this->position = 0;
    }

    public function rewind(): void {
        var_dump(__METHOD__);
        $this->position = 0;
    }

    #[\ReturnTypeWillChange]
    public function current() {
        var_dump(__METHOD__);
        return $this->array[$this->position];
    }

    #[\ReturnTypeWillChange]
    public function key() {
        var_dump(__METHOD__);
        return $this->position;
    }

    public function next(): void {
        var_dump(__METHOD__);
        ++$this->position;
    }

    public function valid(): bool {
        var_dump(__METHOD__);
        return isset($this->array[$this->position]);
    }
}

$it = new myIterator;

foreach($it as $key => $value) {
    var_dump($key, $value);
    echo "\n";
}

Результат:

string(18) "myIterator::rewind"
string(17) "myIterator::valid"
string(19) "myIterator::current"
string(15) "myIterator::key"
int(0)
string(12) "firstelement"

string(16) "myIterator::next"
string(17) "myIterator::valid"
string(19) "myIterator::current"
string(15) "myIterator::key"
int(1)
string(13) "secondelement"

string(16) "myIterator::next"
string(17) "myIterator::valid"
string(19) "myIterator::current"
string(15) "myIterator::key"
int(2)
string(11) "lastelement"

string(16) "myIterator::next"
string(17) "myIterator::valid"

Михайло Петров
Михайло Петров

Мене звати Михайло. Я — WordPress-розробник. Створюю візитки, корпоративні сайти, блоги на WordPress.

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *