Nette Documentation Preview

syntax
Einführung in die objektorientierte Programmierung
**************************************************

.[perex]
Der Begriff "OOP" bezeichnet die objektorientierte Programmierung, was eine Methode ist, Code zu organisieren und zu strukturieren. OOP ermöglicht es uns, ein Programm als eine Sammlung von Objekten zu sehen, die miteinander kommunizieren, anstelle einer Abfolge von Befehlen und Funktionen.

In OOP ist ein "Objekt" eine Einheit, die Daten und Funktionen enthält, die mit diesen Daten arbeiten. Objekte werden nach "Klassen" erstellt, die wir als Entwürfe oder Vorlagen für Objekte verstehen können. Wenn wir eine Klasse haben, können wir ihre "Instanz" erstellen, was ein konkretes Objekt ist, das nach dieser Klasse erstellt wurde.

Lassen Sie uns zeigen, wie wir eine einfache Klasse in PHP erstellen können. Beim Definieren einer Klasse verwenden wir das Schlüsselwort "class", gefolgt vom Klassennamen und dann geschweiften Klammern, die Funktionen (sie werden "Methoden" genannt) und Klassenvariablen (sie werden "Eigenschaften" oder englisch "property" genannt) umschließen:

```php
class Auto
{
	function hupen()
	{
		echo 'Bip bip!';
	}
}
```

In diesem Beispiel haben wir eine Klasse namens `Auto` mit einer Funktion (oder "Methode") namens `hupen` erstellt.

Jede Klasse sollte nur eine Hauptaufgabe lösen. Wenn eine Klasse zu viele Dinge tut, kann es sinnvoll sein, sie in kleinere, spezialisierte Klassen aufzuteilen.

Klassen speichern wir normalerweise in separaten Dateien, damit der Code organisiert ist und man sich leicht darin zurechtfindet. Der Dateiname sollte dem Klassennamen entsprechen, also wäre für die Klasse `Auto` der Dateiname `Auto.php`.

Bei der Benennung von Klassen ist es gut, sich an die Konvention "PascalCase" zu halten, was bedeutet, dass jedes Wort im Namen mit einem Großbuchstaben beginnt und es keine Unterstriche oder andere Trennzeichen dazwischen gibt. Methoden und Eigenschaften verwenden die Konvention "camelCase", das bedeutet, dass sie mit einem Kleinbuchstaben beginnen.

Einige Methoden in PHP haben spezielle Aufgaben und sind mit dem Präfix `__` (zwei Unterstriche) gekennzeichnet. Eine der wichtigsten speziellen Methoden ist der "Konstruktor", der als `__construct` gekennzeichnet ist. Der Konstruktor ist eine Methode, die automatisch aufgerufen wird, wenn Sie eine neue Instanz der Klasse erstellen.

Den Konstruktor verwenden wir oft, um den Anfangszustand des Objekts festzulegen. Zum Beispiel, wenn Sie ein Objekt erstellen, das eine Person repräsentiert, können Sie den Konstruktor verwenden, um ihr Alter, ihren Namen oder andere Eigenschaften einzustellen.

Lassen Sie uns zeigen, wie man den Konstruktor in PHP verwendet:

```php
class Person
{
	private $alter;

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

	function wieAltBistDu()
	{
		return $this->alter;
	}
}

$person = new Person(25);
echo $person->wieAltBistDu(); // Ausgabe: 25
```

In diesem Beispiel hat die Klasse `Person` die Eigenschaft (Variable) `$alter` und weiterhin einen Konstruktor, der diese Eigenschaft setzt. Die Methode `wieAltBistDu()` ermöglicht dann den Zugriff auf das Alter der Person.

Die Pseudovariable `$this` wird innerhalb der Klasse verwendet, für den Zugriff auf Eigenschaften und Methoden des Objekts.

Das Schlüsselwort `new` wird verwendet, um eine neue Instanz der Klasse zu erstellen. Im obigen Beispiel haben wir eine neue Person mit dem Alter 25 erstellt.

Sie können auch Standardwerte für Konstruktorparameter festlegen, wenn sie bei der Objekterstellung nicht angegeben werden. Zum Beispiel:

```php
class Person
{
	private $alter;

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

	function wieAltBistDu()
	{
		return $this->alter;
	}
}

$person = new Person;  // wenn wir kein Argument übergeben, können die Klammern weggelassen werden
echo $person->wieAltBistDu(); // Ausgabe: 20
```

In diesem Beispiel, wenn Sie das Alter nicht angeben beim Erstellen des `Person`-Objekts, wird der Standardwert 20 verwendet.

Angenehm ist, dass die Definition der Eigenschaft mit ihrer Initialisierung über den Konstruktor sich so verkürzen und vereinfachen lässt:

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

Der Vollständigkeit halber können Objekte neben Konstruktoren auch Destruktoren haben (Methode `__destruct`), die aufgerufen werden, bevor das Objekt aus dem Speicher freigegeben wird.


Namensräume
-----------

Namensräume (oder "namespaces" auf Englisch) ermöglichen es uns, zusammengehörige Klassen, Funktionen und Konstanten zu organisieren und zu gruppieren, und gleichzeitig Namenskonflikte zu vermeiden. Sie können sie sich wie Ordner auf einem Computer vorstellen, wo jeder Ordner Dateien enthält, die zu einem bestimmten Projekt oder Thema gehören.

Namensräume sind besonders nützlich in größeren Projekten oder wenn Sie Bibliotheken von Drittanbietern verwenden, wo Namenskonflikte bei Klassen entstehen könnten.

Stellen Sie sich vor, Sie haben eine Klasse namens `Auto` in Ihrem Projekt und möchten sie in einem Namensraum namens `Transport` platzieren. Sie tun dies wie folgt:

```php
namespace Transport;

class Auto
{
	function hupen()
	{
		echo 'Bip bip!';
	}
}
```

Wenn Sie die Klasse `Auto` in einer anderen Datei verwenden möchten, müssen Sie angeben, aus welchem Namensraum die Klasse stammt:

```php
$auto = new Transport\Auto;
```

Zur Vereinfachung können Sie am Anfang der Datei angeben, welche Klasse aus dem gegebenen Namensraum Sie verwenden möchten, was die Erstellung von Instanzen ermöglicht, ohne die Notwendigkeit, den gesamten Pfad anzugeben:

```php
use Transport\Auto;

$auto = new Auto;
```


Vererbung
---------

Vererbung ist ein Werkzeug der objektorientierten Programmierung, das die Erstellung neuer Klassen basierend auf bereits existierenden Klassen ermöglicht, deren Eigenschaften und Methoden zu übernehmen und sie nach Bedarf zu erweitern oder neu zu definieren. Vererbung ermöglicht die Sicherstellung der Wiederverwendbarkeit von Code und einer Klassenhierarchie.

Vereinfacht gesagt, wenn wir eine Klasse haben und eine weitere davon abgeleitete erstellen möchten, aber mit einigen Änderungen, können wir die neue Klasse von der ursprünglichen Klasse "erben".

In PHP realisieren wir Vererbung mit dem Schlüsselwort `extends`.

Unsere Klasse `Person` speichert Informationen über das Alter. Wir können eine weitere Klasse `Student` haben, die `Person` erweitert und Informationen über das Studienfach hinzufügt.

Schauen wir uns ein Beispiel an:

```php
class Person
{
	private $alter;

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

	function gibInformationenAus()
	{
		echo "Alter: {$this->alter} Jahre\n";
	}
}

class Student extends Person
{
	private $studienfach;

	function __construct($alter, $studienfach)
	{
		parent::__construct($alter);
		$this->studienfach = $studienfach;
	}

	function gibInformationenAus()
	{
		parent::gibInformationenAus();
		echo "Studienfach: {$this->studienfach} \n";
	}
}

$student = new Student(20, 'Informatik');
$student->gibInformationenAus();
```

Wie funktioniert dieser Code?

- Wir haben das Schlüsselwort `extends` verwendet, um die Klasse `Person` zu erweitern, was bedeutet, dass die Klasse `Student` alle Methoden und Eigenschaften von `Person` erbt.

- Das Schlüsselwort `parent::` ermöglicht es uns, Methoden aus der übergeordneten Klasse aufzurufen. In diesem Fall haben wir den Konstruktor aus der Klasse `Person` aufgerufen, bevor wir eigene Funktionalität zur Klasse `Student` hinzugefügt haben. Und ähnlich auch die Methode `gibInformationenAus()` des Vorfahren vor der Ausgabe der Informationen über den Studenten.

Vererbung ist für Situationen gedacht, in denen eine "ist-ein"-Beziehung zwischen Klassen besteht. Zum Beispiel ist ein `Student` eine `Person`. Eine Katze ist ein Tier. Es gibt uns die Möglichkeit, in Fällen, in denen wir im Code ein Objekt (z.B. "Person") erwarten, stattdessen ein geerbtes Objekt (z.B. "Student") zu verwenden.

