Skip to content

5. Klassen

Vokabular: Eine Klasse ist ein PHP-Typ. Eine Variable dieses Typs wird als Objekt bezeichnet. Ein Objekt ist eine Instanz (Instanz) einer Klasse.

5.1. Die Skripthierarchie

Image

5.2. Jede Variable kann zu einem Objekt mit Attributen werden

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


<?php
 
// a generic object
// $obj1=new stdClass();
// any variable can have attributes by construction
$obj1->attr1 = "un";
$obj1->attr2 = 100;
// displays the
print "objet1=[$obj1->attr1,$obj1->attr2]\n";
// modify object
$obj1->attr2 += 100;
// displays the
print "objet1=[$obj1->attr1,$obj1->attr2]\n";
// copies the value of object1 (address of the object pointed to) to object2
// the two variables are then different but point to the same object
$obj2 = $obj1;
// modify obj2
$obj2->attr2 = 0;
// displays both objects
print "objet1=[$obj1->attr1,$obj1->attr2]\n";
print "objet2=[$obj2->attr1,$obj2->attr2]\n";
// changes the object pointed to by obj1
$obj1 = new stdClass();
print "obj1 :\n";
print_r($obj1);
print "obj2 :\n";
print_r($obj2);
// assigns the reference (address) from object2 to object3
// $obj2 and $obj3 are then one and the same variable
$obj3 = &$obj2;
print "obj2 :\n";
print_r($obj2);
print "obj3 :\n";
print_r($obj3);
// modify obj3
$obj3->attr2 = 10;
// displays both objects
print "objet2=[$obj2->attr1,$obj2->attr2]\n";
print "objet3=[$obj3->attr1,$obj3->attr2]\n";
// changes the object pointed to by obj2
$obj2 = new stdClass();
$obj2->attr3 = "deux";
$obj2->attr4 = 20;
// displays the two objects $obj2 and $obj3
print "obj2 :\n";
print_r($obj2);
print "obj3 :\n";
print_r($obj3);
 
// is an object a dictionary?
print count($obj3) . "\n";
while (list($attribut, $valeur) = each($obj3)) {
  print "obj3[$attribut]=$valeur\n";
}
// end
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

Kommentare

  • Zeile 6: Die Notation $obj->attr bezieht sich auf das Attribut attr der Variablen $obj. Falls es nicht existiert, wird es erstellt, wodurch die Variable $obj zu einem Objekt mit Attributen wird. Wir haben gesehen, dass PHP dann standardmäßig ein stdClass-Objekt erstellt;
  • Zeile 16: Der Ausdruck $obj2=$obj1 ist, wenn $obj1 ein Objekt ist, eine Kopie von Objekten per Referenz: $obj2 und $obj1 sind Referenzen (Adressen) auf dasselbe Objekt. Das Objekt selbst kann über jede der beiden Referenzen 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 kopiert den Wert von $obj1 in die Variable $obj2 (Schritt 1 oben). Der Wert von $obj1 ist die Adresse eines Objekts. Somit verweisen $obj1 und $obj2 auf dasselbe Objekt. Bei der Bearbeitung einer Variablen $obj, die auf ein Objekt verweist, bearbeitet PHP das Objekt, auf das die Variable $obj verweist. Anhand des folgenden Diagramms können wir sehen, 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 [1 unten]. Man könnte sagen, dass die beiden Variablen Aliase für denselben Speicherort sind. Sie verweisen beide auf ein Objekt, Objekt A unten [2];
    • Die Anweisung $obj2=new stdClass() erstellt ein neues Objekt, Objekt B [3 unten], und die Adresse dieses neuen Objekts wird der Variablen $obj2 zugewiesen. Da $obj2 und $obj3 zwei Aliase für denselben Speicherort sind, verweist auch $obj3 auf das neue Objekt, Objekt B. Dies wird in den Zeilen 16–27 und 30–41 der Ergebnisse gezeigt;

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 sind die Werte derselben 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. Somit weist ein Objekt Ähnlichkeiten mit einem Wörterbuch auf, ist aber keines;

5.3. Eine Person-Klasse ohne deklarierte Attribute

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


<?php
 
class Personne {
 
  // class attributes
  // undeclared - can be created dynamically
  // method
  function identite() {
    // a priori, uses non-existent attributes
    return "[$this->prenom,$this->nom,$this->age]";
  }
 
}
 
// test
// attributes are public and can be created dynamically
$p = new Personne();
$p->prenom = "Paul";
$p->nom = "Langevin";
$p->age = 48;
// method call
print "personne=" . $p->identite() . "\n";
// end
exit;

Ergebnisse:

personne=[Paul,Langevin,48]

Kommentare

  • Zeilen 3–13: Definieren Sie eine Klasse „Person“. Eine Klasse ist eine Vorlage, die zum Erstellen von Objekten verwendet wird. Sie enthält Attribute und Funktionen, die als Methoden bezeichnet werden. Attribute müssen nicht deklariert werden;
  • Zeilen 8–11: Die Methode identity gibt die Werte von drei Attributen aus, die nicht in der Klasse deklariert sind. Das Schlüsselwort $this bezieht sich auf das Objekt, auf das die Methode angewendet wird;
  • Zeile 17: Wir erstellen ein Objekt $p vom Typ Person. Das Schlüsselwort new wird verwendet, um ein neues Objekt zu erstellen. Die Operation gibt eine Referenz auf das erstellte Objekt zurück (d. h. eine Adresse). Es sind verschiedene Schreibweisen möglich: new Person(), new Person, new person. Bei dem Klassennamen wird die Groß-/Kleinschreibung nicht berücksichtigt;
  • Zeilen 18–20: Die drei von der identity-Methode benötigten Attribute werden im Objekt $p angelegt;
  • Zeile 22: Die identity-Methode der Klasse Person wird auf das Objekt $p angewendet. Im Code (Zeilen 8–11) der identity-Methode bezieht sich $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 {
 
  // class attributes
  var $prenom;
  var $nom;
  var $age;
 
  // method
  function identite() {
    return "[$this->prenom,$this->nom,$this->age]";
  }
 
}
 
// test
// attributes are public
$p = new Personne();
$p->prenom = "Paul";
$p->nom = "Langevin";
$p->age = 48;
// method call
print "personne=" . $p->identite() . "\n";
// end
exit;

Ergebnisse:

personne=[Paul,Langevin,48]

Kommentare

  • Zeilen 6–8: Die Klassenattribute werden explizit mit dem Schlüsselwort var deklariert;

5.5. Die Klasse „Person“ mit einem Konstruktor

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


<?php
 
// strict adherence to declared types of function parameters
declare (strict_types=1);
class Personne {
// class attributes
  private $prenom;
  private $nom;
  private $age;
 
// getters and setters
  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;
  }
 
// manufacturer
  public function __construct(string $prenom, string $nom, int $age) {
    // we go through sets
    $this->setPrenom($prenom);
    $this->setNom($nom);
    $this->setAge($age);
  }
 
// method toString
  public function __toString(): string {
    return "[$this->prenom,$this->nom,$this->age]";
  }
 
}
 
