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 Klasse Person zu erweitern, d.h. die Klasse Student erbt alle Methoden und Eigenschaften von Person.
  • Mit dem Schlüsselwort parent:: können wir Methoden der übergeordneten Klasse aufrufen. In diesem Fall haben wir den Konstruktor der Klasse Person aufgerufen, bevor wir der Klasse Student unsere eigenen Funktionen hinzugefügt haben. Ähnlich verhält es sich mit der Vorgängermethode printInformation(), 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.

  1. Öffentlich: Wenn ein Element als public gekennzeichnet ist, bedeutet dies, dass Sie von überall darauf zugreifen können, auch außerhalb der Klasse.
  2. Geschützt: Ein als protected gekennzeichnetes Element ist nur innerhalb der Klasse und aller ihrer Nachkommen (Klassen, die von ihr erben) zugänglich.
  3. 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:

  1. Basistypen: Dazu gehören int (Ganzzahlen), float (Gleitkommazahlen), bool (boolesche Werte), string (Strings), array (Arrays) und null.
  2. Klassen: Wenn ein Wert eine Instanz einer bestimmten Klasse sein soll.
  3. Schnittstellen: Definiert eine Reihe von Methoden, die eine Klasse implementieren muss. Ein Wert, der eine Schnittstelle erfüllt, muss über diese Methoden verfügen.
  4. Mischtypen: Wir können festlegen, dass eine Variable mehrere zulässige Typen haben kann.
  5. 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:

  1. Wertevergleich ==: Es wird geprüft, ob die Objekte der gleichen Klasse angehören und die gleichen Werte in ihren Eigenschaften haben.
  2. 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.

  1. 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.
  2. 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.
  3. 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.
Version: 4.0