Es ist wichtig zu erkennen, dass der Hauptzweck der Vererbung **nicht** darin besteht, Code-Duplizierung zu verhindern. Im Gegenteil, die falsche Verwendung von Vererbung kann zu komplexem und schwer wartbarem Code führen. Wenn keine "ist-ein"-Beziehung zwischen den Klassen besteht, sollten wir anstelle von Vererbung Komposition in Betracht ziehen.

Beachten Sie, dass die Methoden `gibInformationenAus()` in den Klassen `Person` und `Student` leicht unterschiedliche Informationen ausgeben. Und wir können weitere Klassen hinzufügen (zum Beispiel `Angestellter`), die weitere Implementierungen dieser Methode bereitstellen werden. Die Fähigkeit von Objekten verschiedener Klassen, auf dieselbe Methode unterschiedlich zu reagieren, wird Polymorphismus genannt:

```php
$personen = [
	new Person(30),
	new Student(20, 'Informatik'),
	new Angestellter(45, 'Direktor'),
];

foreach ($personen as $person) {
	$person->gibInformationenAus();
}
```


Komposition
-----------

Komposition ist eine Technik, bei der anstatt die Eigenschaften und Methoden einer anderen Klasse zu erben, wir einfach ihre Instanz in unserer Klasse verwenden. Dies ermöglicht es uns, Funktionalitäten und Eigenschaften mehrerer Klassen zu kombinieren, ohne komplexe Vererbungsstrukturen erstellen zu müssen.

Schauen wir uns ein Beispiel an. Wir haben eine Klasse `Motor` und eine Klasse `Auto`. Anstatt zu sagen "Auto ist ein Motor", sagen wir "Auto hat einen Motor", was eine typische Kompositionsbeziehung ist.

```php
class Motor
{
	function starten()
	{
		echo 'Motor läuft.';
	}
}

class Auto
{
	private $motor;

	function __construct()
	{
		$this->motor = new Motor;
	}

	function starten()
	{
		$this->motor->starten();
		echo 'Auto ist fahrbereit!';
	}
}

$auto = new Auto;
$auto->starten();
```

Hier hat `Auto` nicht alle Eigenschaften und Methoden von `Motor`, aber es hat Zugriff darauf über die Eigenschaft `$motor`.

Der Vorteil der Komposition ist eine größere Flexibilität im Design und eine bessere Möglichkeit für zukünftige Anpassungen.


Sichtbarkeit
------------

In PHP können Sie die "Sichtbarkeit" für Eigenschaften, Methoden und Konstanten einer Klasse definieren. Sichtbarkeit bestimmt, von wo aus Sie auf diese Elemente zugreifen können.

1. **Public:** Wenn ein Element als `public` gekennzeichnet ist, bedeutet das, dass Sie von überall darauf zugreifen können, auch außerhalb der Klasse.

2. **Protected:** Ein Element mit der Kennzeichnung `protected` ist nur innerhalb der gegebenen Klasse und all ihrer Nachkommen (Klassen, die von dieser Klasse erben) zugänglich.

3. **Private:** Wenn ein Element `private` ist, können Sie nur innerhalb der Klasse darauf zugreifen, in der es definiert wurde.

Wenn Sie die Sichtbarkeit nicht angeben, setzt PHP sie automatisch auf `public`.

Schauen wir uns Beispielcode an:

```php
class Sichtbarkeitsbeispiel
{
	public $oeffentlicheEigenschaft = 'Öffentlich';
	protected $geschuetzteEigenschaft = 'Geschützt';
	private $privateEigenschaft = 'Privat';

	public function gibEigenschaftenAus()
	{
		echo $this->oeffentlicheEigenschaft;
		echo $this->geschuetzteEigenschaft;
		echo $this->privateEigenschaft; // Funktioniert
	}
}

$objekt = new Sichtbarkeitsbeispiel;
$objekt->gibEigenschaftenAus();
echo $objekt->oeffentlicheEigenschaft;
// echo $objekt->geschuetzteEigenschaft;  // Wirft einen Fehler
// echo $objekt->privateEigenschaft;  // Wirft einen Fehler
```

Wir fahren mit der Vererbung der Klasse fort:

```php
class NachkommenKlasse extends Sichtbarkeitsbeispiel
{
	public function gibEigenschaftenAus()
	{
		echo $this->oeffentlicheEigenschaft;   // Funktioniert
		echo $this->geschuetzteEigenschaft;  // Funktioniert
		// echo $this->privateEigenschaft;  // Wirft einen Fehler
	}
}
```

In diesem Fall kann die Methode `gibEigenschaftenAus()` in der Klasse `NachkommenKlasse` auf öffentliche und geschützte Eigenschaften zugreifen, aber nicht auf private Eigenschaften der Elternklasse.

Daten und Methoden sollten so weit wie möglich verborgen sein und nur über eine definierte Schnittstelle zugänglich sein. Dies ermöglicht es Ihnen, die interne Implementierung der Klasse zu ändern, ohne den Rest des Codes zu beeinflussen.


Das Schlüsselwort `final`
-------------------------

In PHP können wir das Schlüsselwort `final` verwenden, wenn wir verhindern wollen, dass eine Klasse, Methode oder Konstante geerbt oder überschrieben wird. Wenn wir eine Klasse als `final` kennzeichnen, kann sie nicht erweitert werden. Wenn wir eine Methode als `final` kennzeichnen, kann sie in einer Kindklasse nicht überschrieben werden.

Das Wissen, dass eine bestimmte Klasse oder Methode nicht weiter modifiziert wird, ermöglicht es uns, Änderungen leichter durchzuführen, ohne mögliche Konflikte befürchten zu müssen. Zum Beispiel können wir eine neue Methode hinzufügen, ohne Sorge, dass einer ihrer Nachkommen bereits eine gleichnamige Methode hat und es zu einer Kollision kommen würde. Oder wir können die Parameter der Methode ändern, da wiederum keine Gefahr besteht, eine Inkonsistenz mit der überschriebenen Methode im Nachkommen zu verursachen.

```php
final class FinaleKlasse
{
}

// Der folgende Code löst einen Fehler aus, weil wir nicht von einer finalen Klasse erben können.
class NachkommenDerFinalenKlasse extends FinaleKlasse
{
}
```

In diesem Beispiel löst der Versuch, von der finalen Klasse `FinaleKlasse` zu erben, einen Fehler aus.


Statische Eigenschaften und Methoden
------------------------------------

Wenn wir in PHP von "statischen" Elementen einer Klasse sprechen, meinen wir Methoden und Eigenschaften, die zur Klasse selbst gehören, und nicht zu einer bestimmten Instanz dieser Klasse. Das bedeutet, dass Sie keine Instanz der Klasse erstellen müssen, um darauf zugreifen zu können. Stattdessen rufen Sie sie auf oder greifen darauf zu direkt über den Klassennamen.

Beachten Sie, dass statische Elemente zur Klasse gehören, und nicht zu ihren Instanzen, können Sie innerhalb statischer Methoden nicht die Pseudovariable `$this` verwenden.

Die Verwendung statischer Eigenschaften führt zu [unübersichtlichem Code voller Fallstricke|dependency-injection:global-state], deshalb sollten Sie sie niemals verwenden und wir werden hier auch kein Anwendungsbeispiel zeigen. Im Gegensatz dazu sind statische Methoden nützlich. Anwendungsbeispiel:

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

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

// Verwendung einer statischen Methode ohne Erstellung einer Klasseninstanz
echo Rechner::addition(5, 3); // Ergebnis: 8
echo Rechner::subtraktion(5, 3); // Ergebnis: 2
```

In diesem Beispiel haben wir die Klasse `Rechner` mit zwei statischen Methoden erstellt. Diese Methoden können wir direkt ohne eine Instanz der Klasse zu erstellen mit dem `::` Operator aufrufen. Statische Methoden sind besonders nützlich für Operationen, die nicht vom Zustand einer bestimmten Instanz der Klasse abhängen.


Klassenkonstanten
-----------------

Innerhalb von Klassen haben wir die Möglichkeit, Konstanten zu definieren. Konstanten sind Werte, die sich während der Programmausführung niemals ändern. Im Gegensatz zu Variablen bleibt der Wert einer Konstante immer gleich.

```php
class Auto
{
	public const AnzahlRaeder = 4;

	public function zeigeAnzahlRaeder(): int
	{
		echo self::AnzahlRaeder;
	}
}

