Skip to content

5. Klassen

Begriffserklärung: Eine Klasse (class) ist ein Typ PHP. Eine Variable dieses Typs wird als Objekt bezeichnet. Ein Objekt ist eine Instanz (ein Exemplar) einer Klasse.

5.1. Die Skript-Baumstruktur

Image

5.2. Jede Variable kann zu einem Objekt mit Attributen werden

Das Skript [classes-01.php] lautet wie folgt:


<?php

// ein generisches Objekt
// $obj1=new stdClass();
// Jede Variable kann per Definition Attribute haben
$obj1->attr1 = "un";
$obj1->attr2 = 100;
// zeigt das Objekt an
print "objet1=[$obj1->attr1,$obj1->attr2]\n";
// ändert das Objekt
$obj1->attr2 += 100;
// zeigt das Objekt an
print "objet1=[$obj1->attr1,$obj1->attr2]\n";
// kopiert den Wert von Objekt1 (Adresse des referenzierten Objekts) in Objekt2
// Die beiden Variablen sind nun unterschiedlich, verweisen aber auf dasselbe Objekt
$obj2 = $obj1;
// ändert obj2
$obj2->attr2 = 0;
// zeigt die beiden Objekte an
print "objet1=[$obj1->attr1,$obj1->attr2]\n";
print "objet2=[$obj2->attr1,$obj2->attr2]\n";
// ändert das Objekt, auf das „Objekt1“ verweist
$obj1 = new stdClass();
print "obj1 :\n";
print_r($obj1);
print "obj2 :\n";
print_r($obj2);
// weist objekt3 die Referenz (die Adresse) von objekt2 zu
// $obj2 und $obj3 sind dann ein und dieselbe Variable
$obj3 = &$obj2;
print "obj2 :\n";
print_r($obj2);
print "obj3 :\n";
print_r($obj3);
// ändert obj3
$obj3->attr2 = 10;
// zeigt beide Objekte an
print "objet2=[$obj2->attr1,$obj2->attr2]\n";
print "objet3=[$obj3->attr1,$obj3->attr2]\n";
// ändert das Objekt, auf das obj2 verweist
$obj2 = new stdClass();
$obj2->attr3 = "deux";
$obj2->attr4 = 20;
// zeigt die beiden Objekte $obj2 und $obj3 an
print "obj2 :\n";
print_r($obj2);
print "obj3 :\n";
print_r($obj3);

// Ist ein Objekt ein Dictionary?
print count($obj3) . "\n";
while (list($attribut, $valeur) = each($obj3)) {
  print "obj3[$attribut]=$valeur\n";
}
// Ende
exit;

Ergebnisse:


Warning: Creating default object from empty value in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exemple_14.php on line 6
objet1=[un,100]
objet1=[un,200]
objet1=[un,0]
objet2=[un,0]
obj1 :
stdClass Object
(
)
obj2 :
stdClass Object
(
    [attr1] => un
    [attr2] => 0
)
obj2 :
stdClass Object
(
    [attr1] => un
    [attr2] => 0
)
obj3 :
stdClass Object
(
    [attr1] => un
    [attr2] => 0
)
objet2=[un,10]
objet3=[un,10]
obj2 :
stdClass Object
(
    [attr3] => deux
    [attr4] => 20
)
obj3 :
stdClass Object
(
    [attr3] => deux
    [attr4] => 20
)

Warning: count(): Parameter must be an array or an object that implements Countable in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exemple_14.php on line 50
1

Deprecated: The each() function is deprecated. This message will be suppressed on further calls in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exemple_14.php on line 51
obj3[attr3]=deux
obj3[attr4]=20

Anmerkungen

  • Zeile 6: Die Notation $obj->attr bezeichnet das Attribut attr der Variablen $obj. Falls dieses nicht existiert, wird es erstellt, wodurch die Variable $obj zu einem Objekt mit Attributen wird. Wir haben gesehen, dass PHP dann standardmäßig ein Objekt vom Typ stdClass erstellt;
  • Zeile 16: Der Ausdruck $obj2=$obj1 ist, sofern $obj1 ein Objekt ist, eine Kopie von Objekten per Referenz: $obj2 und $obj1 sind Referenzen (Adressen) auf dasselbe Objekt. Das Objekt selbst kann durch die eine oder die andere Referenz geändert werden;
  • Zeilen 23–27: sollen zeigen, dass $obj1 und $obj2 zwei verschiedene Variablen sind: Sie befinden sich nicht an derselben Speicheradresse:
    • $obj2=$obj1 hat den Wert von $obj1 in die Variable $obj2 kopiert (Schritt 1 oben). Der Wert von $obj1 ist die Adresse eines Objekts. Somit verweisen $obj1 und $obj2 auf dasselbe Objekt. Wenn man eine Variable $obj bearbeitet und diese auf ein Objekt verweist, bearbeitet PHP das Objekt, auf das die Variable $obj verweist. Aus dem folgenden Schema geht hervor, dass das Objekt, auf das verwiesen wird, entweder über $obj1 oder über $obj2 geändert werden kann. Dies zeigen die Zeilen 4 und 5 der Ergebnisse;

Image

  • Zeile 30: Der Ausdruck $obj3=&$obj2 bewirkt, dass $obj2 und $obj3 dieselbe Adresse haben wie [1 ci-dessous]. Man könnte sagen, dass die beiden Variablen Aliase desselben Speicherplatzes sind. Sie verweisen beide auf ein Objekt, Objekt A, im folgenden Beispiel [2];
    • die Operation $obj2=new stdClass() bewirkt, dass ein neues Objekt, Objekt B ([3 ci-dessous]), erstellt wird und die Adresse dieses neuen Objekts der Variablen $obj2 zugewiesen wird. Da $obj2 und $obj3 zwei Aliase für denselben Speicherort sind, verweist auch $obj3 auf das neue Objekt „Objekt B“. Dies geht aus den Zeilen 16–27 und 30–41 der Ergebnisse hervor;

Image

  • Zeilen 52–54: zeigen, dass ein Objekt wie ein Wörterbuch durchlaufen werden kann. Die Schlüssel des Wörterbuchs sind die Namen der Attribute und die Werte des Wörterbuchs die Werte eben dieser Attribute;
  • Zeile 51: Die Funktion count kann auf ein Objekt angewendet werden (mit einer Warnung), gibt jedoch nicht, wie man erwarten könnte, die Anzahl der Attribute zurück. Ein Objekt weist also Ähnlichkeiten mit einem Wörterbuch auf, ist aber keines;

5.3. Eine Klasse „Person“ ohne deklarierte Attribute

Das Skript [classes-02.php] lautet wie folgt:


<?php

class Personne {

  // Attribute der Klasse
  // nicht deklariert – können dynamisch erstellt werden
  // Methode
  function identite() {
    // verwendet a priori nicht vorhandene Attribute
    return "[$this->prenom,$this->nom,$this->age]";
  }

}

// Test
// Die Attribute sind öffentlich und können dynamisch erstellt werden
$p = new Personne();
$p->prenom = "Paul";
$p->nom = "Langevin";
$p->age = 48;
// Aufruf einer Methode
print "personne=" . $p->identite() . "\n";
// Ende
exit;

Ergebnisse:

personne=[Paul,Langevin,48]

Kommentare

  • Zeilen 3–13: Definieren eine Klasse Personne. Eine Klasse ist eine Vorlage, anhand derer Objekte erstellt werden. Sie umfasst Attribute und Funktionen, die als Methoden bezeichnet werden. Es besteht keine Verpflichtung, die Attribute zu deklarieren;
  • Zeilen 8–11: Die Methode identité gibt den Wert von drei Attributen aus, die in der Klasse nicht deklariert sind. Das Schlüsselwort $this bezeichnet das Objekt, auf das die Methode angewendet wird;
  • Zeile 17: Es wird ein Objekt $p vom Typ Personne erstellt. Das Schlüsselwort „new“ dient dazu, ein neues Objekt zu erstellen. Der Vorgang gibt eine Referenz auf das erstellte Objekt zurück (also eine Adresse). Verschiedene Schreibweisen sind möglich: new Personne(), new Personne, new personne. Bei der Klasse wird die Groß-/Kleinschreibung nicht berücksichtigt;
  • Zeilen 18–20: Die drei für die Methode identité erforderlichen Attribute werden im Objekt $p angelegt;
  • Zeile 22: Die Methode identité der Klasse Personne wird auf das Objekt $p angewendet. Im Code (Zeilen 8–11) der Methode identité verweist $this auf dasselbe Objekt wie $p;

5.4. Die Klasse „Person“ mit deklarierten Attributen

Das Skript [classes-03.php] lautet wie folgt:


<?php

class Personne {

  // Attribute der Klasse
  var $prenom;
  var $nom;
  var $age;

  // Methode
  function identite() {
    return "[$this->prenom,$this->nom,$this->age]";
  }

}

// Test
// Die Attribute sind öffentlich
$p = new Personne();
$p->prenom = "Paul";
$p->nom = "Langevin";
$p->age = 48;
// Aufruf einer Methode
print "personne=" . $p->identite() . "\n";
// Ende
exit;

Ergebnisse:

personne=[Paul,Langevin,48]

Kommentare

  • Zeilen 6–8: Die Attribute der Klasse werden explizit mit dem Schlüsselwort „var“ deklariert;

5.5. Die Klasse „Person“ mit einem Konstruktor

Die vorherigen Beispiele zeigten exotische Klassen wie Personne, wie sie in PHP 4 zu finden waren. Es wird nicht empfohlen, diesen Beispielen zu folgen. Wir stellen nun eine Klasse Personne [classes-04.php] vor, die den Best Practices von PHP 7 entspricht:


<?php

// Strikte Einhaltung der deklarierten Typen der Funktionsparameter
declare (strict_types=1);
class Personne {
// Attribute der Klasse
  private $prenom;
  private $nom;
  private $age;

// Getter und Setter
  public function getPrenom(): string {
    return $this->prenom;
  }

  public function getNom(): string {
    return $this->nom;
  }

  public function getAge(): int {
    return $this->age;
  }

  public function setPrenom(string $prenom): void {
    $this->prenom = $prenom;
  }

  public function setNom(string $nom): void {
    $this->nom = $nom;
  }

  public function setAge(int $age): void {
    $this->age = $age;
  }

// Konstruktor
  public function __construct(string $prenom, string $nom, int $age) {
    // man nutzt die Setter
    $this->setPrenom($prenom);
    $this->setNom($nom);
    $this->setAge($age);
  }

// Methode toString
  public function __toString(): string {
    return "[$this->prenom,$this->nom,$this->age]";
  }

}