// test
// creation of a Personne object
$p = new Personne("Paul", "Langevin", 48);
// identity of this person
print "personne=$p\n";
// we change the age
$p->setAge(14);
// identity of the person
print "personne=$p\n";
// end
exit;

Ergebnisse:

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

Kommentare

  • Zeilen 6–50: die Klasse „Person“;
  • Zeilen 7–9: die privaten Attribute der Klasse. Diese Attribute sind nur innerhalb der Klasse sichtbar. Weitere Schlüsselwörter, die verwendet werden können, sind:
  • public: macht das Attribut zu einem öffentlichen Attribut, das von außerhalb der Klasse sichtbar ist,
  • protected: macht das Attribut zu einem geschützten Attribut, das innerhalb der Klasse und ihrer abgeleiteten Klassen sichtbar ist;
  • Da die Attribute privat sind, kann von außerhalb der Klasse nicht auf sie zugegriffen werden. Daher können wir den folgenden Code nicht schreiben:
$p=new Personne() ;
$p->nom="Landru" ;

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

  • Verwenden Sie die öffentlichen Set- und Get-Methoden (die Namen dieser Methoden können beliebig sein) in den Zeilen 12–34. Wir können dann schreiben:
$p=new Personne() ;
$p->setNom("Landru") ;
  • Verwenden Sie den Konstruktor in den Zeilen 37–42. Wir würden dann schreiben:
$p=new Personne("Michel","Landru",44) ;

Der obige Code ruft automatisch die Klassenmethode __construct der Klasse Person auf;

  • Zeile 59: Diese Zeile gibt die Person $p als Zeichenkette aus. Dazu wird die Klassenmethode __toString (Zeilen 45–47) der Klasse Person verwendet;
  • Allen Methoden der Klasse (Funktionen) wurde das Schlüsselwort public vorangestellt, was angibt, dass die Funktion außerhalb der Klasse sichtbar ist. Die anderen Schlüsselwörter, die verwendet werden können, sind – wie bei Attributen und mit derselben Bedeutung – private und protected. Ohne explizites Sichtbarkeitsattribut hat die Funktion implizit öffentliche Sichtbarkeit;

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 Set-Methoden oder entsprechende Funktionen initialisiert werden. Um zu vermeiden, dass dieselben Prüfungen an zwei verschiedenen Stellen durchgeführt werden, können diese Prüfungen in die Set-Methoden integriert werden. Wird festgestellt, dass der Initialisierungswert eines Objekts falsch ist, wird eine Ausnahme ausgelöst. Hier ein Beispiel.

Zunächst verschieben wir die Definition der Klasse „Person“ in eine eigene Datei [Person.php]:


<?php
 
// strict adherence to declared types of function parameters
declare (strict_types=1);
 
// namespace;
namespace Exemples;
 
// person class
class Personne {
// class attributes
  private $prenom;
  private $nom;
  private $age;
 
// getters and setters
  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 {
    // first name must be non-empty
    $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 {
    // name must be non-empty
    $nom = trim($nom);
    if ($nom === "") {
      throw new \Exception("Le nom doit être non vide");
    } else {
      $this->nom = $nom;
    }
  }
 
  public function setAge(int $âge): void {
    // age must be valid
    if ($âge < 0) {
      throw new \Exception("L'âge doit être un entier positif ou nul");
    } else {
      $this->age = $âge;
    }
  }
 
// manufacturer
  public function __construct(string $prenom, string $nom, int $age) {
    // we go through sets
    $this->setPrenom($prenom);
    $this->setNom($nom);
    $this->setAge($age);
  }
 
  // method
  public function initWithPersonne(Personne $p): void {
    // initializes the current object with a person $p
    $this->__construct($p->prenom, $p->nom, $p->age);
  }
 
  // method toString
  function __toString(): string {
    return "[$this->prenom,$this->nom,$this->age]";
  }
 
}

Kommentare

  • Zeile 4: legt fest, dass der Typ der Funktionsparameter bei der Deklaration beachtet werden muss;
  • Zeile 7: definiert einen Namensraum. Der vollständige (oder qualifizierte) Name der Klasse „Person“ lautet dann \Examples\Person. Beachten Sie das Zeichen \ am Anfang des qualifizierten Namens: Dies führt zu einem absoluten qualifizierten Namen. Fehlt dieses Zeichen, ist der Name relativ (relativ zum aktuellen Namensraum). Wenn also zwei Klassen A und B Teil desselben Namensraums E sind, kann im Code der Klasse A auf die Klasse B mit der relativen Notation B zugegriffen werden. Wenn Klasse A Teil des Namensraums E1 und B Teil des Namensraums E2 ist, wird im Code von A auf B mit der absoluten Notation \E2\B zugegriffen. Die Definition einer Klasse innerhalb eines Namensraums ist nicht zwingend erforderlich, aber NetBeans gibt eine Warnung aus, wenn Sie dies nicht tun. Daher werden wir es tun. Außerdem sollten Namensräume der Dateiverzeichnisstruktur entsprechen. Somit sollte sich die Klasse A im Namensraum E1 in einer Datei namens E1/A.php befinden. Dies ist nicht zwingend erforderlich, aber auch hier gibt NetBeans eine Warnung aus, wenn Sie dies nicht tun. Im Beispiel der Klasse [\Exemples\Personne] gibt NetBeans eine Warnung aus, da der Dateipfad von [Personne.php] [exemples/classes/Personne.php] lautet und somit nicht mit dem Namespace übereinstimmt. Verwechseln Sie den Dateipfad nicht mit dem Namespace. Der vollqualifizierte Name einer Klasse verwendet einen Namespace und hat nichts mit dem Dateipfad der PHP-Datei der Klasse zu tun. Die Beziehung zwischen Dateipfad und Namespace ist optional und kann ignoriert werden, wie wir es hier getan haben;
  • Zeilen 12–14: die drei privaten Attribute der Klasse;
  • Zeilen 29–37: Initialisierung des Attributs first_name und Überprüfung des Initialisierungswerts;
  • Zeile 31: Die Funktion trim($string) entfernt Leerzeichen am Anfang und am Ende von $string. 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, haben wir hier die vordefinierte Klasse [Exception] verwendet. Hier müssen wir ihren absoluten Namen [\Exception] verwenden. Wenn wir ihren relativen Namen [Exception] verwenden, wird diese Klasse im aktuellen Namensraum gesucht, d. h. im Namensraum [Examples] der Klasse Person. Der PHP-Interpreter sucht also nach einer Klasse mit dem absoluten Namen [\Examples\Exception], die nicht existiert;

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


<?php
 
// strict adherence to function parameter types
declare(strict_types=1);
 
// including definition of the Person class
require_once __DIR__."/Personne.php";
 
// qualified name of the Person class
use \Exemples\Personne;
 
// test
// creation of a Personne object
$p = new Personne("Paul", "Langevin", 48);
// identity of this person
print "Exemple1, personne=$p\n";
// creation of an erroneous Person object
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";
}
// creation of an erroneous Person object
try {
  $p = new Personne("", "yy", 10);
} catch (\Exception $e1) {
  print "Exemple3, erreur : " . $e1->getMessage() . "\n";
} catch (\TypeError $e2) {
  print "Exemple3, erreur : " . $e2->getMessage() . "\n";
}
 
