Introducere în programarea orientată pe obiecte
Termenul “OOP” se referă la programarea orientată pe obiecte, care este o modalitate de a organiza și structura codul. OOP ne permite să vedem programul ca pe un set de obiecte care comunică între ele, în loc de o secvență de comenzi și funcții.
În OOP, un “obiect” este o unitate care conține date și funcții care lucrează cu aceste date. Obiectele sunt create conform “claselor”, pe care le putem înțelege ca planuri sau șabloane pentru obiecte. Când avem o clasă, putem crea o “instanță” a acesteia, care este un obiect specific creat conform acelei clase.
Să vedem cum putem crea o clasă simplă în PHP. Când definim o clasă, folosim cuvântul cheie “class”, urmat de numele clasei și apoi acolade care înconjoară funcțiile (numite “metode”) și variabilele clasei (numite “proprietăți”):
class Masina
{
function claxoneaza()
{
echo 'Bip bip!';
}
}
În acest exemplu, am creat o clasă numită Masina
cu o singură funcție (sau “metodă”) numită
claxoneaza
.
Fiecare clasă ar trebui să rezolve doar o singură sarcină principală. Dacă o clasă face prea multe lucruri, ar putea fi potrivit să o împărțim în clase mai mici, specializate.
Clasele sunt de obicei stocate în fișiere separate pentru a menține codul organizat și ușor de navigat. Numele fișierului
ar trebui să corespundă numelui clasei, deci pentru clasa Masina
, numele fișierului ar fi
Masina.php
.
Când denumim clasele, este bine să respectăm convenția “PascalCase”, ceea ce înseamnă că fiecare cuvânt din nume începe cu literă mare și nu există caractere de subliniere sau alți separatori între ele. Metodele și proprietățile folosesc convenția “camelCase”, ceea ce înseamnă că încep cu literă mică.
Unele metode în PHP au roluri speciale și sunt marcate cu prefixul __
(două caractere de subliniere). Una
dintre cele mai importante metode speciale este “constructorul”, care este marcat ca __construct
. Constructorul
este o metodă care este apelată automat atunci când creați o nouă instanță a clasei.
Constructorul este adesea folosit pentru a seta starea inițială a obiectului. De exemplu, atunci când creați un obiect care reprezintă o persoană, puteți folosi constructorul pentru a seta vârsta, numele sau alte proprietăți ale acesteia.
Să vedem cum să folosim un constructor în PHP:
class Persoana
{
private $varsta;
function __construct($varsta)
{
$this->varsta = $varsta;
}
function catiAniAi()
{
return $this->varsta;
}
}
$persoana = new Persoana(25);
echo $persoana->catiAniAi(); // Afișează: 25
În acest exemplu, clasa Persoana
are o proprietate (variabilă) $varsta
și un constructor care
setează această proprietate. Metoda catiAniAi()
permite apoi accesul la vârsta persoanei.
Pseudovariabila $this
este utilizată în interiorul clasei pentru a accesa proprietățile și metodele
obiectului.
Cuvântul cheie new
este folosit pentru a crea o nouă instanță a clasei. În exemplul de mai sus, am creat
o nouă persoană cu vârsta de 25 de ani.
Puteți, de asemenea, să setați valori implicite pentru parametrii constructorului, dacă aceștia nu sunt specificați la crearea obiectului. De exemplu:
class Persoana
{
private $varsta;
function __construct($varsta = 20)
{
$this->varsta = $varsta;
}
function catiAniAi()
{
return $this->varsta;
}
}
$persoana = new Persoana; // dacă nu se transmite niciun argument, parantezele pot fi omise
echo $persoana->catiAniAi(); // Afișează: 20
În acest exemplu, dacă nu specificați vârsta la crearea obiectului Persoana
, va fi utilizată valoarea
implicită 20.
Este convenabil că definiția proprietății cu inițializarea sa prin constructor poate fi scurtată și simplificată astfel:
class Persoana
{
function __construct(
private $varsta = 20,
) {
}
}
Pentru completitudine, pe lângă constructori, obiectele pot avea și destructori (metoda __destruct
), care sunt
apelați înainte ca obiectul să fie eliberat din memorie.
Spații de nume
Spațiile de nume (sau “namespaces” în engleză) ne permit să organizăm și să grupăm clase, funcții și constante înrudite, evitând în același timp conflictele de nume. Le puteți imagina ca pe niște foldere pe computer, unde fiecare folder conține fișiere care aparțin unui anumit proiect sau subiect.
Spațiile de nume sunt deosebit de utile în proiecte mai mari sau atunci când utilizați biblioteci de la terți, unde ar putea apărea conflicte în numele claselor.
Imaginați-vă că aveți o clasă numită Masina
în proiectul dvs. și doriți să o plasați într-un spațiu
de nume numit Transport
. Faceți acest lucru astfel:
namespace Transport;
class Masina
{
function claxoneaza()
{
echo 'Bip bip!';
}
}
Dacă doriți să utilizați clasa Masina
într-un alt fișier, trebuie să specificați din ce spațiu de nume
provine clasa:
$masina = new Transport\Masina;
Pentru simplificare, puteți specifica la începutul fișierului ce clasă dintr-un anumit spațiu de nume doriți să utilizați, ceea ce permite crearea instanțelor fără a fi nevoie să specificați calea completă:
use Transport\Masina;
$masina = new Masina;
Moștenire
Moștenirea este un instrument al programării orientate pe obiecte care permite crearea de noi clase pe baza claselor existente, preluând proprietățile și metodele acestora și extinzându-le sau redefinindu-le după necesități. Moștenirea permite asigurarea reutilizabilității codului și a ierarhiei claselor.
Pe scurt, dacă avem o clasă și am dori să creăm alta, derivată din ea, dar cu câteva modificări, putem “moșteni” noua clasă din clasa originală.
În PHP, moștenirea se realizează folosind cuvântul cheie extends
.
Clasa noastră Persoana
stochează informații despre vârstă. Putem avea o altă clasă Student
,
care extinde Persoana
și adaugă informații despre domeniul de studiu.
Să vedem un exemplu:
class Persoana
{
private $varsta;
function __construct($varsta)
{
$this->varsta = $varsta;
}
function afiseazaInformatii()
{
echo "Vârstă: {$this->varsta} ani\n";
}
}
class Student extends Persoana
{
private $specializare;
function __construct($varsta, $specializare)
{
parent::__construct($varsta);
$this->specializare = $specializare;
}
function afiseazaInformatii()
{
parent::afiseazaInformatii();
echo "Specializare: {$this->specializare} \n";
}
}
$student = new Student(20, 'Informatică');
$student->afiseazaInformatii();
Cum funcționează acest cod?
- Am folosit cuvântul cheie
extends
pentru a extinde clasaPersoana
, ceea ce înseamnă că clasaStudent
moștenește toate metodele și proprietățile dinPersoana
. - Cuvântul cheie
parent::
ne permite să apelăm metode din clasa părinte. În acest caz, am apelat constructorul din clasaPersoana
înainte de a adăuga funcționalitatea proprie în clasaStudent
. Și în mod similar, metodaafiseazaInformatii()
a părintelui înainte de a afișa informațiile despre student.
Moștenirea este destinată situațiilor în care există o relație “este” între clase. De exemplu, Student
este o Persoana
. Pisica este un animal. Ne oferă posibilitatea, în cazurile în care în cod ne așteptăm la un
obiect (de ex. “Persoana”), să folosim în locul lui un obiect moștenit (de ex. “Student”).
Este important de reținut că scopul principal al moștenirii nu este evitarea duplicării codului. Dimpotrivă, utilizarea incorectă a moștenirii poate duce la un cod complex și greu de întreținut. Dacă relația “este” între clase nu există, ar trebui să luăm în considerare compoziția în locul moștenirii.
Observați că metodele afiseazaInformatii()
din clasele Persoana
și Student
afișează
informații ușor diferite. Și putem adăuga alte clase (de exemplu, Angajat
), care vor oferi alte implementări ale
acestei metode. Capacitatea obiectelor de diferite clase de a răspunde la aceeași metodă în moduri diferite se numește
polimorfism:
$persoane = [
new Persoana(30),
new Student(20, 'Informatică'),
new Angajat(45, 'Director'),
];
foreach ($persoane as $persoana) {
$persoana->afiseazaInformatii();
}
Compoziție
Compoziția este o tehnică în care, în loc să moștenim proprietățile și metodele unei alte clase, pur și simplu folosim instanța acesteia în clasa noastră. Acest lucru ne permite să combinăm funcționalitățile și proprietățile mai multor clase fără a fi nevoie să creăm structuri de moștenire complexe.
Să vedem un exemplu. Avem clasa Motor
și clasa Masina
. În loc să spunem “Masina este un
Motor”, spunem “Masina are un Motor”, ceea ce este o relație tipică de compoziție.
class Motor
{
function porneste()
{
echo 'Motorul funcționează.';
}
}
class Masina
{
private $motor;
function __construct()
{
$this->motor = new Motor;
}
function pornesteMasina()
{
$this->motor->porneste();
echo 'Mașina este gata de drum!';
}
}
$masina = new Masina;
$masina->pornesteMasina();
Aici, Masina
nu are toate proprietățile și metodele lui Motor
, dar are acces la acesta prin
intermediul proprietății $motor
.
Avantajul compoziției este flexibilitatea mai mare în design și posibilitatea mai bună de modificare în viitor.
Vizibilitate
În PHP, puteți defini “vizibilitatea” pentru proprietățile, metodele și constantele unei clase. Vizibilitatea determină de unde puteți accesa aceste elemente.
- Public: Dacă un element este marcat ca
public
, înseamnă că îl puteți accesa de oriunde, chiar și din afara clasei. - Protected: Un element marcat ca
protected
este accesibil numai în cadrul clasei respective și al tuturor descendenților săi (clasele care moștenesc de la această clasă). - Private: Dacă un element este
private
, îl puteți accesa numai din interiorul clasei în care a fost definit.
Dacă nu specificați vizibilitatea, PHP o va seta automat la public
.
Să vedem un exemplu de cod:
class ExempluVizibilitate
{
public $proprietatePublica = 'Publică';
protected $proprietateProtejata = 'Protejată';
private $proprietatePrivata = 'Privată';
public function afiseazaProprietati()
{
echo $this->proprietatePublica; // Funcționează
echo $this->proprietateProtejata; // Funcționează
echo $this->proprietatePrivata; // Funcționează
}
}
$obiect = new ExempluVizibilitate;
$obiect->afiseazaProprietati();
echo $obiect->proprietatePublica; // Funcționează
// echo $obiect->proprietateProtejata; // Generează eroare
// echo $obiect->proprietatePrivata; // Generează eroare
Continuăm cu moștenirea clasei:
class ClasaDescendent extends ExempluVizibilitate
{
public function afiseazaProprietati()
{
echo $this->proprietatePublica; // Funcționează
echo $this->proprietateProtejata; // Funcționează
// echo $this->proprietatePrivata; // Generează eroare
}
}
În acest caz, metoda afiseazaProprietati()
din clasa ClasaDescendent
poate accesa proprietățile
publice și protejate, dar nu poate accesa proprietățile private ale clasei părinte.
Datele și metodele ar trebui să fie cât mai ascunse posibil și accesibile numai printr-o interfață definită. Acest lucru vă permite să modificați implementarea internă a clasei fără a afecta restul codului.
Cuvântul cheie final
În PHP, putem folosi cuvântul cheie final
dacă dorim să împiedicăm o clasă, metodă sau constantă să fie
moștenită sau suprascrisă. Când marcăm o clasă ca final
, aceasta nu poate fi extinsă. Când marcăm
o metodă ca final
, aceasta nu poate fi suprascrisă într-o clasă descendentă.
Conștientizarea faptului că o anumită clasă sau metodă nu va fi modificată ulterior ne permite să facem modificări mai ușor, fără a ne face griji cu privire la posibile conflicte. De exemplu, putem adăuga o nouă metodă fără teama că un descendent al său ar avea deja o metodă cu același nume și ar apărea o coliziune. Sau putem modifica parametrii unei metode, deoarece din nou nu există riscul de a provoca o neconcordanță cu metoda suprascrisă într-un descendent.
final class ClasaFinala
{
}
// Următorul cod va genera o eroare, deoarece nu putem moșteni de la o clasă finală.
class DescendentClasaFinala extends ClasaFinala
{
}
În acest exemplu, încercarea de a moșteni de la clasa finală ClasaFinala
va genera o eroare.
Proprietăți și metode statice
Când vorbim în PHP despre elemente “statice” ale unei clase, ne referim la metode și proprietăți care aparțin clasei în sine, și nu unei instanțe specifice a acestei clase. Acest lucru înseamnă că nu trebuie să creați o instanță a clasei pentru a avea acces la ele. În schimb, le apelați sau accesați direct prin numele clasei.
Rețineți că, deoarece elementele statice aparțin clasei și nu instanțelor sale, nu puteți utiliza pseudovariabila
$this
în interiorul metodelor statice.
Utilizarea proprietăților statice duce la cod neclar plin de capcane, de aceea nu ar trebui să le folosiți niciodată și nici nu vom arăta aici un exemplu de utilizare. În schimb, metodele statice sunt utile. Exemplu de utilizare:
class Calculator
{
public static function adunare($a, $b)
{
return $a + $b;
}
public static function scadere($a, $b)
{
return $a - $b;
}
}
// Utilizarea metodei statice fără a crea o instanță a clasei
echo Calculator::adunare(5, 3); // Rezultat: 8
echo Calculator::scadere(5, 3); // Rezultat: 2
În acest exemplu, am creat clasa Calculator
cu două metode statice. Putem apela aceste metode direct fără a
crea o instanță a clasei folosind operatorul ::
. Metodele statice sunt deosebit de utile pentru operații care nu
depind de starea unei instanțe specifice a clasei.
Constante de clasă
În cadrul claselor, avem posibilitatea de a defini constante. Constantele sunt valori care nu se schimbă niciodată în timpul execuției programului. Spre deosebire de variabile, valoarea unei constante rămâne mereu aceeași.
class Masina
{
public const NumarRoti = 4;
public function afiseazaNumarRoti(): int
{
echo self::NumarRoti;
}
}
echo Masina::NumarRoti; // Ieșire: 4
În acest exemplu, avem clasa Masina
cu constanta NumarRoti
. Când dorim să accesăm constanta în
interiorul clasei, putem folosi cuvântul cheie self
în locul numelui clasei.
Interfețe de obiecte
Interfețele de obiecte funcționează ca niște “contracte” pentru clase. Dacă o clasă trebuie să implementeze o interfață de obiect, trebuie să conțină toate metodele pe care le definește acea interfață. Este o modalitate excelentă de a asigura că anumite clase respectă același “contract” sau structură.
În PHP, interfețele se definesc cu cuvântul cheie interface
. Toate metodele definite într-o interfață sunt
publice (public
). Când o clasă implementează o interfață, folosește cuvântul cheie
implements
.
interface Animal
{
function scoateSunet();
}
class Pisica implements Animal
{
public function scoateSunet()
{
echo 'Miau';
}
}
$pisica = new Pisica;
$pisica->scoateSunet();
Dacă o clasă implementează o interfață, dar nu sunt definite în ea toate metodele așteptate, PHP va genera o eroare.
O clasă poate implementa mai multe interfețe simultan, ceea ce este o diferență față de moștenire, unde o clasă poate moșteni doar de la o singură clasă:
interface Paznic
{
function pazesteCasa();
}
class Caine implements Animal, Paznic
{
public function scoateSunet()
{
echo 'Ham';
}
public function pazesteCasa()
{
echo 'Câinele păzește cu atenție casa';
}
}
Clase abstracte
Clasele abstracte servesc ca șabloane de bază pentru alte clase, dar nu puteți crea instanțe ale acestora direct. Ele conțin o combinație de metode complete și metode abstracte, care nu au conținut definit. Clasele care moștenesc de la clase abstracte trebuie să furnizeze definiții pentru toate metodele abstracte ale părintelui.
Pentru a defini o clasă abstractă, folosim cuvântul cheie abstract
.
abstract class ClasaAbstracta
{
public function metodaObisnuita()
{
echo 'Aceasta este o metodă obișnuită';
}
abstract public function metodaAbstracta();
}
class Descendent extends ClasaAbstracta
{
public function metodaAbstracta()
{
echo 'Aceasta este implementarea metodei abstracte';
}
}
$instanta = new Descendent;
$instanta->metodaObisnuita();
$instanta->metodaAbstracta();
În acest exemplu, avem o clasă abstractă cu o metodă obișnuită și una abstractă. Apoi avem clasa
Descendent
, care moștenește de la ClasaAbstracta
și furnizează implementarea pentru metoda
abstractă.
Cum diferă de fapt interfețele și clasele abstracte? Clasele abstracte pot conține atât metode abstracte, cât și concrete, în timp ce interfețele definesc doar ce metode trebuie să implementeze o clasă, dar nu oferă nicio implementare. O clasă poate moșteni doar de la o singură clasă abstractă, dar poate implementa un număr nelimitat de interfețe.
Verificarea tipurilor
În programare, este foarte important să fim siguri că datele cu care lucrăm sunt de tipul corect. În PHP, avem instrumente care ne asigură acest lucru. Verificarea dacă datele au tipul corect se numește “verificarea tipurilor”.
Tipurile pe care le putem întâlni în PHP:
- Tipuri de bază: Includ
int
(numere întregi),float
(numere zecimale),bool
(valori de adevăr),string
(șiruri de caractere),array
(tablouri) șinull
. - Clase: Dacă dorim ca valoarea să fie o instanță a unei clase specifice.
- Interfețe: Definește un set de metode pe care o clasă trebuie să le implementeze. O valoare care respectă interfața trebuie să aibă aceste metode.
- Tipuri mixte: Putem specifica că o variabilă poate avea mai multe tipuri permise.
- Void: Acest tip special indică faptul că o funcție sau metodă nu returnează nicio valoare.
Să vedem cum să modificăm codul pentru a include tipuri:
class Persoana
{
private int $varsta;
public function __construct(int $varsta)
{
$this->varsta = $varsta;
}
public function afiseazaVarsta(): void
{
echo "Această persoană are {$this->varsta} ani.";
}
}
/**
* Funcție care primește un obiect al clasei Persoana și afișează vârsta persoanei.
*/
function afiseazaVarstaPersoanei(Persoana $persoana): void
{
$persoana->afiseazaVarsta();
}
În acest fel, ne-am asigurat că codul nostru așteaptă și lucrează cu date de tipul corect, ceea ce ne ajută să prevenim potențiale erori.
Unele tipuri nu pot fi scrise direct în PHP. În acest caz, ele sunt specificate într-un comentariu phpDoc, care este un
format standard pentru documentarea codului PHP, începând cu /**
și terminând cu */
. Permite
adăugarea de descrieri pentru clase, metode etc. Și, de asemenea, specificarea tipurilor complexe folosind așa-numitele
adnotări @var
, @param
și @return
. Aceste tipuri sunt apoi utilizate de instrumentele de
analiză statică a codului, dar PHP în sine nu le verifică.
class Lista
{
/** @var array<Persoana> notația indică faptul că este un array de obiecte Persoana */
private array $persoane = [];
public function adaugaPersoana(Persoana $persoana): void
{
$this->persoane[] = $persoana;
}
}
Comparație și identitate
În PHP, puteți compara obiecte în două moduri:
- Comparația valorilor
==
: Verifică dacă obiectele sunt de aceeași clasă și au aceleași valori în proprietățile lor. - Identitatea
===
: Verifică dacă este aceeași instanță a obiectului.
class Masina
{
public string $marca;
public function __construct(string $marca)
{
$this->marca = $marca;
}
}
$masina1 = new Masina('Skoda');
$masina2 = new Masina('Skoda');
$masina3 = $masina1;
var_dump($masina1 == $masina2); // true, deoarece au aceeași valoare
var_dump($masina1 === $masina2); // false, deoarece nu sunt aceeași instanță
var_dump($masina1 === $masina3); // true, deoarece $masina3 este aceeași instanță ca $masina1
Operatorul instanceof
Operatorul instanceof
permite verificarea dacă un obiect dat este o instanță a unei anumite clase, un
descendent al acestei clase sau dacă implementează o anumită interfață.
Să ne imaginăm că avem clasa Persoana
și o altă clasă Student
, care este un descendent al
clasei Persoana
:
class Persoana
{
private int $varsta;
public function __construct(int $varsta)
{
$this->varsta = $varsta;
}
}
class Student extends Persoana
{
private string $specializare;
public function __construct(int $varsta, string $specializare)
{
parent::__construct($varsta);
$this->specializare = $specializare;
}
}
$student = new Student(20, 'Informatică');
// Verifică dacă $student este o instanță a clasei Student
var_dump($student instanceof Student); // Ieșire: bool(true)
// Verifică dacă $student este o instanță a clasei Persoana (deoarece Student este descendent al Persoana)
var_dump($student instanceof Persoana); // Ieșire: bool(true)
Din ieșiri reiese că obiectul $student
este considerat simultan o instanță a ambelor clase –
Student
și Persoana
.
Interfețe fluente
“Interfața fluentă” (în engleză “Fluent Interface”) este o tehnică în OOP care permite înlănțuirea metodelor într-un singur apel. Acest lucru simplifică adesea și clarifică codul.
Elementul cheie al unei interfețe fluente este că fiecare metodă din lanț returnează o referință la obiectul curent.
Realizăm acest lucru folosind return $this;
la sfârșitul metodei. Acest stil de programare este adesea asociat cu
metodele numite “setters”, care setează valorile proprietăților obiectului.
Să vedem cum poate arăta o interfață fluentă pe exemplul trimiterii de emailuri:
public function trimiteMesaj()
{
$email = new Email;
$email->setFrom('sender@example.com')
->setRecipient('admin@example.com')
->setMessage('Hello, this is a message.')
->send();
}
În acest exemplu, metodele setFrom()
, setRecipient()
și setMessage()
servesc la
setarea valorilor corespunzătoare (expeditor, destinatar, conținutul mesajului). După setarea fiecăreia dintre aceste valori,
metodele ne returnează obiectul curent ($email
), ceea ce ne permite să înlănțuim următoarea metodă după ea.
În final, apelăm metoda send()
, care trimite efectiv emailul.
Datorită interfețelor fluente, putem scrie cod care este intuitiv și ușor de citit.
Copierea folosind clone
În PHP, putem crea o copie a unui obiect folosind operatorul clone
. În acest fel, obținem o nouă instanță
cu conținut identic.
Dacă trebuie să modificăm unele proprietăți ale obiectului la copiere, putem defini în clasă o metodă specială
__clone()
. Această metodă este apelată automat atunci când obiectul este clonat.
class Oaie
{
public string $nume;
public function __construct(string $nume)
{
$this->nume = $nume;
}
public function __clone()
{
$this->nume = 'Clona ' . $this->nume;
}
}
$original = new Oaie('Dolly');
echo $original->nume . "\n"; // Afișează: Dolly
$clona = clone $original;
echo $clona->nume . "\n"; // Afișează: Clona Dolly
În acest exemplu, avem clasa Oaie
cu o singură proprietate $nume
. Când clonăm o instanță a
acestei clase, metoda __clone()
se asigură că numele oii clonate primește prefixul “Clona”.
Trait-uri
Trait-urile în PHP sunt un instrument care permite partajarea metodelor, proprietăților și constantelor între clase și evitarea duplicării codului. Le puteți imagina ca pe un mecanism de “copiere și lipire” (Ctrl-C și Ctrl-V), în care conținutul trait-ului este “inserat” în clase. Acest lucru vă permite să reutilizați codul fără a fi nevoie să creați ierarhii de clase complicate.
Să vedem un exemplu simplu despre cum să folosim trait-uri în PHP:
trait Claxonare
{
public function claxoneaza()
{
echo 'Bip bip!';
}
}
class Masina
{
use Claxonare;
}
class Camion
{
use Claxonare;
}
$masina = new Masina;
$masina->claxoneaza(); // Afișează 'Bip bip!'
$camion = new Camion;
$camion->claxoneaza(); // De asemenea, afișează 'Bip bip!'
În acest exemplu, avem un trait numit Claxonare
, care conține o singură metodă claxoneaza()
.
Apoi avem două clase: Masina
și Camion
, care ambele folosesc trait-ul Claxonare
.
Datorită acestui fapt, ambele clase “au” metoda claxoneaza()
, și o putem apela pe obiectele
ambelor clase.
Trait-urile vă permit să partajați codul între clase ușor și eficient. În același timp, ele nu intră în ierarhia de
moștenire, adică $masina instanceof Claxonare
va returna false
.
Excepții
Excepțiile în OOP ne permit să gestionăm elegant erorile și situațiile neașteptate în codul nostru. Sunt obiecte care conțin informații despre eroare sau situația neobișnuită.
În PHP, avem clasa încorporată Exception
, care servește ca bază pentru toate excepțiile. Aceasta are mai
multe metode care ne permit să obținem mai multe informații despre excepție, cum ar fi mesajul de eroare, fișierul și linia
unde a apărut eroarea etc.
Când apare o eroare în cod, putem “arunca” o excepție folosind cuvântul cheie throw
.
function impartire(float $a, float $b): float
{
if ($b === 0) {
throw new Exception('Împărțire la zero!');
}
return $a / $b;
}
Când funcția impartire()
primește zero ca al doilea argument, aruncă o excepție cu mesajul de eroare
'Împărțire la zero!'
. Pentru a preveni căderea programului la aruncarea unei excepții, o prindem într-un bloc
try/catch
:
try {
echo impartire(10, 0);
} catch (Exception $e) {
echo 'Excepție prinsă: '. $e->getMessage();
}
Codul care poate arunca o excepție este încapsulat într-un bloc try
. Dacă o excepție este aruncată,
execuția codului se mută în blocul catch
, unde putem procesa excepția (de exemplu, afișând un mesaj de
eroare).
După blocurile try
și catch
, putem adăuga un bloc opțional finally
, care se execută
întotdeauna, indiferent dacă a fost aruncată sau nu o excepție (chiar și în cazul în care în blocul try
sau
catch
folosim instrucțiunea return
, break
sau continue
):
try {
echo impartire(10, 0);
} catch (Exception $e) {
echo 'Excepție prinsă: '. $e->getMessage();
} finally {
// Codul care se execută întotdeauna, indiferent dacă a fost aruncată sau nu o excepție
}
Putem, de asemenea, crea propriile clase (ierarhie) de excepții, care moștenesc de la clasa Exception. Ca exemplu, să ne imaginăm o aplicație bancară simplă care permite efectuarea de depuneri și retrageri:
class ExceptieBancara extends Exception {}
class ExceptieFonduriInsuficiente extends ExceptieBancara {}
class ExceptieLimitaDepasita extends ExceptieBancara {}
class ContBancar
{
private int $sold = 0;
private int $limitaZilnica = 1000;
public function depune(int $suma): int
{
$this->sold += $suma;
return $this->sold;
}
public function retrage(int $suma): int
{
if ($suma > $this->sold) {
throw new ExceptieFonduriInsuficiente('Nu există suficiente fonduri în cont.');
}
if ($suma > $this->limitaZilnica) {
throw new ExceptieLimitaDepasita('Limita zilnică pentru retrageri a fost depășită.');
}
$this->sold -= $suma;
return $this->sold;
}
}
Pentru un singur bloc try
, se pot specifica mai multe blocuri catch
, dacă vă așteptați la
diferite tipuri de excepții.
$cont = new ContBancar;
$cont->depune(500);
try {
$cont->retrage(1500);
} catch (ExceptieLimitaDepasita $e) {
echo $e->getMessage();
} catch (ExceptieFonduriInsuficiente $e) {
echo $e->getMessage();
} catch (ExceptieBancara $e) {
echo 'A apărut o eroare în timpul efectuării operațiunii.';
}
În acest exemplu, este important de observat ordinea blocurilor catch
. Deoarece toate excepțiile moștenesc de
la ExceptieBancara
, dacă am avea acest bloc primul, toate excepțiile ar fi prinse în el, fără ca codul să
ajungă la blocurile catch
următoare. De aceea, este important să avem excepțiile mai specifice (adică cele care
moștenesc de la altele) în blocul catch
mai sus în ordine decât excepțiile lor părinte.
Iterație
În PHP, puteți parcurge obiecte folosind bucla foreach
, similar cu parcurgerea array-urilor. Pentru ca acest
lucru să funcționeze, obiectul trebuie să implementeze o interfață specială.
Prima opțiune este implementarea interfeței Iterator
, care are metodele current()
returnând
valoarea curentă, key()
returnând cheia, next()
trecând la următoarea valoare, rewind()
revenind la început și valid()
verificând dacă nu am ajuns încă la sfârșit.
A doua opțiune este implementarea interfeței IteratorAggregate
, care are doar o singură metodă
getIterator()
. Aceasta returnează fie un obiect substitut care va asigura parcurgerea, fie poate reprezenta un
generator, care este o funcție specială în care se folosește yield
pentru a returna succesiv chei și
valori:
class Persoana
{
public function __construct(
public int $varsta,
) {
}
}
class Lista implements IteratorAggregate
{
private array $persoane = [];
public function adaugaPersoana(Persoana $persoana): void
{
$this->persoane[] = $persoana;
}
public function getIterator(): Generator
{
foreach ($this->persoane as $persoana) {
yield $persoana;
}
}
}
$lista = new Lista;
$lista->adaugaPersoana(new Persoana(30));
$lista->adaugaPersoana(new Persoana(25));
foreach ($lista as $persoana) {
echo "Vârstă: {$persoana->varsta} ani \n";
}
Bune practici
După ce ați însușit principiile de bază ale programării orientate pe obiecte, este important să vă concentrați asupra bunelor practici în OOP. Acestea vă vor ajuta să scrieți cod care nu este doar funcțional, ci și lizibil, ușor de înțeles și ușor de întreținut.
- Separarea responsabilităților (Separation of Concerns): Fiecare clasă ar trebui să aibă o responsabilitate clar definită și ar trebui să rezolve doar o singură sarcină principală. Dacă o clasă face prea multe lucruri, poate fi potrivit să o împărțiți în clase mai mici, specializate.
- Încapsularea (Encapsulation): Datele și metodele ar trebui să fie cât mai ascunse posibil și accesibile numai printr-o interfață definită. Acest lucru vă permite să modificați implementarea internă a clasei fără a afecta restul codului.
- Injectarea dependențelor (Dependency Injection): În loc să creați dependențe direct în clasă, ar trebui să le “injectați” din exterior. Pentru o înțelegere mai profundă a acestui principiu, recomandăm capitolele despre Dependency Injection.