// Test
// Anlegen eines Objekts vom Typ „Person“
$p = new Personne("Paul", "Langevin", 48);
// Identität dieser Person
print "personne=$p\n";
// Das Alter wird geändert
$p->setAge(14);
// Identität der Person
print "personne=$p\n";
// Ende
exit;

Ergebnisse:

personne=[Paul,Langevin,48]
personne=[Paul,Langevin,14]

Kommentare

  • Zeilen 6–50: die Klasse „Person“;
  • Zeilen 7–9: die privaten (private) Attribute der Klasse. Diese Attribute sind nur innerhalb der Klasse sichtbar. Weitere verwendbare Schlüsselwörter sind:
  • public: Macht das Attribut von außerhalb der Klasse sichtbar,
  • protected: macht das Attribut zu einem Attribut protégé, das innerhalb der Klasse und in von ihr abgeleiteten Klassen sichtbar ist;
  • Da die Attribute privat sind, kann von außerhalb der Klasse nicht auf sie zugegriffen werden. Daher kann der folgende Code nicht geschrieben werden:
$p=new Personne() ;
$p->nom="Landru" ;

Hier befinden wir uns außerhalb der Klasse Personne. Da das Attribut nom privat ist, ist Zeile 2 falsch. Um die privaten Felder des Objekts $p zu initialisieren, gibt es zwei Möglichkeiten:

  • die öffentlichen Methoden „set“ und „get“ (die Namen dieser Methoden können beliebig sein) aus den Zeilen 12–34 verwenden. Man könnte dann schreiben:
$p=new Personne() ;
$p->setNom("Landru") ;
  • den Konstruktor in den Zeilen 37–42 verwenden. Dann schreibt man:
$p=new Personne("Michel","Landru",44) ;

Die obige Schreibweise ruft automatisch die Methode der Klasse Personne namens __construct auf;

  • Zeile 59: Diese Zeile gibt die Person $p in Form einer Zeichenkette aus. Dazu wird die Methode der Klasse Personne namens __toString (Zeilen 45–47) verwendet;
  • allen Methoden der Klasse (Funktionen) wurde das Schlüsselwort „public“ vorangestellt, das angibt, dass die Funktion außerhalb der Klasse sichtbar ist. Die anderen verwendbaren Schlüsselwörter sind, wie bei den Attributen und mit derselben Bedeutung, „private“ und „protected“. Ohne explizites Sichtbarkeitsattribut ist die Funktion implizit öffentlich sichtbar;

5.6. Die Klasse „Person“ mit Gültigkeitsprüfungen im Konstruktor

Der Konstruktor einer Klasse ist der richtige Ort, um zu überprüfen, ob die Initialisierungswerte des Objekts korrekt sind. Ein Objekt kann jedoch auch über seine Methoden set oder gleichwertige Methoden initialisiert werden. Um zu vermeiden, dass dieselben Überprüfungen an zwei verschiedenen Stellen erfolgen, können diese in die Methoden set integriert werden. Sollte sich ein Initialisierungswert eines Objekts als falsch erweisen, wird eine Ausnahme ausgelöst. Hier ein Beispiel.

Zunächst wird die Definition der Klasse Personne in eine eigene Datei [Personne.php] verschoben:


<?php

// Strikte Einhaltung der deklarierten Typen der Funktionsparameter
declare (strict_types=1);

// Namensraum;
namespace Exemples;

// Klasse „Person“
class Personne {
// Attribute der Klasse
  private $prenom;
  private $nom;
  private $age;

// Getter und Setter
  public function getPrenom(): string {
    return $this->prenom;
  }

  public function getNom(): string {
    return $this->nom;
  }

  public function getAge(): int {
    return $this->age;
  }

  public function setPrenom(string $prénom): void {
    // Der Vorname darf nicht leer sein
    $prénom = trim($prénom);
    if ($prénom === "") {
      throw new \Exception("Le prénom doit être non vide");
    } else {
      $this->prenom = $prénom;
    }
  }

  public function setNom(string $nom): void {
    // Der Nachname darf nicht leer sein
    $nom = trim($nom);
    if ($nom === "") {
      throw new \Exception("Le nom doit être non vide");
    } else {
      $this->nom = $nom;
    }
  }

  public function setAge(int $âge): void {
    // Das Alter muss gültig sein
    if ($âge < 0) {
      throw new \Exception("L'âge doit être un entier positif ou nul");
    } else {
      $this->age = $âge;
    }
  }

// Konstruktor
  public function __construct(string $prenom, string $nom, int $age) {
    // Wir verwenden die Setter
    $this->setPrenom($prenom);
    $this->setNom($nom);
    $this->setAge($age);
  }

  // Methode
  public function initWithPersonne(Personne $p): void {
    // initialisiert das aktuelle Objekt mit einer Person $p
    $this->__construct($p->prenom, $p->nom, $p->age);
  }

  // Methode toString
  function __toString(): string {
    return "[$this->prenom,$this->nom,$this->age]";
  }

}

Kommentare

  • Zeile 4: Es wird festgelegt, dass der Typ der Funktionsparameter bei der Deklaration eingehalten werden muss;
  • Zeile 7: Definiert einen Namensraum (Namespace). Der vollständige Name (man sagt qualifié) der Klasse Personne lautet dann \Exemples\Personne. Beachten Sie das Zeichen \, mit dem der qualifizierte Name beginnt: Man erhält dann einen qualifizierten Namen absolu. Fehlt dieses Zeichen, erhält man einen qualifizierten Namen relatif (relativ zum aktuellen Namensraum). Wenn also zwei Klassen A und B zum selben Namensraum E gehören, kann man im Code der Klasse A die Klasse B über die Notation relative B erreichen. Wenn die Klasse A zum Namensraum E1 und B zum Namensraum E2 gehört, wird B im Code von A über die Notation absolue \E2\ erreicht.B. Die Definition einer Klasse innerhalb eines Namensraums ist nicht zwingend erforderlich, aber NetBeans gibt eine Warnung aus, wenn dies nicht geschieht. Daher werden wir dies tun. Außerdem sollten die Namensräume der Dateistruktur entsprechen. So sollte sich die Klasse A im Namensraum E1 in einer Datei namens E1/A.php befinden. Dies ist zwar nicht zwingend erforderlich, aber auch hier gibt NetBeans eine Warnung aus, wenn man es nicht beachtet. Am Beispiel der Klasse [\Exemples\Personne] gibt NetBeans eine Warnung aus, da die Dateistruktur der Datei [Personne.php] [exemples/classes/Personne.php] lautet und somit nicht mit dem Namensraum übereinstimmt. Man darf die Verzeichnisstruktur nicht mit dem Namensraum verwechseln. Der vollqualifizierte Name einer Klasse verwendet einen Namensraum und hat nichts mit der Verzeichnisstruktur der Datei PHP der Klasse zu tun. Die Verknüpfung zwischen Verzeichnisstruktur und Namensraum ist optional und muss nicht beachtet werden, wie wir es hier getan haben;
  • Zeilen 12–14: die drei privaten Attribute der Klasse;
  • Zeilen 29–37: Initialisierung des Attributs prenom und Überprüfung des Initialisierungswerts;
  • Zeile 31: Die Funktion trim($chaine) entfernt die Leerzeichen am Anfang und am Ende von $chaine. Somit ist trim(„abcd “) die Zeichenkette „abcd“ und trim(„ “) die leere Zeichenkette;
  • Zeile 32: Ist der Vorname leer, wird eine Ausnahme ausgelöst (Zeile 33), andernfalls wird der Vorname gespeichert (Zeile 35). Um eine Ausnahme auszulösen, wurde hier die vordefinierte Klasse [Exception] verwendet. Hier muss zwingend der absolute Name „[\Exception]“ verwendet werden. Verwendet man hingegen den relativen Namen [Exception], so wird diese Klasse im aktuellen Namensraum gesucht, d. h. im Namensraum [Exemples] der Klasse Personne. Somit sucht der Interpreter PHP nach einer Klasse mit dem absoluten Namen [\Exemples\Exception], die nicht existiert;

Die Klasse [Personne] wird vom folgenden Skript [classes-05.php] verwendet:


<?php

// Strikte Einhaltung des Typs der Funktionsparameter
declare(strict_types=1);

// Einbindung der Definition der Klasse „Person“
require_once __DIR__."/Personne.php";

// qualifizierter Name der Klasse „Person“
use \Exemples\Personne;

// Test
// Erstellung eines Objekts vom Typ „Person“
$p = new Personne("Paul", "Langevin", 48);
// Identität dieser Person
print "Exemple1, personne=$p\n";
// Anlegen eines fehlerhaften „Person“-Objekts
try {
  $p = new Personne("xx", "yy", "zz");
} catch (\Exception $e1) {
  print "Exemple2, erreur : " . $e1->getMessage() . "\n";
} catch (\TypeError $e2) {
  print "Exemple2, erreur : " . $e2->getMessage() . "\n";
}
// Anlegen eines fehlerhaften „Person“-Objekts
try {
  $p = new Personne("", "yy", 10);
} catch (\Exception $e1) {
  print "Exemple3, erreur : " . $e1->getMessage() . "\n";
} catch (\TypeError $e2) {
  print "Exemple3, erreur : " . $e2->getMessage() . "\n";
}

// Ende
exit;

Anmerkungen

  • Zeile 7: Das Skript wird die Klasse [Personne] verwenden. Daher muss dem Interpreter PHP mitgeteilt werden, wo er die Definition dieser Klasse finden kann. Dies ist die Aufgabe der Anweisungen [include fichier] und [require fichier]. Hier wurde die Anweisung [include] verwendet. Der Unterschied zwischen den beiden Anweisungen besteht darin: Wenn die Anweisung [include fichier] beim Laden von [fichier] auf Fehler stößt, wird ein Fehler der Stufe [E_WARNING] ausgegeben, diedie Ausführung wird fortgesetzt, während [require] im gleichen Fall einen schwerwiegenden Fehler auslöst und die Ausführung des Skripts beendet wird. Für jede der beiden Anweisungen gibt es eine Variante: [include_once] und [require_once]. Diese beiden Varianten ermöglichen es, den Fall mehrerer Einbindungen derselben Datei zu handhaben. Man kann sich hier ein Projekt vorstellen, das aus mehreren Skripten PHP besteht, von denen mehrere auf die Klasse [Personne] verweisen. Ihre Ausführung führt dann dazu, dass die Datei [Personne.php] mehrfach eingebunden wird, was einen Fehler verursacht, da eine Klasse nicht zweimal definiert werden darf. Die Lösung besteht darin, die Varianten [_once] zu verwenden, die sicherstellen, dass die Datei nur einmal in das globale Skript des Projekts eingebunden wird;
  • Zeile 7: Die Konstante [__DIR__] ist eine Konstante PHP, die den vollständigen Namen des Ordners angibt, in dem sich das Skript befindet, das die Konstante [__DIR__] enthält. Somit lautet der Ausdruck in Zeile 17:

require_once __DIR__."/Personne.php";

entspricht in etwa:


require_once ‘C:\Data\st-2019\dev\php7\php5-exemples\exemples\classes/Personnes.php’

Im Dateipfad können die Zeichen / und \ beliebig verwendet werden;

  • Zeile 14: Hier wird die soeben definierte Klasse [Personne] verwendet. Das Skript [classes-05.php] hat keinen Namespace. In Zeile 14 wird der relative Name der Klasse [Personne] ohne Namespace verwendet. Da für die Klasse [Personne] kein Namensraum vorhanden ist, wird sie im Skript [classes-05.php] selbst gesucht und daher nicht gefunden. Es gibt zwei Lösungen für dieses Problem:
    • Verwenden Sie den vollständigen Namen der Klasse [\Exemples\Personne];
    • die Anweisung „use“ in Zeile 10 verwenden. Diese gibt an, dass der nachfolgende Code die Klasse [\Exemples\Personne] verwendet;
  • Zeile 10: Die „use“-Anweisung teilt dem Interpreter mit, dass die in Zeile 14 referenzierte Klasse „[Personne]“ in Wirklichkeit die Klasse „[\Exemples\Personne]“ ist. Doch wo findet der Interpreter nun den Code dieser Klasse? Das geht aus Zeile 7 hervor. Diese gibt an, dass zur Ausführung des aktuellen Skripts auch das Skript [Personne.php] geladen werden muss. Hier wurde der relative Dateiname verwendet. Es wird also in dem Ordner gesucht, der das Skript [classes-05.php] enthält. Die Skripte [Personne.php] und [classes-05.php] müssen sich also im selben Ordner befinden. Dies ist hier der Fall, da sich beide im Ordner [exemples/classes] befinden. Die Anweisung in Zeile 10 entspricht:

use \Exemples\Personne as Personne;

Die obige Anweisung [use] besagt, dass der Alias [Personne] auf die Klasse [\Exemples\Personne] verweist;

  • Zeile 14: Ein Objekt [Personne] wird angelegt. Hier wird implizit die Methode [__construct] der Klasse [Personne] ausgeführt;
  • Zeile 16: Die Person $p wird angezeigt. Um angezeigt zu werden, muss der Wert der Variablen $p in eine Zeichenkette umgewandelt werden. Implizit wird dabei die Methode [Personne.__toString] ausgeführt. Diese muss also eine Zeichenkette zurückgeben;
  • wir haben gesehen, dass der Konstruktor der Klasse [Personne] eine Ausnahme vom Typ [\Exception] auslösen kann. Diese muss daher abgefangen werden. Der Code in Zeile 14 ist daher unvollständig. Es muss der Code der Zeilen 18–24 verwendet werden, um die möglicherweise auftretende Ausnahme korrekt zu behandeln. Hier wird absichtlich eine Ausnahme ausgelöst, indem ein Alter übergeben wird, das keine Ganzzahl ist. In diesem speziellen Fall wird die auftretende Ausnahme vom Interpreter PHP ausgelöst und nicht vom Code der Klasse [Personne]. Tatsächlich lautet die Signatur der Methode [Personne.__construct] wie folgt:

function __construct(string $prenom, string $nom, int $age)

Der an den Konstruktor übergebene Parameter [age] muss daher vom Typ „Ganzzahl“ sein. Ist dies nicht der Fall, löst der Interpreter PHP einen Fehler vom Typ [TypeError] aus. Außerdem lösen die Methoden [set] der Klasse [Personne] eine Ausnahme vom Typ [\Exception] aus. Da der Konstruktor, der sie aufruft, keine try/catch-Struktur enthält, wird die Ausnahme eine Ebene nach oben an den Code weitergeleitet, der den Konstruktor aufgerufen hat, d. h. an den Code des Skripts [classes-05.php]. Schließlich kann das Skript [classes-05.php] zwei Arten von Ausnahmen empfangen: \Exception oder \TypeError. Es sei angemerkt, dass der Entwickler, wenn er sicher ist, dass bestimmte Ausnahmen nicht auftreten können, die entsprechenden catch-Anweisungen nicht verwenden wird. Hier werden sie ausschließlich zu Demonstrationszwecken systematisch verwendet. Die catch-Anweisungen werden jedoch für jede mögliche Ausnahme verwendet, auch wenn diese unwahrscheinlich ist;

Aus diesem Grund enthält die try-Struktur in den Zeilen 18–24 zwei catch-Anweisungen, um die beiden Ausnahmetypen getrennt zu behandeln;

  • Zeile 20: Man kann entweder [Exception] oder [\Exception] schreiben:
    • Die erste Variante verwendet den relativen Namen der Klasse, bezogen auf den Namensraum des Skripts. Dieses hat keinen eigenen Namensraum. Sein Namensraum ist daher die Wurzel aller Namensräume: \. [Exception] zu schreiben, entspricht also dem Schreiben von [\Exception]. Die Klasse [Exception] befindet sich jedoch tatsächlich im Namensraum [\];

Es ist vorzuziehen, den absoluten Namen der vordefinierten Ausnahmen von PHP in einem Skript zu verwenden, das selbst keine Namespaces enthält. Wenn man sich also entscheidet, diesem Skript einen Namensraum zuzuweisen, bleibt die Schreibweise der absoluten Klassennamen gültig, während im anderen Fall die Änderung des Namensraums zu Fehlern bei den relativen Klassennamen führt;

  • Zeile 21: Bei einer Ausnahme ermöglicht die Methode [Exception→getMessage] den Abruf der Fehlermeldung der Ausnahme. Gleiches gilt für einen Fehler vom Typ [TypeError]. In der Methode [Personne.setPrenom] wurde Folgendes geschrieben:

  public function setPrenom(string $prénom) {
    // Der Vorname darf nicht leer sein
    $prénom = trim($prénom);
    if ($prénom === "") {
      throw new \Exception("Le prénom doit être non vide");
    } else {
      $this->prenom = $prénom;
    }
}

In Zeile 5 wird eine Ausnahme mit der Fehlermeldung [Le prénom doit être non vide] ausgelöst. Diese wird von der Methode [Exception→getMessage] in Zeile 29 des Skripts [classes-05.php] abgerufen.

Ergebnisse:


Exemple1, personne=[Paul,Langevin,48]
Exemple2, erreur : Argument 3 passed to Exemples\Personne::__construct() must be of the type integer, string given, called in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exemple_18.php on line 19
Exemple3, erreur : Le prénom doit être non vide

5.7. Hinzufügen einer Methode, die als zweiter Konstruktor fungiert

In PHP 7 ist es nicht möglich, mehrere Konstruktoren mit unterschiedlichen Parametern zu haben, die es ermöglichen würden, ein Objekt auf verschiedene Arten zu erstellen. Man kann daher Methoden verwenden, die als Konstruktor fungieren:


  // Methode
  public function initWithPersonne(Personne $p) {
    // initialisiert das aktuelle Objekt mit einer Person $p
    $this->__construct($p->prenom, $p->nom, $p->age);
}

Kommentare

  • Zeilen 2–5: Die Methode initWithPersonne ermöglicht es, dem aktuellen Objekt die Werte der Attribute eines anderen Objekts Personne zuzuweisen. Hier wird der Konstruktor __construct aufgerufen, dies ist jedoch nicht zwingend erforderlich. Die Methode könnte die Attribute der Klasse [Personne] auch selbst initialisieren;

Die neue Klasse [Personne] wird vom folgenden Skript [classes-06.php] verwendet:


<?php

// Einbindung der Definition der Klasse „Person“
require_once __DIR__."/Personne.php";
// Deklaration des qualifizierten Namens der Klasse „Person“
use \Exemples\Personne;

// Test
// Erstellung eines Objekts vom Typ „Person“
try {
  $p = new Personne("Paul", "Langevin", 48);
} catch (\Exception $e) {
  print "erreur : " . $e->getMessage();
  exit;
}
// Identität dieser Person
print "personne=$p\n";
// Erstellung einer zweiten Person
try {
  $p2 = new Personne("Laure", "Adeline", 67);
} catch (\Exception $e) {
  print "erreur : " . $e->getMessage();
  exit;
}
// Initialisierung der ersten Person mit den Werten der zweiten
try {
  $p->initWithPersonne($p2);
} catch (\Exception $e) {
  print "erreur : " . $e->getMessage();
  exit;
}

// Überprüfung
print "personne=$p\n";
// Ende
exit;
  • Zeilen 14, 23, 30: Häufig muss nach einer Ausnahme die Ausführung eines Konsolenskripts abgebrochen werden, wenn der aufgetretene Fehler nicht behebbar ist. In einem Web-Skript ist dies nicht der Fall: Die Ausführung des Skripts wird nicht abgebrochen, sondern es wird eine Fehlerseite angezeigt. Befindet man sich in einer Funktion, wird nicht die Anweisung „exit“, sondern „return“ verwendet: Die Ausführung des Skripts wird nicht abgebrochen (exit), sondern man verlässt die Funktion (return), nachdem ein Fehler gesetzt wurde;

Ergebnisse:

personne=[Paul,Langevin,48]
personne=[Laure,Adeline,67]

5.8. Ein Array von Objekten [Personne]

Das folgende Beispiel [classes-07.php] zeigt, dass Arrays von Objekten möglich sind.


<?php

require_once __DIR__."/Personne.php";
use \Exemples\Personne;

// Test
// Erstellung eines Arrays mit „Person“-Objekten
// Um das Verständnis des Codes zu erleichtern, wird eine mögliche Ausnahme nicht behandelt
$groupe = [new Personne("Paul", "Langevin", 48), new Personne("Sylvie", "Lefur", 70)];

// Identität dieser Personen
for ($i = 0; $i < count($groupe); $i++) {
  print "groupe[$i]=$groupe[$i]\n";
}

// Ende
exit;

Ergebnisse:

groupe[0]=[Paul,Langevin,48]
groupe[1]=[Sylvie,Lefur,70]

Kommentare

  • Zeile 9: Erstellung eines Arrays mit 2 Personen;
  • Zeile 12: Durchlaufen des Arrays;
  • Zeile 13: $groupe[$i] ist ein Objekt vom Typ Personne. Die Methode [Personne.__toString] wird verwendet, um es anzuzeigen;

5.9. Erstellung einer von der Klasse „Person“ abgeleiteten Klasse

In einer Datei mit dem Namen [Enseignant.php] wird die folgende Klasse [Enseignant] angelegt:


<?php