echo Auto::AnzahlRaeder;  // Ausgabe: 4
```

In diesem Beispiel haben wir eine Klasse `Auto` mit der Konstante `AnzahlRaeder`. Wenn wir auf die Konstante innerhalb der Klasse zugreifen möchten, können wir das Schlüsselwort `self` anstelle des Klassennamens verwenden.


Objekt-Schnittstellen
---------------------

Objekt-Schnittstellen (Interfaces) funktionieren wie "Verträge" für Klassen. Wenn eine Klasse eine Objektschnittstelle implementieren soll, muss sie alle Methoden enthalten, die diese Schnittstelle definiert. Es ist eine großartige Möglichkeit sicherzustellen, dass bestimmte Klassen denselben "Vertrag" oder dieselbe Struktur einhalten.

In PHP wird eine Schnittstelle mit dem Schlüsselwort `interface` definiert. Alle in der Schnittstelle definierten Methoden sind öffentlich (`public`). Wenn eine Klasse eine Schnittstelle implementiert, verwendet sie das Schlüsselwort `implements`.

```php
interface Tier
{
	function gibLaut();
}

class Katze implements Tier
{
	public function gibLaut()
	{
		echo 'Miau';
	}
}

$katze = new Katze;
$katze->gibLaut();
```

Wenn eine Klasse eine Schnittstelle implementiert, aber nicht alle erwarteten Methoden darin definiert sind, wirft PHP einen Fehler.

Eine Klasse kann mehrere Schnittstellen gleichzeitig implementieren, was ein Unterschied zur Vererbung ist, wo eine Klasse nur von einer Klasse erben kann:

```php
interface Wachhund
{
	function bewacheHaus();
}

class Hund implements Tier, Wachhund
{
	public function gibLaut()
	{
		echo 'Wuff';
	}

	public function bewacheHaus()
	{
		echo 'Hund bewacht aufmerksam das Haus';
	}
}
```


Abstrakte Klassen
-----------------

Abstrakte Klassen dienen als grundlegende Vorlagen für andere Klassen, aber Sie können ihre Instanzen nicht direkt erstellen. Sie enthalten eine Kombination aus vollständigen Methoden und abstrakten Methoden, die keinen definierten Inhalt haben. Klassen, die von abstrakten Klassen erben, müssen Definitionen für alle abstrakten Methoden des Vorfahren bereitstellen.

Zum Definieren einer abstrakten Klasse verwenden wir das Schlüsselwort `abstract`.

```php
abstract class AbstrakteKlasse
{
	public function gewoehnlicheMethode()
	{
		echo 'Dies ist eine gewöhnliche Methode';
	}

	abstract public function abstrakteMethode();
}

class Nachkomme extends AbstrakteKlasse
{
	public function abstrakteMethode()
	{
		echo 'Dies ist die Implementierung der abstrakten Methode';
	}
}

$instanz = new Nachkomme;
$instanz->gewoehnlicheMethode();
$instanz->abstrakteMethode();
```

In diesem Beispiel haben wir eine abstrakte Klasse mit einer gewöhnlichen und einer abstrakten Methode. Dann haben wir die Klasse `Nachkomme`, die von `AbstrakteKlasse` erbt und eine Implementierung für die abstrakte Methode bereitstellt.

Wie unterscheiden sich eigentlich Schnittstellen und abstrakte Klassen? Abstrakte Klassen können sowohl abstrakte als auch konkrete Methoden enthalten, während Schnittstellen nur definieren, welche Methoden eine Klasse implementieren muss, aber keine Implementierung bereitstellen. Eine Klasse kann nur von einer abstrakten Klasse erben, aber beliebig viele Schnittstellen implementieren.


Typüberprüfung
--------------

In der Programmierung ist es sehr wichtig, sicherzustellen, dass die Daten, mit denen wir arbeiten, vom richtigen Typ sind. In PHP haben wir Werkzeuge, die uns dies gewährleisten. Die Überprüfung, ob Daten den richtigen Typ haben, wird "Typüberprüfung" (Type Hinting) genannt.

Typen, auf die wir in PHP stoßen können:

1. **Grundtypen**: Umfassen `int` (Ganzzahlen), `float` (Gleitkommazahlen), `bool` (Wahrheitswerte), `string` (Zeichenketten), `array` (Arrays) und `null`.
2. **Klassen**: Wenn wir möchten, dass ein Wert eine Instanz einer bestimmten Klasse ist.
3. **Interfaces**: Definiert eine Reihe von Methoden, die eine Klasse implementieren muss. Ein Wert, der die Schnittstelle erfüllt, muss diese Methoden haben.
4. **Union Types**: Wir können festlegen, dass eine Variable mehrere erlaubte Typen haben kann.
5. **Void**: Dieser spezielle Typ gibt an, dass eine Funktion oder Methode keinen Wert zurückgibt.

Lassen Sie uns zeigen, wie man den Code anpasst, um Typen einzuschließen:

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

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

	public function gibAlterAus(): void
	{
		echo "Diese Person ist {$this->alter} Jahre alt.";
	}
}

/**
 * Funktion, die ein Objekt der Klasse Person akzeptiert und das Alter der Person ausgibt.
 */
function gibAlterDerPersonAus(Person $person): void
{
	$person->gibAlterAus();
}
```

Auf diese Weise haben wir sichergestellt, dass unser Code Daten des richtigen Typs erwartet und verarbeitet, was uns hilft, potenzielle Fehler zu vermeiden.

Einige Typen können in PHP nicht direkt geschrieben werden. In diesem Fall werden sie in einem phpDoc-Kommentar angegeben, was das Standardformat für die Dokumentation von PHP-Code ist, beginnend mit `/**` und endend mit `*/`. Ermöglicht das Hinzufügen von Beschreibungen zu Klassen, Methoden usw. Und auch das Angeben komplexer Typen mithilfe sogenannter Annotationen `@var`, `@param` und `@return`. Diese Typen werden dann von Werkzeugen zur statischen Code-Analyse verwendet, aber PHP selbst überprüft sie nicht.

