Nette Documentation Preview

syntax
Bevezetés az objektumorientált programozásba
********************************************

.[perex]
Az "OOP" kifejezés az objektumorientált programozás rövidítése, amely a kód szervezésének és strukturálásának egy módja. Az OOP lehetővé teszi, hogy a programot egymással kommunikáló objektumok gyűjteményének tekintsük, nem pedig parancsok és függvények sorozatának.

Az OOP-ban az "objektum" olyan egység, amely adatokat és az adatokkal operáló függvényeket tartalmaz. Az objektumok létrehozása "osztályok" alapján történik, amelyek az objektumok tervrajzaként vagy sablonjaként értelmezhetők. Ha már van egy osztályunk, létrehozhatjuk annak "példányát", amely egy konkrét, az osztályból készült objektum.

Nézzük meg, hogyan hozhatunk létre egy egyszerű osztályt PHP-ben. Egy osztály definiálásakor az "class" kulcsszót használjuk, amelyet az osztály neve követ, majd a szögletes zárójelek az osztály függvényeit (az úgynevezett "metódusokat") és az osztály változóit (az úgynevezett "tulajdonságokat" vagy "attribútumokat") zárják körül:

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

Ebben a példában egy `Car` nevű osztályt hoztunk létre egy `honk` nevű függvénnyel (vagy "módszerrel").

Minden osztály csak egy fő feladatot oldhat meg. Ha egy osztály túl sok mindent csinál, akkor célszerű lehet kisebb, speciális osztályokra osztani.

Az osztályokat általában külön fájlokban tároljuk, hogy a kódot rendszerezve tartsuk és könnyen áttekinthetővé tegyük. A fájlnévnek meg kell egyeznie az osztály nevével, így a `Car` osztály esetében a fájl neve `Car.php` lenne.

Az osztályok elnevezésénél célszerű a "PascalCase" konvenciót követni, ami azt jelenti, hogy a névben minden szó nagybetűvel kezdődik, és nincsenek aláhúzások vagy egyéb elválasztójelek. A metódusok és tulajdonságok a "camelCase" konvenciót követik, azaz kisbetűvel kezdődnek.

A PHP egyes metódusai speciális szerepkörrel rendelkeznek, és a `__` (két aláhúzás) előtaggal vannak ellátva. Az egyik legfontosabb speciális metódus a "konstruktor", amelyet a `__construct` jelöléssel látunk el. A konstruktor egy olyan metódus, amely automatikusan meghívásra kerül, amikor egy osztály új példányát létrehozzuk.

A konstruktort gyakran használjuk egy objektum kezdeti állapotának beállítására. Például egy személyt reprezentáló objektum létrehozásakor a konstruktort használhatjuk a kor, a név vagy más attribútumok beállítására.

Lássuk, hogyan használhatunk konstruktort a PHP-ban:

```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
```

Ebben a példában a `Person` osztály rendelkezik egy `$age` tulajdonsággal és egy konstruktorral, amely beállítja ezt a tulajdonságot. A `howOldAreYou()` metódus ezután hozzáférést biztosít a személy életkorához.

A `new` kulcsszó egy osztály új példányának létrehozására szolgál. A fenti példában egy új, 25 éves személyt hoztunk létre.

A konstruktor paramétereihez alapértelmezett értékeket is megadhatunk, ha azok nincsenek megadva egy objektum létrehozásakor. Például:

```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
```

Ebben a példában, ha a `Person` objektum létrehozásakor nem ad meg életkort, az alapértelmezett 20-as értéket fogja használni.

Végül a tulajdonság definíciója a konstruktoron keresztül történő inicializálással a következőképpen rövidíthető és egyszerűsíthető:

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


