


Блог IT-шника
Блог IT-шника
Привіт:) У цьому гайді ви ознайомитеся з основами об'єктно-орієнтованого програмування PHP. Почнемо з понять "клас" та "об'єкт", а далі ви дізнаєтеся про основні принципи та концепції ООП та навчитеся швидко та легко створювати корисні програми на PHP.
Об'єктно-орієнтоване програмування або скорочено ООП — це підхід, який допомагає розробляти складні програми так, щоб їх можна було протягом тривалого часу легко підтримувати та масштабувати.
У процедурному програмуванні ми створюємо структури даних — числа, рядки, масиви, а потім опрацьовуємо ці структури спеціальними функціями, які маніпулюють цими даними.
У світі ООП ми зберігаємо структури даних та функції, що їх обробляють, в одній сутності, яка називається об'єктом. Замість того, щоб обробити дані якоюсь функцією, ми завантажуємо ці дані в об'єкт, а потім викликаємо його методи для маніпулювання ними та отримуємо бажаний результат. Це відрізняється від процедурного програмування, коли ми, насамперед, взаємодіємо з функціями та глобальними змінними.
Person
, Car
або Animal
розглядаються як об'єкти. Це багато в чому спрощує задачу, коли ви тільки починаєте проектувати свою програму, оскільки призначення кожного об'єкта, як і мета відносин між об'єктами, будуть логічно зрозумілі.Перш ніж заглиблюватися у деталі ООП, давайте визначимо важливі терміни, що стосуються об'єктно-орієнтованого програмування:
Отже, клас — це шаблон для об'єктів, а об'єкт — це екземпляр класу. Коли створюються окремі об'єкти, вони успадковують усі властивості та поведінку класу, але кожен об'єкт матиме різні значення властивостей.
до змісту ↑Клас — це шаблон для об'єктів, а об'єкт – це екземпляр класу.
Уявіть, що ми маємо клас Car
. Автомобіль може мати такі властивості, як назва, колір, диски і т.д. Ми можемо визначити змінні класу, наприклад $name
, $color
і $disk
для збереження значень цих властивостей. Якщо ми створимо окремі об'єкти (BMW, Mercedes Benz і т.д.), то вони будуть успадковувати всі властивості та поведінку класу Car
, але кожен об'єкт матиме різні значення властивостей.
Клас визначається за допомогою ключового слова class
, за яким слідує ім'я класу та пара фігурних дужок ({}
). Всі його властивості та методи вкладені у фігурні дужки:
Class Car {
}
Ключове слово class
використовується для визначення класу PHP. Нижче наведено правила створення класу в PHP:
JapaneseCars
, AmericanIdol
, EuropeTour
та інших;Ми викликаємо властивості всередині класу. Властивості можуть приймати такі значення, як рядки, цілі числа та логічні значення (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;
}
}
Цей код нічого не виведе, оскільки ми створили клас, але не створили поки що жодного об'єкта.
Об'єкт — це екземпляр класу. З класу ми можемо створити стільки об'єктів, скільки може знадобитися для проекту. Кожен об'єкт має всі властивості та методи, визначені у класі, але у них будуть різні значення властивостей. Для оголошення об'єкта необхідно використати ключове слово 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
вказує на те, що ми використовуємо власні методи та властивості класу, та дозволяє нам отримати доступ до них у межах області видимості класу.
Ключове слово $this
дозволяє отримати доступ до властивостей і методів класу всередині класу, використовуючи наступний синтаксис:
$this->propertyName;
$this->methodName();
$this
починається зі знака $
, а імена властивостей та методів ні.Ключове слово $this
відноситься до поточного об'єкта і доступне лише всередині методів.
У наступному прикладі створено клас Car
та об'єкт $bmw
.
class Car {
public $name;
}
$bmw = new Car();
Перед нами постає завдання змінити властивість $name
об'єкта. Зробити це можна двома способами.
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
$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
.
Розглянемо випадок, що коли методи класу повертають ключове слово $this
, їх можна об'єднати в ланцюжок, щоб створити набагато більше потокового коду.
Розглянемо приклад, у якому потрібно визначити скільки палива залишилося у баку автомобіля. Кількість палива в баку залежить від кількості кілометрів, які проїхала машина, а також від кількості палива, яку ми залили у бак.
Щоб досягти нашої мети, ми помістимо в наш клас Car
публічну властивість $tank
(бак), яка представляє кількість літрів палива у баку автомобіля.
class Car {
public $tank;
}
Ми також повинні додати два методи до нашого класу Car
:
fill()
— додає літри палива у бак машини;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
до змісту ↑
Конструктори — важлива частина об'єктно-орієнтованого програмування на основі класів. За допомогою конструкторів новостворені об'єкти автоматично ініціалізують свої властивості, перш ніж їх можна буде використати.
Функції-конструктори — це особливий тип функцій (належать до групи методів, відомих як магічні методи), які, на відміну інших функцій, викликаються автоматично під час створення об'єкта.
Імена магічних методів завжди починаються з двох підкреслень, і магічний метод __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()
, що скорочує обсяг коду.
У конструктор можна передавати відразу декілька параметрів:
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 об'єктно-орієнтованого програмування — це спеціальна функція, що належить до групи магічних методів, яка автоматично викликається при видаленні об'єкта. Коли звільняється останнє посилання об'єкт, перед вивільненням пам'яті, займаної цим об'єктом, викликається метод __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()
.До цього моменту ми явно оголошували всі властивості, як 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
(приватних) властивостей ззовні класу, але нам все одно потрібно якимось чином встановити та отримати їх значення. Для взаємодії з приватними властивостями ми використовуємо публічні (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 ми використовуємо ключове слово 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
може бути використане для запобігання наслідування класу або запобігання перевизначення методу. У наведеному нижче прикладі ми оголошуємо клас 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 можуть бути оголошені у межах одного класу. При оголошенні чи зверненні до констант, на відміну від змінних, до них не застосовується символ $
. Область видимості констант за замовчуванням 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
За допомогою підказки типу ми можемо вказати очікуваний тип даних (масиви, об'єкти, інтерфейс тощо) аргументу в оголошенні функції. Цей метод може бути досить корисним, оскільки сприяє кращій організації коду та конкретнішим повідомленням про помилки.
Коли ми хочемо змусити функцію отримувати лише аргументи типів масиву, ми можемо помістити ключове слово 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);
до змісту ↑
У той час як 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 підтримує підказки скалярного типу. Типи, що підтримуються: цілі числа, числа з плаваючою комою, рядки і логічні значення.
до змісту ↑У цьому розділі ми побачимо, що використання підказки типів даних для об'єктів не завжди достатньо. Розберемось, як вирішити цю проблему, використовуючи підказку типів даних для інтерфейсів.
Уявімо ситуацію: менеджер компанії з оренди автомобілів, яка орендує тільки 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. Крім того, розглянемо реалізацію абстрактного класу, розібравши декілька прикладів.
Абстрактні класи — це класи, в яких хоча б один метод є абстрактним. Методи, оголошені абстрактними, несуть, по суті, лише описовий сенс (мають лише ім'я та аргументи) і не мають тіла. Отже, ми не можемо створювати об'єкти з абстрактних класів. Натомість нам потрібно створити дочірні класи, які додають код до тіла методів і використовують ці дочірні класи для створення об'єктів.
до змісту ↑Щоб оголосити абстрактний клас, ми повинні використовувати ключове слово 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 {
//...
}
Видимість абстрактного методу | Видимість дочірнього методу |
---|---|
public | public |
protected | protected або public , але не private |
Неабстрактні методи можна визначити в абстрактному класі. Ці методи працюватимуть так само, як звичайні методи наслідування.
Будь-який клас навіть з одним абстрактним методом має бути оголошений абстрактним. Але абстрактний клас може мати неабстрактні методи, до яких дочірні класи можуть звертатися і використовувати їх безпосередньо, не перевизначаючи їх.
Розширимо наведений вище приклад і включимо в наш клас неабстрактний метод myMethod2
:
abstract class ParentClass {
abstract public function myMethod1();
public function myMethod2() {
echo "Hello, World!";
}
}
Батьківський абстрактний клас:
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();
Результат:
Привіт, Світ! Я - Василь
Добридень! Я - Максим
Доброго дня, студенти! Я - Валентина Федорівна
до змісту ↑
Інтерфейси схожі на абстрактні класи і дозволяють створювати слабопов'язані програми.
Інтерфейси нагадують абстрактні класи тим, що вони включають абстрактні методи, які мають бути визначені у класах, що успадковуються від інтерфейсу.
Інтерфейси дозволяють легко використовувати різноманітні класи однаковим чином. Коли один або декілька класів використовують той самий інтерфейс, це називається поліморфізмом.
Інтерфейси доцільно створювати тоді, коли є загальна задача та кілька варіантів її вирішення, що застосовуються залежно від ситуації.
Інтерфейси можуть містити методи та/або константи, але не атрибути. Інтерфейсні константи мають ті ж обмеження, як і константи класу. Методи інтерфейсу неявно абстрактні. Інтерфейси визначаються за допомогою ключового слова 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 абстрактного класу |
У цьому уроці познайомимося із терміном "поліморфізм" (грец. "Багато форм") — угоду про імена, яка може допомогти писати набагато послідовніший і простіший у використанні код. Відповідно до принципу поліморфізму, методи в різних класах, які роблять схожі речі, повинні мати одне й те ж ім'я.
Поліморфізм — це, по суті, шаблон ООП, який дозволяє безлічі класів з різними функціями виконувати або спільно використовувати спільний інтерфейс.
У світі програмування поліморфізм використовується для того, щоб зробити програми більш модульними та розширюваними. Замість хаотичних умовних тверджень, що описують різні варіанти дій, ви створюєте взаємозамінні об'єкти, які вибираєте залежно від ваших потреб. Це основна мета поліморфізму.
Яскравим прикладом є класи, які представляють геометричні фігури (такі як прямокутники, круги та восьмикутники), які відрізняються один від одного кількістю ребер та формулою, яка обчислює їх площу, але всі вони мають свою площу, яка розраховується своїм методом. Принцип поліморфізму говорить, що у цьому разі всі методи, які обчислюють площу (і неважливо, якої форми чи класу), матимуть одне й те ж ім'я.
Наприклад, ми можемо викликати метод, який обчислює площу, — 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 підтримує лише одиничне успадкування: дочірній клас не може успадковувати від кількох класів відразу, а лише одного єдиного батька. Однак у більшості випадків було б корисно успадковувати від кількох класів. Наприклад, було б бажано успадковувати методи від кількох різних класів, щоб запобігти дублювання коду. Трейти використовуються для заповнення цієї прогалини, дозволяючи нам повторно використовувати одні й ті ж властивості та методи у кількох класах.
до змісту ↑Трейти використовуються для оголошення методів, які можна використовувати у кількох класах. Трейти можуть мати методи та абстрактні методи, які можуть використовуватись у кількох класах. Методи трейтів можуть мати будь-який модифікатор доступу (публічний, приватний чи захищений).
Синтаксис трейту такий самий як і клас, за винятком того, що ім'я трейту потрібно оголошувати за допомогою ключового слова 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
, описаного вище, відбувається наступне:
IBase
включає абстрактний метод без параметрів під назвою SomeMethod()
, який повинен бути реалізований у спадковому класі.BaseClass
надає реалізацію цього методу SomeMethod()
.myTrait
також є функція SomeMethod()
, що викликається без параметрів. Вона має пріоритет над BaseClass
-версіями.MyClass
надає власну версію методу SomeMethod()
, яка має пріоритет над трейт-версіями.Інтерфейс, за замовчуванням, не може надати реалізацію тіла методу, тоді як трейт може.
Інтерфейс є поліморфним — один або декілька класів використовують один і той же інтерфейс. У трейту немає такої поліморфної конструкції, тому що він є просто кодом, який копіюється для зручності програміста в кожен клас, який його використовує.
В одному класі можна використовувати декілька інтерфейсів, а також декілька трейтів.
до змісту ↑У деяких випадках краще підходити до методів та властивостей класу без необхідності створювати об'єкт поза класом. Це може бути досягнуто шляхом визначення методів та властивостей класу як статичних. Ми вже розглянули три модифікатори доступу: 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
. Давайте прояснимо концепції.
$this | self |
---|---|
Представляє екземпляр класу чи об'єкту | Представляє клас |
Завжди починається зі знака долара ($ ) | Ніколи не починається зі знака долара ($ ) |
За ним слідує оператор -> | За ним слідує оператор :: |
Ім'я властивості після оператора -> не має знака долара ($ ). Наприклад, $this->property | Ім'я властивості після оператора :: завжди має знак долара ($ ). |
Доступ до статичних властивостей, як і статичних методів, не передбачає створення об'єкта.
Доступ до статичних властивостей здійснюється аналогічно статичним методам. Застосовуються ті ж правила видимості.
Статичні властивості оголошуються із застосуванням ключового слова 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
до змісту ↑
Використання статичних властивостей та методів вважається поганою практикою. Однак у деяких випадках буває вигідно використовувати властивість або метод без створення об'єкту. У наступному прикладі будемо використовувати статичні властивості як лічильники, оскільки вони можуть зберігати останнє надане їм значення. Наприклад, метод 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 видасть помилку під час запуску коду. Проблема цього обмеження в тому, що якщо ви використовуєте чиюсь сторонню бібліотеку з класом, наприклад, User
, то ви не можете створити свій власний клас з таким же ім'ям.
Простір імен дозволяє нам обійти цю проблему, і ми можемо створити стільки класів User
, скільки знадобиться. Крім того, простори імен дозволять організувати код у зручні пакети, а також позначити свої права володіння цим кодом.
Концепція визначення та виклику просторів імен аналогічна звичайній файловій структурі у наших операційних системах.
Уявіть, що простір імен — це теки, а файли — класи:
Так само працюють простори імен.
до змісту ↑Простір імен задається за допомогою ключового слова 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
.
Аналогічно можна присвоїти псевдонім простору імен.
до змісту ↑Починаючи з версії 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"