Traits

In PHP gibt es eine Methode der Wiederverwendung von Code, die Traits genannt wird.

Traits sind ein Mechanismus zur Wiederverwendung von Code, der in Programmiersprachen mit einfacher Vererbung wie PHP verwendet wird. Ein Trait kann verwendet werden, um den Beschränkungen der einfachen Vererbung auszuweichen, indem er erlaubt, dass Mengen von Methoden frei in mehreren unabhängigen Klassen, die in verschiedenen Klassenhierarchien stecken, wiederzuverwenden. Die Semantik der Kombination von Traits und Klassen ist so definiert, dass die Komplexität reduziert wird und die üblichen Probleme vermeidet, die mit Mehrfachvererbung und Mixins in Verbindung gebracht werden.

Ein Trait hat Ähnlichkeit mit einer Klasse, ist aber nur dafür vorgesehen, Funktionalität in einer feingranularen und konsistenten Art und Weise zu bündeln. Es ist nicht möglich einen Trait alleinstehend zu instantiieren. Es handelt sich um einen Zusatz zur traditionellen Vererbung und erlaubt horizontale Komposition von Verhaltensweisen, d. h. die Verwendung von Klassenmethoden ohne Vererbung vorauszusetzen.

Beispiel #1 Trait-Beispiel

<?php

trait TraitA {
public function
sayHello() {
echo
'Hello';
}
}

trait
TraitB {
public function
sayWorld() {
echo
'World';
}
}

class
MyHelloWorld
{
use
TraitA, TraitB; // Eine Klasse kann mehrere Traits verwenden

public function sayHelloWorld() {
$this->sayHello();
echo
' ';
$this->sayWorld();
echo
"!\n";
}
}

$myHelloWorld = new MyHelloWorld();
$myHelloWorld->sayHelloWorld();

?>

Das oben gezeigte Beispiel erzeugt folgende Ausgabe:

Hello World!

Rangfolge

Methoden der aktuellen Klasse überschreiben Methoden, welche von Traits definiert wurden. Jene wiederum überschreiben von Elternklassen geerbte Methoden.

Beispiel #2 Rangfolge-Beispiel

Die Methode sayHello der Basisklasse wird durch die gleichnamige Methode aus dem Trait überschrieben.

<?php
class Base {
public function
sayHello() {
echo
'Hallo ';
}
}

trait
SayWorld {
public function
sayHello() {
parent::sayHello();
echo
'Welt!';
}
}

class
MyHelloWorld extends Base {
use
SayWorld;
}

$o = new MyHelloWorld();
$o->sayHello();
?>

Das oben gezeigte Beispiel erzeugt folgende Ausgabe:

Hallo Welt!

Beispiel #3 Weiteres Beispiel für Rangfolge

<?php
trait HelloWorld {
public function
sayHello() {
echo
'Hallo Welt!';
}
}

class
TheWorldIsNotEnough {
use
HelloWorld;
public function
sayHello() {
echo
'Hallo Universum!';
}
}

$o = new TheWorldIsNotEnough();
$o->sayHello();
?>

Das oben gezeigte Beispiel erzeugt folgende Ausgabe:

Hallo Universum!

Mehrere Traits

Mehrere Traits können durch eine kommaseparierte Auflistung im use-Statement zu einer Klasse hinzugefügt werden.

Beispiel #4 Verwendung mehrerer Traits

<?php
trait Hello {
public function
sayHello() {
echo
'Hallo ';
}
}

trait
World {
public function
sayWorld() {
echo
' Welt';
}
}

class
MyHelloWorld {
use
Hello, World;
public function
sayExclamationMark() {
echo
'!';
}
}

$o = new MyHelloWorld();
$o->sayHello();
$o->sayWorld();
$o->sayExclamationMark();
?>

Das oben gezeigte Beispiel erzeugt folgende Ausgabe:

Hallo Welt!

Konfliktauflösung

Falls zwei Traits eine Methode mit gleichem Namen einfügen, so wird ein Fatal Error erzeugt, wenn der Konflikt nicht explizit aufgelöst wird.

Um einen Namenskonflikt zwischen Traits in der gleichen Klasse aufzulösen muss der insteadof-Operator verwendet werden, um genau eine der Methoden des Konflikts auszuwählen.

Da dies es nur erlaubt Methoden auszuschließen kann der as-Operator verwendet werden, um einen Alias zu einer der Methoden hinzuzufügen. Es ist zu beachten, dass der as Operator die Methode nicht umbenennt, und auch keine andere Methode beeinflusst.