```php
class Liste
{
	/** @var array<Person> die Notation besagt, dass es sich um ein Array von Person-Objekten handelt */
	private array $personen = [];

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


Vergleich und Identität
-----------------------

In PHP können Sie Objekte auf zwei Arten vergleichen:

1. Wertvergleich `==`: Überprüft, ob die Objekte derselben Klasse angehören und dieselben Werte in ihren Eigenschaften haben.
2. Identität `===`: Überprüft, ob es sich um dieselbe Objektinstanz handelt.

```php
class Auto
{
	public string $marke;

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

$auto1 = new Auto('Skoda');
$auto2 = new Auto('Skoda');
$auto3 = $auto1;

var_dump($auto1 == $auto2);   // true, weil sie denselben Wert haben
var_dump($auto1 === $auto2);  // false, weil sie nicht dieselbe Instanz sind
var_dump($auto1 === $auto3);  // true, weil $auto3 dieselbe Instanz wie $auto1 ist
```


Der `instanceof`-Operator
-------------------------

Der `instanceof`-Operator ermöglicht die Feststellung, ob ein gegebenes Objekt eine Instanz einer bestimmten Klasse ist, eines Nachkommen dieser Klasse, oder ob es eine bestimmte Schnittstelle implementiert.

Stellen wir uns vor, wir haben eine Klasse `Person` und eine weitere Klasse `Student`, die ein Nachkomme der Klasse `Person` ist:

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

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

class Student extends Person
{
	private string $studienfach;

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

$student = new Student(20, 'Informatik');

// Überprüfung, ob $student eine Instanz der Klasse Student ist
var_dump($student instanceof Student);  // Ausgabe: bool(true)

// Überprüfung, ob $student eine Instanz der Klasse Person ist (da Student ein Nachkomme von Person ist)
var_dump($student instanceof Person);     // Ausgabe: bool(true)
```

Aus den Ausgaben ist ersichtlich, dass das Objekt `$student` gleichzeitig als Instanz beider Klassen betrachtet wird - `Student` und `Person`.


Fluent Interfaces
-----------------

"Fluent Interface" (englisch "Fluent Interface") ist eine Technik in OOP, die es ermöglicht, Methoden in einem einzigen Aufruf zu verketten. Dadurch wird der Code oft vereinfacht und übersichtlicher.

Das Schlüsselelement einer Fluent Interface ist, dass jede Methode in der Kette eine Referenz auf das aktuelle Objekt zurückgibt. Dies erreichen wir, indem wir am Ende der Methode `return $this;` verwenden. Dieser Programmierstil wird oft mit Methoden verbunden, die "Setter" genannt werden, die die Werte von Objekteigenschaften setzen.

Wir zeigen, wie eine Fluent Interface aussehen kann am Beispiel des E-Mail-Versands:

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

In diesem Beispiel dienen die Methoden `setFrom()`, `setRecipient()` und `setMessage()` zum Setzen der entsprechenden Werte (Absender, Empfänger, Nachrichteninhalt). Nach dem Setzen jedes dieser Werte geben uns die Methoden das aktuelle Objekt (`$email`) zurück, was es uns ermöglicht, eine weitere Methode daran zu ketten. Schließlich rufen wir die Methode `send()` auf, die die E-Mail tatsächlich sendet.

Dank Fluent Interfaces können wir Code schreiben, der intuitiv und leicht lesbar ist.


Kopieren mit `clone`
--------------------

In PHP können wir eine Kopie eines Objekts mit dem `clone`-Operator erstellen. Auf diese Weise erhalten wir eine neue Instanz mit identischem Inhalt.

Wenn wir beim Kopieren eines Objekts einige seiner Eigenschaften ändern müssen, können wir in der Klasse eine spezielle Methode `__clone()` definieren. Diese Methode wird automatisch aufgerufen, wenn das Objekt geklont wird.

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

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

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

$original = new Schaf('Dolly');
echo $original->name . "\n";  // Gibt aus: Dolly

$klon = clone $original;
echo $klon->name . "\n";      // Gibt aus: Klon Dolly
```

In diesem Beispiel haben wir eine Klasse `Schaf` mit einer Eigenschaft `$name`. Wenn wir eine Instanz dieser Klasse klonen, kümmert sich die Methode `__clone()` darum, dass der Name des geklonten Schafs das Präfix "Klon" erhält.


Traits
------

Traits in PHP sind ein Werkzeug, das es ermöglicht, Methoden, Eigenschaften und Konstanten zwischen Klassen zu teilen und Code-Duplizierung zu verhindern. Sie können sie sich als einen "Kopieren und Einfügen"-Mechanismus (Strg-C und Strg-V) vorstellen, bei dem der Inhalt des Traits in Klassen "eingefügt" wird. Dies ermöglicht es Ihnen, Code wiederzuverwenden, ohne komplizierte Klassenhierarchien erstellen zu müssen.

Lassen Sie uns ein einfaches Beispiel zeigen, wie man Traits in PHP verwendet:

```php
trait Hupen
{
	public function hupen()
	{
		echo 'Bip bip!';
	}
}

class Auto
{
	use Hupen;
}

class LKW
{
	use Hupen;
}

$auto = new Auto;
$auto->hupen(); // Gibt 'Bip bip!' aus

$lkw = new LKW;
$lkw->hupen(); // Gibt ebenfalls 'Bip bip!' aus
```

In diesem Beispiel haben wir ein Trait namens `Hupen`, das eine Methode `hupen()` enthält. Dann haben wir zwei Klassen: `Auto` und `LKW`, die beide das Trait `Hupen` verwenden. Dadurch "haben" beide Klassen die Methode `hupen()`, und wir können sie auf Objekten beider Klassen aufrufen.

Traits ermöglichen es Ihnen, Code einfach und effizient zwischen Klassen zu teilen. Dabei treten sie nicht in die Vererbungshierarchie ein, d.h. `$auto instanceof Hupen` gibt `false` zurück.


Ausnahmen
---------

Ausnahmen (Exceptions) in OOP ermöglichen es uns, Fehler und unerwartete Situationen in unserem Code elegant zu behandeln. Es sind Objekte, die Informationen über einen Fehler oder eine ungewöhnliche Situation tragen.

In PHP haben wir die eingebaute Klasse `Exception`, die als Basis für alle Ausnahmen dient. Sie hat mehrere Methoden, die es uns ermöglichen, mehr Informationen über die Ausnahme zu erhalten, wie die Fehlermeldung, Datei und Zeile, in der der Fehler aufgetreten ist, usw.

Wenn im Code ein Fehler auftritt, können wir eine Ausnahme mit dem Schlüsselwort `throw` "werfen".

```php
function teilung(float $a, float $b): float
{
	if ($b === 0.0) { // $b === 0
		throw new Exception('Division durch Null!');
	}
	return $a / $b;
}
```

Wenn die Funktion `teilung()` als zweites Argument Null erhält, wirft sie eine Ausnahme mit der Fehlermeldung `'Division durch Null!'`. Um einen Programmabsturz beim Werfen einer Ausnahme zu verhindern, fangen wir sie in einem `try/catch`-Block ab:

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

Code, der eine Ausnahme werfen kann, ist in einen `try`-Block eingeschlossen. Wenn eine Ausnahme geworfen wird, verschiebt sich die Codeausführung in den `catch`-Block, wo wir die Ausnahme behandeln können (z.B. die Fehlermeldung ausgeben).

Nach den `try`- und `catch`-Blöcken können wir einen optionalen `finally`-Block hinzufügen, der immer ausgeführt wird, egal ob eine Ausnahme geworfen wurde oder nicht (sogar wenn wir im `try`- oder `catch`-Block die Anweisung `return`, `break` oder `continue` verwenden):

```php
try {
	echo teilung(10, 0);
} catch (Exception $e) {
	echo 'Ausnahme abgefangen: '. $e->getMessage();
} finally {
	// Code, der immer ausgeführt wird, egal ob eine Ausnahme geworfen wurde oder nicht
}
```

Wir können auch eigene Klassen (Hierarchie) von Ausnahmen erstellen, die von der Klasse Exception erben. Als Beispiel stellen wir uns eine einfache Bankanwendung vor, die Ein- und Auszahlungen ermöglicht:

```php
class Bankausnahme extends Exception {}
class UnzureichendeDeckungAusnahme extends Bankausnahme {}
class LimitUeberschrittenAusnahme extends Bankausnahme {}

class Bankkonto
{
	private int $saldo = 0;
	private int $tageslimit = 1000;

	public function einzahlen(int $betrag): int
	{
		$this->saldo += $betrag;
		return $this->saldo;
	}

	public function abheben(int $betrag): int
	{
		if ($betrag > $this->saldo) {
			throw new UnzureichendeDeckungAusnahme('Nicht genügend Geld auf dem Konto.');
		}

		if ($betrag > $this->tageslimit) {
			throw new LimitUeberschrittenAusnahme('Das Tageslimit für Abhebungen wurde überschritten.');
		}

		$this->saldo -= $betrag;
		return $this->saldo;
	}
}
```

Für einen `try`-Block können mehrere `catch`-Blöcke angegeben werden, wenn Sie verschiedene Arten von Ausnahmen erwarten.

```php
$konto = new Bankkonto;
$konto->einzahlen(500);

try {
	$konto->abheben(1500);
} catch (LimitUeberschrittenAusnahme $e) {
	echo $e->getMessage();
} catch (UnzureichendeDeckungAusnahme $e) {
	echo $e->getMessage();
} catch (Bankausnahme $e) {
	echo 'Beim Ausführen der Operation ist ein Fehler aufgetreten.';
}
```

In diesem Beispiel ist die Reihenfolge der `catch`-Blöcke wichtig zu beachten. Da alle Ausnahmen von `Bankausnahme` erben, wenn wir diesen Block zuerst hätten, würden alle Ausnahmen darin gefangen werden, ohne dass der Code zu den folgenden `catch`-Blöcken gelangen würde. Daher ist es wichtig, spezifischere Ausnahmen (d.h. solche, die von anderen erben) im `catch`-Block weiter oben in der Reihenfolge als ihre Eltern-Ausnahmen zu haben.


Iteration
---------

In PHP können Sie Objekte mit einer `foreach`-Schleife durchlaufen, ähnlich wie Sie Arrays durchlaufen. Damit das funktioniert, muss das Objekt spezielle Schnittstellen implementieren.

Die erste Möglichkeit ist die Implementierung der `Iterator`-Schnittstelle, die Methoden hat: `current()` gibt den aktuellen Wert zurück, `key()` gibt den Schlüssel zurück, `next()` geht zum nächsten Wert über, `rewind()` geht zum Anfang zurück und `valid()` prüft, ob wir noch nicht am Ende sind.

Die zweite Möglichkeit ist die Implementierung der `IteratorAggregate`-Schnittstelle, die nur eine Methode `getIterator()` hat. Diese gibt entweder ein Ersatzobjekt zurück, das die Iteration sicherstellt, oder sie kann einen Generator darstellen, was eine spezielle Funktion ist, in der `yield` verwendet wird, um Schlüssel und Werte nacheinander zurückzugeben:

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

class Liste implements IteratorAggregate
{
	private array $personen = [];

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

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

$liste = new Liste;
$liste->fuegePersonHinzu(new Person(30));
$liste->fuegePersonHinzu(new Person(25));

foreach ($liste as $person) {
	echo "Alter: {$person->alter} Jahre \n";
}
```


Best Practices
--------------

Wenn Sie die grundlegenden Prinzipien von OOP hinter sich haben, ist es wichtig, sich auf die richtigen Praktiken in OOP zu konzentrieren. Diese helfen Ihnen, Code zu schreiben, der nicht nur funktional ist, sondern auch lesbar, verständlich und leicht wartbar.

1) **Trennung der Belange (Separation of Concerns)**: Jede Klasse sollte eine klar definierte Verantwortung haben und sollte nur eine Hauptaufgabe lösen. Wenn eine Klasse zu viele Dinge tut, kann es sinnvoll sein, sie in kleinere, spezialisierte Klassen aufzuteilen.
2) **Kapselung (Encapsulation)**: Daten und Methoden sollten so weit wie möglich verborgen sein und nur über eine definierte Schnittstelle zugänglich sein. Dies ermöglicht es Ihnen, die interne Implementierung der Klasse zu ändern, ohne den Rest des Codes zu beeinflussen.
3) **Dependency Injection**: Anstatt Abhängigkeiten direkt in der Klasse zu erstellen, sollten Sie sie von außen "injizieren". Für ein tieferes Verständnis dieses Prinzips empfehlen wir die [Kapitel über Dependency Injection|dependency-injection:introduction].

