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:
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:
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:
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:
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:
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.
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:
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:
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:
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:
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 ...