Nette Documentation Preview

syntax
Introducere în programarea orientată pe obiecte
***********************************************

.[perex]
Termenul "OOP" înseamnă Object-Oriented Programming (programare orientată pe obiecte), care reprezintă o modalitate de organizare și structurare a codului. OOP ne permite să privim un program ca pe o colecție de obiecte care comunică între ele, mai degrabă decât ca pe o secvență de comenzi și funcții.

În OOP, un "obiect" este o unitate care conține date și funcții care operează cu aceste date. Obiectele sunt create pe baza unor "clase", care pot fi înțelese ca planuri sau șabloane pentru obiecte. Odată ce avem o clasă, putem crea o "instanță" a acesteia, care este un obiect specific creat din clasa respectivă.

Să vedem cum putem crea o clasă simplă în PHP. Atunci când definim o clasă, folosim cuvântul cheie "class", urmat de numele clasei și apoi de parantezele curbe care înconjoară funcțiile clasei (numite "metode") și variabilele clasei (numite "proprietăți" sau "atribute"):

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

În acest exemplu, am creat o clasă numită `Car` cu o funcție (sau "metodă") numită `honk`.

Fiecare clasă trebuie să rezolve o singură sarcină principală. În cazul în care o clasă face prea multe lucruri, ar putea fi oportună împărțirea ei în clase mai mici, specializate.

Clasele sunt de obicei stocate în fișiere separate pentru a păstra codul organizat și ușor de navigat. Numele fișierului ar trebui să se potrivească cu numele clasei, astfel încât pentru clasa `Car`, numele fișierului ar fi `Car.php`.

Atunci când denumiți clasele, este bine să urmați convenția "PascalCase", ceea ce înseamnă că fiecare cuvânt din nume începe cu majusculă și nu există sublinieri sau alți separatori. Metodele și proprietățile urmează convenția "camelCase", ceea ce înseamnă că încep cu o literă mică.

Unele metode din PHP au roluri speciale și sunt prefixate cu `__` (două liniuțe de subliniere). Una dintre cele mai importante metode speciale este "constructorul", etichetat cu `__construct`. Constructorul este o metodă care este apelată automat atunci când se creează o nouă instanță a unei clase.

Utilizăm adesea constructorul pentru a stabili starea inițială a unui obiect. De exemplu, atunci când creați un obiect care reprezintă o persoană, ați putea folosi constructorul pentru a seta vârsta, numele sau alte atribute ale acesteia.

Să vedem cum să folosim un constructor în PHP:

```php
class Person
{
	private $age;

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

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

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

În acest exemplu, clasa `Person` are o proprietate (variabilă) `$age` și un constructor care stabilește această proprietate. Metoda `howOldAreYou()` oferă apoi acces la vârsta persoanei.

Pseudo-variabila `$this` este utilizată în interiorul clasei pentru a accesa proprietățile și metodele obiectului.

Cuvântul cheie `new` este utilizat pentru a crea o nouă instanță a unei clase. În exemplul de mai sus, am creat o nouă persoană în vârstă de 25 de ani.

De asemenea, puteți seta valori implicite pentru parametrii constructorului dacă nu sunt specificate la crearea unui obiect. De exemplu:

```php
class Person
{
	private $age;

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

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

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

În acest exemplu, dacă nu se specifică o vârstă la crearea unui obiect `Person`, se va utiliza valoarea implicită de 20 de ani.

Lucrul bun este că definiția proprietății cu inițializarea acesteia prin intermediul constructorului poate fi scurtată și simplificată astfel:

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

Pentru a fi complet, 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 .[#toc-namespaces]
---------------------------------

Spațiile de nume ne permit să organizăm și să grupăm clase, funcții și constante înrudite, evitând în același timp conflictele de denumire. Vă puteți gândi la ele ca la dosarele de pe un computer, unde fiecare dosar conține fișiere legate de un anumit proiect sau subiect.

Spațiile de nume sunt deosebit de utile în proiectele mai mari sau atunci când se utilizează biblioteci de la terți, unde pot apărea conflicte de denumire a claselor.

Imaginați-vă că aveți o clasă numită `Car` în proiectul dumneavoastră și doriți să o plasați într-un namespace numit `Transport`. Ați face acest lucru în felul următor:

```php
namespace Transport;

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

Dacă doriți să utilizați clasa `Car` într-un alt fișier, trebuie să specificați din ce spațiu de nume provine clasa:

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

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 vă permite să creați instanțe fără a menționa calea completă:

```php
use Transport\Car;

$car = new Car;
```


Moștenirea .[#toc-inheritance]
------------------------------

Moștenirea este un instrument al programării orientate pe obiecte care permite crearea de noi clase pe baza celor existente, moștenind proprietățile și metodele acestora și extinzându-le sau redefinindu-le în funcție de necesități. Moștenirea asigură reutilizarea codului și ierarhia claselor.

Pur și simplu, dacă avem o clasă și dorim să creăm o alta derivată din ea, dar cu unele modificări, putem "moșteni" noua clasă din cea originală.

În PHP, moștenirea este implementată cu ajutorul cuvântului cheie `extends`.

Clasa noastră `Person` stochează informații despre vârstă. Putem avea o altă clasă, `Student`, care extinde `Person` și adaugă informații despre domeniul de studiu.

Să ne uităm la un exemplu:

```php
class Person
{
	private $age;

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

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

class Student extends Person
{
	private $fieldOfStudy;

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

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

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

Cum funcționează acest cod?

- Am folosit cuvântul cheie `extends` pentru a extinde clasa `Person`, ceea ce înseamnă că clasa `Student` moștenește toate metodele și proprietățile de la `Person`.

- Cuvântul cheie `parent::` ne permite să apelăm metode din clasa părinte. În acest caz, am apelat constructorul din clasa `Person` înainte de a adăuga propria noastră funcționalitate la clasa `Student`. Și, în mod similar, metoda strămoșului `printInformation()` înainte de a lista informațiile despre elev.

Moștenirea este destinată situațiilor în care există o relație "este o" între clase. De exemplu, un `Student` este un `Person`. O pisică este un animal. Ne permite ca în cazurile în care ne așteptăm ca un obiect (de exemplu, "Persoană") din cod să folosim în schimb un obiect derivat (de exemplu, "Student").

Este esențial să ne dăm seama că scopul principal al moștenirii **nu este** acela de a preveni duplicarea codului. Dimpotrivă, utilizarea greșită a moștenirii poate duce la un cod complex și greu de întreținut. În cazul în care nu există o relație "este un" între clase, ar trebui să luăm în considerare compoziția în locul moștenirii.

Rețineți că metodele `printInformation()` din clasele `Person` și `Student` furnizează informații ușor diferite. Și putem adăuga alte clase (cum ar fi `Employee`) care vor furniza alte implementări ale acestei metode. Capacitatea obiectelor din clase diferite de a răspunde la aceeași metodă în moduri diferite se numește polimorfism:

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

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


Compoziția .[#toc-composition]
------------------------------

Compoziția este o tehnică prin care, în loc să moștenim proprietăți și metode dintr-o altă clasă, 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 crea structuri complexe de moștenire.

De exemplu, avem o clasă `Engine` și o clasă `Car`. În loc să spunem "O mașină este un motor", spunem "O mașină are un motor", ceea ce reprezintă o relație de compoziție tipică.

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

class Car
{
	private $engine;

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

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

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

Aici, `Car` nu are toate proprietățile și metodele clasei `Engine`, dar are acces la acestea prin intermediul proprietății `$engine`.

Avantajul compoziției este o mai mare flexibilitate de proiectare și o mai bună adaptabilitate la schimbările viitoare.


Vizibilitate .[#toc-visibility]
-------------------------------

În PHP, puteți defini "vizibilitatea" pentru proprietățile, metodele și constantele clasei. Vizibilitatea determină unde puteți accesa aceste elemente.

1. **Public:** Dacă un element este marcat ca fiind `public`, înseamnă că îl puteți accesa de oriunde, chiar și din afara clasei.

2. **Protected:** Un element marcat ca `protected` este accesibil numai în cadrul clasei și al tuturor descendenților săi (clase care moștenesc din aceasta).

3. **Privat:** Dacă un element este marcat `private`, acesta poate fi accesat numai din interiorul clasei în care a fost definit.

Dacă nu specificați vizibilitatea, PHP o va seta automat la `public`.

Să ne uităm la un exemplu de cod:

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

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

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

Continuăm cu moștenirea claselor:

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

În acest caz, metoda `printProperties()` din `ChildClass` 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 să fie 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 .[#toc-final-keyword]
------------------------------------------

În PHP, putem folosi cuvântul cheie `final` dacă dorim să împiedicăm moștenirea sau suprapunerea unei clase, metode sau constante. Atunci când o clasă este marcată ca `final`, aceasta nu poate fi extinsă. Atunci când o metodă este marcată ca `final`, aceasta nu poate fi suprascrisă într-o subclasă.

Faptul că știm că o anumită clasă sau metodă nu va mai fi modificată ne permite să facem modificări mai ușor, fără a ne face griji cu privire la eventualele conflicte. De exemplu, putem adăuga o nouă metodă fără să ne temem că un descendent ar putea avea deja o metodă cu același nume, ceea ce ar duce la o coliziune. Sau putem modifica parametrii unei metode, din nou fără riscul de a provoca o inconsecvență cu o metodă suprascrisă într-un descendent.

```php
final class FinalClass
{
}

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

În acest exemplu, încercarea de a moșteni din clasa finală `FinalClass` va duce la o eroare.


Proprietăți și metode statice .[#toc-static-properties-and-methods]
-------------------------------------------------------------------

Când vorbim despre elemente "statice" ale unei clase în PHP, ne referim la metode și proprietăți care aparțin clasei în sine, nu unei instanțe specifice a clasei. Acest lucru înseamnă că nu trebuie să creați o instanță a clasei pentru a le accesa. În schimb, le apelați sau le accesați direct prin intermediul numelui clasei.

Rețineți că, deoarece elementele statice aparțin clasei și nu instanțelor sale, nu puteți utiliza pseudo-variabila `$this` în cadrul metodelor statice.

Utilizarea proprietăților statice duce la un [cod ofuscat și plin de capcane |dependency-injection:global-state], așa că nu ar trebui să le folosiți niciodată și nu vom arăta un exemplu aici. Pe de altă parte, metodele statice sunt utile. Iată un exemplu:

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

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

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

În acest exemplu, am creat o clasă `Calculator` cu două metode statice. Putem apela aceste metode direct fără a crea o instanță a clasei folosind operatorul `::`. Metodele statice sunt utile în special pentru operațiile care nu depind de starea unei instanțe specifice a clasei.


Constantele clasei .[#toc-class-constants]
------------------------------------------

În cadrul claselor, avem opțiunea 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 aceeași.

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

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

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

În acest exemplu, avem o clasă `Car` cu constanta `NumberOfWheels`. Atunci când accesăm constanta în interiorul clasei, putem folosi cuvântul cheie `self` în loc de numele clasei.


Interfețe de obiecte .[#toc-object-interfaces]
----------------------------------------------

Interfețele de obiect acționează ca "contracte" pentru clase. Dacă o clasă trebuie să implementeze o interfață de obiect, aceasta trebuie să conțină toate metodele definite de interfață. Este o modalitate excelentă de a vă asigura că anumite clase respectă același "contract" sau structură.

În PHP, interfețele sunt definite cu ajutorul cuvântului cheie `interface`. Toate metodele definite într-o interfață sunt publice (`public`). Atunci când o clasă implementează o interfață, aceasta utilizează cuvântul cheie `implements`.

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

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

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

În cazul în care o clasă implementează o interfață, dar nu sunt definite toate metodele așteptate, PHP va genera o eroare.

O clasă poate implementa mai multe interfețe în același timp, ceea ce este diferit de moștenire, unde o clasă poate moșteni doar de la o singură clasă:

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

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

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


Clase abstracte .[#toc-abstract-classes]
----------------------------------------

Clasele abstracte servesc drept șabloane de bază pentru alte clase, dar nu puteți crea instanțe ale acestora în mod direct. Ele conțin un amestec de metode complete și metode abstracte care nu au un conținut definit. Clasele care moștenesc din clase abstracte trebuie să furnizeze definiții pentru toate metodele abstracte de la părintele lor.

Utilizăm cuvântul cheie `abstract` pentru a defini o clasă abstractă.

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

	abstract public function abstractMethod();
}

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

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

În acest exemplu, avem o clasă abstractă cu o metodă obișnuită și una abstractă. Apoi avem o clasă `Child` care moștenește din `AbstractClass` și oferă o implementare pentru metoda abstractă.

Prin ce se deosebesc interfețele și clasele abstracte? Clasele abstracte pot conține atât metode abstracte, cât și metode concrete, în timp ce interfețele definesc doar metodele pe care clasa trebuie să le implementeze, dar nu oferă nicio implementare. O clasă poate moșteni dintr-o singură clasă abstractă, dar poate implementa un număr nelimitat de interfețe.


Verificarea tipurilor .[#toc-type-checking]
-------------------------------------------

În programare, este esențial să ne asigurăm că datele cu care lucrăm sunt de tipul corect. În PHP, dispunem de instrumente care oferă această asigurare. Verificarea faptului că datele sunt de tipul corect se numește "verificare de tip".

Tipuri pe care le putem întâlni în PHP:

1. **Tipuri de bază**: Acestea includ `int` (numere întregi), `float` (numere cu virgulă mobilă), `bool` (valori booleene), `string` (șiruri de caractere), `array` (matrice) și `null`.
2. **Classe**: Atunci când dorim ca o valoare să fie o instanță a unei anumite clase.
3. **Interfețe**: Definește un set de metode pe care o clasă trebuie să le implementeze. O valoare care corespunde unei interfețe trebuie să aibă aceste metode.
4. **Tipuri mixte**: Putem specifica faptul că o variabilă poate avea mai multe tipuri permise.
5. **Void**: Acest tip special indică faptul că o funcție sau o metodă nu returnează nicio valoare.

Să vedem cum să modificăm codul pentru a include tipurile:

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

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

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

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

În acest fel, ne asigurăm că codul nostru așteaptă și lucrează cu date de tipul corect, ceea ce ne ajută să prevenim eventualele erori.

Unele tipuri nu pot fi scrise direct în PHP. În acest caz, ele sunt listate în comentariul phpDoc, care este formatul standard pentru documentarea codului PHP, începând cu `/**` și terminând cu `*/`. Acesta vă permite să adăugați descrieri ale claselor, metodelor și așa mai departe. Și, de asemenea, de a enumera tipurile complexe folosind așa-numitele adnotări `@var`, `@param` și `@return`. Aceste tipuri sunt apoi utilizate de instrumentele de analiză statică a codului, dar nu sunt verificate de PHP însuși.

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

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


Comparație și identitate .[#toc-comparison-and-identity]
--------------------------------------------------------

În PHP, puteți compara obiecte în două moduri:

1. Compararea valorilor `==`: Verifică dacă obiectele sunt din aceeași clasă și au aceleași valori în proprietățile lor.
2. Identitate `===`: Verifică dacă este vorba de aceeași instanță a obiectului.

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

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

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

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


Operatorul `instanceof` .[#toc-the-instanceof-operator]
-------------------------------------------------------

Operatorul `instanceof` vă permite să determinați dacă un obiect dat este o instanță a unei anumite clase, un descendent al acelei clase sau dacă implementează o anumită interfață.

Imaginați-vă că avem o clasă `Person` și o altă clasă `Student`, care este un descendent al `Person`:

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

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

class Student extends Person
{
	private string $major;

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

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

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

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

Din rezultatele obținute, este evident că obiectul `$student` este considerat o instanță atât a clasei `Student`, cât și a clasei `Person`.


Interfețe fluente .[#toc-fluent-interfaces]
-------------------------------------------

O "interfață fluidă" este o tehnică din OOP care permite înlănțuirea metodelor într-un singur apel. Acest lucru simplifică și clarifică adesea codul.

Elementul cheie al unei interfețe fluente este că fiecare metodă din lanț returnează o referință la obiectul curent. Acest lucru se realizează prin utilizarea `return $this;` la sfârșitul metodei. Acest stil de programare este adesea asociat cu metode numite "setters", care stabilesc valorile proprietăților unui obiect.

Să vedem cum ar putea arăta o interfață fluentă pentru trimiterea de e-mailuri:

```php
public function sendMessage()
{
	$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()` sunt utilizate pentru a seta valorile corespunzătoare (expeditor, destinatar, conținutul mesajului). După stabilirea fiecăreia dintre aceste valori, metodele returnează obiectul curent (`$email`), permițându-ne să înlănțuim o altă metodă după ea. În cele din urmă, apelăm metoda `send()`, care trimite efectiv e-mailul.

Datorită interfețelor fluente, putem scrie un cod intuitiv și ușor de citit.


Copiere cu `clone` .[#toc-copying-with-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ă avem nevoie să modificăm unele dintre proprietăți atunci când copiem un obiect, putem defini o metodă specială `__clone()` în clasă. Această metodă este apelată automat atunci când obiectul este clonat.

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

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

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

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

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

În acest exemplu, avem o clasă `Sheep` cu o proprietate `$name`. Atunci când clonăm o instanță a acestei clase, metoda `__clone()` se asigură că numele oii clonate primește prefixul "Clone of".


Trăsături .[#toc-traits]
------------------------

În PHP, trăsăturile sunt un instrument care permite partajarea metodelor, proprietăților și constantelor între clase și previne duplicarea codului. Vă puteți gândi la ele ca la un mecanism de "copy and paste" (Ctrl-C și Ctrl-V), în care conținutul unei trăsături este "lipit" în clase. Acest lucru vă permite să reutilizați codul fără a fi nevoie să creați ierarhii complicate de clase.

Să aruncăm o privire la un exemplu simplu de utilizare a trăsăturilor în PHP:

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

class Car
{
	use Honking;
}

class Truck
{
	use Honking;
}

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

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

În acest exemplu, avem o trăsătură numită `Honking` care conține o metodă `honk()`. Apoi, avem două clase: `Car` și `Truck`, ambele folosind trăsătura `Honking`. Ca urmare, ambele clase "au" metoda `honk()` și o putem apela pe obiectele ambelor clase.

Trăsăturile vă permit să partajați ușor și eficient codul între clase. Ele nu intră în ierarhia de moștenire, adică `$car instanceof Honking` va returna `false`.


Excepții
--------

Excepțiile din OOP ne permit să gestionăm cu grație erorile și situațiile neașteptate din codul nostru. Acestea sunt obiecte care conțin informații despre o eroare sau o situație neobișnuită.

În PHP, avem o clasă încorporată `Exception`, care servește drept 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 în care a apărut eroarea etc.

Atunci când apare o eroare în cod, putem "arunca" excepția folosind cuvântul cheie `throw`.

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

Atunci când funcția `division()` primește null ca al doilea argument, se aruncă o excepție cu mesajul de eroare `'Division by zero!'`. Pentru a preveni blocarea programului atunci când este lansată excepția, o blocăm în blocul `try/catch`:

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

Codul care poate arunca o excepție este inclus într-un bloc `try`. În cazul în care excepția este lansată, execuția codului se mută într-un bloc `catch`, unde putem gestiona excepția (de exemplu, scriind un mesaj de eroare).

După blocurile `try` și `catch`, putem adăuga un bloc opțional `finally`, care este întotdeauna executat, indiferent dacă excepția a fost sau nu lansată (chiar dacă folosim `return`, `break` sau `continue` în blocul `try` sau `catch` ):

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

Putem, de asemenea, să ne creăm propriile clase de excepții (ierarhie) care moștenesc din clasa Exception. Ca exemplu, să considerăm o aplicație bancară simplă care permite depuneri și retrageri:

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

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

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

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

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

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

Se pot specifica mai multe blocuri `catch` pentru un singur bloc `try` dacă se așteaptă diferite tipuri de excepții.

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

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

În acest exemplu, este important să rețineți ordinea blocurilor `catch`. Deoarece toate excepțiile moștenesc de la `BankingException`, dacă am avea acest bloc primul, toate excepțiile ar fi prinse în el fără ca codul să ajungă la blocurile `catch` ulterioare. Prin urmare, este important ca excepțiile mai specifice (adică cele care moștenesc de la altele) să fie mai sus în ordinea blocului `catch` decât excepțiile părinte.


Iterații .[#toc-iterations]
---------------------------

În PHP, puteți parcurge obiectele în buclă folosind bucla `foreach`, la fel cum parcurgeți o matrice. Pentru ca acest lucru să funcționeze, obiectul trebuie să implementeze o interfață specială.

Prima opțiune este să implementați interfața `Iterator`, care are metodele `current()` care returnează valoarea curentă, `key()` care returnează cheia, `next()` care trece la următoarea valoare, `rewind()` care trece la început și `valid()` care verifică dacă am ajuns deja la sfârșit.

Cealaltă opțiune este să implementăm o interfață `IteratorAggregate`, care are o singură metodă `getIterator()`. Aceasta fie returnează un obiect de tip "placeholder" care va furniza traversarea, fie poate fi un generator, care este o funcție specială care utilizează `yield` pentru a returna chei și valori în mod secvențial:

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

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

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

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

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

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


Cele mai bune practici .[#toc-best-practices]
---------------------------------------------

Odată ce ați învățat principiile de bază ale programării orientate pe obiecte, este esențial să vă concentrați asupra celor mai bune practici în OOP. Acestea vă vor ajuta să scrieți un cod care să fie nu numai funcțional, ci și ușor de citit, de înțeles și de întreținut.

1) **Separarea preocupărilor**: Fiecare clasă ar trebui să aibă o responsabilitate clar definită și ar trebui să abordeze doar o singură sarcină principală. Dacă o clasă face prea multe lucruri, ar putea fi indicat să o împărțiți în clase mai mici, specializate.
2) **Încapsularea**: Datele și metodele ar trebui să fie cât mai ascunse posibil și să fie accesibile numai printr-o interfață definită. Acest lucru vă permite să modificați implementarea internă a unei clase fără a afecta restul codului.
3) **Injectarea dependenței**: În loc să creați dependențe direct în interiorul unei clase, ar trebui să le "injectați" din exterior. Pentru o înțelegere mai profundă a acestui principiu, vă recomandăm [capitolele despre Injecția de dependență |dependency-injection:introduction].

Introducere în programarea orientată pe obiecte

Termenul „OOP“ înseamnă Object-Oriented Programming (programare orientată pe obiecte), care reprezintă o modalitate de organizare și structurare a codului. OOP ne permite să privim un program ca pe o colecție de obiecte care comunică între ele, mai degrabă decât ca pe o secvență de comenzi și funcții.

În OOP, un „obiect“ este o unitate care conține date și funcții care operează cu aceste date. Obiectele sunt create pe baza unor „clase“, care pot fi înțelese ca planuri sau șabloane pentru obiecte. Odată ce avem o clasă, putem crea o „instanță“ a acesteia, care este un obiect specific creat din clasa respectivă.

Să vedem cum putem crea o clasă simplă în PHP. Atunci când definim o clasă, folosim cuvântul cheie „class“, urmat de numele clasei și apoi de parantezele curbe care înconjoară funcțiile clasei (numite „metode“) și variabilele clasei (numite „proprietăți“ sau „atribute“):

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

În acest exemplu, am creat o clasă numită Car cu o funcție (sau „metodă“) numită honk.

Fiecare clasă trebuie să rezolve o singură sarcină principală. În cazul în care o clasă face prea multe lucruri, ar putea fi oportună împărțirea ei în clase mai mici, specializate.

Clasele sunt de obicei stocate în fișiere separate pentru a păstra codul organizat și ușor de navigat. Numele fișierului ar trebui să se potrivească cu numele clasei, astfel încât pentru clasa Car, numele fișierului ar fi Car.php.

Atunci când denumiți clasele, este bine să urmați convenția „PascalCase“, ceea ce înseamnă că fiecare cuvânt din nume începe cu majusculă și nu există sublinieri sau alți separatori. Metodele și proprietățile urmează convenția „camelCase“, ceea ce înseamnă că încep cu o literă mică.

Unele metode din PHP au roluri speciale și sunt prefixate cu __ (două liniuțe de subliniere). Una dintre cele mai importante metode speciale este „constructorul“, etichetat cu __construct. Constructorul este o metodă care este apelată automat atunci când se creează o nouă instanță a unei clase.

Utilizăm adesea constructorul pentru a stabili starea inițială a unui obiect. De exemplu, atunci când creați un obiect care reprezintă o persoană, ați putea folosi constructorul pentru a seta vârsta, numele sau alte atribute ale acesteia.

Să vedem cum să folosim un constructor în PHP:

class Person
{
	private $age;

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

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

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

În acest exemplu, clasa Person are o proprietate (variabilă) $age și un constructor care stabilește această proprietate. Metoda howOldAreYou() oferă apoi acces la vârsta persoanei.

Pseudo-variabila $this este utilizată în interiorul clasei pentru a accesa proprietățile și metodele obiectului.

Cuvântul cheie new este utilizat pentru a crea o nouă instanță a unei clase. În exemplul de mai sus, am creat o nouă persoană în vârstă de 25 de ani.

De asemenea, puteți seta valori implicite pentru parametrii constructorului dacă nu sunt specificate la crearea unui obiect. De exemplu:

class Person
{
	private $age;

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

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

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

În acest exemplu, dacă nu se specifică o vârstă la crearea unui obiect Person, se va utiliza valoarea implicită de 20 de ani.

Lucrul bun este că definiția proprietății cu inițializarea acesteia prin intermediul constructorului poate fi scurtată și simplificată astfel:

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

Pentru a fi complet, 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 ne permit să organizăm și să grupăm clase, funcții și constante înrudite, evitând în același timp conflictele de denumire. Vă puteți gândi la ele ca la dosarele de pe un computer, unde fiecare dosar conține fișiere legate de un anumit proiect sau subiect.

Spațiile de nume sunt deosebit de utile în proiectele mai mari sau atunci când se utilizează biblioteci de la terți, unde pot apărea conflicte de denumire a claselor.

Imaginați-vă că aveți o clasă numită Car în proiectul dumneavoastră și doriți să o plasați într-un namespace numit Transport. Ați face acest lucru în felul următor:

namespace Transport;

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

Dacă doriți să utilizați clasa Car într-un alt fișier, trebuie să specificați din ce spațiu de nume provine clasa:

$car = new Transport\Car;

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 vă permite să creați instanțe fără a menționa calea completă:

use Transport\Car;

$car = new Car;

Moștenirea

Moștenirea este un instrument al programării orientate pe obiecte care permite crearea de noi clase pe baza celor existente, moștenind proprietățile și metodele acestora și extinzându-le sau redefinindu-le în funcție de necesități. Moștenirea asigură reutilizarea codului și ierarhia claselor.

Pur și simplu, dacă avem o clasă și dorim să creăm o alta derivată din ea, dar cu unele modificări, putem „moșteni“ noua clasă din cea originală.

În PHP, moștenirea este implementată cu ajutorul cuvântului cheie extends.

Clasa noastră Person stochează informații despre vârstă. Putem avea o altă clasă, Student, care extinde Person și adaugă informații despre domeniul de studiu.

Să ne uităm la un exemplu:

class Person
{
	private $age;

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

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

class Student extends Person
{
	private $fieldOfStudy;

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

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

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

Cum funcționează acest cod?

  • Am folosit cuvântul cheie extends pentru a extinde clasa Person, ceea ce înseamnă că clasa Student moștenește toate metodele și proprietățile de la Person.
  • Cuvântul cheie parent:: ne permite să apelăm metode din clasa părinte. În acest caz, am apelat constructorul din clasa Person înainte de a adăuga propria noastră funcționalitate la clasa Student. Și, în mod similar, metoda strămoșului printInformation() înainte de a lista informațiile despre elev.

Moștenirea este destinată situațiilor în care există o relație „este o“ între clase. De exemplu, un Student este un Person. O pisică este un animal. Ne permite ca în cazurile în care ne așteptăm ca un obiect (de exemplu, „Persoană“) din cod să folosim în schimb un obiect derivat (de exemplu, „Student“).

Este esențial să ne dăm seama că scopul principal al moștenirii nu este acela de a preveni duplicarea codului. Dimpotrivă, utilizarea greșită a moștenirii poate duce la un cod complex și greu de întreținut. În cazul în care nu există o relație „este un“ între clase, ar trebui să luăm în considerare compoziția în locul moștenirii.

Rețineți că metodele printInformation() din clasele Person și Student furnizează informații ușor diferite. Și putem adăuga alte clase (cum ar fi Employee) care vor furniza alte implementări ale acestei metode. Capacitatea obiectelor din clase diferite de a răspunde la aceeași metodă în moduri diferite se numește polimorfism:

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

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

Compoziția

Compoziția este o tehnică prin care, în loc să moștenim proprietăți și metode dintr-o altă clasă, 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 crea structuri complexe de moștenire.

De exemplu, avem o clasă Engine și o clasă Car. În loc să spunem „O mașină este un motor“, spunem „O mașină are un motor“, ceea ce reprezintă o relație de compoziție tipică.

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

class Car
{
	private $engine;

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

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

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

Aici, Car nu are toate proprietățile și metodele clasei Engine, dar are acces la acestea prin intermediul proprietății $engine.

Avantajul compoziției este o mai mare flexibilitate de proiectare și o mai bună adaptabilitate la schimbările viitoare.

Vizibilitate

În PHP, puteți defini „vizibilitatea“ pentru proprietățile, metodele și constantele clasei. Vizibilitatea determină unde puteți accesa aceste elemente.

  1. Public: Dacă un element este marcat ca fiind public, înseamnă că îl puteți accesa de oriunde, chiar și din afara clasei.
  2. Protected: Un element marcat ca protected este accesibil numai în cadrul clasei și al tuturor descendenților săi (clase care moștenesc din aceasta).
  3. Privat: Dacă un element este marcat private, acesta poate fi accesat numai din interiorul clasei în care a fost definit.

Dacă nu specificați vizibilitatea, PHP o va seta automat la public.

Să ne uităm la un exemplu de cod:

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

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

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

Continuăm cu moștenirea claselor:

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

În acest caz, metoda printProperties() din ChildClass 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 să fie 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 moștenirea sau suprapunerea unei clase, metode sau constante. Atunci când o clasă este marcată ca final, aceasta nu poate fi extinsă. Atunci când o metodă este marcată ca final, aceasta nu poate fi suprascrisă într-o subclasă.

Faptul că știm că o anumită clasă sau metodă nu va mai fi modificată ne permite să facem modificări mai ușor, fără a ne face griji cu privire la eventualele conflicte. De exemplu, putem adăuga o nouă metodă fără să ne temem că un descendent ar putea avea deja o metodă cu același nume, ceea ce ar duce la o coliziune. Sau putem modifica parametrii unei metode, din nou fără riscul de a provoca o inconsecvență cu o metodă suprascrisă într-un descendent.

final class FinalClass
{
}

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

În acest exemplu, încercarea de a moșteni din clasa finală FinalClass va duce la o eroare.

Proprietăți și metode statice

Când vorbim despre elemente „statice“ ale unei clase în PHP, ne referim la metode și proprietăți care aparțin clasei în sine, nu unei instanțe specifice a clasei. Acest lucru înseamnă că nu trebuie să creați o instanță a clasei pentru a le accesa. În schimb, le apelați sau le accesați direct prin intermediul numelui clasei.

Rețineți că, deoarece elementele statice aparțin clasei și nu instanțelor sale, nu puteți utiliza pseudo-variabila $this în cadrul metodelor statice.

Utilizarea proprietăților statice duce la un cod ofuscat și plin de capcane, așa că nu ar trebui să le folosiți niciodată și nu vom arăta un exemplu aici. Pe de altă parte, metodele statice sunt utile. Iată un exemplu:

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

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

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

În acest exemplu, am creat o clasă Calculator cu două metode statice. Putem apela aceste metode direct fără a crea o instanță a clasei folosind operatorul ::. Metodele statice sunt utile în special pentru operațiile care nu depind de starea unei instanțe specifice a clasei.

Constantele clasei

În cadrul claselor, avem opțiunea 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 aceeași.

class Car
{
	public const NumberOfWheels = 4;

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

echo Car::NumberOfWheels;  // Output: 4

În acest exemplu, avem o clasă Car cu constanta NumberOfWheels. Atunci când accesăm constanta în interiorul clasei, putem folosi cuvântul cheie self în loc de numele clasei.

Interfețe de obiecte

Interfețele de obiect acționează ca „contracte“ pentru clase. Dacă o clasă trebuie să implementeze o interfață de obiect, aceasta trebuie să conțină toate metodele definite de interfață. Este o modalitate excelentă de a vă asigura că anumite clase respectă același „contract“ sau structură.

În PHP, interfețele sunt definite cu ajutorul cuvântului cheie interface. Toate metodele definite într-o interfață sunt publice (public). Atunci când o clasă implementează o interfață, aceasta utilizează cuvântul cheie implements.

interface Animal
{
	function makeSound();
}

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

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

În cazul în care o clasă implementează o interfață, dar nu sunt definite toate metodele așteptate, PHP va genera o eroare.

O clasă poate implementa mai multe interfețe în același timp, ceea ce este diferit de moștenire, unde o clasă poate moșteni doar de la o singură clasă:

interface Guardian
{
	function guardHouse();
}

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

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

Clase abstracte

Clasele abstracte servesc drept șabloane de bază pentru alte clase, dar nu puteți crea instanțe ale acestora în mod direct. Ele conțin un amestec de metode complete și metode abstracte care nu au un conținut definit. Clasele care moștenesc din clase abstracte trebuie să furnizeze definiții pentru toate metodele abstracte de la părintele lor.

Utilizăm cuvântul cheie abstract pentru a defini o clasă abstractă.

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

	abstract public function abstractMethod();
}

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

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

În acest exemplu, avem o clasă abstractă cu o metodă obișnuită și una abstractă. Apoi avem o clasă Child care moștenește din AbstractClass și oferă o implementare pentru metoda abstractă.

Prin ce se deosebesc interfețele și clasele abstracte? Clasele abstracte pot conține atât metode abstracte, cât și metode concrete, în timp ce interfețele definesc doar metodele pe care clasa trebuie să le implementeze, dar nu oferă nicio implementare. O clasă poate moșteni dintr-o singură clasă abstractă, dar poate implementa un număr nelimitat de interfețe.

Verificarea tipurilor

În programare, este esențial să ne asigurăm că datele cu care lucrăm sunt de tipul corect. În PHP, dispunem de instrumente care oferă această asigurare. Verificarea faptului că datele sunt de tipul corect se numește „verificare de tip“.

Tipuri pe care le putem întâlni în PHP:

  1. Tipuri de bază: Acestea includ int (numere întregi), float (numere cu virgulă mobilă), bool (valori booleene), string (șiruri de caractere), array (matrice) și null.
  2. Classe: Atunci când dorim ca o valoare să fie o instanță a unei anumite clase.
  3. Interfețe: Definește un set de metode pe care o clasă trebuie să le implementeze. O valoare care corespunde unei interfețe trebuie să aibă aceste metode.
  4. Tipuri mixte: Putem specifica faptul că o variabilă poate avea mai multe tipuri permise.
  5. Void: Acest tip special indică faptul că o funcție sau o metodă nu returnează nicio valoare.

Să vedem cum să modificăm codul pentru a include tipurile:

class Person
{
	private int $age;

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

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

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

În acest fel, ne asigurăm că codul nostru așteaptă și lucrează cu date de tipul corect, ceea ce ne ajută să prevenim eventualele erori.

Unele tipuri nu pot fi scrise direct în PHP. În acest caz, ele sunt listate în comentariul phpDoc, care este formatul standard pentru documentarea codului PHP, începând cu /** și terminând cu */. Acesta vă permite să adăugați descrieri ale claselor, metodelor și așa mai departe. Și, de asemenea, de a enumera tipurile complexe folosind așa-numitele adnotări @var, @param și @return. Aceste tipuri sunt apoi utilizate de instrumentele de analiză statică a codului, dar nu sunt verificate de PHP însuși.

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

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

Comparație și identitate

În PHP, puteți compara obiecte în două moduri:

  1. Compararea valorilor ==: Verifică dacă obiectele sunt din aceeași clasă și au aceleași valori în proprietățile lor.
  2. Identitate ===: Verifică dacă este vorba de aceeași instanță a obiectului.
class Car
{
	public string $brand;

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

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

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

Operatorul instanceof

Operatorul instanceof vă permite să determinați dacă un obiect dat este o instanță a unei anumite clase, un descendent al acelei clase sau dacă implementează o anumită interfață.

Imaginați-vă că avem o clasă Person și o altă clasă Student, care este un descendent al Person:

class Person
{
	private int $age;

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

class Student extends Person
{
	private string $major;

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

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

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

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

Din rezultatele obținute, este evident că obiectul $student este considerat o instanță atât a clasei Student, cât și a clasei Person.

Interfețe fluente

O „interfață fluidă“ este o tehnică din OOP care permite înlănțuirea metodelor într-un singur apel. Acest lucru simplifică și clarifică adesea codul.

Elementul cheie al unei interfețe fluente este că fiecare metodă din lanț returnează o referință la obiectul curent. Acest lucru se realizează prin utilizarea return $this; la sfârșitul metodei. Acest stil de programare este adesea asociat cu metode numite „setters“, care stabilesc valorile proprietăților unui obiect.

Să vedem cum ar putea arăta o interfață fluentă pentru trimiterea de e-mailuri:

public function sendMessage()
{
	$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() sunt utilizate pentru a seta valorile corespunzătoare (expeditor, destinatar, conținutul mesajului). După stabilirea fiecăreia dintre aceste valori, metodele returnează obiectul curent ($email), permițându-ne să înlănțuim o altă metodă după ea. În cele din urmă, apelăm metoda send(), care trimite efectiv e-mailul.

Datorită interfețelor fluente, putem scrie un cod intuitiv și ușor de citit.

Copiere cu 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ă avem nevoie să modificăm unele dintre proprietăți atunci când copiem un obiect, putem defini o metodă specială __clone() în clasă. Această metodă este apelată automat atunci când obiectul este clonat.

class Sheep
{
	public string $name;

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

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

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

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

În acest exemplu, avem o clasă Sheep cu o proprietate $name. Atunci când clonăm o instanță a acestei clase, metoda __clone() se asigură că numele oii clonate primește prefixul „Clone of“.

Trăsături

În PHP, trăsăturile sunt un instrument care permite partajarea metodelor, proprietăților și constantelor între clase și previne duplicarea codului. Vă puteți gândi la ele ca la un mecanism de „copy and paste“ (Ctrl-C și Ctrl-V), în care conținutul unei trăsături este „lipit“ în clase. Acest lucru vă permite să reutilizați codul fără a fi nevoie să creați ierarhii complicate de clase.

Să aruncăm o privire la un exemplu simplu de utilizare a trăsăturilor în PHP:

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

class Car
{
	use Honking;
}

class Truck
{
	use Honking;
}

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

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

În acest exemplu, avem o trăsătură numită Honking care conține o metodă honk(). Apoi, avem două clase: Car și Truck, ambele folosind trăsătura Honking. Ca urmare, ambele clase „au“ metoda honk() și o putem apela pe obiectele ambelor clase.

Trăsăturile vă permit să partajați ușor și eficient codul între clase. Ele nu intră în ierarhia de moștenire, adică $car instanceof Honking va returna false.

Excepții

Excepțiile din OOP ne permit să gestionăm cu grație erorile și situațiile neașteptate din codul nostru. Acestea sunt obiecte care conțin informații despre o eroare sau o situație neobișnuită.

În PHP, avem o clasă încorporată Exception, care servește drept 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 în care a apărut eroarea etc.

Atunci când apare o eroare în cod, putem „arunca“ excepția folosind cuvântul cheie throw.

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

Atunci când funcția division() primește null ca al doilea argument, se aruncă o excepție cu mesajul de eroare 'Division by zero!'. Pentru a preveni blocarea programului atunci când este lansată excepția, o blocăm în blocul try/catch:

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

Codul care poate arunca o excepție este inclus într-un bloc try. În cazul în care excepția este lansată, execuția codului se mută într-un bloc catch, unde putem gestiona excepția (de exemplu, scriind un mesaj de eroare).

După blocurile try și catch, putem adăuga un bloc opțional finally, care este întotdeauna executat, indiferent dacă excepția a fost sau nu lansată (chiar dacă folosim return, break sau continue în blocul try sau catch ):

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

Putem, de asemenea, să ne creăm propriile clase de excepții (ierarhie) care moștenesc din clasa Exception. Ca exemplu, să considerăm o aplicație bancară simplă care permite depuneri și retrageri:

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

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

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

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

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

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

Se pot specifica mai multe blocuri catch pentru un singur bloc try dacă se așteaptă diferite tipuri de excepții.

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

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

În acest exemplu, este important să rețineți ordinea blocurilor catch. Deoarece toate excepțiile moștenesc de la BankingException, dacă am avea acest bloc primul, toate excepțiile ar fi prinse în el fără ca codul să ajungă la blocurile catch ulterioare. Prin urmare, este important ca excepțiile mai specifice (adică cele care moștenesc de la altele) să fie mai sus în ordinea blocului catch decât excepțiile părinte.

Iterații

În PHP, puteți parcurge obiectele în buclă folosind bucla foreach, la fel cum parcurgeți o matrice. Pentru ca acest lucru să funcționeze, obiectul trebuie să implementeze o interfață specială.

Prima opțiune este să implementați interfața Iterator, care are metodele current() care returnează valoarea curentă, key() care returnează cheia, next() care trece la următoarea valoare, rewind() care trece la început și valid() care verifică dacă am ajuns deja la sfârșit.

Cealaltă opțiune este să implementăm o interfață IteratorAggregate, care are o singură metodă getIterator(). Aceasta fie returnează un obiect de tip „placeholder“ care va furniza traversarea, fie poate fi un generator, care este o funcție specială care utilizează yield pentru a returna chei și valori în mod secvențial:

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

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

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

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

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

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

Cele mai bune practici

Odată ce ați învățat principiile de bază ale programării orientate pe obiecte, este esențial să vă concentrați asupra celor mai bune practici în OOP. Acestea vă vor ajuta să scrieți un cod care să fie nu numai funcțional, ci și ușor de citit, de înțeles și de întreținut.

  1. Separarea preocupărilor: Fiecare clasă ar trebui să aibă o responsabilitate clar definită și ar trebui să abordeze doar o singură sarcină principală. Dacă o clasă face prea multe lucruri, ar putea fi indicat să o împărțiți în clase mai mici, specializate.
  2. Încapsularea: Datele și metodele ar trebui să fie cât mai ascunse posibil și să fie accesibile numai printr-o interfață definită. Acest lucru vă permite să modificați implementarea internă a unei clase fără a afecta restul codului.
  3. Injectarea dependenței: În loc să creați dependențe direct în interiorul unei clase, ar trebui să le „injectați“ din exterior. Pentru o înțelegere mai profundă a acestui principiu, vă recomandăm capitolele despre Injecția de dependență.