// end
exit;

Kommentare

  • Zeile 7: Das Skript verwendet die Klasse [Person]. Wir müssen dem PHP-Interpreter daher mitteilen, wo er die Definition dieser Klasse finden kann. Dies ist die Aufgabe der Anweisungen [include file] und [require file]. Hier haben wir die Anweisung [include] verwendet. Der Unterschied zwischen den beiden Anweisungen ist folgender: Wenn bei der Anweisung [include file] beim Laden der Datei [file] Fehler auftreten, wird ein [E_WARNING]-Fehler ausgegeben, die Ausführung wird jedoch fortgesetzt, während [require] im gleichen Fall einen schwerwiegenden Fehler erzeugt und die Skriptausführung beendet wird. Jede dieser beiden Anweisungen hat eine Variante: [include_once] und [require_once]. Diese beiden Varianten ermöglichen es Ihnen, Fälle zu behandeln, in denen dieselbe Datei mehrfach eingebunden wird. Stellen Sie sich ein Projekt vor, das aus mehreren PHP-Skripten besteht, von denen einige auf die Klasse [Person] verweisen. Ihre Ausführung führt dazu, dass die Datei [Person.php] mehrfach eingebunden wird, was zu einem Fehler führt, da eine Klasse nicht zweimal definiert werden kann. Die Lösung besteht darin, die [_once]-Varianten zu verwenden, die sicherstellen, dass die Datei nur einmal im Gesamtskript des Projekts eingebunden wird;
  • Zeile 7: Die Konstante [__DIR__] ist eine PHP-Konstante, die auf den vollständigen Pfad des Verzeichnisses verweist, das das Skript mit der Konstante [__DIR__] enthält. Daher 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 Sie entweder / oder \ verwenden;

  • Zeile 14: Wir verwenden die soeben definierte Klasse [Person]. Das Skript [classes-05.php] enthält keine Namespaces. In Zeile 14 wird der relative Name der Klasse [Person] ohne Namespace verwendet. Da die Klasse [Person] keinen Namespace hat, wird sie innerhalb des Skripts [classes-05.php] selbst gesucht und daher nicht gefunden. Es gibt zwei Lösungen für dieses Problem:
    • Verwenden Sie den vollständigen Klassennamen [\Examples\Person];
    • Verwenden Sie die `use`-Anweisung in Zeile 10. Diese gibt an, dass der nachfolgende Code die Klasse `[Examples\Person]` verwendet;
  • Zeile 10: Die `use`-Anweisung teilt dem Interpreter mit, dass es sich bei der in Zeile 14 referenzierten Klasse [Person] tatsächlich um die Klasse [\Examples\Person] handelt. Doch wo findet der Interpreter den Code für diese Klasse? Das sagt ihm Zeile 7. Diese Zeile gibt an, dass zur Ausführung des aktuellen Skripts auch das Skript [Person.php] geladen werden muss. Hier wird der relative Dateiname verwendet. Die Datei wird daher in dem Ordner gesucht, der das Skript [classes-05.php] enthält. Die Skripte [Person.php] und [classes-05.php] müssen sich daher im selben Ordner befinden. Dies ist hier der Fall, da sich beide im Ordner [examples/classes] befinden. Die Anweisung in Zeile 10 entspricht:

use \Exemples\Personne as Personne;

Die obige [use]-Anweisung legt fest, dass sich der Alias [Person] auf die Klasse [\Exemples\Personne] bezieht;

  • Zeile 14: Es wird ein [Person]-Objekt erstellt. Die Methode [__construct] der Klasse [Person] wird hier implizit ausgeführt;
  • Zeile 16: Zeigt die Person $p an. Um angezeigt zu werden, muss der Wert der Variablen $p in eine Zeichenkette umgewandelt werden. Implizit wird dann die Methode [Person.__toString] ausgeführt. Diese Methode muss daher eine Zeichenkette zurückgeben;
  • Wir haben gesehen, dass der Konstruktor der Klasse [Person] eine Ausnahme vom Typ [\Exception] auslösen kann. Diese muss daher abgefangen werden. Folglich ist der Code in Zeile 14 unvollständig. Wir müssen den Code in den Zeilen 18–24 verwenden, um die möglicherweise auftretende Ausnahme ordnungsgemäß zu behandeln. Hier lösen wir absichtlich eine aus, indem wir ein Alter übergeben, das keine Ganzzahl ist. In diesem speziellen Fall wird die erzeugte Ausnahme – —vom PHP-Interpreter ausgelöst und nicht vom Code der Klasse [Person]. Tatsächlich lautet die Signatur der Methode [Person.__construct] wie folgt:

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

Daher muss der an den Konstruktor übergebene Parameter [age] vom Typ Integer sein. Ist dies nicht der Fall, löst der PHP-Interpreter einen [TypeError] aus. Außerdem lösen die [set]-Methoden der Klasse [Person] eine [\Exception] aus. Da der Konstruktor, der sie aufruft, keinen try/catch-Block enthält, wird die Ausnahme eine Ebene nach oben an den Code weitergeleitet, der den Konstruktor aufgerufen hat, d. h. an den Code im Skript [classes-05.php]. Letztendlich kann das Skript [classes-05.php] zwei Arten von Ausnahmen empfangen: \Exception oder \TypeError. Beachten Sie, dass Entwickler, wenn sie sicher sind, dass bestimmte Ausnahmen nicht auftreten können, die entsprechenden catch-Blöcke nicht verwenden. Hier werden sie systematisch ausschließlich zu Demonstrationszwecken eingesetzt. Catch-Blöcke werden jedoch für jede mögliche Ausnahme verwendet, auch wenn diese unwahrscheinlich ist;

Aus diesem Grund enthält der try-Block in den Zeilen 18–24 zwei catch-Blöcke, um die beiden Ausnahmetypen getrennt zu behandeln;

  • Zeile 20: Sie können entweder [Exception] oder [\Exception] schreiben:
    • Die erste Version verwendet den relativen Klassennamen, relativ zum Namespace des Skripts. Das Skript hat keinen Namespace. Sein Namespace ist daher die Wurzel aller Namespaces: \. Daher ist das Schreiben von [Exception] hier gleichbedeutend mit dem Schreiben von [\Exception]. Und die Klasse [Exception] befindet sich tatsächlich im Namespace [\];

