Einführung in die objektorientierte Programmierung
Die Abkürzung “OOP” steht für objektorientierte Programmierung, eine Art, Code zu organisieren und zu strukturieren. OOP erlaubt es uns, ein Programm nicht als Abfolge von Befehlen und Funktionen zu betrachten, sondern als Sammlung von Objekten, die miteinander kommunizieren.
In der OOP ist ein “Objekt” eine Einheit, die Daten und Funktionen enthält, die mit diesen Daten arbeiten. Objekte werden auf Basis von “Klassen” erzeugt, die man als Baupläne oder Vorlagen für Objekte verstehen kann. Sobald wir eine Klasse haben, können wir ihre “Instanz” erzeugen, also ein konkretes Objekt, das aus dieser Klasse entsteht.
Sehen wir uns an, wie wir in PHP eine einfache Klasse erstellen können. Bei der Definition einer Klasse verwenden wir das Schlüsselwort “class”, gefolgt vom Namen der Klasse und dann geschweiften Klammern, die die Funktionen der Klasse (die sogenannten “Methoden”) und die Variablen der Klasse (die sogenannten “Properties” oder “Attribute”) umschließen:
class Car
{
function honk()
{
echo 'Tuut tuut!';
}
}
In diesem Beispiel haben wir eine Klasse namens Car mit einer Funktion (oder “Methode”) namens
honk erstellt.
Jede Klasse sollte nur eine Hauptaufgabe lösen. Wenn eine Klasse zu viele Dinge tut, kann es angebracht sein, sie in kleinere, spezialisierte Klassen aufzuteilen.
Klassen werden üblicherweise in eigenen Dateien abgelegt, damit der Code geordnet und leicht zu überblicken bleibt. Der
Dateiname sollte dem Klassennamen entsprechen, für die Klasse Car wäre der Dateiname also Car.php.
Bei der Benennung von Klassen ist es gut, die Konvention “PascalCase” einzuhalten, das heißt jedes Wort des Namens beginnt mit einem Großbuchstaben, und es gibt keine Unterstriche oder anderen Trennzeichen. Methoden und Properties folgen der Konvention “camelCase”, beginnen also mit einem Kleinbuchstaben.
Manche Methoden in PHP haben besondere Rollen und tragen das Präfix __ (zwei Unterstriche). Eine der wichtigsten
speziellen Methoden ist der “Konstruktor”, bezeichnet als __construct. Der Konstruktor ist eine Methode, die beim
Erzeugen einer neuen Instanz einer Klasse automatisch aufgerufen wird.
Den Konstruktor verwenden wir oft, um den Anfangszustand eines Objekts zu setzen. Wenn Sie zum Beispiel ein Objekt erzeugen, das eine Person darstellt, können Sie im Konstruktor deren Alter, Namen oder andere Attribute setzen.
Sehen wir uns an, wie man in PHP einen Konstruktor verwendet:
class Person
{
private $age;
function __construct($age)
{
$this->age = $age;
}
function howOldAreYou()
{
return $this->age;
}
}
$person = new Person(25);
echo $person->howOldAreYou(); // Gibt aus: 25
In diesem Beispiel hat die Klasse Person eine Property (Variable) $age und einen Konstruktor, der
diese Property setzt. Die Methode howOldAreYou() ermöglicht dann den Zugriff auf das Alter der Person.
Die Pseudovariable $this wird innerhalb der Klasse verwendet, um auf die Properties und Methoden des Objekts
zuzugreifen.
Das Schlüsselwort new dient dazu, eine neue Instanz einer Klasse zu erzeugen. Im obigen Beispiel haben wir eine
neue Person im Alter von 25 Jahren erzeugt.
Sie können für die Parameter des Konstruktors auch Standardwerte setzen, falls sie beim Erzeugen eines Objekts nicht angegeben werden. Zum Beispiel:
class Person
{
private $age;
function __construct($age = 20)
{
$this->age = $age;
}
function howOldAreYou()
{
return $this->age;
}
}
$person = new Person; // wird kein Argument übergeben, können die Klammern entfallen
echo $person->howOldAreYou(); // Gibt aus: 20
Wenn Sie in diesem Beispiel beim Erzeugen eines Person-Objekts kein Alter angeben, wird der Standardwert
20 verwendet.
Schön ist, dass sich die Definition der Property samt ihrer Initialisierung über den Konstruktor so verkürzen und vereinfachen lässt:
class Person
{
function __construct(
private $age = 20,
) {
}
}
Der Vollständigkeit halber: Außer Konstruktoren können Objekte auch Destruktoren haben (die Methode
__destruct), die aufgerufen werden, bevor das Objekt aus dem Speicher freigegeben wird.
Namensräume
Namensräume erlauben es uns, verwandte Klassen, Funktionen und Konstanten zu ordnen und zu gruppieren und dabei Namenskonflikte zu vermeiden. Sie können sich das wie Ordner auf einem Computer vorstellen, in denen jeder Ordner die Dateien zu einem bestimmten Projekt oder Thema enthält.
Namensräume sind besonders in größeren Projekten oder bei der Verwendung von Bibliotheken Dritter nützlich, wo es zu Konflikten bei den Klassennamen kommen kann.
Stellen Sie sich vor, Sie haben in Ihrem Projekt eine Klasse namens Car und wollen sie in einen Namensraum namens
Transport legen. Das machen Sie so:
namespace Transport;
class Car
{
function honk()
{
echo 'Tuut tuut!';
}
}
Wenn Sie die Klasse Car in einer anderen Datei verwenden wollen, müssen Sie angeben, aus welchem Namensraum die
Klasse stammt:
$car = new Transport\Car;
Zur Vereinfachung können Sie am Anfang der Datei angeben, welche Klasse aus welchem Namensraum Sie verwenden wollen, sodass Sie Instanzen erzeugen können, ohne den vollständigen Pfad zu nennen:
use Transport\Car;
$car = new Car;
Vererbung
Vererbung ist ein Werkzeug der objektorientierten Programmierung, das es erlaubt, neue Klassen auf Basis bestehender zu erstellen, deren Properties und Methoden zu erben und sie nach Bedarf zu erweitern oder neu zu definieren. Vererbung sorgt für Wiederverwendbarkeit des Codes und für eine Klassenhierarchie.
Einfach gesagt: Wenn wir eine Klasse haben und eine weitere erstellen wollen, die von ihr abgeleitet ist, aber einige Änderungen aufweist, können wir die neue Klasse von der ursprünglichen “erben” lassen.
In PHP wird Vererbung mit dem Schlüsselwort extends umgesetzt.
Unsere Klasse Person speichert die Information über das Alter. Wir können eine weitere Klasse
Student haben, die Person erweitert und die Information über das Studienfach ergänzt.
Sehen wir uns ein Beispiel an:
class Person
{
private $age;
function __construct($age)
{
$this->age = $age;
}
function printInformation()
{
echo "Alter: {$this->age} Jahre\n";
}
}
class Student extends Person
{
private $fieldOfStudy;
function __construct($age, $fieldOfStudy)
{
parent::__construct($age);
$this->fieldOfStudy = $fieldOfStudy;
}
function printInformation()
{
parent::printInformation();
echo "Studienfach: {$this->fieldOfStudy} \n";
}
}
$student = new Student(20, 'Informatik');
$student->printInformation();
Wie funktioniert dieser Code?
- Wir haben das Schlüsselwort
extendsverwendet, um die KlassePersonzu erweitern, das heißt die KlasseStudenterbt alle Methoden und Properties vonPerson. - Das Schlüsselwort
parent::erlaubt uns, Methoden der Elternklasse aufzurufen. In diesem Fall haben wir den Konstruktor der KlassePersonaufgerufen, bevor wir eigene Funktionalität in der KlasseStudentergänzt haben. Und ebenso die MethodeprintInformation()des Vorfahren, bevor wir die Informationen zum Studenten ausgeben.
Vererbung ist für Situationen gedacht, in denen zwischen den Klassen eine “is a”-Beziehung besteht. Ein
Student ist zum Beispiel eine Person. Eine Katze ist ein Tier. Sie erlaubt es uns, an Stellen, an denen
der Code ein bestimmtes Objekt erwartet (z. B. “Person”), stattdessen ein abgeleitetes Objekt zu verwenden (z. B.
“Student”).
Wichtig ist zu verstehen, dass der Hauptzweck der Vererbung nicht darin besteht, Codeduplizierung zu verhindern. Im Gegenteil, der Missbrauch von Vererbung führt zu kompliziertem und schwer wartbarem Code. Besteht zwischen den Klassen keine “is a”-Beziehung, sollten wir statt Vererbung die Komposition in Betracht ziehen.
Beachten Sie, dass die Methoden printInformation() in den Klassen Person und Student
leicht unterschiedliche Informationen ausgeben. Und wir können weitere Klassen ergänzen (etwa Employee), die andere
Implementierungen dieser Methode liefern. Die Fähigkeit von Objekten verschiedener Klassen, auf dieselbe Methode unterschiedlich
zu reagieren, nennt man Polymorphie:
$people = [
new Person(30),
new Student(20, 'Informatik'),
new Employee(45, 'Direktor'),
];
foreach ($people as $person) {
$person->printInformation();
}
Komposition
Komposition ist eine Technik, bei der wir Properties und Methoden nicht von einer anderen Klasse erben, sondern in unserer Klasse einfach deren Instanz verwenden. So können wir Funktionalitäten und Eigenschaften mehrerer Klassen kombinieren, ohne komplizierte Vererbungsstrukturen zu schaffen.
Wir haben zum Beispiel eine Klasse Engine und eine Klasse Car. Statt zu sagen “Ein Auto ist ein
Motor”, sagen wir “Ein Auto hat einen Motor”, was eine typische Kompositionsbeziehung ist.
class Engine
{
function start()
{
echo 'Der Motor läuft.';
}
}
class Car
{
private $engine;
function __construct()
{
$this->engine = new Engine;
}
function start()
{
$this->engine->start();
echo 'Das Auto ist fahrbereit!';
}
}
$car = new Car;
$car->start();
Hier hat das Car nicht alle Properties und Methoden des Engine, aber es hat über die Property
$engine Zugriff darauf.
Der Vorteil der Komposition ist eine größere Flexibilität im Entwurf und eine bessere Anpassbarkeit an künftige Änderungen.
Sichtbarkeit
In PHP können Sie für Properties, Methoden und Konstanten einer Klasse die “Sichtbarkeit” festlegen. Die Sichtbarkeit bestimmt, wo Sie auf diese Elemente zugreifen können.
- Public: Ist ein Element als
publicgekennzeichnet, bedeutet das, dass Sie von überall darauf zugreifen können, auch außerhalb der Klasse. - Protected: Ein als
protectedgekennzeichnetes Element ist nur innerhalb der Klasse und all ihrer Nachkommen zugänglich (also der Klassen, die von ihr erben). - Private: Ist ein Element
private, können Sie nur innerhalb der Klasse darauf zugreifen, in der es definiert wurde.
Wenn Sie keine Sichtbarkeit angeben, setzt PHP sie automatisch auf public.
Sehen wir uns einen Beispielcode an:
class VisibilityExample
{
public $publicProperty = 'Public';
protected $protectedProperty = 'Protected';
private $privateProperty = 'Private';
public function printProperties()
{
echo $this->publicProperty; // Funktioniert
echo $this->protectedProperty; // Funktioniert
echo $this->privateProperty; // Funktioniert
}
}
$object = new VisibilityExample;
$object->printProperties();
echo $object->publicProperty; // Funktioniert
// echo $object->protectedProperty; // Wirft einen Fehler
// echo $object->privateProperty; // Wirft einen Fehler
Weiter mit der Vererbung von Klassen:
class ChildClass extends VisibilityExample
{
public function printProperties()
{
echo $this->publicProperty; // Funktioniert
echo $this->protectedProperty; // Funktioniert
// echo $this->privateProperty; // Wirft einen Fehler
}
}
In diesem Fall kann die Methode printProperties() in der ChildClass auf die public- und
protected-Properties zugreifen, nicht aber auf die private-Properties der Elternklasse.
Daten und Methoden sollten so weit wie möglich verborgen sein und nur über eine definierte Schnittstelle zugänglich sein. Dadurch können Sie die interne Implementierung der Klasse ändern, ohne den übrigen Code zu beeinflussen.
Schlüsselwort final
In PHP können wir das Schlüsselwort final verwenden, wenn wir verhindern wollen, dass eine Klasse, Methode oder
Konstante vererbt oder überschrieben wird. Ist eine Klasse als final gekennzeichnet, lässt sie sich nicht
erweitern. Ist eine Methode als final gekennzeichnet, lässt sie sich in einer abgeleiteten Klasse nicht
überschreiben.
Zu wissen, dass eine bestimmte Klasse oder Methode nicht mehr verändert wird, erlaubt es uns, Änderungen leichter vorzunehmen, ohne mögliche Konflikte fürchten zu müssen. Wir können zum Beispiel eine neue Methode ergänzen, ohne zu befürchten, dass ein Nachkomme bereits eine Methode desselben Namens hat und es zu einer Kollision kommt. Oder wir können die Parameter einer Methode ändern, wiederum ohne Gefahr, eine Inkonsistenz mit einer überschriebenen Methode in einem Nachkommen zu verursachen.
final class FinalClass
{
}
// Der folgende Code wirft einen Fehler, weil wir von einer finalen Klasse nicht erben können.
class ChildOfFinalClass extends FinalClass
{
}
In diesem Beispiel führt der Versuch, von der finalen Klasse FinalClass zu erben, zu einem Fehler.
Statische Properties und Methoden
Wenn wir in PHP von “statischen” Elementen einer Klasse sprechen, meinen wir Methoden und Properties, die zur Klasse selbst gehören und nicht zu einer konkreten Instanz der Klasse. Das heißt, Sie müssen keine Instanz der Klasse erzeugen, um auf sie zuzugreifen. Stattdessen rufen Sie sie direkt über den Klassennamen auf.
Denken Sie daran, dass Sie innerhalb statischer Methoden die Pseudovariable $this nicht verwenden können, weil
statische Elemente zur Klasse und nicht zu ihren Instanzen gehören.
Die Verwendung statischer Properties führt zu unübersichtlichem Code voller Fallstricke, Sie sollten sie also niemals verwenden, und wir zeigen hier auch kein Beispiel dafür. Statische Methoden sind dagegen nützlich. Hier ein Beispiel:
class Calculator
{
public static function add($a, $b)
{
return $a + $b;
}
public static function subtract($a, $b)
{
return $a - $b;
}
}
// Verwendung der statischen Methode, ohne eine Instanz der Klasse zu erzeugen
echo Calculator::add(5, 3); // Ausgabe: 8
echo Calculator::subtract(5, 3); // Ausgabe: 2
In diesem Beispiel haben wir eine Klasse Calculator mit zwei statischen Methoden erstellt. Wir können diese
Methoden mit dem Operator :: direkt aufrufen, ohne eine Instanz der Klasse zu erzeugen. Statische Methoden sind
besonders für Operationen nützlich, die nicht vom Zustand einer konkreten 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 Ausführung des Programms nie ändern. Anders als bei Variablen bleibt der Wert einer Konstanten gleich.
class Car
{
public const NumberOfWheels = 4;
public function displayNumberOfWheels(): void
{
echo self::NumberOfWheels;
}
}
echo Car::NumberOfWheels; // Ausgabe: 4
In diesem Beispiel haben wir eine Klasse Car mit der Konstanten NumberOfWheels. Beim Zugriff auf die
Konstante innerhalb der Klasse können wir statt des Klassennamens das Schlüsselwort self verwenden.
Objekt-Interfaces
Objekt-Interfaces wirken wie “Verträge” für Klassen. Soll eine Klasse ein Objekt-Interface implementieren, muss sie alle Methoden enthalten, die das Interface definiert. Das ist eine hervorragende Möglichkeit, sicherzustellen, dass bestimmte Klassen denselben “Vertrag” oder dieselbe Struktur einhalten.
In PHP werden Interfaces mit dem Schlüsselwort interface definiert. Alle in einem Interface definierten Methoden
sind öffentlich (public). Wenn eine Klasse ein Interface implementiert, verwendet sie das Schlüsselwort
implements.
interface Animal
{
function makeSound();
}
class Cat implements Animal
{
public function makeSound()
{
echo 'Miau';
}
}
$cat = new Cat;
$cat->makeSound();
Implementiert eine Klasse ein Interface, sind aber nicht alle erwarteten Methoden definiert, wirft PHP einen Fehler.
Eine Klasse kann mehrere Interfaces auf einmal implementieren, was ein Unterschied zur Vererbung ist, bei der eine Klasse nur von einer Klasse erben kann:
interface Guardian
{
function guardHouse();
}
class Dog implements Animal, Guardian
{
public function makeSound()
{
echo 'Wau';
}
public function guardHouse()
{
echo 'Der Hund bewacht das Haus fleißig';
}
}
Abstrakte Klassen
Abstrakte Klassen dienen als Grundvorlagen für andere Klassen, aber Sie können ihre Instanzen nicht direkt erzeugen. Sie enthalten eine Mischung aus vollständigen Methoden und abstrakten Methoden, die keinen definierten Inhalt haben. Klassen, die von abstrakten Klassen erben, müssen für alle abstrakten Methoden des Elternteils Definitionen liefern.
Zur Definition einer abstrakten Klasse verwenden wir das Schlüsselwort abstract.
abstract class AbstractClass
{
public function regularMethod()
{
echo 'Das ist eine gewöhnliche Methode';
}
abstract public function abstractMethod();
}
class Child extends AbstractClass
{
public function abstractMethod()
{
echo 'Das ist die Implementierung der abstrakten Methode';
}
}
$instance = new Child;
$instance->regularMethod();
$instance->abstractMethod();
In diesem Beispiel haben wir eine abstrakte Klasse mit einer gewöhnlichen und einer abstrakten Methode. Dann haben wir eine
Klasse Child, die von AbstractClass erbt und eine Implementierung der abstrakten Methode liefert.
Worin unterscheiden sich Interfaces und abstrakte Klassen? Abstrakte Klassen können sowohl abstrakte als auch konkrete Methoden enthalten, während Interfaces nur definieren, welche Methoden die Klasse implementieren muss, aber keine Implementierung liefern. Eine Klasse kann nur von einer abstrakten Klasse erben, aber beliebig viele Interfaces implementieren.
Typüberprüfung
Beim Programmieren ist es entscheidend, sicherzustellen, dass die Daten, mit denen wir arbeiten, vom richtigen Typ sind. In PHP haben wir Werkzeuge, die diese Sicherheit bieten. Die Prüfung, ob Daten vom richtigen Typ sind, nennt man “Typüberprüfung”.
Typen, denen wir in PHP begegnen können:
- Grundtypen: Dazu gehören
int(ganze Zahlen),float(Gleitkommazahlen),bool(boolesche Werte),string(Strings),array(Arrays) undnull. - Klassen: Wenn wir wollen, dass ein Wert eine Instanz einer bestimmten Klasse ist.
- Interfaces: Definiert eine Menge von Methoden, die eine Klasse implementieren muss. Ein Wert, der ein Interface erfüllt, muss diese Methoden haben.
- Gemischte Typen: Wir können angeben, dass eine Variable mehrere erlaubte Typen haben darf.
- Void: Dieser spezielle Typ zeigt an, dass eine Funktion oder Methode keinen Wert zurückgibt.
Sehen wir uns an, wie man den Code um Typen ergänzt:
class Person
{
private int $age;
public function __construct(int $age)
{
$this->age = $age;
}
public function printAge(): void
{
echo "Diese Person ist {$this->age} Jahre alt.";
}
}
/**
* Eine Funktion, die ein Person-Objekt entgegennimmt und das Alter der Person ausgibt.
*/
function printPersonAge(Person $person): void
{
$person->printAge();
}
So stellen wir sicher, dass unser Code Daten des richtigen Typs erwartet und mit ihnen arbeitet, was uns hilft, mögliche Fehler zu vermeiden.
Manche Typen lassen sich in PHP nicht direkt schreiben. In diesem Fall werden sie im phpDoc-Kommentar angegeben, dem
Standardformat zur Dokumentation von PHP-Code, das mit /** beginnt und mit */ endet. Es erlaubt Ihnen,
Beschreibungen von Klassen, Methoden und so weiter zu ergänzen. Und außerdem komplexe Typen über die sogenannten Annotationen
@var, @param und @return anzugeben. Diese Typen nutzen dann Werkzeuge zur statischen
Codeanalyse, PHP selbst prüft sie jedoch nicht.
class Registry
{
/** @var array<Person> gibt an, dass es ein Array von Person-Objekten ist */
private array $persons = [];
public function addPerson(Person $person): void
{
$this->persons[] = $person;
}
}
Vergleich und Identität
In PHP können Sie Objekte auf zwei Arten vergleichen:
- Wertvergleich
==: Prüft, ob die Objekte derselben Klasse angehören und dieselben Werte in ihren Properties haben. - Identität
===: Prüft, ob es sich um dieselbe Instanz des Objekts handelt.
class Car
{
public string $brand;
public function __construct(string $brand)
{
$this->brand = $brand;
}
}
$car1 = new Car('Skoda');
$car2 = new Car('Skoda');
$car3 = $car1;
var_dump($car1 == $car2); // true, weil sie denselben Wert haben
var_dump($car1 === $car2); // false, weil es nicht dieselbe Instanz ist
var_dump($car1 === $car3); // true, weil $car3 dieselbe Instanz wie $car1 ist
Der instanceof-Operator
Der Operator instanceof erlaubt es Ihnen festzustellen, ob ein bestimmtes Objekt eine Instanz einer bestimmten
Klasse oder eines Nachkommen dieser Klasse ist oder ob es ein bestimmtes Interface implementiert.
Stellen wir uns vor, wir haben eine Klasse Person und eine weitere Klasse Student, die ein Nachkomme
von Person ist:
class Person
{
private int $age;
public function __construct(int $age)
{
$this->age = $age;
}
}
class Student extends Person
{
private string $major;
public function __construct(int $age, string $major)
{
parent::__construct($age);
$this->major = $major;
}
}
$student = new Student(20, 'Informatik');
// Prüfen, ob $student eine Instanz der Klasse Student ist
var_dump($student instanceof Student); // Ausgabe: bool(true)
// Prüfen, ob $student eine Instanz der Klasse Person ist (weil Student ein Nachkomme von Person ist)
var_dump($student instanceof Person); // Ausgabe: bool(true)
An den Ausgaben ist zu sehen, dass das Objekt $student sowohl als Instanz der Klasse Student als auch
der Klasse Person gilt.
Fluent Interfaces
Ein “Fluent Interface” ist eine Technik der OOP, die es erlaubt, Methoden in einem einzigen Aufruf zu verketten. Das vereinfacht den Code oft und macht ihn übersichtlicher.
Das Schlüsselelement eines Fluent Interface ist, dass jede Methode in der Kette eine Referenz auf das aktuelle Objekt
zurückgibt. Erreicht wird das mit return $this; am Ende der Methode. Dieser Programmierstil wird oft mit Methoden
namens “Setter” verbunden, die die Werte der Properties eines Objekts setzen.
Sehen wir uns an, wie ein Fluent Interface für das Versenden von E-Mails aussehen kann:
public function sendMessage()
{
$email = new Email;
$email->setFrom('sender@example.com')
->setRecipient('admin@example.com')
->setMessage('Hallo, das ist eine Nachricht.')
->send();
}
In diesem Beispiel dienen die Methoden setFrom(), setRecipient() und setMessage() dazu,
die entsprechenden Werte zu setzen (Absender, Empfänger, Inhalt der Nachricht). Nach dem Setzen jedes dieser Werte geben die
Methoden das aktuelle Objekt ($email) zurück, sodass wir eine weitere Methode daran anhängen können. Zum Schluss
rufen wir die Methode send() auf, die die E-Mail tatsächlich versendet.
Dank Fluent Interfaces können wir Code schreiben, der intuitiv und leicht lesbar ist.
Kopieren mit clone
In PHP können wir mit dem Operator clone eine Kopie eines Objekts erzeugen. So erhalten wir eine neue Instanz mit
identischem Inhalt.
Wenn wir beim Kopieren eines Objekts einige seiner Properties ändern müssen, können wir in der Klasse eine spezielle Methode
__clone() definieren. Diese Methode wird beim Klonen des Objekts automatisch aufgerufen.
class Sheep
{
public string $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function __clone()
{
$this->name = 'Klon von ' . $this->name;
}
}
$original = new Sheep('Dolly');
echo $original->name . "\n"; // Gibt aus: Dolly
$clone = clone $original;
echo $clone->name . "\n"; // Gibt aus: Klon von Dolly
In diesem Beispiel haben wir eine Klasse Sheep mit einer Property $name. Wenn wir eine Instanz dieser
Klasse klonen, sorgt die Methode __clone() dafür, dass der Name des geklonten Schafs das Präfix “Klon von”
erhält.
Traits
Traits sind in PHP ein Werkzeug, das es erlaubt, Methoden, Properties und Konstanten zwischen Klassen zu teilen und Codeduplizierung zu verhindern. Sie können sich das als Mechanismus zum “Kopieren und Einfügen” vorstellen (Strg-C und Strg-V), bei dem der Inhalt eines Traits in die Klassen “eingefügt” wird. So können Sie Code wiederverwenden, ohne komplizierte Klassenhierarchien schaffen zu müssen.
Sehen wir uns ein einfaches Beispiel für die Verwendung von Traits in PHP an:
trait Honking
{
public function honk()
{
echo 'Tuut tuut!';
}
}
class Car
{
use Honking;
}
class Truck
{
use Honking;
}
$car = new Car;
$car->honk(); // Gibt aus: 'Tuut tuut!'
$truck = new Truck;
$truck->honk(); // Gibt ebenfalls aus: 'Tuut tuut!'
In diesem Beispiel haben wir ein Trait namens Honking, das eine Methode honk() enthält. Dann haben
wir zwei Klassen: Car und Truck, die beide das Trait Honking verwenden. Dadurch “haben”
beide Klassen die Methode honk(), und wir können sie auf Objekten beider Klassen aufrufen.
Traits erlauben es Ihnen, Code einfach und effizient zwischen Klassen zu teilen. Sie gehen nicht in die Vererbungshierarchie
ein, $car instanceof Honking gibt also false zurück.
Exceptions
Exceptions erlauben es uns in der OOP, 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 eine eingebaute Klasse Exception, die als Basis für alle Exceptions dient. Sie hat mehrere
Methoden, die es uns erlauben, mehr Informationen über die Exception zu erhalten, etwa die Fehlermeldung, die Datei und die
Zeile, in der der Fehler aufgetreten ist, usw.
Wenn im Code ein Fehler auftritt, können wir die Exception mit dem Schlüsselwort throw “werfen”.
function division(float $a, float $b): float
{
if ($b == 0) {
throw new Exception('Division durch null!');
}
return $a / $b;
}
Wenn die Funktion division() als zweites Argument null bekommt, wirft sie eine Exception mit der Fehlermeldung
'Division durch null!'. Damit das Programm beim Werfen der Exception nicht abstürzt, fangen wir sie im Block
try/catch ab:
try {
echo division(10, 0);
} catch (Exception $e) {
echo 'Exception abgefangen: '. $e->getMessage();
}
Code, der eine Exception werfen kann, wird in einen Block try gepackt. Wird die Exception geworfen, springt die
Ausführung des Codes in den Block catch, wo wir die Exception behandeln können (z. B. eine Fehlermeldung
ausgeben).
Nach den Blöcken try und catch können wir einen optionalen Block finally ergänzen,
der immer ausgeführt wird, ob die Exception geworfen wurde oder nicht (auch wenn wir im Block try oder
catch return, break oder continue verwenden):
try {
echo division(10, 0);
} catch (Exception $e) {
echo 'Exception abgefangen: '. $e->getMessage();
} finally {
// Code, der immer ausgeführt wird, ob die Exception geworfen wurde oder nicht
}
Wir können auch eigene Exception-Klassen (eine Hierarchie) erstellen, die von der Klasse Exception erben. Nehmen wir als Beispiel eine einfache Bankanwendung, die Einzahlungen und Abhebungen erlaubt:
class BankingException extends Exception {}
class InsufficientFundsException extends BankingException {}
class ExceededLimitException extends BankingException {}
class BankAccount
{
private int $balance = 0;
private int $dailyLimit = 1000;
public function deposit(int $amount): int
{
$this->balance += $amount;
return $this->balance;
}
public function withdraw(int $amount): int
{
if ($amount > $this->balance) {
throw new InsufficientFundsException('Nicht genug Guthaben auf dem Konto.');
}
if ($amount > $this->dailyLimit) {
throw new ExceededLimitException('Tageslimit für Abhebungen überschritten.');
}
$this->balance -= $amount;
return $this->balance;
}
}
Für einen einzigen try-Block können mehrere catch-Blöcke angegeben werden, wenn Sie verschiedene
Arten von Exceptions erwarten.
$account = new BankAccount;
$account->deposit(500);
try {
$account->withdraw(1500);
} catch (ExceededLimitException $e) {
echo $e->getMessage();
} catch (InsufficientFundsException $e) {
echo $e->getMessage();
} catch (BankingException $e) {
echo 'Bei der Operation ist ein Fehler aufgetreten.';
}
In diesem Beispiel ist die Reihenfolge der catch-Blöcke wichtig. Da alle Exceptions von
BankingException erben, würden bei diesem Block an erster Stelle alle Exceptions in ihm abgefangen, ohne dass der
Code die folgenden catch-Blöcke erreicht. Deshalb ist es wichtig, spezifischere Exceptions (also solche, die von
anderen erben) in der Reihenfolge der catch-Blöcke höher zu stellen als ihre Eltern-Exceptions.
Iteration
In PHP können Sie Objekte mit der foreach-Schleife durchlaufen, ganz ähnlich wie ein Array. Damit das
funktioniert, muss das Objekt ein spezielles Interface implementieren.
Die erste Möglichkeit ist, das Interface Iterator zu implementieren, das die Methoden current()
(gibt den aktuellen Wert zurück), key() (gibt den Schlüssel zurück), next() (springt zum nächsten
Wert), rewind() (springt an den Anfang) und valid() (prüft, ob wir schon am Ende sind) hat.
Die andere Möglichkeit ist, das Interface IteratorAggregate zu implementieren, das nur eine einzige Methode
getIterator() hat. Sie gibt entweder ein stellvertretendes Objekt zurück, das den Durchlauf übernimmt, oder es kann
ein Generator sein, also eine spezielle Funktion, die mit yield nacheinander Schlüssel und Werte zurückgibt:
class Person
{
public function __construct(
public int $age,
) {
}
}
class Registry implements IteratorAggregate
{
private array $people = [];
public function addPerson(Person $person): void
{
$this->people[] = $person;
}
public function getIterator(): Generator
{
foreach ($this->people as $person) {
yield $person;
}
}
}
$list = new Registry;
$list->addPerson(new Person(30));
$list->addPerson(new Person(25));
foreach ($list as $person) {
echo "Alter: {$person->age} Jahre\n";
}
Best Practices
Sobald Sie die Grundprinzipien der objektorientierten Programmierung beherrschen, ist es entscheidend, sich auf die Best Practices der OOP zu konzentrieren. Sie helfen Ihnen, Code zu schreiben, der nicht nur funktioniert, sondern auch lesbar, verständlich und leicht wartbar ist.
- Trennung der Zuständigkeiten: Jede Klasse sollte eine klar definierte Verantwortung haben und nur eine Hauptaufgabe lösen. Wenn eine Klasse zu viele Dinge tut, kann es angebracht sein, sie in kleinere, spezialisierte Klassen aufzuteilen.
- Kapselung: Daten und Methoden sollten so weit wie möglich verborgen und nur über eine definierte Schnittstelle zugänglich sein. Dadurch können Sie die interne Implementierung einer Klasse ändern, ohne den übrigen Code zu beeinflussen.
- Dependency Injection: Statt Abhängigkeiten direkt in einer Klasse zu erzeugen, sollten Sie sie von außen “injizieren”. Für ein tieferes Verständnis dieses Prinzips empfehlen wir die Kapitel über Dependency Injection.