Névterek .[#toc-namespaces]
---------------------------

A névterek lehetővé teszik, hogy az egymással összefüggő osztályokat, függvényeket és konstansokat rendszerezzük és csoportosítsuk, miközben elkerüljük a névkonfliktusokat. Úgy gondolhat rájuk, mint a számítógép mappáira, ahol minden mappa egy adott projekthez vagy témához kapcsolódó fájlokat tartalmaz.

A névterek különösen hasznosak nagyobb projekteknél vagy harmadik féltől származó könyvtárak használatakor, ahol osztályok elnevezési konfliktusok merülhetnek fel.

Képzelje el, hogy van egy `Car` nevű osztálya a projektjében, és a `Transport` nevű névtérben szeretné elhelyezni. Ezt a következőképpen tenné:

```php
namespace Transport;

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

Ha a `Car` osztályt egy másik fájlban szeretné használni, akkor meg kell adnia, hogy az osztály melyik névtérből származik:

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

Az egyszerűsítés érdekében a fájl elején megadhatja, hogy melyik osztályt szeretné használni egy adott névtérből, így a teljes elérési út megadása nélkül hozhat létre példányokat:

```php
use Transport\Car;

$car = new Car;
```


Öröklés .[#toc-inheritance]
---------------------------

Az öröklés az objektumorientált programozás egyik eszköze, amely lehetővé teszi új osztályok létrehozását a már létező osztályok alapján, örökölve azok tulajdonságait és metódusait, és szükség szerint kibővítve vagy átdefiniálva azokat. Az öröklés biztosítja a kód újrafelhasználhatóságát és az osztályhierarchiát.

Egyszerűen fogalmazva, ha van egy osztályunk, és szeretnénk létrehozni egy másik, abból származtatott, de némi módosítással rendelkező osztályt, akkor az új osztályt "örökölhetjük" az eredetiből.

A PHP-ben az öröklés a `extends` kulcsszóval valósul meg.

A mi `Person` osztályunk az életkori információkat tárolja. Létrehozhatunk egy másik osztályt, a `Student`, amely a `Person` kiterjesztése, és hozzáadhatja a tanulmányi területre vonatkozó információkat.

Nézzünk egy példát:

```php
class Person
{
	private $age;

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

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

class Student extends Person
{
	private $fieldOfStudy;

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

	function printInformation()
	{
		echo 'Age of student: ', $this->howOldAreYou();
		echo 'Field of study: ', $this->fieldOfStudy;
	}
}

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

Hogyan működik ez a kód?

- A `extends` kulcsszóval bővítettük a `Person` osztályt, ami azt jelenti, hogy a `Student` osztály minden metódust és tulajdonságot a `Person` osztályból örököl.

- A `parent::` kulcsszó lehetővé teszi számunkra, hogy a szülő osztály metódusait hívjuk. Ebben az esetben a `Person` osztály konstruktorát hívtuk meg, mielőtt a `Student` osztályhoz hozzáadtuk volna a saját funkcióinkat.

Az öröklés olyan helyzetekre való, amikor az osztályok között "is a" kapcsolat van. Például egy `Student` egy `Person`. A macska egy állat. Lehetővé teszi számunkra, hogy olyan esetekben, amikor egy objektumot (pl. "Person") várunk a kódban, egy származtatott objektumot használjunk helyette (pl. "Student").

Lényeges felismerni, hogy az öröklés elsődleges célja **nem** a kódduplikáció megakadályozása. Éppen ellenkezőleg, az öröklés helytelen használata összetett és nehezen karbantartható kódhoz vezethet. Ha nincs "is a" kapcsolat az osztályok között, akkor az öröklés helyett a kompozíciót kell fontolóra vennünk.


Kompozíció .[#toc-composition]
------------------------------

A kompozíció egy olyan technika, ahol ahelyett, hogy egy másik osztály tulajdonságait és metódusait örökölnénk, egyszerűen csak használjuk annak példányát a saját osztályunkban. Ez lehetővé teszi, hogy több osztály funkcionalitását és tulajdonságait kombináljuk anélkül, hogy bonyolult öröklési struktúrákat hoznánk létre.

Például van egy `Engine` és egy `Car` osztályunk. Ahelyett, hogy azt mondanánk, hogy "Egy autó egy motor", azt mondjuk, hogy "Egy autónak van egy motorja", ami egy tipikus kompozíciós kapcsolat.

```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();
```

Itt a `Car` nem rendelkezik a `Engine` osztály összes tulajdonságával és metódusával, de a `$engine` tulajdonságon keresztül hozzáférhet azokhoz.

A kompozíció előnye a nagyobb tervezési rugalmasság és a jobb alkalmazkodóképesség a jövőbeli változásokhoz.


Láthatóság .[#toc-visibility]
-----------------------------

A PHP-ben az osztályok tulajdonságai, metódusai és konstansai számára meghatározható a "láthatóság". A láthatóság határozza meg, hogy hol lehet hozzáférni ezekhez az elemekhez.

1. **Public:** Ha egy elemet `public`-ként jelölünk, az azt jelenti, hogy bárhonnan, akár az osztályon kívülről is elérhetjük.

2. **Védett:** Egy `protected` jelű elem csak az osztályon belül és annak összes leszármazottján (az osztályból öröklődő osztályok) belül érhető el.

3. **Privát:** Ha egy elem `private`, akkor csak azon az osztályon belül érhető el, ahol definiálták.

Ha nem adja meg a láthatóságot, a PHP automatikusan `public` értékre állítja.

Nézzünk meg egy példakódot:

```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
```

Folytatva az osztályöröklést:

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

Ebben az esetben a `printProperties()` metódus a `ChildClass` osztályban hozzáférhet a nyilvános és védett tulajdonságokhoz, de nem férhet hozzá a szülőosztály privát tulajdonságaihoz.

Az adatoknak és a metódusoknak a lehető legrejtettebbnek kell lenniük, és csak egy meghatározott interfészen keresztül lehet őket elérni. Ez lehetővé teszi, hogy az osztály belső implementációját megváltoztassa anélkül, hogy a kód többi részét befolyásolná.


Végleges kulcsszó .[#toc-final-keyword]
---------------------------------------

A PHP-ben a `final` kulcsszót használhatjuk, ha meg akarjuk akadályozni, hogy egy osztály, metódus vagy konstans öröklődjön vagy felülíródjon. Ha egy osztályt `final` jelöli, akkor nem bővíthető. Ha egy metódus `final`-ként van megjelölve, akkor nem lehet felülírni egy alosztályban.

Ha tudatában vagyunk annak, hogy egy bizonyos osztály vagy metódus nem lesz többé módosítható, könnyebben tudunk változtatásokat végrehajtani anélkül, hogy aggódnánk a lehetséges konfliktusok miatt. Például hozzáadhatunk egy új metódust anélkül, hogy attól kellene tartanunk, hogy egy leszármazottnak már van egy ugyanilyen nevű metódusa, ami ütközéshez vezethet. Vagy megváltoztathatjuk egy metódus paramétereit, szintén anélkül, hogy azt kockáztatnánk, hogy következetlenséget okozunk egy leszármazottban lévő felülhajtott metódussal.

```php
final class FinalClass
{
}

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

Ebben a példában a `FinalClass` végleges osztályból való öröklés megkísérlése hibát eredményez.


Statikus tulajdonságok és metódusok .[#toc-static-properties-and-methods]
-------------------------------------------------------------------------

Amikor egy osztály "statikus" elemeiről beszélünk a PHP-ben, akkor olyan metódusokra és tulajdonságokra gondolunk, amelyek magához az osztályhoz tartoznak, nem pedig az osztály egy adott példányához. Ez azt jelenti, hogy nem kell létrehozni az osztály egy példányát ahhoz, hogy hozzáférjünk hozzájuk. Ehelyett közvetlenül az osztály nevén keresztül hívja meg vagy éri el őket.

Ne feledje, hogy mivel a statikus elemek az osztályhoz tartoznak, nem pedig annak példányaihoz, nem használhatja a `$this` pszeudováltozót statikus metódusokon belül.

A statikus tulajdonságok használata [buktatókkal teli homályos kódhoz |dependency-injection:global-state] vezet, ezért soha ne használd őket, és itt nem is mutatunk példát. A statikus metódusok viszont hasznosak. Íme egy példa:

```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
```

Ebben a példában létrehoztunk egy `Calculator` osztályt két statikus metódussal. Ezeket a metódusokat közvetlenül meg tudjuk hívni anélkül, hogy az osztály példányát létrehoznánk a `::` operátor segítségével. A statikus metódusok különösen hasznosak olyan műveleteknél, amelyek nem függnek egy adott osztálypéldány állapotától.


Osztálykonstansok .[#toc-class-constants]
-----------------------------------------

Az osztályokon belül lehetőségünk van konstansok definiálására. A konstansok olyan értékek, amelyek a program végrehajtása során soha nem változnak. A változókkal ellentétben a konstansok értéke változatlan marad.

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

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

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

Ebben a példában van egy `Car` osztályunk a `NumberOfWheels` konstanssal. Az osztályon belüli konstans elérésekor az osztály neve helyett a `self` kulcsszót használhatjuk.


Objektum interfészek .[#toc-object-interfaces]
----------------------------------------------

Az objektum interfészek az osztályok "szerződéseiként" működnek. Ha egy osztálynak egy objektum interfészt kell implementálnia, akkor tartalmaznia kell az összes metódust, amelyet az interfész definiál. Ez egy nagyszerű módja annak, hogy bizonyos osztályok azonos "szerződést" vagy struktúrát tartsanak be.

A PHP-ben az interfészeket a `interface` kulcsszóval definiáljuk. Az interfészben definiált összes metódus nyilvános (`public`). Amikor egy osztály megvalósít egy interfészt, akkor a `implements` kulcsszót használja.

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

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

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

Ha egy osztály megvalósít egy interfészt, de nem minden elvárt metódus van definiálva, a PHP hibát fog dobni. Egy osztály egyszerre több interfészt is megvalósíthat, ami eltér az örökléstől, ahol egy osztály csak egy osztálytól örökölhet.


Absztrakt osztályok .[#toc-abstract-classes]
--------------------------------------------

Az absztrakt osztályok más osztályok alapsablonjaiként szolgálnak, de közvetlenül nem hozhatók létre példányaik. Teljes metódusok és olyan absztrakt metódusok keverékét tartalmazzák, amelyeknek nincs meghatározott tartalma. Az absztrakt osztályokból öröklődő osztályoknak meg kell adniuk a szülőktől származó összes absztrakt metódus definícióját.

Az absztrakt osztályok definiálására a `abstract` kulcsszót használjuk.

```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();
```

Ebben a példában egy absztrakt osztályunk van egy reguláris és egy absztrakt metódussal. Ezután van egy `Child` osztályunk, amely a `AbstractClass` osztályból örököl, és implementációt biztosít az absztrakt metódushoz.


Típusellenőrzés .[#toc-type-checking]
-------------------------------------

A programozásban alapvető fontosságú annak biztosítása, hogy az adatok, amelyekkel dolgozunk, megfelelő típusúak legyenek. A PHP-ben vannak olyan eszközeink, amelyek ezt a biztosítékot nyújtják. Az adatok megfelelő típusának ellenőrzését "típusellenőrzésnek" nevezzük.

Típusok, amelyekkel a PHP-ben találkozhatunk:

1. **Bázis típusok**: Ezek közé tartozik a `int` (egész számok), `float` (lebegőpontos számok), `bool` (boolék), `string` (karakterláncok), `array` (tömbök) és `null`.
2. **osztályok**: Amikor azt akarjuk, hogy egy érték egy adott osztály példánya legyen.
3. **Interfészek**: Meghatározza azon metódusok halmazát, amelyeket egy osztálynak implementálnia kell. Egy interfésznek megfelelő értéknek rendelkeznie kell ezekkel a metódusokkal.
4. **Keverék típusok**: Megadhatjuk, hogy egy változónak több megengedett típusa is lehet.
5. **Void**: Ez a speciális típus azt jelzi, hogy egy függvény vagy metódus nem ad vissza értéket.

Lássuk, hogyan módosíthatjuk a kódot a típusok felvételére:

```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();
}
```

Így biztosítjuk, hogy a kódunk a megfelelő típusú adatokat várja el és a megfelelő típusú adatokkal dolgozik, ami segít megelőzni a lehetséges hibákat.


Összehasonlítás és azonosság .[#toc-comparison-and-identity]
------------------------------------------------------------

A PHP-ben kétféleképpen lehet objektumokat összehasonlítani:

1. Érték-összehasonlítás `==`: Ellenőrzi, hogy az objektumok ugyanabba az osztályba tartoznak-e, és tulajdonságaikban ugyanazok az értékek szerepelnek-e.
2. Azonosság `===`: Ellenőrzi, hogy az objektum azonos példányáról van-e szó.

```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
```


A `instanceof` Üzemeltető .[#toc-the-instanceof-operator]
---------------------------------------------------------

A `instanceof` operátor lehetővé teszi annak meghatározását, hogy egy adott objektum egy adott osztály példánya, leszármazottja, vagy egy bizonyos interfész megvalósítója-e.

Képzeljük el, hogy van egy `Person` osztályunk és egy másik osztályunk, a `Student`, amely a `Person` leszármazottja:

```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)
```

A kimenetekből látható, hogy a `$student` objektumot a `Student` és a `Person` osztályok példányának tekintjük.


Folyékony interfészek .[#toc-fluent-interfaces]
-----------------------------------------------

A "folyékony interfész" egy olyan technika az OOP-ban, amely lehetővé teszi a metódusok egyetlen hívással történő láncolását. Ez gyakran egyszerűsíti és egyértelművé teszi a kódot.

A folyékony interfész kulcseleme, hogy a lánc minden egyes metódusa az aktuális objektumra való hivatkozást adja vissza. Ezt a `return $this;` használatával érjük el a metódus végén. Ez a programozási stílus gyakran társul a "setters" nevű metódusokkal, amelyek az objektum tulajdonságainak értékeit állítják be.

Nézzük meg, hogyan nézhet ki egy folyékony interfész az e-mailek küldéséhez:

```php
public function sendMessage()
{
	$email = new Email;
	$email->setFrom('sender@example.com')
		  ->setRecipient('admin@example.com')
		  ->setMessage('Hello, this is a message.')
		  ->send();
}
```

Ebben a példában a `setFrom()`, `setRecipient()` és `setMessage()` metódusok a megfelelő értékek (feladó, címzett, üzenet tartalma) beállítására szolgálnak. Az egyes értékek beállítása után a metódusok visszaadják az aktuális objektumot (`$email`), ami lehetővé teszi számunkra, hogy egy másik metódust láncoljunk utána. Végül meghívjuk a `send()` metódust, amely ténylegesen elküldi az e-mailt.

A folyékony interfészeknek köszönhetően olyan kódot írhatunk, amely intuitív és könnyen olvasható.


Másolás a `clone` címen .[#toc-copying-with-clone]
--------------------------------------------------

A PHP-ben a `clone` operátor segítségével létrehozhatjuk egy objektum másolatát. Így egy új, azonos tartalmú példányt kapunk.

Ha egy objektum másolásakor szükségünk van néhány tulajdonságának módosítására, akkor az osztályban definiálhatunk egy speciális `__clone()` metódust. Ez a metódus automatikusan meghívódik, amikor az objektumot klónozzuk.

```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
```

Ebben a példában van egy `Sheep` osztályunk, amelynek egy tulajdonsága a `$name`. Amikor klónozzuk ennek az osztálynak egy példányát, a `__clone()` metódus biztosítja, hogy a klónozott bárány neve a "Clone of" előtagot kapja.


A  tulajdonságok. .[#toc-traits]
--------------------------------

A PHP-ban a Traits egy olyan eszköz, amely lehetővé teszi a metódusok, tulajdonságok és konstansok megosztását az osztályok között, és megakadályozza a kód duplikálását. Úgy gondolhatsz rájuk, mint egy "másolás és beillesztés" mechanizmusra (Ctrl-C és Ctrl-V), ahol egy tulajdonság tartalma "beillesztésre" kerül az osztályokba. Ez lehetővé teszi a kód újrafelhasználását anélkül, hogy bonyolult osztályhierarchiákat kellene létrehozni.

Nézzünk meg egy egyszerű példát a vonások PHP-ban való használatára:

```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!'
```

Ebben a példában van egy `Honking` nevű tulajdonságunk, amely egy metódust tartalmaz: `honk()`. Ezután van két osztályunk: `Car` és `Truck`, amelyek mindkettő a `Honking` tulajdonságot használja. Ennek eredményeképpen mindkét osztály "rendelkezik" a `honk()` metódussal, és mindkét osztály objektumain meg tudjuk hívni azt.

A tulajdonságok lehetővé teszik az osztályok közötti egyszerű és hatékony kódmegosztást. Nem lépnek be az öröklési hierarchiába, azaz a `$car instanceof Honking` a `false`-t fogja visszaadni.


Kivételek .[#toc-exceptions]
----------------------------

A kivételek az OOP-ban lehetővé teszik számunkra a kódunk végrehajtása során felmerülő hibák kezelését és kezelését. Ezek lényegében olyan objektumok, amelyeket arra terveztek, hogy rögzítsék a programban előforduló hibákat vagy váratlan helyzeteket.

A PHP-ben ezekre az objektumokra a beépített `Exception` osztály áll rendelkezésünkre. Számos olyan metódussal rendelkezik, amelyek lehetővé teszik számunkra, hogy több információt kapjunk a kivételről, például a hibaüzenetet, a fájlt és a sort, ahol a hiba előfordult, stb.

Ha probléma merül fel, "dobhatunk" egy kivételt (a `throw`) segítségével. Ha ezt a kivételt "el akarjuk kapni" és feldolgozni, akkor a `try` és a `catch` blokkokat használjuk.

Lássuk, hogyan működik ez:

```php
try {
	throw new Exception('Message explaining the reason for the exception');

	// This code won't execute
	echo 'I am a message that nobody will read';

} catch (Exception $e) {
	echo 'Exception caught: '. $e->getMessage();
}
```

Fontos megjegyezni, hogy a kivétel mélyebben, más metódusok hívása közben is dobható.

Egy `try` blokkhoz több `catch` blokk is megadható, ha különböző típusú kivételekre számítunk.

Létrehozhatunk egy kivételhierarchiát is, ahol minden kivételosztály örökli az előzőt. Példaként tekintsünk egy egyszerű banki alkalmazást, amely lehetővé teszi a befizetéseket és a kifizetéseket:

```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;
	}
}

$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.';
}
```

Ebben a példában fontos megjegyezni a `catch` blokkok sorrendjét. Mivel minden kivétel a `BankingException`-tól öröklődik, ha ez a blokk lenne az első, akkor minden kivételt elkapnánk benne anélkül, hogy a kód elérné a következő `catch` blokkokat. Ezért fontos, hogy a specifikusabb (azaz másoktól öröklődő) kivételek a `catch` blokkok sorrendjében előrébb legyenek, mint a szülő kivételek.


Legjobb gyakorlatok .[#toc-best-practices]
------------------------------------------

Ha már az objektumorientált programozás alapelveivel megismerkedtél, elengedhetetlen, hogy az OOP legjobb gyakorlataira koncentrálj. Ezek segítenek olyan kódot írni, amely nemcsak funkcionális, hanem olvasható, érthető és könnyen karbantartható is.

1) **Separation of Concerns**: Minden osztálynak egyértelműen meghatározott felelősségi körrel kell rendelkeznie, és csak egy elsődleges feladattal kell foglalkoznia. Ha egy osztály túl sok mindent csinál, célszerű lehet kisebb, specializált osztályokra bontani.
2) **Kapszulázás**: Az adatoknak és a metódusoknak a lehető legrejtettebbnek kell lenniük, és csak egy meghatározott interfészen keresztül lehet hozzájuk hozzáférni. Ez lehetővé teszi, hogy egy osztály belső megvalósítását megváltoztassuk anélkül, hogy a kód többi részét befolyásolná.
3) **Függőségi injektálás**: Ahelyett, hogy közvetlenül egy osztályon belül hoznánk létre függőségeket, inkább kívülről "injektáljuk" azokat. Ennek az elvnek a mélyebb megértéséhez ajánljuk a [Dependency Injection fejezeteket |dependency-injection:introduction].

Bevezetés az objektumorientált programozásba

Az „OOP“ kifejezés az objektumorientált programozás rövidítése, amely a kód szervezésének és strukturálásának egy módja. Az OOP lehetővé teszi, hogy a programot egymással kommunikáló objektumok gyűjteményének tekintsük, nem pedig parancsok és függvények sorozatának.

Az OOP-ban az „objektum“ olyan egység, amely adatokat és az adatokkal operáló függvényeket tartalmaz. Az objektumok létrehozása „osztályok“ alapján történik, amelyek az objektumok tervrajzaként vagy sablonjaként értelmezhetők. Ha már van egy osztályunk, létrehozhatjuk annak „példányát“, amely egy konkrét, az osztályból készült objektum.

Nézzük meg, hogyan hozhatunk létre egy egyszerű osztályt PHP-ben. Egy osztály definiálásakor az „class“ kulcsszót használjuk, amelyet az osztály neve követ, majd a szögletes zárójelek az osztály függvényeit (az úgynevezett „metódusokat“) és az osztály változóit (az úgynevezett „tulajdonságokat“ vagy „attribútumokat“) zárják körül:

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

Ebben a példában egy Car nevű osztályt hoztunk létre egy honk nevű függvénnyel (vagy „módszerrel“).

Minden osztály csak egy fő feladatot oldhat meg. Ha egy osztály túl sok mindent csinál, akkor célszerű lehet kisebb, speciális osztályokra osztani.

Az osztályokat általában külön fájlokban tároljuk, hogy a kódot rendszerezve tartsuk és könnyen áttekinthetővé tegyük. A fájlnévnek meg kell egyeznie az osztály nevével, így a Car osztály esetében a fájl neve Car.php lenne.

Az osztályok elnevezésénél célszerű a „PascalCase“ konvenciót követni, ami azt jelenti, hogy a névben minden szó nagybetűvel kezdődik, és nincsenek aláhúzások vagy egyéb elválasztójelek. A metódusok és tulajdonságok a „camelCase“ konvenciót követik, azaz kisbetűvel kezdődnek.

A PHP egyes metódusai speciális szerepkörrel rendelkeznek, és a __ (két aláhúzás) előtaggal vannak ellátva. Az egyik legfontosabb speciális metódus a „konstruktor“, amelyet a __construct jelöléssel látunk el. A konstruktor egy olyan metódus, amely automatikusan meghívásra kerül, amikor egy osztály új példányát létrehozzuk.

A konstruktort gyakran használjuk egy objektum kezdeti állapotának beállítására. Például egy személyt reprezentáló objektum létrehozásakor a konstruktort használhatjuk a kor, a név vagy más attribútumok beállítására.

Lássuk, hogyan használhatunk konstruktort a PHP-ban:

class Person
{
	private $age;

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

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

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

Ebben a példában a Person osztály rendelkezik egy $age tulajdonsággal és egy konstruktorral, amely beállítja ezt a tulajdonságot. A howOldAreYou() metódus ezután hozzáférést biztosít a személy életkorához.

A new kulcsszó egy osztály új példányának létrehozására szolgál. A fenti példában egy új, 25 éves személyt hoztunk létre.

A konstruktor paramétereihez alapértelmezett értékeket is megadhatunk, ha azok nincsenek megadva egy objektum létrehozásakor. Például:

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

Ebben a példában, ha a Person objektum létrehozásakor nem ad meg életkort, az alapértelmezett 20-as értéket fogja használni.

Végül a tulajdonság definíciója a konstruktoron keresztül történő inicializálással a következőképpen rövidíthető és egyszerűsíthető:

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

Névterek

A névterek lehetővé teszik, hogy az egymással összefüggő osztályokat, függvényeket és konstansokat rendszerezzük és csoportosítsuk, miközben elkerüljük a névkonfliktusokat. Úgy gondolhat rájuk, mint a számítógép mappáira, ahol minden mappa egy adott projekthez vagy témához kapcsolódó fájlokat tartalmaz.

A névterek különösen hasznosak nagyobb projekteknél vagy harmadik féltől származó könyvtárak használatakor, ahol osztályok elnevezési konfliktusok merülhetnek fel.

Képzelje el, hogy van egy Car nevű osztálya a projektjében, és a Transport nevű névtérben szeretné elhelyezni. Ezt a következőképpen tenné:

namespace Transport;

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

Ha a Car osztályt egy másik fájlban szeretné használni, akkor meg kell adnia, hogy az osztály melyik névtérből származik:

$car = new Transport\Car;

Az egyszerűsítés érdekében a fájl elején megadhatja, hogy melyik osztályt szeretné használni egy adott névtérből, így a teljes elérési út megadása nélkül hozhat létre példányokat:

use Transport\Car;

$car = new Car;

Öröklés

Az öröklés az objektumorientált programozás egyik eszköze, amely lehetővé teszi új osztályok létrehozását a már létező osztályok alapján, örökölve azok tulajdonságait és metódusait, és szükség szerint kibővítve vagy átdefiniálva azokat. Az öröklés biztosítja a kód újrafelhasználhatóságát és az osztályhierarchiát.

Egyszerűen fogalmazva, ha van egy osztályunk, és szeretnénk létrehozni egy másik, abból származtatott, de némi módosítással rendelkező osztályt, akkor az új osztályt „örökölhetjük“ az eredetiből.

A PHP-ben az öröklés a extends kulcsszóval valósul meg.

A mi Person osztályunk az életkori információkat tárolja. Létrehozhatunk egy másik osztályt, a Student, amely a Person kiterjesztése, és hozzáadhatja a tanulmányi területre vonatkozó információkat.

Nézzünk egy példát:

class Person
{
	private $age;

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

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

class Student extends Person
{
	private $fieldOfStudy;

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

	function printInformation()
	{
		echo 'Age of student: ', $this->howOldAreYou();
		echo 'Field of study: ', $this->fieldOfStudy;
	}
}

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

Hogyan működik ez a kód?

  • A extends kulcsszóval bővítettük a Person osztályt, ami azt jelenti, hogy a Student osztály minden metódust és tulajdonságot a Person osztályból örököl.
  • A parent:: kulcsszó lehetővé teszi számunkra, hogy a szülő osztály metódusait hívjuk. Ebben az esetben a Person osztály konstruktorát hívtuk meg, mielőtt a Student osztályhoz hozzáadtuk volna a saját funkcióinkat.

Az öröklés olyan helyzetekre való, amikor az osztályok között „is a“ kapcsolat van. Például egy Student egy Person. A macska egy állat. Lehetővé teszi számunkra, hogy olyan esetekben, amikor egy objektumot (pl. „Person“) várunk a kódban, egy származtatott objektumot használjunk helyette (pl. „Student“).

Lényeges felismerni, hogy az öröklés elsődleges célja nem a kódduplikáció megakadályozása. Éppen ellenkezőleg, az öröklés helytelen használata összetett és nehezen karbantartható kódhoz vezethet. Ha nincs „is a“ kapcsolat az osztályok között, akkor az öröklés helyett a kompozíciót kell fontolóra vennünk.

Kompozíció

A kompozíció egy olyan technika, ahol ahelyett, hogy egy másik osztály tulajdonságait és metódusait örökölnénk, egyszerűen csak használjuk annak példányát a saját osztályunkban. Ez lehetővé teszi, hogy több osztály funkcionalitását és tulajdonságait kombináljuk anélkül, hogy bonyolult öröklési struktúrákat hoznánk létre.

Például van egy Engine és egy Car osztályunk. Ahelyett, hogy azt mondanánk, hogy „Egy autó egy motor“, azt mondjuk, hogy „Egy autónak van egy motorja“, ami egy tipikus kompozíciós kapcsolat.

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();

Itt a Car nem rendelkezik a Engine osztály összes tulajdonságával és metódusával, de a $engine tulajdonságon keresztül hozzáférhet azokhoz.

A kompozíció előnye a nagyobb tervezési rugalmasság és a jobb alkalmazkodóképesség a jövőbeli változásokhoz.

Láthatóság

A PHP-ben az osztályok tulajdonságai, metódusai és konstansai számára meghatározható a „láthatóság“. A láthatóság határozza meg, hogy hol lehet hozzáférni ezekhez az elemekhez.

  1. Public: Ha egy elemet public-ként jelölünk, az azt jelenti, hogy bárhonnan, akár az osztályon kívülről is elérhetjük.
  2. Védett: Egy protected jelű elem csak az osztályon belül és annak összes leszármazottján (az osztályból öröklődő osztályok) belül érhető el.
  3. Privát: Ha egy elem private, akkor csak azon az osztályon belül érhető el, ahol definiálták.

Ha nem adja meg a láthatóságot, a PHP automatikusan public értékre állítja.

Nézzünk meg egy példakódot:

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

Folytatva az osztályöröklést:

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

Ebben az esetben a printProperties() metódus a ChildClass osztályban hozzáférhet a nyilvános és védett tulajdonságokhoz, de nem férhet hozzá a szülőosztály privát tulajdonságaihoz.

Az adatoknak és a metódusoknak a lehető legrejtettebbnek kell lenniük, és csak egy meghatározott interfészen keresztül lehet őket elérni. Ez lehetővé teszi, hogy az osztály belső implementációját megváltoztassa anélkül, hogy a kód többi részét befolyásolná.

Végleges kulcsszó

A PHP-ben a final kulcsszót használhatjuk, ha meg akarjuk akadályozni, hogy egy osztály, metódus vagy konstans öröklődjön vagy felülíródjon. Ha egy osztályt final jelöli, akkor nem bővíthető. Ha egy metódus final-ként van megjelölve, akkor nem lehet felülírni egy alosztályban.

Ha tudatában vagyunk annak, hogy egy bizonyos osztály vagy metódus nem lesz többé módosítható, könnyebben tudunk változtatásokat végrehajtani anélkül, hogy aggódnánk a lehetséges konfliktusok miatt. Például hozzáadhatunk egy új metódust anélkül, hogy attól kellene tartanunk, hogy egy leszármazottnak már van egy ugyanilyen nevű metódusa, ami ütközéshez vezethet. Vagy megváltoztathatjuk egy metódus paramétereit, szintén anélkül, hogy azt kockáztatnánk, hogy következetlenséget okozunk egy leszármazottban lévő felülhajtott metódussal.

final class FinalClass
{
}

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

Ebben a példában a FinalClass végleges osztályból való öröklés megkísérlése hibát eredményez.

Statikus tulajdonságok és metódusok

Amikor egy osztály „statikus“ elemeiről beszélünk a PHP-ben, akkor olyan metódusokra és tulajdonságokra gondolunk, amelyek magához az osztályhoz tartoznak, nem pedig az osztály egy adott példányához. Ez azt jelenti, hogy nem kell létrehozni az osztály egy példányát ahhoz, hogy hozzáférjünk hozzájuk. Ehelyett közvetlenül az osztály nevén keresztül hívja meg vagy éri el őket.

Ne feledje, hogy mivel a statikus elemek az osztályhoz tartoznak, nem pedig annak példányaihoz, nem használhatja a $this pszeudováltozót statikus metódusokon belül.

A statikus tulajdonságok használata buktatókkal teli homályos kódhoz vezet, ezért soha ne használd őket, és itt nem is mutatunk példát. A statikus metódusok viszont hasznosak. Íme egy példa:

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

Ebben a példában létrehoztunk egy Calculator osztályt két statikus metódussal. Ezeket a metódusokat közvetlenül meg tudjuk hívni anélkül, hogy az osztály példányát létrehoznánk a :: operátor segítségével. A statikus metódusok különösen hasznosak olyan műveleteknél, amelyek nem függnek egy adott osztálypéldány állapotától.

Osztálykonstansok

Az osztályokon belül lehetőségünk van konstansok definiálására. A konstansok olyan értékek, amelyek a program végrehajtása során soha nem változnak. A változókkal ellentétben a konstansok értéke változatlan marad.

class Car
{
	public const NumberOfWheels = 4;

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

echo Car::NumberOfWheels;  // Output: 4

Ebben a példában van egy Car osztályunk a NumberOfWheels konstanssal. Az osztályon belüli konstans elérésekor az osztály neve helyett a self kulcsszót használhatjuk.

Objektum interfészek

Az objektum interfészek az osztályok „szerződéseiként“ működnek. Ha egy osztálynak egy objektum interfészt kell implementálnia, akkor tartalmaznia kell az összes metódust, amelyet az interfész definiál. Ez egy nagyszerű módja annak, hogy bizonyos osztályok azonos „szerződést“ vagy struktúrát tartsanak be.

A PHP-ben az interfészeket a interface kulcsszóval definiáljuk. Az interfészben definiált összes metódus nyilvános (public). Amikor egy osztály megvalósít egy interfészt, akkor a implements kulcsszót használja.

interface Animal
{
	function makeSound();
}

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

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

Ha egy osztály megvalósít egy interfészt, de nem minden elvárt metódus van definiálva, a PHP hibát fog dobni. Egy osztály egyszerre több interfészt is megvalósíthat, ami eltér az örökléstől, ahol egy osztály csak egy osztálytól örökölhet.

Absztrakt osztályok

Az absztrakt osztályok más osztályok alapsablonjaiként szolgálnak, de közvetlenül nem hozhatók létre példányaik. Teljes metódusok és olyan absztrakt metódusok keverékét tartalmazzák, amelyeknek nincs meghatározott tartalma. Az absztrakt osztályokból öröklődő osztályoknak meg kell adniuk a szülőktől származó összes absztrakt metódus definícióját.

Az absztrakt osztályok definiálására a abstract kulcsszót használjuk.

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();

Ebben a példában egy absztrakt osztályunk van egy reguláris és egy absztrakt metódussal. Ezután van egy Child osztályunk, amely a AbstractClass osztályból örököl, és implementációt biztosít az absztrakt metódushoz.

Típusellenőrzés

A programozásban alapvető fontosságú annak biztosítása, hogy az adatok, amelyekkel dolgozunk, megfelelő típusúak legyenek. A PHP-ben vannak olyan eszközeink, amelyek ezt a biztosítékot nyújtják. Az adatok megfelelő típusának ellenőrzését „típusellenőrzésnek“ nevezzük.

Típusok, amelyekkel a PHP-ben találkozhatunk:

  1. Bázis típusok: Ezek közé tartozik a int (egész számok), float (lebegőpontos számok), bool (boolék), string (karakterláncok), array (tömbök) és null.
  2. osztályok: Amikor azt akarjuk, hogy egy érték egy adott osztály példánya legyen.
  3. Interfészek: Meghatározza azon metódusok halmazát, amelyeket egy osztálynak implementálnia kell. Egy interfésznek megfelelő értéknek rendelkeznie kell ezekkel a metódusokkal.
  4. Keverék típusok: Megadhatjuk, hogy egy változónak több megengedett típusa is lehet.
  5. Void: Ez a speciális típus azt jelzi, hogy egy függvény vagy metódus nem ad vissza értéket.

Lássuk, hogyan módosíthatjuk a kódot a típusok felvételére:

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();
}

Így biztosítjuk, hogy a kódunk a megfelelő típusú adatokat várja el és a megfelelő típusú adatokkal dolgozik, ami segít megelőzni a lehetséges hibákat.

Összehasonlítás és azonosság

A PHP-ben kétféleképpen lehet objektumokat összehasonlítani:

  1. Érték-összehasonlítás ==: Ellenőrzi, hogy az objektumok ugyanabba az osztályba tartoznak-e, és tulajdonságaikban ugyanazok az értékek szerepelnek-e.
  2. Azonosság ===: Ellenőrzi, hogy az objektum azonos példányáról van-e szó.
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

A instanceof Üzemeltető

A instanceof operátor lehetővé teszi annak meghatározását, hogy egy adott objektum egy adott osztály példánya, leszármazottja, vagy egy bizonyos interfész megvalósítója-e.

Képzeljük el, hogy van egy Person osztályunk és egy másik osztályunk, a Student, amely a Person leszármazottja:

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)

A kimenetekből látható, hogy a $student objektumot a Student és a Person osztályok példányának tekintjük.

Folyékony interfészek

A „folyékony interfész“ egy olyan technika az OOP-ban, amely lehetővé teszi a metódusok egyetlen hívással történő láncolását. Ez gyakran egyszerűsíti és egyértelművé teszi a kódot.

A folyékony interfész kulcseleme, hogy a lánc minden egyes metódusa az aktuális objektumra való hivatkozást adja vissza. Ezt a return $this; használatával érjük el a metódus végén. Ez a programozási stílus gyakran társul a „setters“ nevű metódusokkal, amelyek az objektum tulajdonságainak értékeit állítják be.

Nézzük meg, hogyan nézhet ki egy folyékony interfész az e-mailek küldéséhez:

public function sendMessage()
{
	$email = new Email;
	$email->setFrom('sender@example.com')
		  ->setRecipient('admin@example.com')
		  ->setMessage('Hello, this is a message.')
		  ->send();
}

Ebben a példában a setFrom(), setRecipient() és setMessage() metódusok a megfelelő értékek (feladó, címzett, üzenet tartalma) beállítására szolgálnak. Az egyes értékek beállítása után a metódusok visszaadják az aktuális objektumot ($email), ami lehetővé teszi számunkra, hogy egy másik metódust láncoljunk utána. Végül meghívjuk a send() metódust, amely ténylegesen elküldi az e-mailt.

A folyékony interfészeknek köszönhetően olyan kódot írhatunk, amely intuitív és könnyen olvasható.

Másolás a clone címen

A PHP-ben a clone operátor segítségével létrehozhatjuk egy objektum másolatát. Így egy új, azonos tartalmú példányt kapunk.

Ha egy objektum másolásakor szükségünk van néhány tulajdonságának módosítására, akkor az osztályban definiálhatunk egy speciális __clone() metódust. Ez a metódus automatikusan meghívódik, amikor az objektumot klónozzuk.

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

Ebben a példában van egy Sheep osztályunk, amelynek egy tulajdonsága a $name. Amikor klónozzuk ennek az osztálynak egy példányát, a __clone() metódus biztosítja, hogy a klónozott bárány neve a „Clone of“ előtagot kapja.

A tulajdonságok.

A PHP-ban a Traits egy olyan eszköz, amely lehetővé teszi a metódusok, tulajdonságok és konstansok megosztását az osztályok között, és megakadályozza a kód duplikálását. Úgy gondolhatsz rájuk, mint egy „másolás és beillesztés“ mechanizmusra (Ctrl-C és Ctrl-V), ahol egy tulajdonság tartalma „beillesztésre“ kerül az osztályokba. Ez lehetővé teszi a kód újrafelhasználását anélkül, hogy bonyolult osztályhierarchiákat kellene létrehozni.

Nézzünk meg egy egyszerű példát a vonások PHP-ban való használatára:

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!'

Ebben a példában van egy Honking nevű tulajdonságunk, amely egy metódust tartalmaz: honk(). Ezután van két osztályunk: Car és Truck, amelyek mindkettő a Honking tulajdonságot használja. Ennek eredményeképpen mindkét osztály „rendelkezik“ a honk() metódussal, és mindkét osztály objektumain meg tudjuk hívni azt.

A tulajdonságok lehetővé teszik az osztályok közötti egyszerű és hatékony kódmegosztást. Nem lépnek be az öröklési hierarchiába, azaz a $car instanceof Honking a false-t fogja visszaadni.

Kivételek

A kivételek az OOP-ban lehetővé teszik számunkra a kódunk végrehajtása során felmerülő hibák kezelését és kezelését. Ezek lényegében olyan objektumok, amelyeket arra terveztek, hogy rögzítsék a programban előforduló hibákat vagy váratlan helyzeteket.

A PHP-ben ezekre az objektumokra a beépített Exception osztály áll rendelkezésünkre. Számos olyan metódussal rendelkezik, amelyek lehetővé teszik számunkra, hogy több információt kapjunk a kivételről, például a hibaüzenetet, a fájlt és a sort, ahol a hiba előfordult, stb.

Ha probléma merül fel, „dobhatunk“ egy kivételt (a throw) segítségével. Ha ezt a kivételt „el akarjuk kapni“ és feldolgozni, akkor a try és a catch blokkokat használjuk.

Lássuk, hogyan működik ez:

try {
	throw new Exception('Message explaining the reason for the exception');

	// This code won't execute
	echo 'I am a message that nobody will read';

} catch (Exception $e) {
	echo 'Exception caught: '. $e->getMessage();
}

Fontos megjegyezni, hogy a kivétel mélyebben, más metódusok hívása közben is dobható.

Egy try blokkhoz több catch blokk is megadható, ha különböző típusú kivételekre számítunk.

Létrehozhatunk egy kivételhierarchiát is, ahol minden kivételosztály örökli az előzőt. Példaként tekintsünk egy egyszerű banki alkalmazást, amely lehetővé teszi a befizetéseket és a kifizetéseket:

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;
	}
}

$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.';
}

Ebben a példában fontos megjegyezni a catch blokkok sorrendjét. Mivel minden kivétel a BankingException-tól öröklődik, ha ez a blokk lenne az első, akkor minden kivételt elkapnánk benne anélkül, hogy a kód elérné a következő catch blokkokat. Ezért fontos, hogy a specifikusabb (azaz másoktól öröklődő) kivételek a catch blokkok sorrendjében előrébb legyenek, mint a szülő kivételek.

Legjobb gyakorlatok

Ha már az objektumorientált programozás alapelveivel megismerkedtél, elengedhetetlen, hogy az OOP legjobb gyakorlataira koncentrálj. Ezek segítenek olyan kódot írni, amely nemcsak funkcionális, hanem olvasható, érthető és könnyen karbantartható is.

  1. Separation of Concerns: Minden osztálynak egyértelműen meghatározott felelősségi körrel kell rendelkeznie, és csak egy elsődleges feladattal kell foglalkoznia. Ha egy osztály túl sok mindent csinál, célszerű lehet kisebb, specializált osztályokra bontani.
  2. Kapszulázás: Az adatoknak és a metódusoknak a lehető legrejtettebbnek kell lenniük, és csak egy meghatározott interfészen keresztül lehet hozzájuk hozzáférni. Ez lehetővé teszi, hogy egy osztály belső megvalósítását megváltoztassuk anélkül, hogy a kód többi részét befolyásolná.
  3. Függőségi injektálás: Ahelyett, hogy közvetlenül egy osztályon belül hoznánk létre függőségeket, inkább kívülről „injektáljuk“ azokat. Ennek az elvnek a mélyebb megértéséhez ajánljuk a Dependency Injection fejezeteket.