Es ist vorzuziehen, die absoluten Namen der vordefinierten PHP-Ausnahmen in einem Skript zu verwenden, das selbst keinen Namespace hat. Wenn Sie sich also entscheiden, diesem Skript einen Namespace zuzuweisen, bleibt die Verwendung der absoluten Klassennamen gültig, während im anderen Fall eine Änderung des Namespaces zu Fehlern bei den relativen Klassennamen führt;

  • Zeile 21: Wenn eine Ausnahme auftritt, ruft die Methode [Exception→getMessage] die Fehlermeldung der Ausnahme ab. Dasselbe gilt für einen [TypeError]. In der Methode [Person.setFirstName] haben wir geschrieben:

  public function setPrenom(string $prénom) {
    // le prénom doit être non vide
    $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 [Der Vorname darf nicht leer sein] ausgelöst. Dies ist der Wert, den die Methode [Exception→getMessage] in Zeile 29 des Skripts [classes-05.php] zurückgibt.

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 sekundärer 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 konstruieren. Wir können daher Methoden verwenden, die als Konstruktoren fungieren:


  // méthode
  public function initWithPersonne(Personne $p) {
    // initialise l'objet courant avec une personne $p
    $this->__construct($p->prenom, $p->nom, $p->age);
}

Kommentare

  • Zeilen 2–5: Die Methode `initWithPerson` ermöglicht es, die Werte der Attribute eines anderen `Person`-Objekts dem aktuellen Objekt zuzuweisen. Hier ruft sie den Konstruktor `__construct` auf, was jedoch nicht zwingend erforderlich ist. Sie könnte die Attribute der Klasse `Person` selbst initialisieren;

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


<?php
 
// including definition of the Person class
require_once __DIR__."/Personne.php";
// declaration of the qualified name of the Personne class
use \Exemples\Personne;
 
// test
// creation of a Personne object
try {
  $p = new Personne("Paul", "Langevin", 48);
} catch (\Exception $e) {
  print "erreur : " . $e->getMessage();
  exit;
}
// identity of this person
print "personne=$p\n";
// creation of a second person
try {
  $p2 = new Personne("Laure", "Adeline", 67);
} catch (\Exception $e) {
  print "erreur : " . $e->getMessage();
  exit;
}
// initialization of the first with the values of the second
try {
  $p->initWithPersonne($p2);
} catch (\Exception $e) {
  print "erreur : " . $e->getMessage();
  exit;
}
 
// check
print "personne=$p\n";
// end
exit;
  • Zeilen 14, 23, 30: In der Regel muss man nach einer Ausnahme die Ausführung eines Konsolenskripts beenden, wenn der aufgetretene Fehler nicht behebbar ist. Bei einem Webskript ist dies nicht der Fall: Wir beenden die Ausführung des Skripts nicht, sondern zeigen eine Fehlerseite an. Befinden wir uns innerhalb einer Funktion, verwenden wir nicht die Anweisung „exit“, sondern „return“: Wir brechen die Ausführung des Skripts nicht ab (exit), sondern verlassen die Funktion (return), nachdem wir einen Fehler gesetzt haben;

Ergebnisse:

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

5.8. Ein Array von Objekten [Person]

Das folgende Beispiel [classes-07.php] zeigt, dass Sie Arrays von Objekten haben können.


<?php
 
require_once __DIR__."/Personne.php";
use \Exemples\Personne;
 
// test
// create an array of Personne objects
// to make the code easier to understand, we don't handle the possible exception
$groupe = [new Personne("Paul", "Langevin", 48), new Personne("Sylvie", "Lefur", 70)];
 
// identity of these persons
for ($i = 0; $i < count($groupe); $i++) {
  print "groupe[$i]=$groupe[$i]\n";
}
 
// end
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: $group[$i] ist ein Objekt vom Typ Person. Zur Anzeige wird die Methode [Person.__toString] verwendet;

5.9. Erstellen einer von der Klasse Person abgeleiteten Klasse

Wir erstellen die folgende Klasse [Teacher] in einer Datei namens [Teacher.php]:


<?php
 
// strict adherence to declared function parameter types
declare (strict_types=1);
 
// namespace
namespace Exemples;
 
// a class derived from personne
class Enseignant extends Personne {
  // attributes
  private $discipline;   // subject taught
 
  // getter and setter
 
  public function getDiscipline(): string {
    return $this->discipline;
  }
 
  public function setDiscipline(string $discipline): void {
    $this->discipline = $discipline;
  }
 
  // manufacturer
  public function __construct(string $prénom, string $nom, int $âge, string $discipline) {
    // parent attributes
    parent::__construct($prénom, $nom, $âge);
    // other attributes
    $this->setDiscipline($discipline);
  }
 
  // overloading the __toString function of the parent class
  public function __toString(): string {
    return "[" . parent::__toString() . ",$this->discipline]";
  }
 
}

Kommentare

  • Zeile 7: Die Klasse [Teacher] ist ebenfalls Teil des Namespace [Examples];
  • Zeile 10: Die Klasse „Teacher“ erweitert die Klasse „Person“. Die abgeleitete Klasse „Teacher“ erbt die Attribute und Methoden ihrer übergeordneten Klasse;
  • Zeile 12: Die Klasse „Teacher“ fügt ein neues Attribut hinzu, „subject“, das für sie spezifisch ist;
  • Zeile 25: Der Konstruktor der Klasse „Teacher“ nimmt 4 Parameter entgegen:
    • 3 zur Initialisierung ihrer übergeordneten Klasse (first_name, last_name, age), Zeile 27;
    • 1 für ihre eigene Initialisierung (subject), Zeile 29;
  • Zeile 27: Die abgeleitete Klasse hat über das Schlüsselwort parent:: Zugriff auf die Methoden und Konstruktoren ihrer übergeordneten Klasse. Hier übergeben wir die Parameter (first_name, last_name, age) an den Konstruktor der übergeordneten Klasse;
  • Zeilen 33–35: Die Methode __toString der abgeleiteten Klasse verwendet die Methode __toString der übergeordneten Klasse;

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


<?php
 
// inclusion of the definition of the two classes
require_once __DIR__."/Personne.php";
require_once __DIR__."/Enseignant.php";
// declaration of the two classes used
use \Exemples\Personne;
use \Exemples\Enseignant;
 
// test
// creation of an array of Personne and derived objects
// for the simplicity of the example, we don't handle exceptions
$groupe = array(new Enseignant("Paul", "Langevin", 48, "anglais"), new Personne("Sylvie", "Lefur", 70));
 
// identity of these persons
for ($i = 0; $i < count($groupe); $i++) {
  print "groupe[$i]=$groupe[$i]\n";
}
// end
exit;

Kommentare

  • Zeilen 4–5: Wir müssen dem PHP-Interpreter mitteilen, wo sich die beiden Klassen [Teacher, Person] befinden;
  • Zeilen 7–8: Deklaration der vollständigen Namen der beiden Klassen. Dadurch können wir sie im Code einfach mit ihren Namen ohne das Namespace-Suffix referenzieren;
  • Zeile 13: Wir erstellen ein Array, das einen Typ [Person] und einen Typ [Teacher] enthält;
  • Zeilen 16–18: Anzeige der Elemente des Arrays;
  • Zeile 17: Die Methode __toString jedes Elements $group[$i] wird aufgerufen. Die Klasse Person verfügt über eine Methode __toString. Die Klasse Teacher verfügt über zwei: die ihrer übergeordneten Klasse und ihre eigene. Man könnte sich fragen, welche davon aufgerufen wird. Die Ausführung zeigt, dass es diejenige aus der Klasse „Teacher“ war, die aufgerufen wurde. Dies ist immer der Fall: Wenn eine Methode für ein Objekt aufgerufen wird, wird in der folgenden Reihenfolge danach gesucht: im Objekt selbst, in seiner übergeordneten Klasse, falls vorhanden, dann in der übergeordneten Klasse der übergeordneten Klasse und so weiter… Die Suche wird beendet, sobald die Methode gefunden wurde.

Ergebnisse:

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

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

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


<?php
 
// strict adherence to declared types of function parameters
declare (strict_types=1);
 
// namespace
namespace Exemples;
 
class Etudiant extends Personne {
  // attributes
  private $formation;   // training pursued
 
  // getter and setter
  public function getFormation(): string {
    return $this->formation;
  }
 
  public function setFormation(string $formation): void {
    $this->formation = $formation;
  }
 
  // manufacturer
  public function __construct(string $prénom, string $nom, int $âge, string $formation) {
    // parent attributes
    parent::__construct($prénom, $nom, $âge);
    // other attributes
    $this->setFormation($formation);
  }
 
  // overloading the __toString function of the parent class
  public function __toString(): string {
    return "[" . parent::__toString() . ",$this->formation]]";
  }
 
}

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


<?php
 
// inclusion and definition of classes used by the script
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
// creation of a table of person objects and derivatives
// to make the example easier to understand, we don't handle exceptions
$groupe = array(new Enseignant("Paul", "Langevin", 48, "anglais"), new Personne("Sylvie", "Lefur", 70), new Etudiant("Steve", "Boer", 23, "iup2 qualité"));
 
// identity of these persons
for ($i = 0; $i < count($groupe); $i++) {
  print "groupe[$i]=$groupe[$i]\n";
}
// end
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 der übergeordneten Klasse auf. Der folgende Code [classes-16.php] zeigt, dass dies bei PHP 7 nicht der Fall ist:


<?php
 
class Classe1 {
 
  // manufacturer
  public function __construct() {
    print "constructeur de la classe Classe1\n";
  }
 
}
 
class Classe2 extends Classe1 {

  // manufacturer
  public function __construct() {
    // the constructor of the parent class is not called implicitly
    print "constructeur de la classe Classe2\n";
  }
 
}
 
class Classe3 extends Classe1 {
 
  // manufacturer
  public function __construct() {
    // explicit call to parent class constructor
    parent::__construct();
    // code specific to Classe3
    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. Überschreiben einer Methode der übergeordneten Klasse

Wir haben bereits gesehen, dass eine Methode der übergeordneten Klasse in einer untergeordneten Klasse überschrieben werden kann. So wurde die Methode [__toString] der Klasse [Person] (siehe Link) in den untergeordneten Klassen [Teacher] (siehe Link) und [Student] (siehe Link) überschrieben. Das Skript [classes-13.php] veranschaulicht dieses Konzept noch einmal:


<?php
 
// strict adherence to function parameter types
declare(strict_types=1);
 
// main class
class Classe1 {
 
  public function f(): int {
    return 1;
  }
 
  function g(): int {
    return 2;
  }
 
}
 
// derived class
class Classe2 extends Classe1 {
 
  // redefine the f function of the parent class
  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 [Class1] definiert zwei Methoden, f und g;
  • Zeilen 20–27: Die Klasse [Class2] erweitert die Klasse [Class1] 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 [Class2] erstellt;
  • Zeile 31 des Codes ruft die Methode f des Objekts $c2 auf. Da diese Methode existiert, wird sie ausgeführt;
  • Zeile 32 des Codes ruft die Methode g des Objekts $c2 auf. Da diese Methode nicht existiert, wird in der übergeordneten Klasse danach gesucht, wo sie gefunden und ausgeführt wird;
  • Zeile 33 des Codes erstellt ein Objekt $c1 vom Typ [Class1];
  • Zeile 34 des Codes ruft die Methode f des Objekts $c1 auf. Da diese Methode existiert, wird sie ausgeführt;

5.13. Ein Objekt als Funktionsparameter übergeben

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


<?php
 
// strict adherence to function parameter types
declare(strict_types=1);
 
// main class
class Classe1 {
 
  public function f(): int {
    return 1;
  }
 
  function g(): int {
    return 2;
  }
 
}
 
// derived class
class Classe2 extends Classe1 {
 
  // redefine the f function of the parent class
  public function f(): int {
    return parent::f() + 10;
  }
 
}
 
// the function parameter is of type Class1 or derived
function doSomething(Classe1 $c1): void {
  print $c1->f() + $c1->g() . "\n";
}
 
// code
// create a Class2 object derived from Class1
$c2 = new Classe2();
// we call doSomething with
doSomething($c2);

Kommentare

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

Ergebnisse

13

5.14. Abstrakte Klassen

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

Wozu dient eine abstrakte Klasse? Manchmal haben wir Klassen, die eine oder mehrere Methoden gemeinsam haben, sich aber in anderen Methoden oder Attributen unterscheiden. Dann ist es wünschenswert, alles, was gemeinsam ist, in einer übergeordneten Klasse zusammenzufassen. Im Moment brauchen wir noch keine abstrakte Klasse. Nehmen wir aber an, die Unterklassen unterscheiden sich nur in einer Methode, M: Die Signatur der Methode wäre in allen Unterklassen gleich, ihre Implementierung würde sich jedoch unterscheiden. Um zu verlangen, dass die Unterklassen die Methode M implementieren:

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

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


<?php
 
// strict adherence to function parameter types
declare(strict_types=1);
 
// abstract main class
abstract Class Classe1 {
 
  // method known to all derived classes
  public function f(): int {
    return 1;
  }
 
  // abstract g method - will be defined by derived classes
  abstract function g(): int;
}
 
// derived class
Class Classe2 extends Classe1 {
 
  // the g method of the parent class must be defined
  public function g(): int {
    return parent::f() + 10;
  }

}
 
// derived class
Class Classe3 extends Classe1 {
 
  // the g method of the parent class must be defined
  public function g(): int {
    return parent::f() + 20;
  }
 
  // we can redefine the f method of the parent class
  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";

Kommentare

  • Zeilen 7–16: Die Klasse [Class1] ist abstrakt (Zeile 7), da sie nicht weiß, wie die Methode g in Zeile 15 implementiert werden soll. Sie muss daher abgeleitet werden, um verwendet werden zu können;
  • Zeilen 19–26: Die Klasse [Class2] erweitert die Klasse [Class1] und definiert die Methode g ihrer übergeordneten Klasse neu (Zeilen 22–24);
  • Zeilen 29–41: Die Klasse [Class3] erweitert die Klasse [Class1] und definiert die Methode g ihrer übergeordneten Klasse neu (Zeilen 32–34);
  • Zeilen 37–39: Die Klasse [Class3] definiert die Methode f ihrer übergeordneten Klasse neu;
  • Zeilen 44–49: Es werden zwei Objekte der Typen [Class2] und [Class3] erstellt und ihre Methoden f und g aufgerufen;

Ergebnisse

1
2
3
4
1
11
2
21

5.15. Endgültige Klassen

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


<?php
 
// namespace
namespace Exemples;
 
// non-derivable class
final Class Classe1 {
  
}
 
// derived class
Class Classe2 extends Classe1 {
  
}
 
// code - must cause an error
new Classe2();

Kommentare

  • Zeilen 7–9: Das Schlüsselwort „final“ macht die Klasse [Class1] zu einer finalen Klasse, von der keine abgeleiteten Klassen erstellt werden können;
  • Zeilen 12–14: Die Klasse [Class2] erweitert die finale Klasse [Class1], was einen Fehler darstellt;
  • Zeile 17: Der Fehler wird erst gemeldet, wenn das Skript ausgeführt wird und versucht wird, ein Objekt vom Typ [Class2] zu manipulieren;

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. Final-Methoden

Eine finale Methode ist eine Methode, die von Unterklassen nicht überschrieben werden kann. Hier ist ein Beispiel [classes-12.php]:


<?php
 
// strict adherence to function parameter types
declare(strict_types=1);
 
// namespace
namespace Exemples;
 
// main class
Class Classe1 {
 
  // this method cannot be redefined in a derived class
  public final function f(): int {
    return 1;
  }
 
}
 
// derived class
Class Classe2 extends Classe1 {
 
  public function f(): int {
    return 2;
  }
 
}
 
// code - must cause an error
new Classe2();

Kommentare

  • Zeile 13: Die Methode f der Klasse [Class1] wird mit dem Schlüsselwort final als final deklariert;
  • Zeile 20: Die Klasse [Class2] erweitert die Klasse [Class1];
  • Zeilen 22–23: Die Funktion f der übergeordneten Klasse [Class1] wird neu definiert. Dies sollte einen Fehler verursachen;
  • Zeile 29: Es wird ein Objekt vom Typ [Class2] erstellt, um den PHP-Interpreter zu zwingen, die Klasse [Class2] zu prü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 der Klasse zugeordnet ist, in der sie definiert ist, nicht den Instanzobjekten der Klasse. Wenn also die Klasse C eine statische Methode M deklariert, würden wir zur Verwendung schreiben:

  • C::M, wenn Sie sich außerhalb der Klasse befinden;
  • self::M, wenn wir uns innerhalb der Klasse befinden;

Hier ist ein Beispiel [classes-17.php]:


<?php
 
class Classe1 {
 
  // static method
  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 Syntax: Class1::say;

Ergebnisse

hello

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


<?php
 
class Classe1 {
  // static attribute
  private static $nbObjects = 0;
 
  public function __construct() {
    print "constructeur Classe1\n";
    self::$nbObjects++;
  }
 
  // static method
  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: Wir deklarieren ein statisches Attribut, das die Anzahl der Instanzen der Klasse [Class1] zählt, die erstellt wurden. Dies ist kein Attribut, das zu einer Instanz der Klasse gehören kann. Wenn zwei Objekte, O1 und O2, erstellt werden, ist sich keines von beiden des anderen bewusst. Einen Zähler innerhalb der Instanz zu haben, macht keinen Sinn: Wenn ein neues Objekt erstellt wird, in welcher Instanz würden wir den Zähler erhöhen? Wir wären gezwungen, den Zähler eines bestimmten Objekts zu inkrementieren und dabei die Zähler der anderen Instanzen zu ignorieren. Ein statisches Attribut ist ein Klassenattribut, kein Instanzattribut der Klasse;
  • Zeilen 7–10: Im Konstruktor werden wir die erstellten Objekte zählen, da die Erstellung jedes neuen Objekts die Ausführung des Konstruktors auslöst;
  • Zeile 14: Beachten Sie die Notation `self::$nbObjects`, um anzugeben, dass wir uns auf ein statisches Attribut der Klasse beziehen, in der sich der ausgeführte Code befindet;
  • Zeilen 13–15: Die statische Methode [say] ist dafür zuständig, die Anzahl der erstellten Objekte anzuzeigen;
  • Zeilen 20–22: Wir erstellen zwei Objekte und zeigen den Objektzähler an;

Ergebnisse


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

5.18. Sichtbarkeit zwischen übergeordneter Klasse und untergeordneter Klasse

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


<?php
 
class SomeParent {
  // attribute
  private $attributeOfParent = 4;
 
  // method
  public function doTest(): void {
    // who's calling?
    print "parent :\n";
    var_dump($this);
    // parent display
    print "parent : attributeOfParent={$this->attributeOfParent}\n";
    print "parent : attributeOfChild={$this->attributeOfChild}\n";
  }
 
}
 
class SomeChild extends SomeParent {
  // attribute
  private $attributeOfChild = 14;
 
  // method
  public function doTest(): void {
    // children's display
    print "child : attributeOfParent={$this->attributeOfParent}\n";
    print "child : attributeOfChild={$this->attributeOfChild}\n";
 
    // parent
    parent::doTest();
  }
}
 
// main script
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]. Wir sehen, dass sie die Klasse [SomeParent] erweitert (Zeile 19);
  • Zeile 5: Die Klasse [SomeParent] hat nur ein Attribut;
  • Zeilen 8–17: Die Methode [SomeParent::doTest] soll zwei Attribute anzeigen:
    • [$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 auf zwei verschiedene Arten aufgerufen:
    • von der übergeordneten Klasse [SomeParent];
    • von 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] soll zwei Attribute anzeigen:
    • [$attributeOfParent], das zur Klasse [SomeParent] gehört;
    • [$attributeOfChild], das zur Klasse [SomeChild] gehört;
  • Zeilen 26–27: Anzeige der beiden Attribute;
  • Zeile 30: Ruft die Methode [doTest] der übergeordneten Klasse auf, 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 ist die Sichtbarkeit beider Attribute [private]. Wir können daher erwarten, dass die untergeordnete Klasse das Attribut ihrer übergeordneten Klasse nicht sieht. Dieses Attribut 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: Wir sehen, dass das Objekt, das die Methode aufruft, vom Typ [SomeParent] ist;
  • Zeile 7: Anzeige des Attributs [$attributeOfParent];
  • Zeilen 9–10: Wir sehen, 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: Wir sehen, dass das Attribut [SomeChild::$attributeOfParent] nicht existiert. Es wird daher nicht angezeigt. Dies ist normal: Das Attribut [SomeParent::$attributeOfParent] ist [private] und 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: Wir sehen, dass [$this] vom Typ [SomeChild] ist und zwei private Attribute hat;
  • Zeile 23: Überraschenderweise kann [$this] vom Typ [SomeChild] hier das Attribut [$attributeOfParent] des Elternteils sehen;
  • Zeilen 25–30: Ebenso überraschend ist, dass [$this] vom Typ [SomeChild] sein eigenes Attribut [$attributeOfChild] nicht sieht;

Dieses Ergebnis ist sehr überraschend: Obwohl die Zeilen 17–21 darauf hindeuten, 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 {
  // attribute
  protected $attributeOfParent = 4;
 
  // method
  public function doTest(): void {
    // who's calling?
    print "parent :\n";
    var_dump($this);
    // parent display
    print "parent : attributeOfParent={$this->attributeOfParent}\n";
    print "parent : attributeOfChild={$this->attributeOfChild}\n";
  }
 
}
 
class SomeChild extends SomeParent {
  // attribute
  private $attributeOfChild = 14;
 
  // method
  public function doTest(): void {
    // children's display
    print "child : attributeOfParent={$this->attributeOfParent}\n";
    print "child : attributeOfChild={$this->attributeOfChild}\n";
 
    // parent
    parent::doTest();
  }
 
}
 
// main script
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] sieht nun das Attribut [$attributeOfParent] ihres übergeordneten Objekts. Dies ist normal, da das Attribut nun den Gültigkeitsbereich [protected] hat;
  • In der Methode [someParent::doTest] ist das Objekt [$this] vom Typ [SomeChild] (Zeilen 15–20). Es sieht das Attribut [$attributeOfParent] seines Elternelements (Zeile 21), aber immer noch nicht sein eigenes Attribut [$attributeOfChild] (Zeilen 23–28);

Im dritten Test hat das Attribut [$attributeOfChild] ebenfalls den Gültigkeitsbereich [protected]:


<?php
 
class SomeParent {
  // attribute
  protected $attributeOfParent = 4;
 
  // method
  public function doTest(): void {
    // who's calling?
    print "parent :\n";
    var_dump($this);
    // parent display
    print "parent : attributeOfParent={$this->attributeOfParent}\n";
    print "parent : attributeOfChild={$this->attributeOfChild}\n";
  }
 
}
 
class SomeChild extends SomeParent {
  // attribute
  protected $attributeOfChild = 14;
 
  // method
  public function doTest(): void {
    // children's display
    print "child : attributeOfParent={$this->attributeOfParent}\n";
    print "child : attributeOfChild={$this->attributeOfChild}\n";
 
    // parent
    parent::doTest();
  }
 
}
 
// main script
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 sieht [$this] vom Typ [SomeChild] (Zeilen 15–20) innerhalb des Elternelements das geschützte Attribut [$attributeOfChild] seiner eigenen Klasse [SomeChild].

Was können wir aus diesen Tests lernen?

  • 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;
    • keines der Attribute und Methoden ihrer untergeordneten Klassen sieht;

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.

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

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 repräsentiert, das sie aufruft. Es mag verlockend sein, diese Zeichenkette als JSON-Zeichenkette zu gestalten. Wir werden diesen Weg nun untersuchen.

Image

Wir verwenden die folgende [Person]-Klasse:


<?php
 
class Personne {
  // attributes
  private $nom;
  private $prénom;
  private $âge;
  private $enfants;
 
  // setter global
  public function setFromArray(array $arrayOfAttributes): Personne {
    // initialization of certain class attributes
    foreach ($arrayOfAttributes as $attribute => $value) {
      $this->$attribute = $value;
    }
    // object is returned
    return $this;
  }
 
  // getters
  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 {
    // we identify the object
    var_dump($this);
    // retrieve its attributes
    $attributes = \get_object_vars($this);
    var_dump($attributes);
    // render the jSON attribute string
    return \json_encode($attributes, JSON_UNESCAPED_UNICODE);
  }
 
}

Kommentare

  • Zeilen 5–8: die vier Attribute der Klasse;
  • Zeilen 20–35: die Getter, mit denen Sie die Werte dieser Attribute abrufen können;
  • Zeilen 11–18: Ein globaler Setter, der die Attribute aus einem assoziativen Array [$arrayOfAttributes] initialisiert, dessen Schlüssel mit den Klassenattributen übereinstimmen;
  • Zeilen 38–46: die Methode [__toString] der Klasse;
  • Zeile 42: Die PHP-Funktion [get_object_vars] ruft die Werte der Klassenattribute als assoziatives Array [‘name’=>’name1’, ‘first_name’=>’first_name1’, ‘age’=>’age1’, ‘children’=>[]] ab;
  • Zeile 45: Wir geben die JSON-Zeichenkette dieses Attribut-Arrays zurück;

Sehen wir uns das Skript [json-01.php] an, das die Klasse [Person] verwendet:


<?php
 
// person class
require "Personne.php";
 
// father instantiation
$père = new Personne();
// father initialization
$père->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Dieudonné",
  "âge" => 58
]);
// instantiation and initialization child1
$enfant1 = (new Personne())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Sylvain",
  "âge" => 17
  ]);
// instantiation and initialization child2
$enfant2 = (new Personne())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Géraldine",
  "âge" => 12
  ]);
