Einführung in die objektorientierte Programmierung
Der Begriff “OOP” steht für Object-Oriented Programming (objektorientierte Programmierung), eine Methode zur Organisation und Strukturierung von Code. OOP ermöglicht es uns, ein Programm als eine Sammlung von Objekten zu betrachten, die miteinander kommunizieren, und nicht als eine 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 auf der Grundlage von “Klassen” erstellt, die als Entwürfe oder Vorlagen für Objekte verstanden werden können. Sobald wir eine Klasse haben, können wir ihre “Instanz” erstellen, d. h. ein spezifisches Objekt, das aus dieser Klasse besteht.
Schauen wir uns an, wie wir eine einfache Klasse in PHP erstellen können. Bei der Definition einer Klasse verwenden wir das Schlüsselwort “class”, gefolgt vom Klassennamen und geschweiften Klammern, die die Funktionen der Klasse (genannt “Methoden”) und die Variablen der Klasse (genannt “Eigenschaften” oder “Attribute”) einschließen:
class Car
{
function honk()
{
echo 'Beep beep!';
}
}
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 Aufgaben hat, kann es sinnvoll sein, sie in kleinere, spezialisierte Klassen aufzuteilen.
Klassen werden in der Regel in separaten Dateien gespeichert, damit der Code übersichtlich bleibt und leicht zu navigieren
ist. Der Dateiname sollte mit dem Klassennamen übereinstimmen, für die Klasse Car
wäre der Dateiname also
Car.php
.
Bei der Benennung von Klassen sollte man sich an die “PascalCase”-Konvention halten, d. h. jedes Wort im Namen beginnt mit einem Großbuchstaben, und es gibt keine Unterstriche oder andere Trennzeichen. Methoden und Eigenschaften folgen der “camelCase”-Konvention, das heißt, sie beginnen mit einem Kleinbuchstaben.
Einige Methoden in PHP haben besondere Funktionen und werden mit __
(zwei Unterstrichen) eingeleitet. Eine der
wichtigsten Spezialmethoden ist der “Konstruktor”, der mit __construct
gekennzeichnet ist. Der Konstruktor ist
eine Methode, die automatisch aufgerufen wird, wenn eine neue Instanz einer Klasse erstellt wird.
Wir verwenden den Konstruktor häufig, um den Anfangszustand eines Objekts festzulegen. Wenn Sie beispielsweise ein Objekt erstellen, das eine Person darstellt, können Sie den Konstruktor verwenden, um deren Alter, Namen oder andere Attribute festzulegen.
Schauen wir uns an, wie man einen Konstruktor in PHP verwendet:
class Person
{
private $age;
function __construct($age)
{
$this->age = $age;
}
function howOldAreYou()
{
return $this->age;
}
}
$person = new Person(25);
echo $person->howOldAreYou(); // Outputs: 25
In diesem Beispiel hat die Klasse Person
eine Eigenschaft (Variable) $age
und einen Konstruktor, der
diese Eigenschaft setzt. Die Methode howOldAreYou()
ermöglicht dann den Zugriff auf das Alter der Person.
Die Pseudovariable $this
wird innerhalb der Klasse für den Zugriff auf die Eigenschaften und Methoden des Objekts
verwendet.
Das Schlüsselwort new
wird verwendet, um eine neue Instanz einer Klasse zu erstellen. Im obigen Beispiel haben
wir eine neue Person im Alter von 25 Jahren erstellt.
Sie können auch Standardwerte für Konstruktorparameter festlegen, wenn diese bei der Erstellung 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; // if no argument is passed, parentheses can be omitted
echo $person->howOldAreYou(); // Outputs: 20
Wenn Sie in diesem Beispiel bei der Erstellung eines Person
Objekts kein Alter angeben, wird der Standardwert von
20 verwendet.
Das Schöne daran ist, dass die Eigenschaftsdefinition mit ihrer Initialisierung über den Konstruktor wie folgt verkürzt und vereinfacht werden kann:
class Person
{
function __construct(
private $age = 20,
) {
}
}
Der Vollständigkeit halber sei erwähnt, dass Objekte neben Konstruktoren auch Destruktoren haben können (Methode
__destruct
), die aufgerufen werden, bevor das Objekt aus dem Speicher freigegeben wird.
Namespaces
Namensräume ermöglichen es uns, verwandte Klassen, Funktionen und Konstanten zu organisieren und zu gruppieren und gleichzeitig Benennungskonflikte zu vermeiden. Man kann sie sich wie Ordner auf einem Computer vorstellen, wobei jeder Ordner Dateien enthält, die sich auf ein bestimmtes Projekt oder Thema beziehen.
Namensräume sind besonders nützlich in größeren Projekten oder bei der Verwendung von Bibliotheken von Drittanbietern, wo es zu Konflikten bei der Benennung von Klassen kommen kann.
Stellen Sie sich vor, Sie haben in Ihrem Projekt eine Klasse mit dem Namen Car
, die Sie in einen Namensraum mit
dem Namen Transport
einfügen möchten. Sie würden folgendermaßen vorgehen:
namespace Transport;
class Car
{
function honk()
{
echo 'Beep beep!';
}
}
Wenn Sie die Klasse Car
in einer anderen Datei verwenden wollen, müssen Sie angeben, aus welchem Namespace die
Klasse stammt:
$car = new Transport\Car;
Zur Vereinfachung können Sie am Anfang der Datei angeben, welche Klasse aus einem bestimmten Namespace Sie verwenden wollen, so dass Sie Instanzen erstellen können, ohne den vollständigen Pfad angeben zu müssen:
use Transport\Car;
$car = new Car;
Vererbung
Die Vererbung ist ein Werkzeug der objektorientierten Programmierung, das es ermöglicht, neue Klassen auf der Grundlage bestehender Klassen zu erstellen, deren Eigenschaften und Methoden zu erben und sie bei Bedarf zu erweitern oder neu zu definieren. Die Vererbung gewährleistet die Wiederverwendbarkeit des Codes und die Klassenhierarchie.
Einfach ausgedrückt: Wenn wir eine Klasse haben und eine andere davon abgeleitete Klasse mit einigen Änderungen erstellen möchten, können wir die neue Klasse von der ursprünglichen Klasse “erben”.
In PHP wird die Vererbung mit dem Schlüsselwort extends
realisiert.
Unsere Klasse Person
speichert Altersinformationen. Wir können eine weitere Klasse, Student
,
erstellen, die Person
erweitert und Informationen über das Studienfach hinzufügt.
Schauen wir uns ein Beispiel an:
class Person
{
private $age;
function __construct($age)
{
$this->age = $age;
}
function printInformation()
{
echo "Age: {$this->age} years\n";
}
}
class Student extends Person
{
private $fieldOfStudy;
function __construct($age, $fieldOfStudy)
{
parent::__construct($age);
$this->fieldOfStudy = $fieldOfStudy;
}
function printInformation()
{
parent::printInformation();
echo "Field of study: {$this->fieldOfStudy} \n";
}
}
$student = new Student(20, 'Computer Science');
$student->printInformation();
Wie funktioniert dieser Code?
- Wir haben das Schlüsselwort
extends
verwendet, um die KlassePerson
zu erweitern, d.h. die KlasseStudent
erbt alle Methoden und Eigenschaften vonPerson
. - Mit dem Schlüsselwort
parent::
können wir Methoden der übergeordneten Klasse aufrufen. In diesem Fall haben wir den Konstruktor der KlassePerson
aufgerufen, bevor wir der KlasseStudent
unsere eigenen Funktionen hinzugefügt haben. Ähnlich verhält es sich mit der VorgängermethodeprintInformation()
, bevor wir die Schülerinformationen auflisten.
Vererbung ist für Situationen gedacht, in denen es eine “ist ein”-Beziehung zwischen Klassen gibt. Zum Beispiel ist eine
Student
eine Person
. Eine Katze ist ein Tier. Sie ermöglicht es uns, in Fällen, in denen wir ein
Objekt (z. B. “Person”) im Code erwarten, stattdessen ein abgeleitetes Objekt zu verwenden (z. B. “Student”).
Es ist wichtig zu erkennen, dass der Hauptzweck der Vererbung nicht darin besteht, doppelten Code zu verhindern. Im Gegenteil, der Missbrauch von Vererbung kann zu komplexem und schwer zu wartendem Code führen. Wenn es keine “ist ein”-Beziehung zwischen Klassen gibt, sollten wir Komposition anstelle von Vererbung 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 hinzufügen (z. B. Employee
), die
andere Implementierungen dieser Methode bereitstellen werden. Die Fähigkeit von Objekten verschiedener Klassen, auf dieselbe
Methode auf unterschiedliche Weise zu reagieren, wird als Polymorphismus bezeichnet:
$people = [
new Person(30),
new Student(20, 'Computer Science'),
new Employee(45, 'Director'),
];
foreach ($people as $person) {
$person->printInformation();
}
Komposition
Komposition ist eine Technik, bei der wir, anstatt Eigenschaften und Methoden einer anderen Klasse zu erben, einfach deren Instanz in unserer Klasse verwenden. Auf diese Weise können wir Funktionalitäten und Eigenschaften mehrerer Klassen kombinieren, ohne komplexe Vererbungsstrukturen zu schaffen.
Ein Beispiel: Wir haben eine Klasse Engine
und eine Klasse Car
. Anstatt zu sagen “Ein Auto ist ein
Motor”, sagen wir “Ein Auto hat einen Motor”, was eine typische Kompositionsbeziehung ist.
class Engine
{
function start()
{
echo 'Engine is running.';
}
}
class Car
{
private $engine;
function __construct()
{
$this->engine = new Engine;
}
function start()
{
$this->engine->start();
echo 'The car is ready to drive!';
}
}
$car = new Car;
$car->start();
In diesem Fall verfügt die Klasse Car
nicht über alle Eigenschaften und Methoden der Klasse Engine
,
aber sie hat über die Eigenschaft $engine
Zugang zu ihnen.
Der Vorteil der Komposition ist eine größere Flexibilität bei der Gestaltung und eine bessere Anpassungsfähigkeit an künftige Änderungen.
Sichtbarkeit
In PHP können Sie “Sichtbarkeit” für Klasseneigenschaften, Methoden und Konstanten definieren. Die Sichtbarkeit bestimmt, wo Sie auf diese Elemente zugreifen können.
- Öffentlich: Wenn ein Element als
public
gekennzeichnet ist, bedeutet dies, dass Sie von überall darauf zugreifen können, auch außerhalb der Klasse. - Geschützt: Ein als
protected
gekennzeichnetes Element ist nur innerhalb der Klasse und aller ihrer Nachkommen (Klassen, die von ihr erben) zugänglich. - Privat: Wenn ein Element als
private
gekennzeichnet ist, kann man nur innerhalb der Klasse, in der es definiert wurde, darauf zugreifen.
Wenn Sie die Sichtbarkeit nicht angeben, setzt PHP sie automatisch auf public
.
Schauen wir uns einen Beispielcode an:
class VisibilityExample
{
public $publicProperty = 'Public';
protected $protectedProperty = 'Protected';
private $privateProperty = 'Private';
public function printProperties()
{
echo $this->publicProperty; // Works
echo $this->protectedProperty; // Works
echo $this->privateProperty; // Works
}
}
$object = new VisibilityExample;
$object->printProperties();
echo $object->publicProperty; // Works
// echo $object->protectedProperty; // Throws an error
// echo $object->privateProperty; // Throws an error
Fortsetzung der Klassenvererbung:
class ChildClass extends VisibilityExample
{
public function printProperties()
{
echo $this->publicProperty; // Works
echo $this->protectedProperty; // Works
// echo $this->privateProperty; // Throws an error
}
}
In diesem Fall kann die Methode printProperties()
in der ChildClass
auf die öffentlichen und
geschützten Eigenschaften zugreifen, aber nicht auf die privaten Eigenschaften der übergeordneten Klasse.
Daten und Methoden sollten so versteckt wie möglich sein und nur über eine definierte Schnittstelle zugänglich sein. Auf diese Weise können Sie die interne Implementierung der Klasse ändern, ohne dass sich dies auf den restlichen Code auswirkt.
Final-Schlüsselwort
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 eine Klasse als final
markiert ist, kann sie nicht erweitert werden.
Wenn eine Methode als final
markiert ist, kann sie in einer Unterklasse nicht überschrieben werden.
Wenn wir wissen, dass eine bestimmte Klasse oder Methode nicht mehr geändert wird, können wir leichter Änderungen vornehmen, ohne uns Gedanken über mögliche Konflikte zu machen. Wir können zum Beispiel eine neue Methode hinzufügen, ohne befürchten zu müssen, dass ein Nachkomme bereits eine Methode mit demselben Namen hat, was zu einer Kollision führen würde. Oder wir können die Parameter einer Methode ändern, ohne das Risiko einer Inkonsistenz mit einer überschriebenen Methode in einem Nachfahren einzugehen.
final class FinalClass
{
}
// The following code will throw an error because we cannot inherit from a final class.
class ChildOfFinalClass extends FinalClass
{
}
In diesem Beispiel führt der Versuch, von der endgültigen Klasse FinalClass
zu erben, zu einem Fehler.
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, nicht zu einer bestimmten Instanz der Klasse. Das bedeutet, dass Sie keine Instanz der Klasse erstellen müssen, um auf sie zuzugreifen. Stattdessen können Sie sie direkt über den Klassennamen aufrufen oder auf sie zugreifen.
Da statische Elemente zur Klasse und nicht zu ihren Instanzen gehören, können Sie die Pseudovariable $this
nicht
in statischen Methoden verwenden.
Die Verwendung von statischen Eigenschaften führt zu verschleiertem Code voller Fallstricke, so dass Sie sie niemals verwenden sollten, und wir werden hier auch kein Beispiel zeigen. Andererseits sind statische Methoden nützlich. Hier ist ein Beispiel:
class Calculator
{
public static function add($a, $b)
{
return $a + $b;
}
public static function subtract($a, $b)
{
return $a - $b;
}
}
// Using the static method without creating an instance of the class
echo Calculator::add(5, 3); // Output: 8
echo Calculator::subtract(5, 3); // Output: 2
In diesem Beispiel haben wir eine Klasse Calculator
mit zwei statischen Methoden erstellt. Wir können diese
Methoden direkt aufrufen, ohne eine Instanz der Klasse mit dem ::
Operator zu erstellen. Statische Methoden sind
besonders nützlich für Operationen, die nicht vom Zustand einer bestimmten Klasseninstanz 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 nicht ändern. Im Gegensatz zu Variablen bleibt der Wert einer Konstante gleich.
class Car
{
public const NumberOfWheels = 4;
public function displayNumberOfWheels(): int
{
echo self::NumberOfWheels;
}
}
echo Car::NumberOfWheels; // Output: 4
In diesem Beispiel haben wir eine Klasse Car
mit der Konstante NumberOfWheels
. Beim Zugriff auf die
Konstante innerhalb der Klasse können wir das Schlüsselwort self
anstelle des Klassennamens verwenden.
Objektschnittstellen
Objektschnittstellen fungieren als “Verträge” für Klassen. Wenn eine Klasse eine Objektschnittstelle implementieren soll, muss sie alle Methoden enthalten, die die Schnittstelle definiert. Auf diese Weise kann man sicherstellen, dass sich bestimmte Klassen an denselben “Vertrag” oder dieselbe Struktur halten.
In PHP werden Schnittstellen mit dem Schlüsselwort interface
definiert. Alle Methoden, die in einer Schnittstelle
definiert sind, sind öffentlich (public
). Wenn eine Klasse eine Schnittstelle implementiert, verwendet sie das
Schlüsselwort implements
.
interface Animal
{
function makeSound();
}
class Cat implements Animal
{
public function makeSound()
{
echo 'Meow';
}
}
$cat = new Cat;
$cat->makeSound();
Wenn eine Klasse eine Schnittstelle implementiert, aber nicht alle erwarteten Methoden definiert sind, wird PHP einen Fehler ausgeben.
Eine Klasse kann mehrere Schnittstellen auf einmal implementieren, was sich von der Vererbung unterscheidet, bei der eine Klasse nur von einer Klasse erben kann:
interface Guardian
{
function guardHouse();
}
class Dog implements Animal, Guardian
{
public function makeSound()
{
echo 'Bark';
}
public function guardHouse()
{
echo 'Dog diligently guards the house';
}
}
Abstrakte Klassen
Abstrakte Klassen dienen als Basisvorlagen für andere Klassen, aber Sie können ihre Instanzen nicht direkt erstellen. Sie enthalten eine Mischung 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 der Elternklasse bereitstellen.
Wir verwenden das Schlüsselwort abstract
, um eine abstrakte Klasse zu definieren.
abstract class AbstractClass
{
public function regularMethod()
{
echo 'This is a regular method';
}
abstract public function abstractMethod();
}
class Child extends AbstractClass
{
public function abstractMethod()
{
echo 'This is the implementation of the abstract method';
}
}
$instance = new Child;
$instance->regularMethod();
$instance->abstractMethod();
In diesem Beispiel haben wir eine abstrakte Klasse mit einer regulären und einer abstrakten Methode. Dann haben wir eine
Klasse Child
, die von AbstractClass
erbt und eine Implementierung für die abstrakte Methode
bereitstellt.
Was ist der Unterschied zwischen Schnittstellen und abstrakten Klassen? Abstrakte Klassen können sowohl abstrakte als auch konkrete Methoden enthalten, während Schnittstellen nur festlegen, welche Methoden die Klasse implementieren muss, aber keine Implementierung anbieten. Eine Klasse kann nur von einer abstrakten Klasse erben, kann aber eine beliebige Anzahl von Schnittstellen implementieren.
Typ-Prüfung
Beim Programmieren ist es wichtig, sicherzustellen, dass die Daten, mit denen wir arbeiten, vom richtigen Typ sind. In PHP gibt es Werkzeuge, die diese Sicherheit bieten. Die Überprüfung, ob die Daten vom richtigen Typ sind, nennt man “Typüberprüfung”.
Typen, denen wir in PHP begegnen können:
- Basistypen: Dazu gehören
int
(Ganzzahlen),float
(Gleitkommazahlen),bool
(boolesche Werte),string
(Strings),array
(Arrays) undnull
. - Klassen: Wenn ein Wert eine Instanz einer bestimmten Klasse sein soll.
- Schnittstellen: Definiert eine Reihe von Methoden, die eine Klasse implementieren muss. Ein Wert, der eine Schnittstelle erfüllt, muss über diese Methoden verfügen.
- Mischtypen: Wir können festlegen, dass eine Variable mehrere zulässige Typen haben kann.
- Void: Dieser spezielle Typ gibt an, dass eine Funktion oder Methode keinen Wert zurückgibt.
Schauen wir uns an, wie wir den Code ändern können, um Typen einzubeziehen:
class Person
{
private int $age;
public function __construct(int $age)
{
$this->age = $age;
}
public function printAge(): void
{
echo "This person is {$this->age} years old.";
}
}
/**
* A function that accepts a Person object and prints the person's age.
*/
function printPersonAge(Person $person): void
{
$person->printAge();
}
Auf diese Weise stellen wir sicher, dass unser Code Daten des richtigen Typs erwartet und mit ihnen arbeitet, was uns hilft, mögliche Fehler zu vermeiden.
Einige Typen können nicht direkt in PHP geschrieben werden. In diesem Fall werden sie im phpDoc-Kommentar aufgeführt, dem
Standardformat für die Dokumentation von PHP-Code, das mit /**
beginnt und mit */
endet. Es ermöglicht
Ihnen, Beschreibungen von Klassen, Methoden usw. hinzuzufügen. Außerdem können Sie komplexe Typen mit den so genannten
Annotationen @var
, @param
und @return
auflisten. Diese Typen werden dann von statischen
Code-Analyse-Tools verwendet, aber nicht von PHP selbst überprüft.
class Registry
{
/** @var array<Person> indicates that it's an array of Person objects */
private array $persons = [];
public function addPerson(Person $person): void
{
$this->persons[] = $person;
}
}
Vergleich und Identität
In PHP können Sie Objekte auf zwei Arten vergleichen:
- Wertevergleich
==
: Es wird geprüft, ob die Objekte der gleichen Klasse angehören und die gleichen Werte in ihren Eigenschaften haben. - Identität
===
: Prüft, ob es sich um die gleiche 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, because they have the same value
var_dump($car1 === $car2); // false, because they are not the same instance
var_dump($car1 === $car3); // true, because $car3 is the same instance as $car1
Der instanceof
Operator
Mit dem Operator instanceof
können Sie feststellen, ob ein bestimmtes Objekt eine Instanz einer bestimmten Klasse
ist, ein Nachkomme dieser Klasse oder ob es eine bestimmte Schnittstelle implementiert.
Stellen Sie sich 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, 'Computer Science');
// Check if $student is an instance of the Student class
var_dump($student instanceof Student); // Output: bool(true)
// Check if $student is an instance of the Person class (because Student is a descendant of Person)
var_dump($student instanceof Person); // Output: bool(true)
Aus den Ausgaben ist ersichtlich, dass das Objekt $student
als Instanz der Klassen Student
und
Person
betrachtet wird.
Fließende Schnittstellen
Eine “Fluent Interface” ist eine Technik in der OOP, die es ermöglicht, Methoden in einem einzigen Aufruf zu verketten. Dies vereinfacht und verdeutlicht oft den Code.
Das Schlüsselelement einer fließenden Schnittstelle ist, dass jede Methode in der Kette einen Verweis auf das aktuelle Objekt
zurückgibt. Dies wird durch die Verwendung von return $this;
am Ende der Methode erreicht. Dieser Programmierstil
wird häufig mit Methoden in Verbindung gebracht, die “Setter” genannt werden und die Werte der Eigenschaften eines Objekts
festlegen.
Schauen wir uns an, wie eine fließende Schnittstelle für das Senden von E-Mails aussehen könnte:
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 werden die Methoden setFrom()
, setRecipient()
und setMessage()
verwendet, um die entsprechenden Werte (Absender, Empfänger, Nachrichteninhalt) zu setzen. Nach dem Setzen jedes dieser Werte
geben die Methoden das aktuelle Objekt ($email
) zurück, so dass wir eine weitere Methode dahinter verketten können.
Schließlich rufen wir die Methode send()
auf, die die E-Mail tatsächlich versendet.
Dank der fließenden Schnittstellen 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 Operator clone
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 eine spezielle Methode
__clone()
in der Klasse definieren. Diese Methode wird automatisch aufgerufen, wenn das Objekt geklont wird.
class Sheep
{
public string $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function __clone()
{
$this->name = 'Clone of ' . $this->name;
}
}
$original = new Sheep('Dolly');
echo $original->name . "\n"; // Outputs: Dolly
$clone = clone $original;
echo $clone->name . "\n"; // Outputs: Clone of Dolly
In diesem Beispiel haben wir eine Klasse Sheep
mit einer Eigenschaft $name
. Wenn wir eine Instanz
dieser Klasse klonen, sorgt die Methode __clone()
dafür, dass der Name des geklonten Schafs das Präfix “Clone
of” erhält.
Eigenschaften
Traits in PHP sind ein Werkzeug, das die gemeinsame Nutzung von Methoden, Eigenschaften und Konstanten zwischen Klassen ermöglicht und die Duplizierung von Code verhindert. Man kann sie sich wie einen “Kopieren und Einfügen”-Mechanismus vorstellen (Strg-C und Strg-V), bei dem der Inhalt eines Traits in Klassen “eingefügt” wird. So können Sie Code wiederverwenden, ohne komplizierte Klassenhierarchien erstellen zu müssen.
Werfen wir einen Blick auf ein einfaches Beispiel für die Verwendung von Traits in PHP:
trait Honking
{
public function honk()
{
echo 'Beep beep!';
}
}
class Car
{
use Honking;
}
class Truck
{
use Honking;
}
$car = new Car;
$car->honk(); // Outputs 'Beep beep!'
$truck = new Truck;
$truck->honk(); // Also outputs 'Beep beep!'
In diesem Beispiel haben wir einen Trait namens Honking
, der eine Methode honk()
enthält. Dann haben
wir zwei Klassen: Car
und Truck
, die beide den Trait Honking
verwenden. Infolgedessen
“besitzen” beide Klassen die Methode honk()
, und wir können sie für Objekte beider Klassen aufrufen.
Traits ermöglichen eine einfache und effiziente gemeinsame Nutzung von Code zwischen Klassen. Sie greifen nicht in die
Vererbungshierarchie ein, d. h. $car instanceof Honking
gibt false
zurück.
Ausnahmen
Ausnahmen in der OOP ermöglichen es uns, Fehler und unerwartete Situationen in unserem Code anständig zu behandeln. Sie sind Objekte, die Informationen über einen Fehler oder eine ungewöhnliche Situation enthalten.
In PHP haben wir eine eingebaute Klasse Exception
, die als Basis für alle Ausnahmen dient. Sie verfügt über
mehrere Methoden, mit denen wir weitere Informationen über die Ausnahme erhalten können, z. B. die Fehlermeldung, die Datei und
die Zeile, in der der Fehler aufgetreten ist, usw.
Wenn ein Fehler im Code auftritt, können wir die Ausnahme mit dem Schlüsselwort throw
“werfen”.
function division(float $a, float $b): float
{
if ($b === 0) {
throw new Exception('Division by zero!');
}
return $a / $b;
}
Wenn die Funktion division()
null als zweites Argument erhält, löst sie eine Ausnahme mit der Fehlermeldung
'Division by zero!'
aus. Um zu verhindern, dass das Programm abstürzt, wenn die Ausnahme ausgelöst wird, fangen wir
sie im Block try/catch
ab:
try {
echo division(10, 0);
} catch (Exception $e) {
echo 'Exception caught: '. $e->getMessage();
}
Code, der eine Ausnahme auslösen kann, wird in einen Block try
eingeschlossen. Wenn die Ausnahme ausgelöst wird,
geht die Codeausführung in einen Block catch
über, in dem wir die Ausnahme behandeln können (z. B. eine
Fehlermeldung schreiben).
Nach den Blöcken try
und catch
kann ein optionaler Block finally
eingefügt werden, der
immer ausgeführt wird, unabhängig davon, ob die Ausnahme ausgelöst wurde oder nicht (auch wenn return
,
break
oder continue
im Block try
oder catch
verwendet wird):
try {
echo division(10, 0);
} catch (Exception $e) {
echo 'Exception caught: '. $e->getMessage();
} finally {
// Code that is always executed whether the exception has been thrown or not
}
Wir können auch unsere eigenen Ausnahmeklassen (Hierarchie) erstellen, die von der Klasse Exception erben. Betrachten 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('Not enough funds in the account.');
}
if ($amount > $this->dailyLimit) {
throw new ExceededLimitException('Daily withdrawal limit exceeded.');
}
$this->balance -= $amount;
return $this->balance;
}
}
Mehrere catch
Blöcke können für einen einzigen try
Block angegeben werden, wenn Sie verschiedene
Arten von Ausnahmen 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 'An error occurred during the operation.';
}
In diesem Beispiel ist es wichtig, die Reihenfolge der catch
Blöcke zu beachten. Da alle Ausnahmen von
BankingException
erben, würden alle Ausnahmen in diesem Block abgefangen werden, ohne dass der Code die
nachfolgenden Blöcke von catch
erreicht. Daher ist es wichtig, spezifischere Ausnahmen (d. h. solche, die von
anderen erben) in der Reihenfolge der catch
-Blöcke höher anzusiedeln als ihre übergeordneten Ausnahmen.
Wiederholungen
In PHP können Sie mit der foreach
Schleife durch Objekte laufen, ähnlich wie Sie durch ein Array laufen. Damit
dies funktioniert, muss das Objekt eine spezielle Schnittstelle implementieren.
Die erste Möglichkeit besteht darin, die Schnittstelle Iterator
zu implementieren, die über die Methoden
current()
, die den aktuellen Wert zurückgeben, key()
, die den Schlüssel zurückgeben,
next()
, die zum nächsten Wert springen, rewind()
, die zum Anfang springen, und valid()
,
die überprüft, ob wir schon am Ende sind, verfügt.
Die andere Möglichkeit besteht darin, eine Schnittstelle IteratorAggregate
zu implementieren, die nur eine
Methode getIterator()
hat. Diese gibt entweder ein Platzhalterobjekt zurück, das die Durchquerung ermöglicht, oder
sie kann ein Generator sein, eine spezielle Funktion, die yield
verwendet, um Schlüssel und Werte nacheinander
zurückzugeben:
class Person
{
public function __construct(
public int $age,
) {
}
}
class Registry implements IteratorAggregate
{
private array $people = [];
public function addPerson(Person $person): void
{
$this->people[] = $person;
}
public function getIterator(): Generator
{
foreach ($this->people as $person) {
yield $person;
}
}
}
$list = new Registry;
$list->addPerson(new Person(30));
$list->addPerson(new Person(25));
foreach ($list as $person) {
echo "Age: {$person->age} years\n";
}
Bewährte Praktiken
Sobald Sie die Grundprinzipien der objektorientierten Programmierung beherrschen, ist es wichtig, sich auf die besten Praktiken der OOP zu konzentrieren. Diese helfen Ihnen, Code zu schreiben, der nicht nur funktional, sondern auch lesbar, verständlich und leicht zu warten ist.
- Trennung von Belangen: Jede Klasse sollte eine klar definierte Verantwortung haben und sich nur mit einer Hauptaufgabe befassen. Wenn eine Klasse zu viele Aufgaben hat, kann es sinnvoll sein, sie in kleinere, spezialisierte Klassen aufzuteilen.
- Kapselung: Daten und Methoden sollten so versteckt wie möglich sein und nur über eine definierte Schnittstelle zugänglich sein. Auf diese Weise können Sie die interne Implementierung einer Klasse ändern, ohne dass sich dies auf den restlichen Code auswirkt.
- Dependency Injection: Anstatt Abhängigkeiten direkt innerhalb einer Klasse zu schaffen, sollten Sie sie von außen “injizieren”. Für ein tieferes Verständnis dieses Prinzips empfehlen wir die Kapitel über Dependency Injection.