Einführung in die objektorientierte Programmierung

Der Begriff „OOP“ bezeichnet die objektorientierte Programmierung, was eine Methode ist, Code zu organisieren und zu strukturieren. OOP ermöglicht es uns, ein Programm als eine Sammlung von Objekten zu sehen, die miteinander kommunizieren, anstelle einer Abfolge von Befehlen und Funktionen.

In OOP ist ein „Objekt“ eine Einheit, die Daten und Funktionen enthält, die mit diesen Daten arbeiten. Objekte werden nach „Klassen“ erstellt, die wir als Entwürfe oder Vorlagen für Objekte verstehen können. Wenn wir eine Klasse haben, können wir ihre „Instanz“ erstellen, was ein konkretes Objekt ist, das nach dieser Klasse erstellt wurde.

Lassen Sie uns zeigen, wie wir eine einfache Klasse in PHP erstellen können. Beim Definieren einer Klasse verwenden wir das Schlüsselwort „class“, gefolgt vom Klassennamen und dann geschweiften Klammern, die Funktionen (sie werden „Methoden“ genannt) und Klassenvariablen (sie werden „Eigenschaften“ oder englisch „property“ genannt) umschließen:

class Auto
{
	function hupen()
	{
		echo 'Bip bip!';
	}
}

In diesem Beispiel haben wir eine Klasse namens Auto mit einer Funktion (oder „Methode“) namens hupen erstellt.

Jede Klasse sollte nur eine Hauptaufgabe lösen. Wenn eine Klasse zu viele Dinge tut, kann es sinnvoll sein, sie in kleinere, spezialisierte Klassen aufzuteilen.

Klassen speichern wir normalerweise in separaten Dateien, damit der Code organisiert ist und man sich leicht darin zurechtfindet. Der Dateiname sollte dem Klassennamen entsprechen, also wäre für die Klasse Auto der Dateiname Auto.php.

Bei der Benennung von Klassen ist es gut, sich an die Konvention „PascalCase“ zu halten, was bedeutet, dass jedes Wort im Namen mit einem Großbuchstaben beginnt und es keine Unterstriche oder andere Trennzeichen dazwischen gibt. Methoden und Eigenschaften verwenden die Konvention „camelCase“, das bedeutet, dass sie mit einem Kleinbuchstaben beginnen.

Einige Methoden in PHP haben spezielle Aufgaben und sind mit dem Präfix __ (zwei Unterstriche) gekennzeichnet. Eine der wichtigsten speziellen Methoden ist der „Konstruktor“, der als __construct gekennzeichnet ist. Der Konstruktor ist eine Methode, die automatisch aufgerufen wird, wenn Sie eine neue Instanz der Klasse erstellen.

Den Konstruktor verwenden wir oft, um den Anfangszustand des Objekts festzulegen. Zum Beispiel, wenn Sie ein Objekt erstellen, das eine Person repräsentiert, können Sie den Konstruktor verwenden, um ihr Alter, ihren Namen oder andere Eigenschaften einzustellen.

Lassen Sie uns zeigen, wie man den Konstruktor in PHP verwendet:

class Person
{
	private $alter;

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

	function wieAltBistDu()
	{
		return $this->alter;
	}
}

$person = new Person(25);
echo $person->wieAltBistDu(); // Ausgabe: 25

In diesem Beispiel hat die Klasse Person die Eigenschaft (Variable) $alter und weiterhin einen Konstruktor, der diese Eigenschaft setzt. Die Methode wieAltBistDu() ermöglicht dann den Zugriff auf das Alter der Person.

Die Pseudovariable $this wird innerhalb der Klasse verwendet, für den Zugriff auf Eigenschaften und Methoden des Objekts.

Das Schlüsselwort new wird verwendet, um eine neue Instanz der Klasse zu erstellen. Im obigen Beispiel haben wir eine neue Person mit dem Alter 25 erstellt.

Sie können auch Standardwerte für Konstruktorparameter festlegen, wenn sie bei der Objekterstellung nicht angegeben werden. Zum Beispiel:

class Person
{
	private $alter;

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

	function wieAltBistDu()
	{
		return $this->alter;
	}
}

$person = new Person;  // wenn wir kein Argument übergeben, können die Klammern weggelassen werden
echo $person->wieAltBistDu(); // Ausgabe: 20

In diesem Beispiel, wenn Sie das Alter nicht angeben beim Erstellen des Person-Objekts, wird der Standardwert 20 verwendet.

Angenehm ist, dass die Definition der Eigenschaft mit ihrer Initialisierung über den Konstruktor sich so verkürzen und vereinfachen lässt:

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

Der Vollständigkeit halber können Objekte neben Konstruktoren auch Destruktoren haben (Methode __destruct), die aufgerufen werden, bevor das Objekt aus dem Speicher freigegeben wird.

Namensräume

Namensräume (oder „namespaces“ auf Englisch) ermöglichen es uns, zusammengehörige Klassen, Funktionen und Konstanten zu organisieren und zu gruppieren, und gleichzeitig Namenskonflikte zu vermeiden. Sie können sie sich wie Ordner auf einem Computer vorstellen, wo jeder Ordner Dateien enthält, die zu einem bestimmten Projekt oder Thema gehören.

Namensräume sind besonders nützlich in größeren Projekten oder wenn Sie Bibliotheken von Drittanbietern verwenden, wo Namenskonflikte bei Klassen entstehen könnten.

Stellen Sie sich vor, Sie haben eine Klasse namens Auto in Ihrem Projekt und möchten sie in einem Namensraum namens Transport platzieren. Sie tun dies wie folgt:

namespace Transport;

class Auto
{
	function hupen()
	{
		echo 'Bip bip!';
	}
}

Wenn Sie die Klasse Auto in einer anderen Datei verwenden möchten, müssen Sie angeben, aus welchem Namensraum die Klasse stammt:

$auto = new Transport\Auto;

Zur Vereinfachung können Sie am Anfang der Datei angeben, welche Klasse aus dem gegebenen Namensraum Sie verwenden möchten, was die Erstellung von Instanzen ermöglicht, ohne die Notwendigkeit, den gesamten Pfad anzugeben:

use Transport\Auto;

$auto = new Auto;

Vererbung

Vererbung ist ein Werkzeug der objektorientierten Programmierung, das die Erstellung neuer Klassen basierend auf bereits existierenden Klassen ermöglicht, deren Eigenschaften und Methoden zu übernehmen und sie nach Bedarf zu erweitern oder neu zu definieren. Vererbung ermöglicht die Sicherstellung der Wiederverwendbarkeit von Code und einer Klassenhierarchie.

Vereinfacht gesagt, wenn wir eine Klasse haben und eine weitere davon abgeleitete erstellen möchten, aber mit einigen Änderungen, können wir die neue Klasse von der ursprünglichen Klasse „erben“.

In PHP realisieren wir Vererbung mit dem Schlüsselwort extends.

Unsere Klasse Person speichert Informationen über das Alter. Wir können eine weitere Klasse Student haben, die Person erweitert und Informationen über das Studienfach hinzufügt.

Schauen wir uns ein Beispiel an:

class Person
{
	private $alter;

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

	function gibInformationenAus()
	{
		echo "Alter: {$this->alter} Jahre\n";
	}
}

class Student extends Person
{
	private $studienfach;

	function __construct($alter, $studienfach)
	{
		parent::__construct($alter);
		$this->studienfach = $studienfach;
	}

	function gibInformationenAus()
	{
		parent::gibInformationenAus();
		echo "Studienfach: {$this->studienfach} \n";
	}
}

$student = new Student(20, 'Informatik');
$student->gibInformationenAus();

Wie funktioniert dieser Code?

  • Wir haben das Schlüsselwort extends verwendet, um die Klasse Person zu erweitern, was bedeutet, dass die Klasse Student alle Methoden und Eigenschaften von Person erbt.
  • Das Schlüsselwort parent:: ermöglicht es uns, Methoden aus der übergeordneten Klasse aufzurufen. In diesem Fall haben wir den Konstruktor aus der Klasse Person aufgerufen, bevor wir eigene Funktionalität zur Klasse Student hinzugefügt haben. Und ähnlich auch die Methode gibInformationenAus() des Vorfahren vor der Ausgabe der Informationen über den Studenten.