// Strikte Einhaltung der deklarierten Typen der Funktionsparameter
declare (strict_types=1);

// Namensraum
namespace Exemples;

// eine von „Person“ abgeleitete Klasse
class Enseignant extends Personne {
  // Attribute
  private $discipline;   // Unterrichtsfächer

  // Getter und Setter

  public function getDiscipline(): string {
    return $this->discipline;
  }

  public function setDiscipline(string $discipline): void {
    $this->discipline = $discipline;
  }

  // Konstruktor
  public function __construct(string $prénom, string $nom, int $âge, string $discipline) {
    // übergeordnete Attribute
    parent::__construct($prénom, $nom, $âge);
    // weitere Attribute
    $this->setDiscipline($discipline);
  }

  // Überschreiben der Funktion __toString der übergeordneten Klasse
  public function __toString(): string {
    return "[" . parent::__toString() . ",$this->discipline]";
  }

}

Anmerkungen

  • Zeile 7: Die Klasse [Enseignant] gehört ebenfalls zum Namensraum [Exemples];
  • Zeile 10: Die Klasse Enseignant leitet sich von der Klasse Personne. ab (extends). Die abgeleitete Klasse Enseignant erbt die Attribute und Methoden ihrer übergeordneten Klasse;
  • Zeile 12: Die Klasse Enseignant fügt ein neues, ihr eigenes Attribut discipline hinzu;
  • Zeile 25: Der Konstruktor der Klasse Enseignant erhält 4 Parameter:
    • 3 zur Initialisierung ihrer übergeordneten Klasse (Vorname, Nachname, Alter), Zeile 27;
    • 1 für die eigene Initialisierung (Fach), Zeile 29;
  • Zeile 27: Die abgeleitete Klasse hat über das Schlüsselwort `parent::` Zugriff auf die Methoden und Konstruktoren ihrer übergeordneten Klasse. Hier werden die Parameter (Vorname, Nachname, Alter) an den Konstruktor der übergeordneten Klasse übergeben;
  • Zeilen 33–35: Die Methode __toString der abgeleiteten Klasse nutzt die Methode __toString der übergeordneten Klasse;

Die Klasse [Enseignant] wird vom folgenden Skript [classes-08.php] verwendet:


<?php

// Einbindung der Definitionen beider Klassen
require_once __DIR__."/Personne.php";
require_once __DIR__."/Enseignant.php";
// Deklaration der beiden verwendeten Klassen
use \Exemples\Personne;
use \Exemples\Enseignant;

// Test
// Erstellung eines Arrays mit Objekten vom Typ „Person“ und abgeleiteten Klassen
// Der Einfachheit halber werden in diesem Beispiel keine Ausnahmen behandelt
$groupe = array(new Enseignant("Paul", "Langevin", 48, "anglais"), new Personne("Sylvie", "Lefur", 70));

// Identität dieser Personen
for ($i = 0; $i < count($groupe); $i++) {
  print "groupe[$i]=$groupe[$i]\n";
}
// Ende
exit;

Kommentare

  • Zeilen 4–5: Wir müssen dem Interpreter PHP mitteilen, wo sich die beiden Klassen [Enseignant, Personne] befinden;
  • Zeilen 7–8: Deklaration der vollständigen Namen der beiden Klassen. Dadurch können wir sie im Code einfach mit ihrem Namen ohne das Suffix ihres Namensraums bezeichnen;
  • Zeile 13: Wir erstellen ein Array mit einem Typ [Personne] und einem Typ [Enseignant];
  • Zeilen 16–18: Zeigen die Elemente des Arrays an;
  • Zeile 17: Die Methode __toString jedes Elements $groupe[$i] wird aufgerufen. Die Klasse Personne verfügt über eine Methode __toString. Die Klasse Enseignant verfügt über zwei Methoden: die ihrer übergeordneten Klasse und ihre eigene. Man könnte sich fragen, welche davon aufgerufen wird. Die Ausführung zeigt, dass die Methode der Klasse Enseignant aufgerufen wurde. Das ist immer so: Wenn eine Methode für ein Objekt aufgerufen wird, wird sie in der folgenden Reihenfolge gesucht: im Objekt selbst, in seiner übergeordneten Klasse, falls es eine gibt, dann in der übergeordneten Klasse der übergeordneten Klasse usw. Die Suche wird beendet, sobald die Methode gefunden wurde.

Ergebnisse:

groupe[0]=[[Paul,Langevin,48],anglais]
groupe[1]=[Sylvie,Lefur,70]

5.10. Erstellung einer zweiten Klasse, die von der Klasse „Person“ abgeleitet ist

Das folgende Beispiel erstellt in der Datei „[Etudiant.php]“ eine Klasse „Etudiant“, die von der Klasse „Personne“ abgeleitet ist:


<?php

// Strikte Einhaltung der deklarierten Typen der Funktionsparameter
declare (strict_types=1);

// Namensraum
namespace Exemples;

class Etudiant extends Personne {
  // Attribute
  private $formation;   // absolvierte Schulung

  // Getter und Setter
  public function getFormation(): string {
    return $this->formation;
  }

  public function setFormation(string $formation): void {
    $this->formation = $formation;
  }

  // Konstruktor
  public function __construct(string $prénom, string $nom, int $âge, string $formation) {
    // übergeordnete Attribute
    parent::__construct($prénom, $nom, $âge);
    // weitere Attribute
    $this->setFormation($formation);
  }

  // Überschreiben der Funktion __toString der übergeordneten Klasse
  public function __toString(): string {
    return "[" . parent::__toString() . ",$this->formation]]";
  }

}

Diese Klasse wird vom folgenden Skript „[classes-09.php]“ verwendet:


<?php

// Einbindung und Definition der vom Skript verwendeten Klassen
require_once __DIR__."/Personne.php";
use \Exemples\Personne;
require_once __DIR__."/Enseignant.php";
use \Exemples\Enseignant;
require_once __DIR__."/Etudiant.php";
use \Exemples\Etudiant;

// Test
// Erstellung eines Arrays aus „Person“-Objekten und abgeleiteten Objekten
// Um das Verständnis des Beispiels zu erleichtern, werden Ausnahmen nicht behandelt
$groupe = array(new Enseignant("Paul", "Langevin", 48, "anglais"), new Personne("Sylvie", "Lefur", 70), new Etudiant("Steve", "Boer", 23, "iup2 qualité"));

// Identität dieser Personen
for ($i = 0; $i < count($groupe); $i++) {
  print "groupe[$i]=$groupe[$i]\n";
}
// Ende
exit;

Ergebnisse:


groupe[0]=[[Paul,Langevin,48],anglais]
groupe[1]=[Sylvie,Lefur,70]
groupe[2]=[[Steve,Boer,23],iup2 qualité]

5.11. Beziehung zwischen dem Konstruktor einer abgeleiteten Klasse und dem der übergeordneten Klasse

In einigen objektorientierten Sprachen ruft der Konstruktor einer abgeleiteten Klasse automatisch den ihrer übergeordneten Klasse auf. Der folgende Code [classes-16.php] zeigt, dass dies bei PHP 7 nicht der Fall ist:


<?php

class Classe1 {

  // Konstruktor
  public function __construct() {
    print "constructeur de la classe Classe1\n";
  }

}

class Classe2 extends Classe1 {

  // Konstruktor
  public function __construct() {
    // Der Konstruktor der übergeordneten Klasse wird nicht implizit aufgerufen
    print "constructeur de la classe Classe2\n";
  }

}

class Classe3 extends Classe1 {

  // Konstruktor
  public function __construct() {
    // expliziter Aufruf des Konstruktors der übergeordneten Klasse
    parent::__construct();
    // spezifischer Code der Klasse 3
    print "constructeur de la classe Classe3\n";
  }

}

// Tests
print "test1---------\n";
new Classe2();
print "test2---------\n";
new Classe3();

Ergebnisse

1
2
3
4
5
test1---------
constructeur de la classe Classe2
test2---------
constructeur de la classe Classe1
constructeur de la classe Classe3

5.12. Neudefinition einer Methode der übergeordneten Klasse

Wir haben bereits gesehen, dass eine Methode der übergeordneten Klasse in einer untergeordneten Klasse neu definiert werden kann. So wurde die Methode [__toString] der Klasse [Personne] (siehe Link) in den untergeordneten Klassen [Enseignant] (siehe Link) und [Etudiant] (siehe Link) neu definiert. Das Skript [classes-13.php] veranschaulicht das Konzept erneut:


<?php

// Strikte Einhaltung der Typen der Funktionsparameter
declare(strict_types=1);

// Hauptklasse
class Classe1 {

  public function f(): int {
    return 1;
  }

  function g(): int {
    return 2;
  }

}

// abgeleitete Klasse
class Classe2 extends Classe1 {

  // Die Funktion f der übergeordneten Klasse wird neu definiert
  public function f(): int {
    return parent::f() + 10;
  }

}

// Code
$c2 = new Classe2();
print $c2->f() . "\n";
print $c2->g() . "\n";
$c1=new Classe1();
print $c1->f()."\n";

Kommentare

  • Zeilen 7–17: Die Klasse [Classe1] definiert zwei Methoden f und g;
  • Zeilen 20–27: Die Klasse [Classe2] erweitert die Klasse [Classe1] und definiert deren Methode f neu;

Ergebnisse

1
2
3
11
2
1

Kommentare

  • In Zeile 30 des Codes wird ein Objekt $c2 vom Typ [Classe2] erstellt;
  • In Zeile 31 des Codes wird die Methode f des Objekts $c2 aufgerufen. Da diese existiert, wird sie ausgeführt;
  • Zeile 32 des Codes ruft die Methode g des Objekts $c2 auf. Da diese nicht existiert, wird sie in der übergeordneten Klasse gesucht, wo sie gefunden und ausgeführt wird;
  • Zeile 33 des Codes erstellt ein Objekt $c1 vom Typ [Classe1];
  • Zeile 34 des Codes ruft die Methode f des Objekts $c1 auf. Da diese existiert, wird sie ausgeführt;

5.13. Übergabe eines Objekts als Funktionsparameter

Betrachten wir das folgende Skript [classes-14.php]:


<?php

// Strikte Einhaltung des Typs der Funktionsparameter
declare(strict_types=1);

// Hauptklasse
class Classe1 {

  public function f(): int {
    return 1;
  }

  function g(): int {
    return 2;
  }

}

// abgeleitete Klasse
class Classe2 extends Classe1 {

  // Die Funktion f der übergeordneten Klasse wird neu definiert
  public function f(): int {
    return parent::f() + 10;
  }

}

// Der Parameter der Funktion ist vom Typ „Klasse1“ oder einer abgeleiteten Klasse
function doSomething(Classe1 $c1): void {
  print $c1->f() + $c1->g() . "\n";
}