Beispiel #5 Konfliktauflösung

In diesem Beispiel verwendet die Klasse Talker die Traits A und B. Nachdem A und B sich widersprechende Methoden besitzen definiert die Klasse, dass sie die Variante von smallTalk aus dem Trait B und die Variante von bigTalk des Traits A verwenden möchte.

Die Klasse Aliased_Talker verwendet den as-Operator, damit sie außerdem die Implementierung der bigTalk-Methode von B unter dem Alias talk verwenden kann.

<?php
trait A {
public function
smallTalk() {
echo
'a';
}
public function
bigTalk() {
echo
'A';
}
}

trait
B {
public function
smallTalk() {
echo
'b';
}
public function
bigTalk() {
echo
'B';
}
}

class
Talker {
use
A, B {
B::smallTalk insteadof A;
A::bigTalk insteadof B;
}
}

class
Aliased_Talker {
use
A, B {
B::smallTalk insteadof A;
A::bigTalk insteadof B;
B::bigTalk as talk;
}
}
?>

Veränderung der Sichtbarkeit von Methoden

Mit der as-Syntax ist es ebenso möglich, die Sichtbarkeit einer Methode in der darstellenden Klasse zu verändern.

Beispiel #6 Veränderung der Sichtbarkeit von Methoden

<?php
trait HelloWorld {
public function
sayHello() {
echo
'Hallo Welt!';
}
}

// Ändern der Sichtbarkeit von sayHello
class MyClass1 {
use
HelloWorld { sayHello as protected; }
}

// Alias der Methode mit geänderter Sichtbarkeit
// Die Sichtbarkeit von sayHello bleibt unverändert
class MyClass2 {
use
HelloWorld { sayHello as private myPrivateHello; }
}
?>

Trait-Zusammensetzung aus Traits

Ebenso wie Klassen können auch Traits andere Traits verwenden. Indem man einen oder mehrere Traits in einer Traitdefinition verwendet kann man Traits entweder teilweise oder vollständig aus Methoden, welche in anderen Traits definiert sind, zusammensetzen.

Beispiel #7 Trait-Zusammensetzung aus Traits

<?php
trait Hello {
public function
sayHello() {
echo
'Hallo ';
}
}

trait
World {
public function
sayWorld() {
echo
'Welt!';
}
}

trait
HelloWorld {
use
Hello, World;
}

class
MyHelloWorld {
use
HelloWorld;
}

$o = new MyHelloWorld();
$o->sayHello();
$o->sayWorld();
?>

Das oben gezeigte Beispiel erzeugt folgende Ausgabe:

Hallo Welt!

Abstrakte Traitmember

Traits unterstützen auch die Verwendung von abstrakten Methoden, um darstellenden Klassen Anforderungen vorzugeben. Es werden öffentliche, geschützte und private Methoden unterstützt. Vor PHP 8.0.0 wurden nur öffentliche und geschützte abstrakte Methoden unterstützt.

Achtung

Seit PHP 8.0.0 muss die Signatur einer konkreten Methode den Regeln zur Signaturkompatibilität entsprechen; zuvor durfte ihre Signatur davon abweichen.

Beispiel #8 Ausdrücken von Anforderungen durch abstrakte Methoden

<?php
trait Hello {
public function
sayHelloWorld() {
echo
'Hallo'.$this->getWorld();
}
abstract public function
getWorld();
}

class
MyHelloWorld {
private
$world;
use
Hello;
public function
getWorld() {
return
$this->world;
}
public function
setWorld($val) {
$this->world = $val;
}
}
?>

Statische Traitmember

Traits unterstützen sowohl statische Variablen, statische Methoden und statische Eignschaften.

Hinweis:

Seit PHP 8.1.0 sind der Aufruf einer statischen Methode oder der Zugriff auf eine statische Eigenschaft direkt in einem Trait veraltet. Auf statische Methoden und Eigenschaften sollte nur in einer Klasse zugegriffen werden, die den Trait verwendet.

Beispiel #9 Statische Variablen

<?php

trait Counter
{
public function
inc()
{
static
$c = 0;
$c = $c + 1;
echo
"$c\n";
}
}

class
C1
{
use
Counter;
}

class
C2
{
use
Counter;
}

$o = new C1();
$o->inc();
$p = new C2();
$p->inc();