Vererbung ist für Situationen gedacht, in denen eine „ist-ein“-Beziehung zwischen Klassen besteht. Zum Beispiel ist ein Student eine Person. Eine Katze ist ein Tier. Es gibt uns die Möglichkeit, in Fällen, in denen wir im Code ein Objekt (z.B. „Person“) erwarten, stattdessen ein geerbtes Objekt (z.B. „Student“) zu verwenden.

Es ist wichtig zu erkennen, dass der Hauptzweck der Vererbung nicht darin besteht, Code-Duplizierung zu verhindern. Im Gegenteil, die falsche Verwendung von Vererbung kann zu komplexem und schwer wartbarem Code führen. Wenn keine „ist-ein“-Beziehung zwischen den Klassen besteht, sollten wir anstelle von Vererbung Komposition in Betracht ziehen.

Beachten Sie, dass die Methoden gibInformationenAus() in den Klassen Person und Student leicht unterschiedliche Informationen ausgeben. Und wir können weitere Klassen hinzufügen (zum Beispiel Angestellter), die weitere Implementierungen dieser Methode bereitstellen werden. Die Fähigkeit von Objekten verschiedener Klassen, auf dieselbe Methode unterschiedlich zu reagieren, wird Polymorphismus genannt:

$personen = [
	new Person(30),
	new Student(20, 'Informatik'),
	new Angestellter(45, 'Direktor'),
];

foreach ($personen as $person) {
	$person->gibInformationenAus();
}

Komposition

Komposition ist eine Technik, bei der anstatt die Eigenschaften und Methoden einer anderen Klasse zu erben, wir einfach ihre Instanz in unserer Klasse verwenden. Dies ermöglicht es uns, Funktionalitäten und Eigenschaften mehrerer Klassen zu kombinieren, ohne komplexe Vererbungsstrukturen erstellen zu müssen.

Schauen wir uns ein Beispiel an. Wir haben eine Klasse Motor und eine Klasse Auto. Anstatt zu sagen „Auto ist ein Motor“, sagen wir „Auto hat einen Motor“, was eine typische Kompositionsbeziehung ist.

class Motor
{
	function starten()
	{
		echo 'Motor läuft.';
	}
}

class Auto
{
	private $motor;

	function __construct()
	{
		$this->motor = new Motor;
	}

	function starten()
	{
		$this->motor->starten();
		echo 'Auto ist fahrbereit!';
	}
}

$auto = new Auto;
$auto->starten();

Hier hat Auto nicht alle Eigenschaften und Methoden von Motor, aber es hat Zugriff darauf über die Eigenschaft $motor.

Der Vorteil der Komposition ist eine größere Flexibilität im Design und eine bessere Möglichkeit für zukünftige Anpassungen.

Sichtbarkeit

In PHP können Sie die „Sichtbarkeit“ für Eigenschaften, Methoden und Konstanten einer Klasse definieren. Sichtbarkeit bestimmt, von wo aus Sie auf diese Elemente zugreifen können.

  1. Public: Wenn ein Element als public gekennzeichnet ist, bedeutet das, dass Sie von überall darauf zugreifen können, auch außerhalb der Klasse.
  2. Protected: Ein Element mit der Kennzeichnung protected ist nur innerhalb der gegebenen Klasse und all ihrer Nachkommen (Klassen, die von dieser Klasse erben) zugänglich.
  3. Private: Wenn ein Element private ist, können Sie nur innerhalb der Klasse darauf zugreifen, in der es definiert wurde.

Wenn Sie die Sichtbarkeit nicht angeben, setzt PHP sie automatisch auf public.

Schauen wir uns Beispielcode an:

class Sichtbarkeitsbeispiel
{
	public $oeffentlicheEigenschaft = 'Öffentlich';
	protected $geschuetzteEigenschaft = 'Geschützt';
	private $privateEigenschaft = 'Privat';

	public function gibEigenschaftenAus()
	{
		echo $this->oeffentlicheEigenschaft;
		echo $this->geschuetzteEigenschaft;
		echo $this->privateEigenschaft; // Funktioniert
	}
}

$objekt = new Sichtbarkeitsbeispiel;
$objekt->gibEigenschaftenAus();
echo $objekt->oeffentlicheEigenschaft;
// echo $objekt->geschuetzteEigenschaft;  // Wirft einen Fehler
// echo $objekt->privateEigenschaft;  // Wirft einen Fehler

Wir fahren mit der Vererbung der Klasse fort:

class NachkommenKlasse extends Sichtbarkeitsbeispiel
{
	public function gibEigenschaftenAus()
	{
		echo $this->oeffentlicheEigenschaft;   // Funktioniert
		echo $this->geschuetzteEigenschaft;  // Funktioniert
		// echo $this->privateEigenschaft;  // Wirft einen Fehler
	}
}

In diesem Fall kann die Methode gibEigenschaftenAus() in der Klasse NachkommenKlasse auf öffentliche und geschützte Eigenschaften zugreifen, aber nicht auf private Eigenschaften der Elternklasse.

Daten und Methoden sollten so weit wie möglich verborgen sein und nur über eine definierte Schnittstelle zugänglich sein. Dies ermöglicht es Ihnen, die interne Implementierung der Klasse zu ändern, ohne den Rest des Codes zu beeinflussen.

Das Schlüsselwort final

In PHP können wir das Schlüsselwort final verwenden, wenn wir verhindern wollen, dass eine Klasse, Methode oder Konstante geerbt oder überschrieben wird. Wenn wir eine Klasse als final kennzeichnen, kann sie nicht erweitert werden. Wenn wir eine Methode als final kennzeichnen, kann sie in einer Kindklasse nicht überschrieben werden.

Das Wissen, dass eine bestimmte Klasse oder Methode nicht weiter modifiziert wird, ermöglicht es uns, Änderungen leichter durchzuführen, ohne mögliche Konflikte befürchten zu müssen. Zum Beispiel können wir eine neue Methode hinzufügen, ohne Sorge, dass einer ihrer Nachkommen bereits eine gleichnamige Methode hat und es zu einer Kollision kommen würde. Oder wir können die Parameter der Methode ändern, da wiederum keine Gefahr besteht, eine Inkonsistenz mit der überschriebenen Methode im Nachkommen zu verursachen.

final class FinaleKlasse
{
}

// Der folgende Code löst einen Fehler aus, weil wir nicht von einer finalen Klasse erben können.
class NachkommenDerFinalenKlasse extends FinaleKlasse
{
}

In diesem Beispiel löst der Versuch, von der finalen Klasse FinaleKlasse zu erben, einen Fehler aus.

Statische Eigenschaften und Methoden

Wenn wir in PHP von „statischen“ Elementen einer Klasse sprechen, meinen wir Methoden und Eigenschaften, die zur Klasse selbst gehören, und nicht zu einer bestimmten Instanz dieser Klasse. Das bedeutet, dass Sie keine Instanz der Klasse erstellen müssen, um darauf zugreifen zu können. Stattdessen rufen Sie sie auf oder greifen darauf zu direkt über den Klassennamen.

Beachten Sie, dass statische Elemente zur Klasse gehören, und nicht zu ihren Instanzen, können Sie innerhalb statischer Methoden nicht die Pseudovariable $this verwenden.

Die Verwendung statischer Eigenschaften führt zu unübersichtlichem Code voller Fallstricke, deshalb sollten Sie sie niemals verwenden und wir werden hier auch kein Anwendungsbeispiel zeigen. Im Gegensatz dazu sind statische Methoden nützlich. Anwendungsbeispiel:

class Rechner
{
	public static function addition($a, $b)
	{
		return $a + $b;
	}

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

// Verwendung einer statischen Methode ohne Erstellung einer Klasseninstanz
echo Rechner::addition(5, 3); // Ergebnis: 8
echo Rechner::subtraktion(5, 3); // Ergebnis: 2

In diesem Beispiel haben wir die Klasse Rechner mit zwei statischen Methoden erstellt. Diese Methoden können wir direkt ohne eine Instanz der Klasse zu erstellen mit dem :: Operator aufrufen. Statische Methoden sind besonders nützlich für Operationen, die nicht vom Zustand einer bestimmten Instanz der Klasse abhängen.

Klassenkonstanten

Innerhalb von Klassen haben wir die Möglichkeit, Konstanten zu definieren. Konstanten sind Werte, die sich während der Programmausführung niemals ändern. Im Gegensatz zu Variablen bleibt der Wert einer Konstante immer gleich.

class Auto
{
	public const AnzahlRaeder = 4;