// Code
// Es wird ein Objekt vom Typ „Klasse2“ erstellt, das von „Klasse1“ abgeleitet ist
$c2 = new Classe2();
// doSomething wird aufgerufen mit
doSomething($c2);

Kommentare

  • Zeilen 7–17: die Klasse [Classe1];
  • Zeilen 20–27: eine von [Classe1] abgeleitete Klasse [Classe2];
  • Zeile 30: eine Funktion, die einen Parameter vom Typ [Classe1] erwartet. Wenn der erwartete Typ eine Klasse ist, kann der tatsächliche Parameter ein Objekt des erwarteten oder eines abgeleiteten Typs sein;
  • Zeilen 35–38: Die Funktion [doSomething] wird mit einem Parameter vom Typ [Classe2] aufgerufen, obwohl der Typ [Classe1] erwartet wird;

Ergebnisse

13

5.14. Abstrakte Klassen

Eine abstrakte Klasse ist eine unvollständige Klasse, die nicht instanziiert werden kann. Sie muss zwingend abgeleitet werden, um verwendet werden zu können.

Wozu dient eine abstrakte Klasse? Manchmal gibt es Klassen, die eine oder mehrere Methoden gemeinsam haben, sich aber durch andere Methoden oder Attribute unterscheiden. In diesem Fall ist es sinnvoll, alle gemeinsamen Elemente in einer übergeordneten Klasse zusammenzufassen. Derzeit benötigen wir keine abstrakte Klasse. Nehmen wir jedoch an, die Unterklassen unterscheiden sich nur durch eine Methode M: Die Signatur der Methode wäre in allen Unterklassen gleich, ihre Implementierung würde sich jedoch unterscheiden. Um die Unterklassen zu verpflichten, die Methode M zu implementieren:

  • deklarieren wir die Signatur der Methode M in der übergeordneten Klasse. Da diese nicht weiß, wie sie die Methode implementieren soll, versehen wir die Methode mit dem Schlüsselwort „abstract“: Das bedeutet, dass die Implementierung der Methode M auf die untergeordneten Klassen verlagert wird;
  • da die übergeordnete Klasse nicht vollständig implementiert ist, wird sie ebenfalls mit demselben Schlüsselwort „abstract“ als abstrakt deklariert. Dadurch kann die Klasse nicht mehr instanziiert werden. Es muss zwingend eine Unterklasse erstellt werden, die die Implementierung der Methode M definiert, damit der Körper der übergeordneten Klasse nutzbar ist;

Hier ein Beispiel: [classes-15.php]:


<?php

// strikter Einhaltung des Typs der Funktionsparameter
declare(strict_types=1);

// abstrakte Hauptklasse
abstract Class Classe1 {

  // Methode, die allen abgeleiteten Klassen bekannt ist
  public function f(): int {
    return 1;
  }

  // abstrakte Methode g – wird von den abgeleiteten Klassen definiert
  abstract function g(): int;
}

// abgeleitete Klasse
Class Classe2 extends Classe1 {

  // Die Methode „g“ der übergeordneten Klasse muss definiert werden
  public function g(): int {
    return parent::f() + 10;
  }

}

// abgeleitete Klasse
Class Classe3 extends Classe1 {

  // Die Methode g der übergeordneten Klasse muss definiert sein
  public function g(): int {
    return parent::f() + 20;
  }

  // Die Methode f der übergeordneten Klasse kann neu definiert werden
  public function f(): int {
    return 2;
  }

}

// Code
$c2 = new Classe2();
print $c2->f() . "\n";
print $c2->g() . "\n";
$c3 = new Classe3();
print $c3->f() . "\n";
print $c3->g() . "\n";

Anmerkungen

  • Zeilen 7–16: Die Klasse [Classe1] ist abstrakt (Zeile 7), da sie die Methode g in Zeile 15 nicht implementieren kann. Sie muss daher zwingend abgeleitet werden, um verwendet werden zu können;
  • Zeilen 19–26: Die Klasse [Classe2] erweitert die Klasse [Classe1] und definiert die Methode g ihrer übergeordneten Klasse neu (Zeilen 22–24);
  • Zeilen 29–41: Die Klasse [Classe3] erweitert die Klasse [Classe1] und definiert die Methode g ihrer übergeordneten Klasse neu (Zeilen 32–34);
  • Zeilen 37–39: Die Klasse [Classe3] definiert die Methode f ihrer übergeordneten Klasse neu;
  • Zeilen 44–49: Es werden zwei Objekte vom Typ [Classe2] und [Classe3] erstellt und ihre Methoden f und g aufgerufen;

Ergebnisse

1
2
3
4
1
11
2
21

5.15. Endklassen

Eine finale Klasse ist eine Klasse, von der keine Unterklassen abgeleitet werden können. Betrachten wir das folgende Skript [classes-11.php]:


<?php

// Namensraum
namespace Exemples;

// nicht ableitbare Klasse
final Class Classe1 {
  
}

// abgeleitete Klasse
Class Classe2 extends Classe1 {
  
}

// Code – muss einen Fehler auslösen
new Classe2();

Kommentare

  • Zeilen 7–9: Das Schlüsselwort „final“ macht die Klasse [Classe1] zu einer finalen Klasse, von der keine Unterklassen abgeleitet werden können;
  • Zeilen 12–14: Die Klasse „[Classe2]“ erweitert die finale Klasse „[Classe1]“, was einen Fehler darstellt;
  • Zeile 17: Der Fehler wird erst bei der Ausführung des Skripts gemeldet, wenn versucht wird, ein Objekt vom Typ [Classe2] zu bearbeiten;

Ergebnisse

Fatal error: Class Exemples\Classe2 may not inherit from final class (Exemples\Classe1) in C:\Data\st-2019\dev\php7\php5-exemples\exemples\classes\classes-11.php on line 14

5.16. Finale Methoden

Eine finale Methode ist eine Methode, die durch Ableitung nicht neu definiert werden kann. Hier ein Beispiel: [classes-12.php]:


<?php

// Strikte Einhaltung des Typs der Funktionsparameter
declare(strict_types=1);

// Namensraum
namespace Exemples;

// Hauptklasse
Class Classe1 {

  // Diese Methode darf in einer abgeleiteten Klasse nicht neu definiert werden
  public final function f(): int {
    return 1;
  }

}

// abgeleitete Klasse
Class Classe2 extends Classe1 {

  public function f(): int {
    return 2;
  }

}

// Code – muss einen Fehler auslösen
new Classe2();

Kommentare

  • Zeile 13: Die Methode f der Klasse [Classe1] wird durch das Schlüsselwort „final“ als final deklariert;
  • Zeile 20: Die Klasse [Classe2] erweitert die Klasse [Classe1];
  • Zeilen 22–23: Die Funktion `f` der übergeordneten Klasse `[Classe1]` wird neu definiert. Dies sollte einen Fehler verursachen;
  • Zeile 29: Es wird ein Objekt vom Typ [Classe2] erstellt, um den Interpreter PHP zu zwingen, die Klasse [Classe2] zu überprüfen;

Ergebnisse

Fatal error: Cannot override final method Exemples\Classe1::f() in C:\Data\st-2019\dev\php7\php5-exemples\exemples\classes\classes-12.php on line 26

5.17. Statische Methoden und Attribute

Eine statische Methode ist eine Methode, die an die Klasse gebunden ist, in der sie definiert ist, und nicht an die Instanzobjekte der Klasse. Wenn also die Klasse C eine statische Methode M deklariert, schreibt man zur Verwendung dieser Methode:

  • C::M, wenn man sich außerhalb der Klasse befindet;
  • self::M, wenn man sich innerhalb der Klasse befindet;

Hier ein Beispiel [classes-17.php]:


<?php

class Classe1 {

  // statische Methode
  static function say(string $message): void {
    print "$message\n";
  }

}

// Test -------------------
Classe1::say("hello");

Kommentare

  • Zeile 6: Die Methode [say] wird mit dem Schlüsselwort „static“ als statisch deklariert;
  • Zeile 13: Aufruf der statischen Methode [say] mit der Notation: Klasse1::say;

Ergebnisse

hello

Betrachten wir nun den folgenden Code [classes-18.php]:


<?php

class Classe1 {
  // statisches Attribut
  private static $nbObjects = 0;

  public function __construct() {
    print "constructeur Classe1\n";
    self::$nbObjects++;
  }

  // statische Methode
  static function say(): void {
    print self::$nbObjects ." objets de type [Classe1] ont été construits\n";
  }

}

// Test -------------------
new Classe1();
new Classe1();
Classe1::say();

Kommentare

  • Zeile 5: Hier wird ein statisches Attribut deklariert, das die Anzahl der erstellten Instanzen der Klasse [Classe1] zählt. Es handelt sich nicht um ein Attribut, das zu einer Instanz der Klasse gehören kann. Denn wenn zwei Objekte O1 und O2 erstellt werden, weiß keines von beiden vom anderen. Einen Zähler in der Instanz zu haben, macht keinen Sinn: Wenn ein neues Objekt erstellt wird, in welcher Instanz soll dann ein Zähler erhöht werden? Man wäre gezwungen, den Zähler eines bestimmten Objekts zu erhöhen und dabei die Zähler der anderen Instanzen außer Acht zu lassen. Ein statisches Attribut ist ein Klassenattribut und kein Attribut einer Instanz der Klasse;
  • Zeilen 7–10: Die Anzahl der erstellten Objekte wird im Konstruktor gezählt, da die Erstellung jedes neuen Objekts die Ausführung des Konstruktors auslöst;
  • Zeile 14: Man beachte die Notation `self::$nbObjects`, um anzugeben, dass auf ein statisches Attribut der Klasse verwiesen wird, in der sich der ausgeführte Code befindet;
  • Zeilen 13–15: Die statische Methode [say] dient dazu, die Anzahl der erstellten Objekte anzuzeigen;
  • Zeilen 20–22: Es werden zwei Objekte erstellt und der Objektzähler angezeigt;

Ergebnisse


constructeur Classe1
constructeur Classe1
2 objets de type [Classe1] ont été construits

5.18. Sichtbarkeit zwischen übergeordneter und untergeordneter Klasse

Betrachten wir das folgende Skript [classes-19.php]:


<?php

class SomeParent {
  // Attribut
  private $attributeOfParent = 4;

  // Methode
  public function doTest(): void {
    // Wer ruft auf?
    print "parent :\n";
    var_dump($this);
    // übergeordnete Ansicht
    print "parent : attributeOfParent={$this->attributeOfParent}\n";
    print "parent : attributeOfChild={$this->attributeOfChild}\n";
  }

}

