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 KlassePerson
zu erweitern, was bedeutet, dass die KlasseStudent
alle Methoden und Eigenschaften vonPerson
erbt. - Das Schlüsselwort
parent::
ermöglicht es uns, Methoden aus der übergeordneten Klasse aufzurufen. In diesem Fall haben wir den Konstruktor aus der KlassePerson
aufgerufen, bevor wir eigene Funktionalität zur KlasseStudent
hinzugefügt haben. Und ähnlich auch die MethodegibInformationenAus()
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.
- Public: Wenn ein Element als
public
gekennzeichnet ist, bedeutet das, dass Sie von überall darauf zugreifen können, auch außerhalb der Klasse. - 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. - 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:
- Grundtypen: Umfassen
int
(Ganzzahlen),float
(Gleitkommazahlen),bool
(Wahrheitswerte),string
(Zeichenketten),array
(Arrays) undnull
. - Klassen: Wenn wir möchten, dass ein Wert eine Instanz einer bestimmten Klasse ist.
- Interfaces: Definiert eine Reihe von Methoden, die eine Klasse implementieren muss. Ein Wert, der die Schnittstelle erfüllt, muss diese Methoden haben.
- Union Types: Wir können festlegen, dass eine Variable mehrere erlaubte Typen haben kann.
- 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:
- Wertvergleich
==
: Überprüft, ob die Objekte derselben Klasse angehören und dieselben Werte in ihren Eigenschaften haben. - 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.
- 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.
- 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.
- 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.