// father's children initialization
$père->setFromArray([
  "enfants" => [$enfant1, $enfant2]
]);
 
// display father elements
$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: Wir initialisieren ein [Person]-Objekt [$father] mit der Methode [Person::setFromArray], die es uns ermöglicht, ein [Person]-Objekt mit einem Array zu initialisieren, dessen Schlüssel mit den Attributen der Klasse [Person] übereinstimmen;
  • Zeilen 14–19: Wir initialisieren ein [Person]-Objekt [$child1] auf die gleiche Weise;
  • Zeilen 21–25: Wir initialisieren ein [Person]-Objekt [$child2];
  • Zeilen 27–29: Das Attribut [$father→children] wird mit einem Array der beiden Kinder initialisiert;
  • Zeilen 32–33: Wir weisen dem Vater die beiden Kinder zu;
  • Zeile 35: Die Operation [print] versucht, das Objekt [$child1] in eine Zeichenkette umzuwandeln. Dazu verwendet sie die Methode [__toString] des Objekts. Wir erwarten daher, die JSON-Zeichenkette des Objekts zu sehen;
  • Zeilen 38–39: Wir machen dasselbe mit dem Vater;

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 [$child1];
  • Zeilen 12–21: das Array der Attribute für das Objekt [$child1]. Wir haben sie alle;
  • Zeile 22: Wir haben die JSON-Zeichenkette für das Objekt [$child1];
  • Zeilen 23–44: dasselbe für das Objekt [$child2];
  • Zeilen 45–112: Beim übergeordneten Objekt ist es etwas anders, da sein Attribut [children] nicht NULL ist, wie es bei den untergeordneten Objekten der Fall war;
  • Zeile 112: Wir sehen, dass die Kinder in der JSON-Zeichenkette des Vaters fehlen;
  • Zeilen 79–111: Wir sehen, dass im Attribut-Array des Vaters Kind 1 (Zeilen 89–98) ein Objekt bleibt, ebenso wie Kind 2 (Zeilen 99–110). Kurz gesagt ist der Ausdruck [\get_object_vars($this)], wobei [$this] das übergeordnete Element darstellt, nicht rekursiv: Wenn ein Attribut der Klasse [Person] selbst ein Objekt ist, versucht der Ausdruck [\get_object_vars($this)] nicht, dessen Attributarray abzurufen;