class SomeChild extends SomeParent {
  // Attribut
  private $attributeOfChild = 14;

  // Methode
  public function doTest(): void {
    // untergeordnete Ansicht
    print "child : attributeOfParent={$this->attributeOfParent}\n";
    print "child : attributeOfChild={$this->attributeOfChild}\n";

    // übergeordnet
    parent::doTest();
  }
}

// Hauptskript
print "---test1\n";
(new SomeParent())->doTest();
print "---test2\n";
(new SomeChild())->doTest();

Kommentare

  • Zeilen 3–17: die Klasse [SomeParent];
  • Zeilen 19–32: die untergeordnete Klasse [SomeChild]. Man sieht, dass sie die Klasse [SomeParent] erweitert (Zeile 19);
  • Zeile 5: Die Klasse [SomeParent] hat nur ein Attribut;
  • Zeilen 8–17: Die Methode [SomeParent::doTest] dient dazu, zwei Attribute anzuzeigen:
    • [$attributeOfParent], das zur Klasse [SomeParent] gehört;
    • [$attributeOfChild], das zur Klasse [SomeChild] gehört (Zeile 21);
  • Zeilen 10–11: Die Identität des Aufrufers wird angezeigt: Die Methode wird nämlich auf zwei verschiedene Arten aufgerufen:
    • aus der übergeordneten Klasse [SomeParent];
    • aus der untergeordneten Klasse [SomeChild];
  • Zeilen 13–14: Anzeige der beiden Attribute;
  • Zeilen 19–32: Die untergeordnete Klasse [SomeChild], die die Klasse [SomeParent] erweitert (Zeile 19);
  • Zeile 21: Die Klasse [SomeChild] hat nur ein Attribut;
  • Zeile 24: Die Methode [SomeChild::doTest] dient dazu, zwei Attribute anzuzeigen:
    • [$attributeOfParent], das zur Klasse [SomeParent] gehört;
    • [$attributeOfChild], das zur Klasse [SomeChild] gehört;
  • Zeilen 26–27: Anzeige der beiden Attribute;
  • Zeile 30: Aufruf der Methode [doTest] der übergeordneten Klasse, die wiederum die beiden Attribute anzeigt;
  • Zeile 36: Die Methode [SomeParent::doTest] wird aufgerufen;
  • Zeile 38: Die Methode [SomeChild::doTest] wird aufgerufen;

Im ersten Test lautet die Sichtbarkeit der beiden Attribute [private]. Man kann also davon ausgehen, dass die untergeordnete Klasse das Attribut ihrer übergeordneten Klasse nicht sieht. Dieses müsste mindestens die Sichtbarkeit [protected] haben. Aber wie sieht es mit dem Attribut der untergeordneten Klasse aus? Ist es in der übergeordneten Klasse sichtbar?

Hier sind die Ergebnisse dieses ersten Tests:

---------------------------test1
parent :
object(SomeParent)#1 (1) {
  ["attributeOfParent":"SomeParent":private]=>
  int(4)
}
parent : attributeOfParent=4

Notice: Undefined property: SomeParent::$attributeOfChild in C:\Data\st-2019\dev\php7\poly\scripts-console\classes\classes-19.php on line 14
parent : attributeOfChild=
---------------------------test2

Notice: Undefined property: SomeChild::$attributeOfParent in C:\Data\st-2019\dev\php7\poly\scripts-console\classes\classes-19.php on line 26
child : attributeOfParent=
child : attributeOfChild=14
parent :
object(SomeChild)#1 (2) {
  ["attributeOfChild":"SomeChild":private]=>
  int(14)
  ["attributeOfParent":"SomeParent":private]=>
  int(4)
}
parent : attributeOfParent=4

Fatal error: Uncaught Error: Cannot access private property SomeChild::$attributeOfChild in C:\Data\st-2019\dev\php7\poly\scripts-console\classes\classes-19.php:14
Stack trace:
#0 C:\Data\st-2019\dev\php7\poly\scripts-console\classes\classes-19.php(30): SomeParent->doTest()
#1 C:\Data\st-2019\dev\php7\poly\scripts-console\classes\classes-19.php(39): SomeChild->doTest()
#2 {main}
  thrown in C:\Data\st-2019\dev\php7\poly\scripts-console\classes\classes-19.php on line 14

Kommentare

  • Zeilen 1–10: Ergebnisse des ersten Tests, bei dem die Methode [SomeParent::doTest] aufgerufen wird;
  • Zeilen 3–6: Man sieht, dass das Objekt, das die Methode aufruft, vom Typ [SomeParent] ist;
  • Zeile 7: Anzeige des Attributs [$attributeOfParent];
  • Zeilen 9–10: Man sieht, dass das Attribut [SomeParent::$attributeOfChild] nicht existiert. Es wird daher nicht angezeigt;
  • Zeilen 11–30: Ergebnisse des zweiten Tests, bei dem die Methode [SomeChild::doTest] aufgerufen wird;
  • Zeilen 13–14: Man sieht, dass das Attribut [SomeChild::$attributeOfParent] nicht existiert. Es wird daher nicht angezeigt. Das ist normal: Das Attribut [SomeParent::$attributeOfParent] lautet [private] und ist daher in der untergeordneten Klasse nicht bekannt;
  • Zeile 15: Anzeige des Attributs [$attributeOfChild];
  • Zeilen 16–30: Wir befinden uns in der Methode [SomeParent::doTest], die von der untergeordneten Klasse aufgerufen wird;
  • Zeilen 17–22: Man sieht, dass [$this] vom Typ [SomeChild] ist und zwei private Attribute besitzt;
  • Zeile 23: Überraschenderweise sieht [$this] vom Typ [SomeChild] hier das Attribut des übergeordneten Objekts [$attributeOfParent];
  • Zeilen 25–30: Ebenso überraschend ist, dass [$this] vom Typ [SomeChild] sein Attribut [$attributeOfChild] nicht sieht;

Dieses Ergebnis ist sehr überraschend: Obwohl die Zeilen 17–21 angeben, dass [$this] vom Typ [SomeChild] ist, verhält sich dieses [$this] innerhalb der Methode [SomeParent::doTest] so, als wäre es eine Instanz der Klasse [SomeParent] und nicht der Klasse [SomeChild].

Führen wir einen neuen Test mit dem Skript [classes-20.php] durch. Das Attribut [$attributeOfParent] hat nun die Sichtbarkeit [protected] (Zeile 5):


<?php

class SomeParent {
  // Attribut
  protected $attributeOfParent = 4;

  // Methode
  public function doTest(): void {
    // Wer ruft sie auf?
    print "parent :\n";
    var_dump($this);
    // übergeordnete Ansicht
    print "parent : attributeOfParent={$this->attributeOfParent}\n";
    print "parent : attributeOfChild={$this->attributeOfChild}\n";
  }

}

class SomeChild extends SomeParent {
  // Attribut
  private $attributeOfChild = 14;

  // Methode
  public function doTest(): void {
    // untergeordnete Ansicht
    print "child : attributeOfParent={$this->attributeOfParent}\n";
    print "child : attributeOfChild={$this->attributeOfChild}\n";

    // übergeordnet
    parent::doTest();
  }

}

// Hauptskript
print "---------------------------test1\n";
(new SomeParent())->doTest();
print "---------------------------test2\n";
(new SomeChild())->doTest();

Ergebnisse

---------------------------test1
parent :
object(SomeParent)#1 (1) {
  ["attributeOfParent":protected]=>
  int(4)
}
parent : attributeOfParent=4

Notice: Undefined property: SomeParent::$attributeOfChild in C:\Data\st-2019\dev\php7\poly\scripts-console\classes\classes-20.php on line 14
parent : attributeOfChild=
---------------------------test2
child : attributeOfParent=4
child : attributeOfChild=14
parent :
object(SomeChild)#1 (2) {
  ["attributeOfChild":"SomeChild":private]=>
  int(14)
  ["attributeOfParent":protected]=>
  int(4)
}
parent : attributeOfParent=4

Fatal error: Uncaught Error: Cannot access private property SomeChild::$attributeOfChild in C:\Data\st-2019\dev\php7\poly\scripts-console\classes\classes-20.php:14
Stack trace:
#0 C:\Data\st-2019\dev\php7\poly\scripts-console\classes\classes-20.php(30): SomeParent->doTest()
#1 C:\Data\st-2019\dev\php7\poly\scripts-console\classes\classes-20.php(39): SomeChild->doTest()
#2 {main}
  thrown in C:\Data\st-2019\dev\php7\poly\scripts-console\classes\classes-20.php on line 14

Kommentare

  • Zeile 12: Die Klasse [SomeChild] kann nun das Attribut ihres übergeordneten Objekts [$attributeOfParent] sehen. Dies ist normal, da dieses nun den Geltungsbereich [protected] hat;
  • In der Methode [someParent::doTest] ist das Objekt [$this] vom Typ [SomeChild] (Zeilen 15–20). Es sieht das Attribut seines übergeordneten Objekts [$attributeOfparent] (Zeile 21), aber immer noch nicht sein eigenes Attribut [$attributeOfChild] (Zeilen 23–28);

Im dritten Versuch hat das Attribut [$attributeOfChild] ebenfalls den Geltungsbereich [protected]:


<?php

class SomeParent {
  // Attribut
  protected $attributeOfParent = 4;

  // Methode
  public function doTest(): void {
    // Wer ruft sie auf?
    print "parent :\n";
    var_dump($this);
    // übergeordnete Ansicht
    print "parent : attributeOfParent={$this->attributeOfParent}\n";
    print "parent : attributeOfChild={$this->attributeOfChild}\n";
  }

}

class SomeChild extends SomeParent {
  // Attribut
  protected $attributeOfChild = 14;

  // Methode
  public function doTest(): void {
    // untergeordnete Ansicht
    print "child : attributeOfParent={$this->attributeOfParent}\n";
    print "child : attributeOfChild={$this->attributeOfChild}\n";

    // übergeordnet
    parent::doTest();
  }

}

// Hauptskript
print "---------------------------test1\n";
(new SomeParent())->doTest();
print "---------------------------test2\n";
(new SomeChild())->doTest();

Die Ergebnisse der Ausführung lauten wie folgt:

---------------------------test1
parent :
object(SomeParent)#1 (1) {
  ["attributeOfParent":protected]=>
  int(4)
}
parent : attributeOfParent=4