?>

Das oben gezeigte Beispiel erzeugt folgende Ausgabe:

1
1

Beispiel #10 Statische Methoden

<?php

trait StaticExample
{
public static function
doSomething()
{
return
'Tue etwas';
}
}

class
Example
{
use
StaticExample;
}

echo
Example::doSomething();

?>

Das oben gezeigte Beispiel erzeugt folgende Ausgabe:

Doing something

Beispiel #11 Statische Eigenschaften

Achtung

Vor PHP 8.3.0 wurden statische Eigenschaften, die in einem Trait definiert waren, von allen Klassen in derselben Vererbungshierarchie, die diesen Trait verwendeten, gemeinsam genutzt. Seit PHP 8.3.0 wird eine statische Eigenschaft, die von einer Kindklasse über einen Trait verwendet wird, als von der in der Elternklasse definierte Eigenschaft unterschieden betrachtet.

<?php

trait T
{
public static
$counter = 1;
}

class
A
{
use
T;

public static function
incrementCounter()
{
static::
$counter++;
}
}

class
B extends A
{
use
T;
}

A::incrementCounter();

echo
A::$counter, "\n";
echo
B::$counter, "\n";

?>

Das oben gezeigte Beispiel erzeugt mit PHP 8.3 folgende Ausgabe:

2
1

Eigenschaften

Traits können ebenfalls Eigenschaften definieren.

Beispiel #12 Eigenschaften definieren

<?php

trait PropertiesTrait
{
public
$x = 1;
}

class
PropertiesExample
{
use
PropertiesTrait;
}

$example = new PropertiesExample();
$example->x;

?>

Definiert ein Trait eine Eigenschaft, dann kann eine Klasse keine Eigenschaft mit dem selben Namen definieren, es sei denn, sie ist kompatibel (selbe Sichtbarkeit und Typ, readonly-Modifikator und Initialwert); andernfalls wird ein fataler Fehler auftreten.

Beispiel #13 Konfliktauflösung

<?php
trait PropertiesTrait {
public
$same = true;
public
$different1 = false;
public
bool $different2;
public
bool $different3;
}

class
PropertiesExample {
use
PropertiesTrait;
public
$same = true;
public
$different1 = true; // Fatal error
public string $different2; // Fatal error
readonly protected bool $different3; // Fatal error
}
?>

Konstanten

Seit PHP 8.2.0 können Traits auch Konstanten definieren.

Beispiel #14 Definieren von Konstanten

<?php
trait ConstantsTrait {
public const
FLAG_MUTABLE = 1;
final public const
FLAG_IMMUTABLE = 5;
}

class
ConstantsExample {
use
ConstantsTrait;
}

$example = new ConstantsExample;
echo
$example::FLAG_MUTABLE;
?>

Das oben gezeigte Beispiel erzeugt folgende Ausgabe:

1

Wenn ein Trait eine Konstante definiert, kann eine Klasse keine Konstante mit demselben Namen definieren, es sei denn, sie ist kompatibel (dieselbe Sichtbarkeit, derselbe Initialwert und dieselbe Finalität), andernfalls wird ein fataler Fehler ausgegeben.

Beispiel #15 Konfliktauflösung

<?php
trait ConstantsTrait {
public const
FLAG_MUTABLE = 1;
final public const
FLAG_IMMUTABLE = 5;
}

class
ConstantsExample {
use
ConstantsTrait;
public const
FLAG_IMMUTABLE = 5; // Fatal error
}
?>

Finale Methoden

Seit PHP 8.3.0 kann der Modifikator final mit dem Operator as auf Methoden angewendet werden, die aus Traits importiert wurden. Dies kann verwendet werden, um zu verhindern, dass Kindklassen die Methode überschreiben. Die Klasse, die den Trait verwendet, kann die Methode jedoch weiterhin überschreiben.

Beispiel #16 Definieren einer Methode aus einem Trait als final

<?php

trait CommonTrait
{
public function
method()
{
echo
'Hello';
}
}

class
FinalExampleA
{
use
CommonTrait {
CommonTrait::method as final; // Das "final" verhindert, dass Kindklassen
// die Methode überschreiben.
}
}

class
FinalExampleB extends FinalExampleA
{
public function
method() {}
}

?>

Das oben gezeigte Beispiel erzeugt eine ähnliche Ausgabe wie:

Fatal error: Cannot override final method FinalExampleA::method() in ...