Wir können dies verbessern. Wir ändern die Klasse [Person] in die folgende Klasse [Person2]:


<?php
 
class Personne2 {
  // attributes
  private $nom;
  private $prénom;
  private $âge;
  private $enfants;
 
  // setter global
  public function setFromArray(array $arrayOfAttributes): Personne2 {

    // object is returned
    return $this;
  }
 
  // getters
  public function getNom() {
    return $this->nom;
  }
 

 
  // __toString
  public function __toString(): string {
    // retrieve the object's attributes
    $attributes = $this->getAttributes($this);
    $enfants = $attributes["enfants"];
    if ($enfants != NULL) {
      $attributes["enfants"] = [$enfants[0]->getAttributes(), $enfants[1]->getAttributes()];
    }
    // render the JSON attribute string
    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 Array der Attribute des Objekts zurück, das sie aufruft;
  • Zeilen 25–34: die Funktion [__toString];
  • Zeile 27: Wir rufen die Attribute der Klasse [Person] in das Array [$attributes] ab;
  • Zeile 28: Aus dem vorherigen Beispiel wissen wir, dass [$attributes["children"]] ein Array aus zwei Objekten vom Typ [Person] ist;
  • Zeilen 29–31: Die beiden Objekte werden durch ihr Array von Attributen ersetzt;
  • Zeile 33: Jetzt muss nur noch das erstellte Array von Attributen in JSON kodiert werden;

Das Skript [json-02.php] verwendet die Klasse [Person2] wie folgt:


<?php
 
// person2 class
require "Personne2.php";

// father instantiation
$père = new Personne2();
// initialization
$père->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Dieudonné",
  "âge" => 58
]);
// instantiation and initialization child1
$enfant1 = (new Personne2())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Sylvain",
  "âge" => 17
  ]);
