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