Notice: Undefined property: SomeParent::$attributeOfChild in C:\Data\st-2019\dev\php7\poly\scripts-console\classes\classes-21.php on line 14
parent : attributeOfChild=
---------------------------test2
child : attributeOfParent=4
child : attributeOfChild=14
parent :
object(SomeChild)#1 (2) {
  ["attributeOfChild":protected]=>
  int(14)
  ["attributeOfParent":protected]=>
  int(4)
}
parent : attributeOfParent=4
parent : attributeOfChild=14
  • Zeile 22: Diesmal wird innerhalb des übergeordneten Elements [$this] vom Typ [SomeChild] (Zeilen 15–20) das geschützte Attribut [$attributeOfChild] seiner eigenen Klasse [SomeChild].

Was lässt sich aus diesen Tests ableiten?

  • Dass die Instanz [$this] einer übergeordneten Klasse, die in einer Methode der übergeordneten Klasse verwendet wird, Folgendes sieht:
    • die Attribute und Methoden der übergeordneten Klasse, unabhängig von deren Sichtbarkeit;
    • sie sieht nichts von den Attributen und Methoden ihrer untergeordneten Klassen;

Dies ist das erwartete Verhalten.

  • dass die Instanz [$this] einer Unterklasse, die in einer Methode der Unterklasse verwendet wird, Folgendes sieht:
    • die Attribute und Methoden der übergeordneten Klasse, sofern diese mindestens die Sichtbarkeit [protected] haben. Diejenigen mit der Sichtbarkeit [private] werden nicht gesehen;
    • die Attribute und Methoden der Unterklasse, unabhängig von ihrer Sichtbarkeit;

Dies ist das erwartete Verhalten.

  • Eine Instanz der Unterklasse mit der Sichtbarkeit [$this], die in einer Methode der Oberklasse verwendet wird, sieht:
    • die Attribute und Methoden der übergeordneten Klasse, unabhängig von deren Sichtbarkeit;
    • die Attribute und Methoden ihrer eigenen Klasse nur dann, wenn diese mindestens die Sichtbarkeit [protected] haben. Diejenigen mit der Sichtbarkeit [private] werden nicht erkannt;

Dies ist ein unerwartetes Verhalten.

5.19. jSON-Kodierung einer Klasse

In einer Klasse ist häufig die Methode [__toString] vorhanden: Sie soll eine Zeichenkette zurückgeben, die das Objekt darstellt, das sie aufruft. Es mag verlockend sein, diese Zeichenkette als jSON zu definieren. Diesen Ansatz untersuchen wir nun.

Image

Wir verwenden die folgende Klasse „[Personne]“:


<?php

class Personne {
  // Attribute
  private $nom;
  private $prénom;
  private $âge;
  private $enfants;

  // globaler Setter
  public function setFromArray(array $arrayOfAttributes): Personne {
    // Initialisierung bestimmter Attribute der Klasse
    foreach ($arrayOfAttributes as $attribute => $value) {
      $this->$attribute = $value;
    }
    // Das Objekt wird zurückgegeben
    return $this;
  }

  // Getter
  public function getNom() {
    return $this->nom;
  }

  public function getPrénom() {
    return $this->prénom;
  }

  public function getÂge() {
    return $this->âge;
  }

  public function getEnfants() {
    return $this->enfants;
  }

  // __toString
  public function __toString(): string {
    // Das Objekt wird identifiziert
    var_dump($this);
    // seine Attribute werden abgerufen
    $attributes = \get_object_vars($this);
    var_dump($attributes);
    // die Zeichenkette jSON der Attribute wird zurückgegeben
    return \json_encode($attributes, JSON_UNESCAPED_UNICODE);
  }

}

Kommentare

  • Zeilen 5–8: die vier Attribute der Klasse;
  • Zeilen 20–35: die Getter, mit denen der Wert dieser Attribute abgerufen werden kann;
  • Zeilen 11–18: ein globaler Setter, mit dem die Attribute anhand eines assoziativen Arrays [$arrayOfAttributes] initialisiert werden können, dessen Schlüssel mit den Attributen der Klasse übereinstimmen;
  • Zeilen 38–46: die Methode [__toString] der Klasse;
  • Zeile 42: Die Funktion PHP [get_object_vars] ermöglicht es, den Wert der Attribute der Klasse in Form eines assoziativen Arrays [‘name’=>’name1’, ‘surname’=>’surname1’, ‘age’=>’age1’, ‘children’=>[]] abzurufen;
  • Zeile 45: Die Zeichenkette jSON wird aus diesem Attributarray zurückgegeben;

Betrachten wir das Skript [json-01.php], das die Klasse [Personne] nutzt:


<?php

// Klasse „Person“
require "Personne.php";

// Instanzierung des Vaters
$père = new Personne();
// Initialisierung des Vaters
$père->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Dieudonné",
  "âge" => 58
]);
// Instanziierung und Initialisierung von Kind1
$enfant1 = (new Personne())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Sylvain",
  "âge" => 17
  ]);
// Instanziierung und Initialisierung von Kind2
$enfant2 = (new Personne())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Géraldine",
  "âge" => 12
  ]);
// Initialisierung der Kinder des Vaters
$père->setFromArray([
  "enfants" => [$enfant1, $enfant2]
]);

// Anzeige der Elemente des Vaters
$enfant1=($père->getEnfants())[0];
$enfant2=($père->getEnfants())[1];
print "------------------------enfant1\n";
print "enfant1=$enfant1\n";
print "------------------------enfant2\n";
print "enfant2=$enfant2\n";
print "------------------------père\n";
print "père=$père\n";

Kommentare

  • Zeilen 6–13: Ein Objekt [Personne] [$père] wird mit der Methode [Personne::setFromArray] initialisiert, die es ermöglicht,ein Objekt [Personne] mit einem Array initialisiert wird, dessen Schlüssel mit den Attributen der Klasse [Personne] übereinstimmen;
  • Zeilen 14–19: Ein Objekt [Personne] [$enfant1] wird auf die gleiche Weise initialisiert;
  • Zeilen 21–25: Ein Objekt vom Typ [Personne] [$enfant2] wird initialisiert;
  • Zeilen 27–29: Das Attribut [$père→enfants] wird mit einem Array der beiden Kinder initialisiert;
  • Zeilen 32–33: Die beiden Kinder werden dem Vater zugewiesen;
  • Zeile 35: Die Operation [print] versucht, das Objekt [$enfant1] in eine Zeichenkette umzuwandeln. Dazu verwendet sie die Methode [__toString] des Objekts. Wir erwarten also die Zeichenkette jSON des Objekts;
  • Zeilen 38–39: Das Gleiche wird mit dem übergeordneten Objekt durchgeführt;

Die Ergebnisse lauten wie folgt:


------------------------enfant1
object(Personne)#2 (4) {
  ["nom":"Personne":private]=>
  string(11) "Bertholomé"
  ["prénom":"Personne":private]=>
  string(7) "Sylvain"
  ["âge":"Personne":private]=>
  int(17)
  ["enfants":"Personne":private]=>
  NULL
}
array(4) {
  ["nom"]=>
  string(11) "Bertholomé"
  ["prénom"]=>
  string(7) "Sylvain"
  ["âge"]=>
  int(17)
  ["enfants"]=>
  NULL
}
enfant1={"nom":"Bertholomé","prénom":"Sylvain","âge":17,"enfants":null}
------------------------enfant2
object(Personne)#3 (4) {
  ["nom":"Personne":private]=>
  string(11) "Bertholomé"
  ["prénom":"Personne":private]=>
  string(10) "Géraldine"
  ["âge":"Personne":private]=>
  int(12)
  ["enfants":"Personne":private]=>
  NULL
}
array(4) {
  ["nom"]=>
  string(11) "Bertholomé"
  ["prénom"]=>
  string(10) "Géraldine"
  ["âge"]=>
  int(12)
  ["enfants"]=>
  NULL
}
enfant2={"nom":"Bertholomé","prénom":"Géraldine","âge":12,"enfants":null}
------------------------père
object(Personne)#1 (4) {
  ["nom":"Personne":private]=>
  string(11) "Bertholomé"
  ["prénom":"Personne":private]=>
  string(10) "Dieudonné"
  ["âge":"Personne":private]=>
  int(58)
  ["enfants":"Personne":private]=>
  array(2) {
    [0]=>
    object(Personne)#2 (4) {
      ["nom":"Personne":private]=>
      string(11) "Bertholomé"
      ["prénom":"Personne":private]=>
      string(7) "Sylvain"
      ["âge":"Personne":private]=>
      int(17)
      ["enfants":"Personne":private]=>
      NULL
    }
    [1]=>
    object(Personne)#3 (4) {
      ["nom":"Personne":private]=>
      string(11) "Bertholomé"
      ["prénom":"Personne":private]=>
      string(10) "Géraldine"
      ["âge":"Personne":private]=>
      int(12)
      ["enfants":"Personne":private]=>
      NULL
    }
  }
}
array(4) {
  ["nom"]=>
  string(11) "Bertholomé"
  ["prénom"]=>
  string(10) "Dieudonné"
  ["âge"]=>
  int(58)
  ["enfants"]=>
  array(2) {
    [0]=>
    object(Personne)#2 (4) {
      ["nom":"Personne":private]=>
      string(11) "Bertholomé"
      ["prénom":"Personne":private]=>
      string(7) "Sylvain"
      ["âge":"Personne":private]=>
      int(17)
      ["enfants":"Personne":private]=>
      NULL
    }
    [1]=>
    object(Personne)#3 (4) {
      ["nom":"Personne":private]=>
      string(11) "Bertholomé"
      ["prénom":"Personne":private]=>
      string(10) "Géraldine"
      ["âge":"Personne":private]=>
      int(12)
      ["enfants":"Personne":private]=>
      NULL
    }
  }
}
père={"nom":"Bertholomé","prénom":"Dieudonné","âge":58,"enfants":[{},{}]}

Kommentare

  • Zeilen 2–11: das Objekt [$enfant1];
  • Zeilen 12–21: das Attributarray des Objekts [$enfant1]. Wir haben tatsächlich alle;
  • Zeile 22: Wir haben die Zeichenfolge jSON des Objekts [$enfant1];
  • Zeilen 23–44: dasselbe gilt für das Objekt [$enfant2];
  • Zeilen 45–112: Beim Vater ist es etwas anders, da sein Attribut [enfants] nicht NULL lautet, wie es bei den Kindern der Fall war;
  • Zeile 112: Man sieht, dass in der Zeichenfolge jSON des Vaters die Kinder fehlen;
  • Zeilen 79–111: Man sieht, dass in der Attributtabelle des Vaters Kind 1 (Zeilen 89–98) ein Objekt geblieben ist, ebenso wie Kind 2 (Zeilen 99–110). Im Klartext: Der Ausdruck [\get_object_vars($this)], wobei [$this] den Vater darstellt, ist nicht rekursiv: Wenn ein Attribut der Klasse [Personne] selbst ein Objekt ist, versucht der Ausdruck [\get_object_vars($this)] nicht, dessen Attributarray abzurufen;