// instantiation and initialization child2
$enfant2 = (new Personne2())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Géraldine",
  "âge" => 12
  ]);
// father's children initialization
$père->setFromArray([
  "enfants" => [$enfant1, $enfant2]
]);
 
// father display
print "------------------------père\n";
print "père=$père\n";

Das Skript [json-02.php] ist identisch mit dem Skript [json-01.php], außer dass die Klasse [Person2] die Klasse [Person] 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 haben wir die Kinder zusammen mit dem Vater erfolgreich abgerufen.

Die vorherige Lösung ist unbefriedigend, da die Kinder möglicherweise selbst Kinder haben. Dann stoßen wir auf das vorherige Problem.

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


<?php
 
class Personne3 {
  // attributes
  private $nom;
  private $prénom;
  private $âge;
  private $enfants;
 
  // setter global
  public function setFromArray(array $arrayOfAttributes): Personne3 {

  }
 
  // getters

 
  // __toString
  public function __toString(): string {
    // render the JSON attribute string
    $attributes = [];
    $this->getRecursiveAttributes($attributes, $this, []);
    // string JSON of attributes
    return \json_encode($attributes, JSON_UNESCAPED_UNICODE);
  }
 
  public function getAttributes(): array {
    return \get_object_vars($this);
  }
 
  private function getRecursiveAttributes(array &$attributes, $value, $keys): void {
    // value analysis [$value]
    // $keys is an array [key1, key2, .., keyn]
    // $value=$attributes[key1][key2]….[keyn]
    // if [$value] is an object, we use its method [getAttributes]
    if (\is_object($value)) {
      // attributs de l'objet [$value]
      $objectAttributes = $value->getAttributes();
      // what do we do with the result?
      if ($keys) {
        // in [$attributes], we replace $value with the array of its attributes
        // element $attributes[key1][key2]...[keyn] must be constructed
        // where $keys is the array [key1, key2, .., keyn]
        // we take the table reference [$attributes]
        $attribute = &$attributes;
        // scan the key table
        foreach ($keys as $key) {
          // we take the reference of the
          $attribute = &$attribute[$key];
        }
        // here $attribut and $attributes[key1][key2]...[key(n)] are identical
        // they share the same memory location
        // object [$value] is replaced by its array of attributes;
        // write $attributes[key1][key2]...[keyn]=$objectAttributes
        // which is equivalent to $attribute = $objectAttributes
        $attribute = $objectAttributes;
      } else {
        // no keys - we're just beginning to explore the object
        // $objectAttributes represents the 1st level attributes of the class
        $attributes += $objectAttributes;
      }
      // maybe in [$objectAttributes] there are still objects
      // explore [$objectAttributes] attributes
      $this->getRecursiveAttributes($attributes, $objectAttributes, $keys);
    } else {
      if (\is_array($value)) {
        // we have a table - we analyze each of its elements
        foreach ($value as $key => $élément) {
          // add the current key to the $keys array
          \array_push($keys, $key);
          // we analyze $élément
          $this->getRecursiveAttributes($attributes, $élément, $keys);
          // remove the key just analyzed from the $keys array
          \array_pop($keys);
        }
      }
    }
  }

Kommentare