	public function zeigeAnzahlRaeder(): int
	{
		echo self::AnzahlRaeder;
	}
}

echo Auto::AnzahlRaeder;  // Ausgabe: 4

In diesem Beispiel haben wir eine Klasse Auto mit der Konstante AnzahlRaeder. Wenn wir auf die Konstante innerhalb der Klasse zugreifen möchten, können wir das Schlüsselwort self anstelle des Klassennamens verwenden.

Objekt-Schnittstellen

Objekt-Schnittstellen (Interfaces) funktionieren wie „Verträge“ für Klassen. Wenn eine Klasse eine Objektschnittstelle implementieren soll, muss sie alle Methoden enthalten, die diese Schnittstelle definiert. Es ist eine großartige Möglichkeit sicherzustellen, dass bestimmte Klassen denselben „Vertrag“ oder dieselbe Struktur einhalten.

In PHP wird eine Schnittstelle mit dem Schlüsselwort interface definiert. Alle in der Schnittstelle definierten Methoden sind öffentlich (public). Wenn eine Klasse eine Schnittstelle implementiert, verwendet sie das Schlüsselwort implements.

interface Tier
{
	function gibLaut();
}

class Katze implements Tier
{
	public function gibLaut()
	{
		echo 'Miau';
	}
}

$katze = new Katze;
$katze->gibLaut();

Wenn eine Klasse eine Schnittstelle implementiert, aber nicht alle erwarteten Methoden darin definiert sind, wirft PHP einen Fehler.

Eine Klasse kann mehrere Schnittstellen gleichzeitig implementieren, was ein Unterschied zur Vererbung ist, wo eine Klasse nur von einer Klasse erben kann:

interface Wachhund
{
	function bewacheHaus();
}

class Hund implements Tier, Wachhund
{
	public function gibLaut()
	{
		echo 'Wuff';
	}

	public function bewacheHaus()
	{
		echo 'Hund bewacht aufmerksam das Haus';
	}
}

Abstrakte Klassen

Abstrakte Klassen dienen als grundlegende Vorlagen für andere Klassen, aber Sie können ihre Instanzen nicht direkt erstellen. Sie enthalten eine Kombination aus vollständigen Methoden und abstrakten Methoden, die keinen definierten Inhalt haben. Klassen, die von abstrakten Klassen erben, müssen Definitionen für alle abstrakten Methoden des Vorfahren bereitstellen.

Zum Definieren einer abstrakten Klasse verwenden wir das Schlüsselwort abstract.

abstract class AbstrakteKlasse
{
	public function gewoehnlicheMethode()
	{
		echo 'Dies ist eine gewöhnliche Methode';
	}

	abstract public function abstrakteMethode();
}

class Nachkomme extends AbstrakteKlasse
{
	public function abstrakteMethode()
	{
		echo 'Dies ist die Implementierung der abstrakten Methode';
	}
}

$instanz = new Nachkomme;
$instanz->gewoehnlicheMethode();
$instanz->abstrakteMethode();

In diesem Beispiel haben wir eine abstrakte Klasse mit einer gewöhnlichen und einer abstrakten Methode. Dann haben wir die Klasse Nachkomme, die von AbstrakteKlasse erbt und eine Implementierung für die abstrakte Methode bereitstellt.

Wie unterscheiden sich eigentlich Schnittstellen und abstrakte Klassen? Abstrakte Klassen können sowohl abstrakte als auch konkrete Methoden enthalten, während Schnittstellen nur definieren, welche Methoden eine Klasse implementieren muss, aber keine Implementierung bereitstellen. Eine Klasse kann nur von einer abstrakten Klasse erben, aber beliebig viele Schnittstellen implementieren.

Typüberprüfung

In der Programmierung ist es sehr wichtig, sicherzustellen, dass die Daten, mit denen wir arbeiten, vom richtigen Typ sind. In PHP haben wir Werkzeuge, die uns dies gewährleisten. Die Überprüfung, ob Daten den richtigen Typ haben, wird „Typüberprüfung“ (Type Hinting) genannt.

Typen, auf die wir in PHP stoßen können:

  1. Grundtypen: Umfassen int (Ganzzahlen), float (Gleitkommazahlen), bool (Wahrheitswerte), string (Zeichenketten), array (Arrays) und null.
  2. Klassen: Wenn wir möchten, dass ein Wert eine Instanz einer bestimmten Klasse ist.
  3. Interfaces: Definiert eine Reihe von Methoden, die eine Klasse implementieren muss. Ein Wert, der die Schnittstelle erfüllt, muss diese Methoden haben.
  4. Union Types: Wir können festlegen, dass eine Variable mehrere erlaubte Typen haben kann.
  5. Void: Dieser spezielle Typ gibt an, dass eine Funktion oder Methode keinen Wert zurückgibt.

Lassen Sie uns zeigen, wie man den Code anpasst, um Typen einzuschließen:

class Person
{
	private int $alter;

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

	public function gibAlterAus(): void
	{
		echo "Diese Person ist {$this->alter} Jahre alt.";
	}
}

/**
 * Funktion, die ein Objekt der Klasse Person akzeptiert und das Alter der Person ausgibt.
 */
function gibAlterDerPersonAus(Person $person): void
{
	$person->gibAlterAus();
}

Auf diese Weise haben wir sichergestellt, dass unser Code Daten des richtigen Typs erwartet und verarbeitet, was uns hilft, potenzielle Fehler zu vermeiden.

Einige Typen können in PHP nicht direkt geschrieben werden. In diesem Fall werden sie in einem phpDoc-Kommentar angegeben, was das Standardformat für die Dokumentation von PHP-Code ist, beginnend mit /** und endend mit */. Ermöglicht das Hinzufügen von Beschreibungen zu Klassen, Methoden usw. Und auch das Angeben komplexer Typen mithilfe sogenannter Annotationen @var@param und @return. Diese Typen werden dann von Werkzeugen zur statischen Code-Analyse verwendet, aber PHP selbst überprüft sie nicht.

class Liste
{
	/** @var array<Person> die Notation besagt, dass es sich um ein Array von Person-Objekten handelt */
	private array $personen = [];

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

Vergleich und Identität

In PHP können Sie Objekte auf zwei Arten vergleichen:

  1. Wertvergleich ==: Überprüft, ob die Objekte derselben Klasse angehören und dieselben Werte in ihren Eigenschaften haben.
  2. Identität ===: Überprüft, ob es sich um dieselbe Objektinstanz handelt.
class Auto
{
	public string $marke;

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

$auto1 = new Auto('Skoda');
$auto2 = new Auto('Skoda');
$auto3 = $auto1;

var_dump($auto1 == $auto2);   // true, weil sie denselben Wert haben
var_dump($auto1 === $auto2);  // false, weil sie nicht dieselbe Instanz sind
var_dump($auto1 === $auto3);  // true, weil $auto3 dieselbe Instanz wie $auto1 ist

Der instanceof-Operator

Der instanceof-Operator ermöglicht die Feststellung, ob ein gegebenes Objekt eine Instanz einer bestimmten Klasse ist, eines Nachkommen dieser Klasse, oder ob es eine bestimmte Schnittstelle implementiert.

Stellen wir uns vor, wir haben eine Klasse Person und eine weitere Klasse Student, die ein Nachkomme der Klasse Person ist:

class Person
{
	private int $alter;

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

class Student extends Person
{
	private string $studienfach;

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

$student = new Student(20, 'Informatik');

// Überprüfung, ob $student eine Instanz der Klasse Student ist
var_dump($student instanceof Student);  // Ausgabe: bool(true)

// Überprüfung, ob $student eine Instanz der Klasse Person ist (da Student ein Nachkomme von Person ist)
var_dump($student instanceof Person);     // Ausgabe: bool(true)

Aus den Ausgaben ist ersichtlich, dass das Objekt $student gleichzeitig als Instanz beider Klassen betrachtet wird – Student und Person.

Fluent Interfaces

„Fluent Interface“ (englisch „Fluent Interface“) ist eine Technik in OOP, die es ermöglicht, Methoden in einem einzigen Aufruf zu verketten. Dadurch wird der Code oft vereinfacht und übersichtlicher.

Das Schlüsselelement einer Fluent Interface ist, dass jede Methode in der Kette eine Referenz auf das aktuelle Objekt zurückgibt. Dies erreichen wir, indem wir am Ende der Methode return $this; verwenden. Dieser Programmierstil wird oft mit Methoden verbunden, die „Setter“ genannt werden, die die Werte von Objekteigenschaften setzen.

Wir zeigen, wie eine Fluent Interface aussehen kann am Beispiel des E-Mail-Versands:

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

In diesem Beispiel dienen die Methoden setFrom(), setRecipient() und setMessage() zum Setzen der entsprechenden Werte (Absender, Empfänger, Nachrichteninhalt). Nach dem Setzen jedes dieser Werte geben uns die Methoden das aktuelle Objekt ($email) zurück, was es uns ermöglicht, eine weitere Methode daran zu ketten. Schließlich rufen wir die Methode send() auf, die die E-Mail tatsächlich sendet.

Dank Fluent Interfaces können wir Code schreiben, der intuitiv und leicht lesbar ist.

Kopieren mit clone

In PHP können wir eine Kopie eines Objekts mit dem clone-Operator erstellen. Auf diese Weise erhalten wir eine neue Instanz mit identischem Inhalt.

Wenn wir beim Kopieren eines Objekts einige seiner Eigenschaften ändern müssen, können wir in der Klasse eine spezielle Methode __clone() definieren. Diese Methode wird automatisch aufgerufen, wenn das Objekt geklont wird.

class Schaf
{
	public string $name;

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

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

$original = new Schaf('Dolly');
echo $original->name . "\n";  // Gibt aus: Dolly

$klon = clone $original;
echo $klon->name . "\n";      // Gibt aus: Klon Dolly

In diesem Beispiel haben wir eine Klasse Schaf mit einer Eigenschaft $name. Wenn wir eine Instanz dieser Klasse klonen, kümmert sich die Methode __clone() darum, dass der Name des geklonten Schafs das Präfix „Klon“ erhält.

Traits

Traits in PHP sind ein Werkzeug, das es ermöglicht, Methoden, Eigenschaften und Konstanten zwischen Klassen zu teilen und Code-Duplizierung zu verhindern. Sie können sie sich als einen „Kopieren und Einfügen“-Mechanismus (Strg-C und Strg-V) vorstellen, bei dem der Inhalt des Traits in Klassen „eingefügt“ wird. Dies ermöglicht es Ihnen, Code wiederzuverwenden, ohne komplizierte Klassenhierarchien erstellen zu müssen.

Lassen Sie uns ein einfaches Beispiel zeigen, wie man Traits in PHP verwendet:

trait Hupen
{
	public function hupen()
	{
		echo 'Bip bip!';
	}
}

class Auto
{
	use Hupen;
}

class LKW
{
	use Hupen;
}

$auto = new Auto;
$auto->hupen(); // Gibt 'Bip bip!' aus

$lkw = new LKW;
$lkw->hupen(); // Gibt ebenfalls 'Bip bip!' aus

In diesem Beispiel haben wir ein Trait namens Hupen, das eine Methode hupen() enthält. Dann haben wir zwei Klassen: Auto und LKW, die beide das Trait Hupen verwenden. Dadurch „haben“ beide Klassen die Methode hupen(), und wir können sie auf Objekten beider Klassen aufrufen.

Traits ermöglichen es Ihnen, Code einfach und effizient zwischen Klassen zu teilen. Dabei treten sie nicht in die Vererbungshierarchie ein, d.h. $auto instanceof Hupen gibt false zurück.

Ausnahmen

Ausnahmen (Exceptions) in OOP ermöglichen es uns, Fehler und unerwartete Situationen in unserem Code elegant zu behandeln. Es sind Objekte, die Informationen über einen Fehler oder eine ungewöhnliche Situation tragen.

In PHP haben wir die eingebaute Klasse Exception, die als Basis für alle Ausnahmen dient. Sie hat mehrere Methoden, die es uns ermöglichen, mehr Informationen über die Ausnahme zu erhalten, wie die Fehlermeldung, Datei und Zeile, in der der Fehler aufgetreten ist, usw.

Wenn im Code ein Fehler auftritt, können wir eine Ausnahme mit dem Schlüsselwort throw „werfen“.

function teilung(float $a, float $b): float
{
	if ($b === 0.0) { // $b === 0
		throw new Exception('Division durch Null!');
	}
	return $a / $b;
}

Wenn die Funktion teilung() als zweites Argument Null erhält, wirft sie eine Ausnahme mit der Fehlermeldung 'Division durch Null!'. Um einen Programmabsturz beim Werfen einer Ausnahme zu verhindern, fangen wir sie in einem try/catch-Block ab:

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

Code, der eine Ausnahme werfen kann, ist in einen try-Block eingeschlossen. Wenn eine Ausnahme geworfen wird, verschiebt sich die Codeausführung in den catch-Block, wo wir die Ausnahme behandeln können (z.B. die Fehlermeldung ausgeben).

Nach den try- und catch-Blöcken können wir einen optionalen finally-Block hinzufügen, der immer ausgeführt wird, egal ob eine Ausnahme geworfen wurde oder nicht (sogar wenn wir im try- oder catch-Block die Anweisung return, break oder continue verwenden):

try {
	echo teilung(10, 0);
} catch (Exception $e) {
	echo 'Ausnahme abgefangen: '. $e->getMessage();
} finally {
	// Code, der immer ausgeführt wird, egal ob eine Ausnahme geworfen wurde oder nicht
}

Wir können auch eigene Klassen (Hierarchie) von Ausnahmen erstellen, die von der Klasse Exception erben. Als Beispiel stellen wir uns eine einfache Bankanwendung vor, die Ein- und Auszahlungen ermöglicht:

class Bankausnahme extends Exception {}
class UnzureichendeDeckungAusnahme extends Bankausnahme {}
class LimitUeberschrittenAusnahme extends Bankausnahme {}

class Bankkonto
{
	private int $saldo = 0;
	private int $tageslimit = 1000;

	public function einzahlen(int $betrag): int
	{
		$this->saldo += $betrag;
		return $this->saldo;
	}

	public function abheben(int $betrag): int
	{
		if ($betrag > $this->saldo) {
			throw new UnzureichendeDeckungAusnahme('Nicht genügend Geld auf dem Konto.');
		}

		if ($betrag > $this->tageslimit) {
			throw new LimitUeberschrittenAusnahme('Das Tageslimit für Abhebungen wurde überschritten.');
		}

		$this->saldo -= $betrag;
		return $this->saldo;
	}
}

Für einen try-Block können mehrere catch-Blöcke angegeben werden, wenn Sie verschiedene Arten von Ausnahmen erwarten.

$konto = new Bankkonto;
$konto->einzahlen(500);

try {
	$konto->abheben(1500);
} catch (LimitUeberschrittenAusnahme $e) {
	echo $e->getMessage();
} catch (UnzureichendeDeckungAusnahme $e) {
	echo $e->getMessage();
} catch (Bankausnahme $e) {
	echo 'Beim Ausführen der Operation ist ein Fehler aufgetreten.';
}

In diesem Beispiel ist die Reihenfolge der catch-Blöcke wichtig zu beachten. Da alle Ausnahmen von Bankausnahme erben, wenn wir diesen Block zuerst hätten, würden alle Ausnahmen darin gefangen werden, ohne dass der Code zu den folgenden catch-Blöcken gelangen würde. Daher ist es wichtig, spezifischere Ausnahmen (d.h. solche, die von anderen erben) im catch-Block weiter oben in der Reihenfolge als ihre Eltern-Ausnahmen zu haben.

Iteration

In PHP können Sie Objekte mit einer foreach-Schleife durchlaufen, ähnlich wie Sie Arrays durchlaufen. Damit das funktioniert, muss das Objekt spezielle Schnittstellen implementieren.

Die erste Möglichkeit ist die Implementierung der Iterator-Schnittstelle, die Methoden hat: current() gibt den aktuellen Wert zurück, key() gibt den Schlüssel zurück, next() geht zum nächsten Wert über, rewind() geht zum Anfang zurück und valid() prüft, ob wir noch nicht am Ende sind.

Die zweite Möglichkeit ist die Implementierung der IteratorAggregate-Schnittstelle, die nur eine Methode getIterator() hat. Diese gibt entweder ein Ersatzobjekt zurück, das die Iteration sicherstellt, oder sie kann einen Generator darstellen, was eine spezielle Funktion ist, in der yield verwendet wird, um Schlüssel und Werte nacheinander zurückzugeben:

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

class Liste implements IteratorAggregate
{
	private array $personen = [];

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

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

$liste = new Liste;
$liste->fuegePersonHinzu(new Person(30));
$liste->fuegePersonHinzu(new Person(25));

foreach ($liste as $person) {
	echo "Alter: {$person->alter} Jahre \n";
}

Best Practices

Wenn Sie die grundlegenden Prinzipien von OOP hinter sich haben, ist es wichtig, sich auf die richtigen Praktiken in OOP zu konzentrieren. Diese helfen Ihnen, Code zu schreiben, der nicht nur funktional ist, sondern auch lesbar, verständlich und leicht wartbar.

  1. Trennung der Belange (Separation of Concerns): Jede Klasse sollte eine klar definierte Verantwortung haben und sollte nur eine Hauptaufgabe lösen. Wenn eine Klasse zu viele Dinge tut, kann es sinnvoll sein, sie in kleinere, spezialisierte Klassen aufzuteilen.
  2. Kapselung (Encapsulation): Daten und Methoden sollten so weit wie möglich verborgen sein und nur über eine definierte Schnittstelle zugänglich sein. Dies ermöglicht es Ihnen, die interne Implementierung der Klasse zu ändern, ohne den Rest des Codes zu beeinflussen.
  3. Dependency Injection: Anstatt Abhängigkeiten direkt in der Klasse zu erstellen, sollten Sie sie von außen „injizieren“. Für ein tieferes Verständnis dieses Prinzips empfehlen wir die Kapitel über Dependency Injection.