Man kann dies verbessern. Man ändert die Klasse [Personne] in die folgende Klasse [Personne2]:


<?php

class Personne2 {
  // Attribute
  private $nom;
  private $prénom;
  private $âge;
  private $enfants;

  // globaler Setter
  public function setFromArray(array $arrayOfAttributes): Personne2 {

    // Das Objekt wird zurückgegeben
    return $this;
  }

  // Getter
  public function getNom() {
    return $this->nom;
  }



  // __toString
  public function __toString(): string {
    // die Attribute des Objekts werden abgerufen
    $attributes = $this->getAttributes($this);
    $enfants = $attributes["enfants"];
    if ($enfants != NULL) {
      $attributes["enfants"] = [$enfants[0]->getAttributes(), $enfants[1]->getAttributes()];
    }
    // Die Zeichenkette JSON der Attribute wird zurückgegeben
    return \json_encode($attributes, JSON_UNESCAPED_UNICODE);
  }

  public function getAttributes(): array {
    return \get_object_vars($this);
  }

}

Kommentare

  • Zeilen 36–38: Die Funktion [getAttributes] gibt das Attributarray des Objekts zurück, das sie aufruft;
  • Zeilen 25–34: die Funktion [__toString];
  • Zeile 27: Die Attribute der Klasse [Personne] werden in das Array [$attributes] übernommen;
  • Zeile 28: Aus dem vorherigen Beispiel wissen wir, dass [$attributes["enfants"]] ein Array aus zwei Objekten vom Typ [Personne] ist;
  • Zeilen 29–31: Die beiden Objekte werden durch ihr Attributarray ersetzt;
  • Zeile 33: Nun muss nur noch das erstellte Attributarray in jSON kodiert werden;

Das Skript [json-02.php] nutzt die Klasse [Personne2] wie folgt:


<?php

// Klasse „Person2“
require "Personne2.php";

// Instanziierung des übergeordneten Objekts
$père = new Personne2();
// Initialisierung
$père->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Dieudonné",
  "âge" => 58
]);
// Instanziierung und Initialisierung von Kind1
$enfant1 = (new Personne2())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Sylvain",
  "âge" => 17
  ]);
// Instanziierung und Initialisierung von Kind2
$enfant2 = (new Personne2())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Géraldine",
  "âge" => 12
  ]);
// Initialisierung der Kinder des übergeordneten Objekts
$père->setFromArray([
  "enfants" => [$enfant1, $enfant2]
]);

// Anzeige des Vaters
print "------------------------père\n";
print "père=$père\n";

Das Skript [json-02.php] ist identisch mit dem Skript [json-01.php], mit dem Unterschied, dass die Klasse [Personne2] die Klasse [Personne] ersetzt hat.

Die Ergebnisse der Ausführung lauten wie folgt:

------------------------père
père={"nom":"Bertholomé","prénom":"Dieudonné","âge":58,"enfants":[{"nom":"Bertholomé","prénom":"Sylvain","âge":17,"enfants":null},{"nom":"Bertholomé","prénom":"Géraldine","âge":12,"enfants":null}]}

Diesmal wurden die Kinder tatsächlich zusammen mit dem Vater angezeigt.

Die vorherige Lösung ist nicht zufriedenstellend, da die Kinder selbst wiederum Kinder haben können. Damit tritt das vorherige Problem erneut auf.

Die Klasse [Personne3] löst dieses Problem wie folgt:


<?php

class Personne3 {
  // Attribute
  private $nom;
  private $prénom;
  private $âge;
  private $enfants;

  // globaler Setter
  public function setFromArray(array $arrayOfAttributes): Personne3 {

  }

  // Getter


  // __toString
  public function __toString(): string {
    // Die Zeichenkette JSON der Attribute wird zurückgegeben
    $attributes = [];
    $this->getRecursiveAttributes($attributes, $this, []);
    // Zeichenkette JSON der Attribute
    return \json_encode($attributes, JSON_UNESCAPED_UNICODE);
  }

  public function getAttributes(): array {
    return \get_object_vars($this);
  }

  private function getRecursiveAttributes(array &$attributes, $value, $keys): void {
    // Analyse des Werts [$value]
    // $keys ist ein Array [key1, key2, .., keyn]
    // $value=$attributesQZXW2HTMLBW2tleTFdZQXQZXW2HTMLBW2tleTJdZQX….[keyn]
    // Wenn [$value] ein Objekt ist, verwendet man dessen Methode [getAttributes]
    if (\is_object($value)) {
      // Attribute des Objekts [$value]
      $objectAttributes = $value->getAttributes();
      // Was macht man mit dem Ergebnis?
      if ($keys) {
        // In [$attributes] ersetzen wir $value durch das Array seiner Attribute
        // Es muss das Element $attributesQZXW2HTMLBW2tleTFdZQXQZXW2HTMLBW2tleTJdZQX…[keyn] erstellt werden
        //, wobei $keys das Array [key1, key2, .., keyn] ist
        // wird die Referenz des Arrays [$attributes] verwendet
        $attribute = &$attributes;
        // man durchsucht das Schlüsselarray
        foreach ($keys as $key) {
          // man nimmt die Referenz des Attributs
          $attribute = &$attribute[$key];
        }
        // hier sind $attribut und $attributesQZXW2HTMLBW2tleTFdZQXQZXW2HTMLBW2tleTJdZQX…[key(n)] identisch
        // und sie teilen sich denselben Speicherplatz
        // Das Objekt [$value] wird durch sein Attributarray ersetzt;
        // es muss $attributesQZXW2HTMLBW2tleTFdZQXQZXW2HTMLBW2tleTJdZQX… geschrieben werden[keyn]=$objectAttributes
        // was gleichbedeutend ist mit $attribute = $objectAttributes
        $attribute = $objectAttributes;
      } else {
        // keine Schlüssel – wir stehen am Anfang der Erkundung des Objekts
        // $objectAttributes stellt die Attribute der ersten Ebene der Klasse dar
        $attributes += $objectAttributes;
      }
      // Möglicherweise gibt es in [$objectAttributes] noch weitere Objekte
      // Wir untersuchen die Attribute von [$objectAttributes]
      $this->getRecursiveAttributes($attributes, $objectAttributes, $keys);
    } else {
      if (\is_array($value)) {
        // Wir haben ein Array – wir analysieren jedes seiner Elemente
        foreach ($value as $key => $élément) {
          // man fügt den aktuellen Schlüssel zum Array $keys hinzu
          \array_push($keys, $key);
          // man analysiert $élément
          $this->getRecursiveAttributes($attributes, $élément, $keys);
          // Man entfernt den gerade analysierten Schlüssel aus dem Array $keys
          \array_pop($keys);
        }
      }
    }
  }

Kommentare

  • Zeilen 21–22: Diesmal fordert die Methode [__toString] die Attribute ihrer Klasse ab und verlangt, dass dies rekursiv erfolgt: Ist ein Attribut ein Objekt oder ein Array von Objekten, so muss jedes Objekt durch sein Attribut-Array im endgültigen Attribut-Array der Klasse ersetzt werden;
  • Zeilen 31–78: Die Funktion [getRecursiveAttributes] übernimmt diese Aufgabe. Der Code wurde auskommentiert. Das Schreiben einer rekursiven Funktion ist oft eine komplexe Angelegenheit. Das ist auch hier der Fall. Der Leser verpasst nichts, wenn er den Code nicht versteht. Es gibt Bibliotheken, die diese Aufgabe übernehmen. Der rekursive Aufruf erfolgt in den Zeilen 64 und 72;
  • Der Vorteil dieses Codes besteht darin, dass er nicht ausschließlich für die Klasse [Personne3] geschrieben wurde. Er gilt für jede Klasse, deren Attribute Werte unterschiedlicher Objekttypen annehmen, sofern die von der Hauptklasse verwendeten Klassen ebenso wie diese über die Methode [getAttributes] in den Zeilen 27–29 verfügen

Das Skript [json-03.php] verwendet die Klasse [Personne3] wie folgt:


<?php

// Klasse „Person3“
require "Personne3.php";

// Instanziierung des übergeordneten Objekts
$père = new Personne3();
// Initialisierung
$père->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Dieudonné",
  "âge" => 58
]);
// Instanziierung und Initialisierung von Kind1
$enfant1 = (new Personne3())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Sylvain",
  "âge" => 27
  ]);
// Instanziierung und Initialisierung von Kind2
$enfant2 = (new Personne3())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Géraldine",
  "âge" => 12
  ]);
// Initialisierung der Kinder des Vaters
$père->setFromArray([
  "enfants" => [$enfant1, $enfant2]
]);
// Instanziierung und Initialisierung von Kind11
$enfant11 = (new Personne3())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Gaëtan",
  "âge" => 2
  ]);
// Instanziierung und Initialisierung von Kind12
$enfant12 = (new Personne3())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Mathilde",
  "âge" => 1
  ]);
// Initialisierung der Kinder von Kind1
$enfant1->setFromArray([
  "enfants" => [$enfant11, $enfant12]
]);
// Anzeige des übergeordneten Elements
print "------------------------père\n";
print "père=$père\n";
  • Zeilen 30–45: [$enfant1] werden zwei Kinder zugewiesen;

Die Ergebnisse der Ausführung lauten wie folgt:

------------------------père
père={"nom":"Bertholomé","prénom":"Dieudonné","âge":58,"enfants":[{"nom":"Bertholomé","prénom":"Sylvain","âge":27,"enfants":[{"nom":"Bertholomé","prénom":"Gaëtan","âge":2,"enfants":null},{"nom":"Bertholomé","prénom":"Mathilde","âge":1,"enfants":null}]},{"nom":"Bertholomé","prénom":"Géraldine","âge":12,"enfants":null}]}

Wenn man dieses Ergebnis formatiert, erhält man Folgendes:


père={
    "nom": "Bertholomé",
    "prénom": "Dieudonné",
    "âge": 58,
    "enfants": [
        {
            "nom": "Bertholomé",
            "prénom": "Sylvain",
            "âge": 27,
            "enfants": [
                {
                    "nom": "Bertholomé",
                    "prénom": "Gaëtan",
                    "âge": 2,
                    "enfants": null
                },
                {
                    "nom": "Bertholomé",
                    "prénom": "Mathilde",
                    "âge": 1,
                    "enfants": null
                }
            ]
        },
        {
            "nom": "Bertholomé",
            "prénom": "Géraldine",
            "âge": 12,
            "enfants": null
        }
    ]
}

Die Zeichenfolgen jSON aller Objekte [Personne], die den übergeordneten Knoten bilden, wurden korrekt abgerufen.