  • Zeilen 21–22: Diesmal ruft die Methode [__toString] die Attribute ihrer Klasse ab und fordert, dass dies rekursiv erfolgt: Wenn ein Attribut ein Objekt oder ein Array von Objekten ist, muss jedes Objekt durch sein Array von Attributen im endgültigen Array von Attributen der Klasse ersetzt werden;
  • Zeilen 31–78: Die Funktion [getRecursiveAttributes] übernimmt diese Aufgabe. Wir haben den Code kommentiert. Das Schreiben einer rekursiven Funktion ist oft komplex. Das ist hier der Fall. Der Leser verpasst nichts, wenn er sie nicht versteht. Es gibt Bibliotheken, die diese Aufgabe übernehmen. Die rekursiven Aufrufe finden in den Zeilen 64 und 72 statt;
  • Der Vorteil dieses Codes besteht darin, dass er nicht ausschließlich für die Klasse [Person3] geschrieben wurde. Er gilt für jede Klasse mit Attributen, deren Werte unterschiedliche Objekttypen haben, solange die von der Hauptklasse verwendeten Klassen wie diese über die Methode [getAttributes] in den Zeilen 27–29 verfügen.

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


<?php
 
// person3 class
require "Personne3.php";
 
// father instantiation
$père = new Personne3();
// initialization
$père->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Dieudonné",
  "âge" => 58
]);
// instantiation and initialization child1
$enfant1 = (new Personne3())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Sylvain",
  "âge" => 27
  ]);
// instantiation and initialization child2
$enfant2 = (new Personne3())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Géraldine",
  "âge" => 12
  ]);
// father's children initialization
$père->setFromArray([
  "enfants" => [$enfant1, $enfant2]
]);
// instantiation and initialization child11
$enfant11 = (new Personne3())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Gaëtan",
  "âge" => 2
  ]);
// instantiation and initialization child12
$enfant12 = (new Personne3())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Mathilde",
  "âge" => 1
  ]);
// initialization children of child1
$enfant1->setFromArray([
  "enfants" => [$enfant11, $enfant12]
]);
// father display
print "------------------------père\n";
print "père=$père\n";
  • Zeilen 30–45: Wir weisen [$child1] zwei Kinder zu;

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 wir dieses Ergebnis formatieren, erhalten wir 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
        }
    ]
}

Wir haben die JSON-Strings für alle [Person]-Objekte, aus denen sich das übergeordnete Objekt zusammensetzt, erfolgreich abgerufen.