Skip to content

5. De klassen

Woordenschat: Een klasse (class) is een type PHP. Een variabele van dit type wordt een object genoemd. Een object is een instantie (exemplaar) van een klasse.

5.1. De scriptstructuur

Image

5.2. Elke variabele kan een object met attributen worden

Het script [classes-01.php] luidt als volgt:


<?php

// een generiek object
// $obj1=new stdClass();
// elke variabele kan per definitie attributen hebben
$obj1->attr1 = "un";
$obj1->attr2 = 100;
// geeft het object weer
print "objet1=[$obj1->attr1,$obj1->attr2]\n";
// wijzigt het object
$obj1->attr2 += 100;
// geeft het object weer
print "objet1=[$obj1->attr1,$obj1->attr2]\n";
// kopieert de waarde van object1 (het adres van het object waarnaar wordt verwezen) naar object2
// de twee variabelen zijn nu verschillend, maar verwijzen naar hetzelfde object
$obj2 = $obj1;
// wijzigt obj2
$obj2->attr2 = 0;
// geeft de twee objecten weer
print "objet1=[$obj1->attr1,$obj1->attr2]\n";
print "objet2=[$obj2->attr1,$obj2->attr2]\n";
// wijzigt het object waarnaar obj1 verwijst
$obj1 = new stdClass();
print "obj1 :\n";
print_r($obj1);
print "obj2 :\n";
print_r($obj2);
// wijst de referentie (het adres) van object2 toe aan object3
// $obj2 en $obj3 zijn dan één en dezelfde variabele
$obj3 = &$obj2;
print "obj2 :\n";
print_r($obj2);
print "obj3 :\n";
print_r($obj3);
// wijzigt obj3
$obj3->attr2 = 10;
// geeft beide objecten weer
print "objet2=[$obj2->attr1,$obj2->attr2]\n";
print "objet3=[$obj3->attr1,$obj3->attr2]\n";
// wijzigt het object waarnaar obj2 verwijst
$obj2 = new stdClass();
$obj2->attr3 = "deux";
$obj2->attr4 = 20;
// geeft de twee objecten $obj2 en $obj3 weer
print "obj2 :\n";
print_r($obj2);
print "obj3 :\n";
print_r($obj3);

// is een object een woordenboek?
print count($obj3) . "\n";
while (list($attribut, $valeur) = each($obj3)) {
  print "obj3[$attribut]=$valeur\n";
}
// einde
exit;

Resultaten:


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

Opmerkingen

  • regel 6: de notatie $obj->attr verwijst naar het attribuut attr van de variabele $obj. Als dit niet bestaat, wordt het aangemaakt, waardoor de variabele $obj een object met attributen wordt. We hebben gezien dat PHP dan standaard een object van het type stdClass aanmaakt;
  • regel 16: de uitdrukking $obj2=$obj1 is, wanneer $obj1 een object is, een kopie van objecten via verwijzing: $obj2 en $obj1 zijn verwijzingen (adressen) naar hetzelfde object. Het object zelf kan door beide verwijzingen worden gewijzigd;
  • regels 23-27: zijn bedoeld om aan te tonen dat $obj1 en $obj2 twee verschillende variabelen zijn: ze bevinden zich niet op hetzelfde geheugenadres:
    • $obj2=$obj1 heeft de waarde van $obj1 gekopieerd naar de variabele $obj2 (bewerking 1 hierboven). De waarde van $obj1 is het adres van een object. $obj1 en $obj2 verwijzen dus naar hetzelfde object. Wanneer de variabele $obj wordt gemanipuleerd en deze naar een object verwijst, manipuleert PHP het object waarnaar de variabele $obj verwijst. Uit het onderstaande schema blijkt dat het object waarnaar wordt verwezen kan worden gewijzigd via zowel $obj1 als via $obj2. Dit blijkt uit de regels 4 en 5 van de resultaten;

Image

  • regel 30: de uitdrukking $obj3=&$obj2 zorgt ervoor dat $obj2 en $obj3 hetzelfde adres hebben, namelijk [1 ci-dessous]. Je zou kunnen zeggen dat de twee variabelen aliassen zijn van dezelfde geheugenlocatie. Ze verwijzen allebei naar één object, Object A hieronder: [2];
    • de bewerking $obj2=new stdClass() zorgt ervoor dat er een nieuw object, Object B, wordt aangemaakt ([3 ci-dessous]) en dat het adres van dit nieuwe object wordt toegewezen aan de variabele $obj2. Aangezien $obj2 en $obj3 twee aliassen zijn van dezelfde geheugenlocatie, verwijst $obj3 eveneens naar het nieuwe object Object B. Dit blijkt uit de regels 16-27 en 30-41 van de resultaten;

Image

  • regels 52-54: laten zien dat een object als een woordenboek kan worden doorlopen. De sleutels van het woordenboek zijn de namen van de attributen en de waarden van het woordenboek zijn de waarden van diezelfde attributen;
  • regel 51: de functie count kan op een object worden toegepast (met een waarschuwing), maar geeft niet, zoals men zou verwachten, het aantal attributen weer. Een object vertoont dus overeenkomsten met een woordenboek, maar is er geen;

5.3. Een klasse Personne zonder gedeclareerde attributen

Het script [classes-02.php] is als volgt:


<?php

class Personne {

  // klasseattributen
  // niet gedeclareerd – kunnen dynamisch worden aangemaakt
  // methode
  function identite() {
    // gebruikt a priori niet-bestaande attributen
    return "[$this->prenom,$this->nom,$this->age]";
  }

}

// test
// de attributen zijn openbaar en kunnen dynamisch worden aangemaakt
$p = new Personne();
$p->prenom = "Paul";
$p->nom = "Langevin";
$p->age = 48;
// aanroep van een methode
print "personne=" . $p->identite() . "\n";
// einde
exit;

Resultaten:

personne=[Paul,Langevin,48]

Opmerkingen

  • regels 3-13: definiëren een klasse Personne. Een klasse is een mal op basis waarvan objecten worden aangemaakt. Ze bevat attributen en functies die methoden worden genoemd. Het is niet verplicht om de attributen te declareren;
  • regels 8-11: de methode identité geeft de waarde weer van drie attributen die niet in de klasse zijn gedeclareerd. Het sleutelwoord $this verwijst naar het object waarop de methode wordt toegepast;
  • regel 17: er wordt een object $p van het type Personne aangemaakt. Het sleutelwoord new wordt gebruikt om een nieuw object aan te maken. De bewerking retourneert een verwijzing naar het aangemaakte object (dus een adres). Er zijn verschillende schrijfwijzen mogelijk: new Personne(), new Personne, new personne. De naam van de klasse is hoofdlettergevoelig;
  • regels 18-20: de drie attributen die nodig zijn voor de methode identité worden aangemaakt in het object $p;
  • regel 22: de methode identité van de klasse Personne wordt toegepast op het object $p. In de code (regels 8-11) van de methode identité verwijst $this naar hetzelfde object als $p;

5.4. De klasse Personne met gedeclareerde attributen

Het script [classes-03.php] is als volgt:


<?php

class Personne {

  // attributen van de klasse
  var $prenom;
  var $nom;
  var $age;

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

}

// test
// de attributen zijn openbaar
$p = new Personne();
$p->prenom = "Paul";
$p->nom = "Langevin";
$p->age = 48;
// aanroep van een methode
print "personne=" . $p->identite() . "\n";
// einde
exit;

Resultaten:

personne=[Paul,Langevin,48]

Opmerkingen

  • regels 6-8: de attributen van de klasse worden expliciet gedeclareerd met het sleutelwoord var;

5.5. De klasse Personne met een constructor

De voorgaande voorbeelden toonden exotische klassen zoals die te vinden waren in Personne 4. Het wordt afgeraden om deze voorbeelden te volgen. We presenteren nu een klasse Personne [classes-04.php] die voldoet aan de best practices van PHP 7:


<?php

// strikte naleving van de gedeclareerde typen van de functieparameters
declare (strict_types=1);
class Personne {
// klasseattributen
  private $prenom;
  private $nom;
  private $age;

// getters en 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;
  }

// constructor
  public function __construct(string $prenom, string $nom, int $age) {
    // we gebruiken de set-methoden
    $this->setPrenom($prenom);
    $this->setNom($nom);
    $this->setAge($age);
  }

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

}

// test
// aanmaken van een Person-object
$p = new Personne("Paul", "Langevin", 48);
// identiteit van deze persoon
print "personne=$p\n";
// de leeftijd wijzigen
$p->setAge(14);
// identiteit van de persoon
print "personne=$p\n";
// einde
exit;

Resultaten:

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

Opmerkingen

  • regels 6-50: de klasse Personne;
  • regels 7-9: de privé-attributen (private) van de klasse. Deze attributen zijn alleen zichtbaar binnen de klasse. Andere bruikbare sleutelwoorden zijn:
  • public: maakt het attribuut zichtbaar van buiten de klasse,
  • protected: maakt het attribuut zichtbaar vanuit de klasse zelf en vanuit klassen die daarvan zijn afgeleid;
  • omdat de attributen privé zijn, is er geen toegang toe van buiten de klasse. De volgende code kan dus niet worden geschreven:
$p=new Personne() ;
$p->nom="Landru" ;

Hier bevinden we ons buiten de klasse Personne. Aangezien het attribuut nom privé is, is regel 2 onjuist. Om de privévelden van het object $p te initialiseren, zijn er twee manieren:

  • gebruik maken van de openbare set- en get-methoden (de naam van deze methoden kan willekeurig zijn) in de regels 12-34. We kunnen dan het volgende schrijven:
$p=new Personne() ;
$p->setNom("Landru") ;
  • de constructor uit de regels 37-42 gebruiken. Dan schrijf je:
$p=new Personne("Michel","Landru",44) ;

De bovenstaande schrijfwijze roept automatisch de methode van de klasse Personne op, genaamd __construct;

  • regel 59: deze regel geeft de persoon $p weer in de vorm van een tekenreeks. Hiervoor wordt de methode van de klasse Personne, genaamd __toString (regels 45-47), gebruikt;
  • alle methoden van de klasse (functies) zijn voorafgegaan door het sleutelwoord public, wat aangeeft dat de functie buiten de klasse zichtbaar is. De andere bruikbare sleutelwoorden zijn, net als bij de attributen en met dezelfde betekenis, private en protected. Zonder expliciet zichtbaarheidsattribuut heeft de functie impliciet de zichtbaarheid public;

5.6. De klasse Personne met geldigheidscontroles in de constructor

De constructor van een klasse is de juiste plaats om te controleren of de initialisatiewaarden van het object correct zijn. Maar een object kan ook worden geïnitialiseerd via zijn methoden set of gelijkwaardige methoden. Om te voorkomen dat dezelfde controles op twee verschillende plaatsen worden uitgevoerd, kunnen deze in de methoden set worden geplaatst. Als een initialisatiewaarde van een object onjuist blijkt te zijn, wordt er een uitzondering gegenereerd. Hier volgt een voorbeeld.

Eerst verplaatsen we de definitie van de klasse Personne naar een eigen bestand [Personne.php]:


<?php

// strikte naleving van de gedeclareerde typen van de functieparameters
declare (strict_types=1);

// naamruimte;
namespace Exemples;

// klasse Persoon
class Personne {
// attributen van de klasse
  private $prenom;
  private $nom;
  private $age;

// getters en 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 {
    // de voornaam mag niet leeg zijn
    $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 {
    // de achternaam mag niet leeg zijn
    $nom = trim($nom);
    if ($nom === "") {
      throw new \Exception("Le nom doit être non vide");
    } else {
      $this->nom = $nom;
    }
  }

  public function setAge(int $âge): void {
    // de leeftijd moet geldig zijn
    if ($âge < 0) {
      throw new \Exception("L'âge doit être un entier positif ou nul");
    } else {
      $this->age = $âge;
    }
  }

// constructor
  public function __construct(string $prenom, string $nom, int $age) {
    // we gebruiken de set-methoden
    $this->setPrenom($prenom);
    $this->setNom($nom);
    $this->setAge($age);
  }

  // methode
  public function initWithPersonne(Personne $p): void {
    // initialiseert het huidige object met een persoon $p
    $this->__construct($p->prenom, $p->nom, $p->age);
  }

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

}

Opmerkingen

  • regel 4: er wordt gevraagd dat het type van de functieparameters wordt gerespecteerd wanneer deze wordt gedeclareerd;
  • regel 7: definieert een naamruimte (namespace). De volledige naam (ook wel qualifié genoemd) van de klasse Personne is dan \Exemples\Personne. Let op het teken \ waarmee de gekwalificeerde naam begint: we hebben dan een gekwalificeerde naam absolu. Als dit teken ontbreekt, hebben we een gekwalificeerde naam relatif (relatief ten opzichte van de huidige naamruimte). Als twee klassen A en B dus deel uitmaken van dezelfde naamruimte E, kan men in de code van klasse A klasse B bereiken via de notatie relative B. Als klasse A deel uitmaakt van de naamruimte E1 en B van de naamruimte E2, dan kan men in de code van A klasse B bereiken via de notatie absolue \E2\B. Het is niet verplicht om een klasse binnen een naamruimte te definiëren, maar NetBeans geeft een waarschuwing als je dit niet doet. Daarom doen we het toch. Bovendien moeten de naamruimten overeenkomen met de bestandsstructuur. Zo zou klasse A in de naamruimte E1 zich dus in een bestand E1/A.php moeten bevinden. Dit is niet verplicht, maar ook hier geeft NetBeans een waarschuwing als je dit niet doet. In het voorbeeld van de klasse [\Exemples\Personne] geeft NetBeans een waarschuwing omdat de bestandsstructuur van het bestand [Personne.php] [exemples/classes/Personne.php] is en dus niet overeenkomt met de naamruimte. Verwar de bestandsstructuur niet met de naamruimte. De volledig gekwalificeerde naam van een klasse maakt gebruik van een naamruimte en heeft niets te maken met de bestandsstructuur van het bestand PHP van de klasse. De koppeling tussen bestandsstructuur en naamruimte is optioneel en hoeft niet te worden aangehouden, zoals we hier hebben gedaan;
  • regels 12-14: de drie privé-attributen van de klasse;
  • regels 29-37: initialisatie van het attribuut prenom en controle van de initialisatiewaarde;
  • regel 31: de functie trim($chaine) verwijdert de spaties aan het begin en einde van $chaine. Dus trim(„abcd ”) is de tekenreeks „abcd” en trim„ ” is de lege tekenreeks;
  • regel 32: als de voornaam leeg is, wordt er een uitzondering gegenereerd (regel 33), anders wordt de voornaam opgeslagen (regel 35). Om een uitzondering te genereren, is hier de vooraf gedefinieerde klasse [Exception] gebruikt. We zijn hier verplicht om de absolute naam [\Exception] te gebruiken. Als we de relatieve naam [Exception] gebruiken, wordt deze klasse gezocht in de huidige naamruimte, d.w.z. de naamruimte [Exemples] van de klasse Personne. De interpreter PHP zal dus zoeken naar een klasse met de absolute naam [\Exemples\Exception], die niet bestaat;

De klasse [Personne] wordt gebruikt door het volgende script [classes-05.php]:


<?php

// strikte naleving van het type van de functieparameters
declare(strict_types=1);

// opname van de definitie van de klasse Persoon
require_once __DIR__."/Personne.php";

// gekwalificeerde naam van de klasse Personne
use \Exemples\Personne;

// test
// aanmaken van een Personne-object
$p = new Personne("Paul", "Langevin", 48);
// identiteit van deze persoon
print "Exemple1, personne=$p\n";
// aanmaken van een foutief Personne-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";
}
// aanmaken van een foutief Personne-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";
}

// einde
exit;

Opmerkingen

  • regel 7: het script gaat de klasse [Personne] gebruiken. We moeten de interpreter PHP dus laten weten waar hij de definitie van deze klasse kan vinden. Dat is de taak van de instructies [include fichier] en [require fichier]. Hier is de instructie [include] gebruikt. Het verschil tussen de twee instructies is als volgt: als de instructie [include fichier] fouten tegenkomt bij het laden van [fichier], wordt er een fout op niveau [E_WARNING] gegenereerd, maar dede uitvoering gaat door, terwijl [require] in hetzelfde geval een fatale fout genereert en de uitvoering van het script wordt gestopt. Beide instructies hebben een variant: [include_once] en [require_once]. Met deze twee varianten kan het geval van meervoudige inclusies van hetzelfde bestand worden afgehandeld. Men kan zich hier een project voorstellen dat bestaat uit meerdere PHP-scripts, waarvan er meerdere verwijzen naar de klasse [Personne]. Bij de uitvoering hiervan wordt het bestand [Personne.php] meerdere keren opgenomen, wat een fout veroorzaakt omdat een klasse niet twee keer kan worden gedefinieerd. De oplossing is om de varianten [_once] te gebruiken, die ervoor zorgen dat het bestand slechts één keer in het algemene script van het project wordt opgenomen;
  • regel 7: de constante [__DIR__] is een constante PHP die verwijst naar de volledige naam van de map waarin het script met de constante [__DIR__] zich bevindt. De uitdrukking op regel 17:

require_once __DIR__."/Personne.php";

komt neer op iets als:


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

In het bestandspad kunnen de tekens / en \ door elkaar worden gebruikt;

  • regel 14: hier wordt de klasse [Personne] gebruikt die we zojuist hebben gedefinieerd. Het script [classes-05.php] heeft geen naamruimten. Regel 14 gebruikt de relatieve naam van de klasse [Personne] zonder naamruimte. Aangezien de klasse [Personne] geen naamruimte heeft, wordt deze gezocht in het script [classes-05.php] zelf en zal deze dus niet worden gevonden. Er zijn twee oplossingen voor dit probleem:
    • de volledige naam van de klasse [\Exemples\Personne] gebruiken;
    • de `use`-instructie op regel 10 gebruiken. Deze geeft aan dat de daaropvolgende code gebruikmaakt van de klasse [\Exemples\Personne];
  • regel 10: dankzij de `use`-instructie weet de interpreter dat de klasse `[Personne]`, waarnaar op regel 14 wordt verwezen, in werkelijkheid de klasse `[\Exemples\Personne]` is. Maar waar vindt de interpreter dan de code van deze klasse? Dat wordt aangegeven in regel 7. Deze regel geeft aan dat voor het uitvoeren van het huidige script ook het script [Personne.php] moet worden geladen. Hier is de relatieve bestandsnaam gebruikt. Het bestand wordt dus gezocht in de map die het script [classes-05.php] bevat. De scripts [Personne.php] en [classes-05.php] moeten dus in dezelfde map staan. Dat is hier het geval: ze staan allebei in de map [exemples/classes]. De instructie op regel 10 komt overeen met:

use \Exemples\Personne as Personne;

De bovenstaande instructie [use] geeft aan dat de alias [Personne] verwijst naar de klasse [\Exemples\Personne];

  • regel 14: er wordt een object [Personne] aangemaakt. Hier wordt impliciet de methode [__construct] van de klasse [Personne] uitgevoerd;
  • regel 16: zorgt ervoor dat de persoon $p wordt weergegeven. Om te kunnen worden weergegeven, moet de waarde van de variabele $p worden omgezet in een tekenreeks. Impliciet wordt dan de methode [Personne.__toString] uitgevoerd. Deze moet dus een tekenreeks retourneren;
  • we hebben gezien dat de constructor van de klasse [Personne] een uitzondering van het type [\Exception] kan genereren. Deze moet dus worden afgehandeld. De code op regel 14 is dus onvolledig. De code op de regels 18-24 moet worden gebruikt om de uitzondering die kan optreden correct af te handelen. Hier wordt er opzettelijk een uitzondering gegenereerd door een leeftijd door te geven die geen geheel getal is. In dit specifieke geval wordt de uitzondering veroorzaakt door de interpreter PHP en niet door de code van de klasse [Personne]. De signatuur van de methode [Personne.__construct] is namelijk als volgt:

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

De parameter [age] die aan de constructor wordt doorgegeven, moet dus van het type integer zijn. Als dat niet het geval is, genereert de interpreter PHP een fout van het type [TypeError]. Bovendien genereren de methoden [set] van de klasse [Personne] een uitzondering van het type [\Exception]. Aangezien de constructor die deze methoden aanroept geen try/catch-structuur heeft, wordt de uitzondering één niveau hoger doorgegeven, naar de code die de constructor heeft aangeroepen, d.w.z. de code van het script [classes-05.php]. Uiteindelijk kan het script [classes-05.php] twee soorten uitzonderingen ontvangen: \Exception of \TypeError. Merk op dat wanneer de ontwikkelaar er zeker van is dat bepaalde uitzonderingen niet kunnen optreden, hij de bijbehorende catch-opties niet zal gebruiken. Hier worden ze uitsluitend ter illustratie systematisch gebruikt. De catch-opties zullen echter worden gebruikt voor elke mogelijke uitzondering, zelfs als deze onwaarschijnlijk is;

Om deze reden bevat de try-structuur in de regels 18-24 twee catch-blokken om de twee soorten uitzonderingen afzonderlijk af te handelen;

  • regel 20: men kan zowel [Exception] als [\Exception] schrijven:
    • de eerste versie gebruikt de relatieve naam van de klasse, ten opzichte van de naamruimte van het script. Het script heeft geen naamruimte. De naamruimte is dan de root van de naamruimten: \. [Exception] schrijven komt hier dus neer op het schrijven van [\Exception]. De klasse [Exception] bevindt zich echter wel degelijk in de naamruimte [\];

Het verdient de voorkeur om de absolute naam van de vooraf gedefinieerde uitzonderingen van PHP te gebruiken in een script dat zelf geen naamruimten heeft. Als men dus besluit om dit script een naamruimte te geven, blijft het gebruik van de absolute namen van klassen geldig, terwijl in het andere geval de wijziging van de naamruimte fouten zal veroorzaken bij de relatieve namen van de klassen;

  • regel 21: wanneer er een uitzondering optreedt, kan met de methode [Exception→getMessage] de foutmelding van de uitzondering worden opgehaald. Hetzelfde geldt voor een fout van het type [TypeError]. In de methode [Personne.setPrenom] is het volgende geschreven:

  public function setPrenom(string $prénom) {
    // de voornaam mag niet leeg zijn
    $prénom = trim($prénom);
    if ($prénom === "") {
      throw new \Exception("Le prénom doit être non vide");
    } else {
      $this->prenom = $prénom;
    }
}

Op regel 5 wordt een uitzondering gegenereerd met de foutmelding [Le prénom doit être non vide]. Dit is wat de methode [Exception→getMessage] op regel 29 van het script [classes-05.php] zal ophalen.

Resultaten:


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. Toevoeging van een methode die als tweede constructor fungeert

In PHP 7 is het niet mogelijk om meerdere constructors met verschillende parameters te hebben waarmee een object op verschillende manieren kan worden geconstrueerd. Men kan dan methoden gebruiken die als constructor fungeren:


  // methode
  public function initWithPersonne(Personne $p) {
    // initialiseert het huidige object met een persoon $p
    $this->__construct($p->prenom, $p->nom, $p->age);
}

Opmerkingen

  • regels 2-5: met de methode initWithPersonne kunnen de waarden van de attributen van een ander object Personne aan het huidige object worden toegewezen. Hier wordt de constructor __construct aangeroepen, maar dat is niet verplicht. De methode zou zelf de attributen van de klasse [Personne] kunnen initialiseren;

De nieuwe klasse [Personne] wordt gebruikt door het volgende script [classes-06.php]:


<?php

// inclusie van de definitie van de klasse Persoon
require_once __DIR__."/Personne.php";
// verklaring van de gekwalificeerde naam van de klasse Personne
use \Exemples\Personne;

// test
// aanmaken van een Person-object
try {
  $p = new Personne("Paul", "Langevin", 48);
} catch (\Exception $e) {
  print "erreur : " . $e->getMessage();
  exit;
}
// identiteit van deze persoon
print "personne=$p\n";
// aanmaken van een tweede persoon
try {
  $p2 = new Personne("Laure", "Adeline", 67);
} catch (\Exception $e) {
  print "erreur : " . $e->getMessage();
  exit;
}
// initialisatie van de eerste met de waarden van de tweede
try {
  $p->initWithPersonne($p2);
} catch (\Exception $e) {
  print "erreur : " . $e->getMessage();
  exit;
}

// controle
print "personne=$p\n";
// einde
exit;
  • regels 14, 23, 30: het komt vaak voor dat na een uitzondering de uitvoering van een consolescript moet worden gestopt als de opgetreden fout onherstelbaar is. Dit is niet het geval in een webscript: de uitvoering van het script wordt niet gestopt, maar er wordt een foutpagina weergegeven. Als men zich in een functie bevindt, wordt niet de instructie exit gebruikt, maar return: de uitvoering van het script wordt niet gestopt (exit), maar men verlaat de functie (return) nadat een fout is gegenereerd;

Resultaten:

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

5.8. Een tabel met objecten [Personne]

Het volgende voorbeeld [classes-07.php] laat zien dat er objectenlijsten kunnen bestaan.


<?php

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

// test
// aanmaken van een array met Personne-objecten
// om de code begrijpelijker te maken, wordt er geen rekening gehouden met een eventuele uitzondering
$groupe = [new Personne("Paul", "Langevin", 48), new Personne("Sylvie", "Lefur", 70)];

// identiteit van deze personen
for ($i = 0; $i < count($groupe); $i++) {
  print "groupe[$i]=$groupe[$i]\n";
}

// einde
exit;

Resultaten:

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

Opmerkingen

  • regel 9: aanmaken van een array met 2 personen;
  • regel 12: doorlopen van de tabel;
  • regel 13: $groupe[$i] is een object van het type Personne. De methode [Personne.__toString] wordt gebruikt om het weer te geven;

5.9. Een klasse aanmaken die is afgeleid van de klasse Personne

In een bestand met de naam [Enseignant.php] maken we de volgende klasse [Enseignant] aan:


<?php

// strikte naleving van de gedeclareerde typen van de functieparameters
declare (strict_types=1);

// naamruimte
namespace Exemples;

// een van de klasse ‘persoon’ afgeleide klasse
class Enseignant extends Personne {
  // attributen
  private $discipline;   // onderwezen vak

  // getter en setter

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

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

  // constructor
  public function __construct(string $prénom, string $nom, int $âge, string $discipline) {
    // bovenliggende attributen
    parent::__construct($prénom, $nom, $âge);
    // andere attributen
    $this->setDiscipline($discipline);
  }

  // overbelasting van de functie __toString van de bovenliggende klasse
  public function __toString(): string {
    return "[" . parent::__toString() . ",$this->discipline]";
  }

}

Opmerkingen

  • regel 7: de klasse [Enseignant] maakt ook deel uit van de naamruimte [Exemples];
  • regel 10: de klasse Enseignant is afgeleid (extends) van de klasse Personne.. De afgeleide klasse Enseignant erft de attributen en methoden van haar bovenliggende klasse;
  • regel 12: de klasse Enseignant voegt een nieuw, eigen attribuut discipline toe;
  • regel 25: de constructor van de klasse Enseignant ontvangt 4 parameters:
    • 3 om de bovenliggende klasse te initialiseren (voornaam, achternaam, leeftijd), regel 27;
    • 1 voor de eigen initialisatie (vak), regel 29;
  • regel 27: de afgeleide klasse heeft toegang tot de methoden en constructors van de bovenliggende klasse via het sleutelwoord parent ::. Hier worden de parameters (voornaam, achternaam, leeftijd) doorgegeven aan de constructor van de bovenliggende klasse;
  • regels 33-35: de methode __toString van de afgeleide klasse maakt gebruik van de methode __toString van de bovenliggende klasse;

De klasse [Enseignant] wordt gebruikt door het volgende script [classes-08.php]:


<?php

// opname van de definitie van beide klassen
require_once __DIR__."/Personne.php";
require_once __DIR__."/Enseignant.php";
// verklaring van de twee gebruikte klassen
use \Exemples\Personne;
use \Exemples\Enseignant;

// test
// aanmaken van een array met Personne-objecten en afgeleide objecten
// om het voorbeeld eenvoudig te houden, worden uitzonderingen niet afgehandeld
$groupe = array(new Enseignant("Paul", "Langevin", 48, "anglais"), new Personne("Sylvie", "Lefur", 70));

// identiteit van deze personen
for ($i = 0; $i < count($groupe); $i++) {
  print "groupe[$i]=$groupe[$i]\n";
}
// einde
exit;

Opmerkingen

  • regels 4-5: we moeten de interpreter PHP aangeven waar de twee klassen [Enseignant, Personne] zich bevinden;
  • regels 7-8: declaratie van de volledige namen van de twee klassen. Hierdoor kunnen we ze in de code eenvoudig aanduiden met hun naam, zonder het achtervoegsel van hun naamruimten;
  • regel 13: we maken een array aan met een type [Personne] en een type [Enseignant];
  • regels 16-18: geven de elementen van de array weer;
  • regel 17: de methode __toString van elk element $groupe[$i] wordt aangeroepen. De klasse Personne heeft een methode __toString. De klasse Enseignant heeft er twee: die van de bovenliggende klasse en die van zichzelf. Je kunt je afvragen welke er wordt aangeroepen. Uit de uitvoering blijkt dat die van de klasse Enseignant is aangeroepen. Dit is altijd het geval: wanneer een methode op een object wordt aangeroepen, wordt deze in de volgende volgorde gezocht: in het object zelf, in de bovenliggende klasse als die er is, vervolgens in de bovenliggende klasse van de bovenliggende klasse, enzovoort … De zoektocht stopt zodra de methode is gevonden.

Resultaten:

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

5.10. Een tweede klasse aanmaken die is afgeleid van de klasse Personne

Het volgende voorbeeld maakt een klasse Etudiant aan, afgeleid van de klasse Personne, in het bestand [Etudiant.php]:


<?php

// strikte naleving van de gedeclareerde typen van de functieparameters
declare (strict_types=1);

// naamruimte
namespace Exemples;

class Etudiant extends Personne {
  // attributen
  private $formation;   // gevolgde opleiding

  // getter en setter
  public function getFormation(): string {
    return $this->formation;
  }

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

  // constructor
  public function __construct(string $prénom, string $nom, int $âge, string $formation) {
    // bovenliggende attributen
    parent::__construct($prénom, $nom, $âge);
    // andere attributen
    $this->setFormation($formation);
  }

  // overbelasting van de functie __toString van de bovenliggende klasse
  public function __toString(): string {
    return "[" . parent::__toString() . ",$this->formation]]";
  }

}

Deze klasse wordt gebruikt door het volgende script [classes-09.php]:


<?php

// opname en definitie van de klassen die door het script worden gebruikt
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
// aanmaken van een array met Person-objecten en afgeleide objecten
// om het voorbeeld begrijpelijker te maken, worden uitzonderingen niet afgehandeld
$groupe = array(new Enseignant("Paul", "Langevin", 48, "anglais"), new Personne("Sylvie", "Lefur", 70), new Etudiant("Steve", "Boer", 23, "iup2 qualité"));

// identiteit van deze personen
for ($i = 0; $i < count($groupe); $i++) {
  print "groupe[$i]=$groupe[$i]\n";
}
// einde
exit;

Resultaten:


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

5.11. Relatie tussen de constructor van een afgeleide klasse en die van de bovenliggende klasse

In sommige objectgeoriënteerde talen roept de constructor van een afgeleide klasse automatisch die van de bovenliggende klasse aan. De volgende code [classes-16.php] laat zien dat dit bij PHP 7 niet het geval is:


<?php

class Classe1 {

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

}

class Classe2 extends Classe1 {

  // constructor
  public function __construct() {
    // de constructor van de bovenliggende klasse wordt niet impliciet aangeroepen
    print "constructeur de la classe Classe2\n";
  }

}

class Classe3 extends Classe1 {

  // constructor
  public function __construct() {
    // expliciete aanroep van de constructor van de bovenliggende klasse
    parent::__construct();
    // specifieke code voor Klasse3
    print "constructeur de la classe Classe3\n";
  }

}

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

Resultaten

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

5.12. Een methode van de bovenliggende klasse herdefiniëren

We hebben al gezien dat een methode van de bovenliggende klasse in een onderliggende klasse opnieuw gedefinieerd kan worden. Zo is de methode [__toString] van de klasse [Personne] (zie link) opnieuw gedefinieerd in de onderliggende klassen [Enseignant] (zie link) en [Etudiant] (zie link). Het script [classes-13.php] illustreert het concept nogmaals:


<?php

// strikte naleving van het type van de functieparameters
declare(strict_types=1);

// hoofdklasse
class Classe1 {

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

  function g(): int {
    return 2;
  }

}

// afgeleide klasse
class Classe2 extends Classe1 {

  // de functie f van de bovenliggende klasse wordt opnieuw gedefinieerd
  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";

Opmerkingen

  • regels 7-17: de klasse [Classe1] definieert twee methoden f en g;
  • regels 20-27: de klasse [Classe2] is een uitbreiding van de klasse [Classe1] en herdefinieert de methode f daarvan;

Resultaten

1
2
3
11
2
1

Opmerkingen

  • op regel 30 van de code wordt een object $c2 van het type [Classe2] aangemaakt;
  • op regel 31 van de code wordt de methode f van het object $c2 aangeroepen. Aangezien deze methode bestaat, wordt deze uitgevoerd;
  • regel 32 van de code roept de methode g van het object $c2 aan. Aangezien deze methode niet bestaat, wordt ernaar gezocht in de bovenliggende klasse, waar deze wordt gevonden en uitgevoerd;
  • regel 33 van de code maakt een object $c1 van het type [Classe1] aan;
  • regel 34 van de code roept de methode f van het object $c1 aan. Aangezien deze methode wel bestaat, wordt deze uitgevoerd;

5.13. Een object doorgeven als parameter aan een functie

Laten we het volgende script [classes-14.php] eens bekijken:


<?php

// strikte naleving van het type van de functieparameters
declare(strict_types=1);

// hoofdklasse
class Classe1 {

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

  function g(): int {
    return 2;
  }

}

// afgeleide klasse
class Classe2 extends Classe1 {

  // de functie f van de bovenliggende klasse wordt opnieuw gedefinieerd
  public function f(): int {
    return parent::f() + 10;
  }

}

// de parameter van de functie is van het type Klasse1 of een afgeleide
function doSomething(Classe1 $c1): void {
  print $c1->f() + $c1->g() . "\n";
}

// code
// er wordt een object van het type Klasse2 aangemaakt, afgeleid van Klasse1
$c2 = new Classe2();
// doSomething wordt aangeroepen met
doSomething($c2);

Opmerkingen

  • regels 7-17: de klasse [Classe1];
  • regels 20-27: een klasse [Classe2] afgeleid van [Classe1];
  • regel 30: een functie die een parameter van het type [Classe1] verwacht. Wanneer het verwachte type een klasse is, kan de daadwerkelijke parameter een object zijn van het verwachte type of een afgeleid type;
  • regels 35-38: de functie [doSomething] wordt aangeroepen met een parameter van het type [Classe2], terwijl het type [Classe1] wordt verwacht;

Resultaten

13

5.14. Abstracte klassen

Een abstracte klasse is een onvolledige klasse die niet kan worden geïnstantieerd. Deze moet verplicht worden afgeleid om bruikbaar te zijn.

Waarvoor dient een abstracte klasse? Soms zijn er klassen die één of meerdere methoden delen, maar die zich onderscheiden door andere methoden of andere attributen. Het is dan wenselijk om alles wat gemeenschappelijk is samen te brengen in een bovenliggende klasse. Op dit moment hebben we geen abstracte klasse nodig. Maar stel dat de dochterklassen alleen verschillen in één methode M: de signatuur van de methode zou in alle dochterklassen hetzelfde zijn, maar de implementatie ervan zou verschillen. Om de dochterklassen te verplichten de methode M te implementeren:

  • gaan we de signatuur van methode M in de bovenliggende klasse declareren. Aangezien deze niet weet hoe ze deze moet implementeren, voorzien we de methode van het sleutelwoord abstract: dit betekent dat de implementatie van methode M wordt overgelaten aan de onderliggende klassen;
  • omdat de bovenliggende klasse niet volledig is geïmplementeerd, wordt deze ook als abstract gedeclareerd met hetzelfde trefwoord `abstract`. Hierdoor kan de klasse niet meer worden geïnstantieerd. Er moet verplicht een onderliggende klasse worden aangemaakt die de implementatie van methode M definieert, zodat de body van de bovenliggende klasse bruikbaar is;

Hier volgt een voorbeeld [classes-15.php]:


<?php

// strikte naleving van het type van de functieparameters
declare(strict_types=1);

// abstract hoofdklasse
abstract Class Classe1 {

  // methode die bekend is bij alle afgeleide klassen
  public function f(): int {
    return 1;
  }

  // abstracta methode g – wordt gedefinieerd door de afgeleide klassen
  abstract function g(): int;
}

// afgeleide klasse
Class Classe2 extends Classe1 {

  // de methode g van de bovenliggende klasse moet worden gedefinieerd
  public function g(): int {
    return parent::f() + 10;
  }

}

// afgeleide klasse
Class Classe3 extends Classe1 {

  // de methode g van de bovenliggende klasse moet worden gedefinieerd
  public function g(): int {
    return parent::f() + 20;
  }

  // de methode f van de bovenliggende klasse kan opnieuw worden gedefinieerd
  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";

Opmerkingen

  • regels 7-16: de klasse [Classe1] is abstract (regel 7) omdat ze de methode g op regel 15 niet kan implementeren. Ze moet dus verplicht worden afgeleid om bruikbaar te zijn;
  • regels 19-26: de klasse [Classe2] is een uitbreiding van de klasse [Classe1] en herdefinieert de methode g van haar bovenliggende klasse (regels 22-24);
  • regels 29-41: de klasse [Classe3] is een uitbreiding van de klasse [Classe1] en herdefinieert de methode g van de bovenliggende klasse (regels 32-34);
  • regels 37-39: de klasse [Classe3] herdefinieert de methode f van de bovenliggende klasse;
  • regels 44-49: er worden twee objecten van het type [Classe2] en [Classe3] aangemaakt en hun methoden f en g worden aangeroepen;

Resultaten

1
2
3
4
1
11
2
21

5.15. Finale klassen

Een finale klasse is een klasse waarvan geen afgeleide klassen kunnen worden gemaakt. Laten we het volgende script [classes-11.php] eens bekijken:


<?php

// naamruimte
namespace Exemples;

// niet-afleidbare klasse
final Class Classe1 {
  
}

// afgeleide klasse
Class Classe2 extends Classe1 {
  
}

// code – moet een fout veroorzaken
new Classe2();

Opmerkingen

  • regels 7-9: het sleutelwoord final maakt van de klasse [Classe1] een finale klasse waarvan geen afgeleide kan worden gemaakt;
  • regels 12-14: de klasse [Classe2] is een afgeleide van de finale klasse [Classe1], wat een fout is;
  • regel 17: de fout wordt pas gemeld bij het uitvoeren van het script, wanneer geprobeerd wordt een object van het type [Classe2] te bewerken;

Resultaten

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

Een finale methode is een methode die niet via afleiding kan worden geherdefinieerd. Hier volgt een voorbeeld: [classes-12.php]:


<?php

// strikte naleving van het type van de functieparameters
declare(strict_types=1);

// naamruimte
namespace Exemples;

// hoofdklasse
Class Classe1 {

  // deze methode kan niet opnieuw worden gedefinieerd in een afgeleide klasse
  public final function f(): int {
    return 1;
  }

}

// afgeleide klasse
Class Classe2 extends Classe1 {

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

}

// code - moet een fout veroorzaken
new Classe2();

Opmerkingen

  • regel 13: de methode f van de klasse [Classe1] wordt met het sleutelwoord final als final gedeclareerd;
  • regel 20: de klasse [Classe2] is een uitbreiding van de klasse [Classe1];
  • regels 22-23: de functie f van de bovenliggende klasse [Classe1] wordt opnieuw gedefinieerd. Dit moet een fout veroorzaken;
  • regel 29: er wordt een object van het type [Classe2] aangemaakt om de interpreter PHP te dwingen de klasse [Classe2] te inspecteren;

Resultaten

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 en attributen

Een statische methode is een methode die gekoppeld is aan de klasse waarin deze is gedefinieerd en niet aan de objecten die instanties van de klasse zijn. Als de klasse C dus een statische methode M declareert, schrijft men om deze te gebruiken:

  • C::M als men zich buiten de klasse bevindt;
  • self::M als men zich binnen de klasse bevindt;

Hier volgt een voorbeeld [classes-17.php]:


<?php

class Classe1 {

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

}

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

Opmerkingen

  • regel 6: de methode [say] wordt als statisch gedeclareerd met het sleutelwoord static;
  • regel 13: aanroep van de statische methode [say] met de notatie: Klasse1::say;

Resultaten

hello

Laten we nu eens kijken naar de volgende code [classes-18.php]:


<?php

class Classe1 {
  // statisch attribuut
  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();

Opmerkingen

  • regel 5: er wordt een statisch attribuut gedeclareerd dat het aantal aangemaakte instanties van de klasse [Classe1] gaat tellen. Dit is geen attribuut dat bij een instantie van de klasse kan horen. Als er namelijk twee objecten O1 en O2 worden aangemaakt, is geen van beide op de hoogte van het bestaan van de ander. Het heeft geen zin om een teller in de instantie te hebben: wanneer er een nieuw object wordt aangemaakt, in welke instantie moet de teller dan worden verhoogd? We zouden dan gedwongen zijn de teller van een specifiek object te verhogen, terwijl de tellers van de andere instanties buiten beschouwing blijven. Een statisch attribuut is een attribuut van de klasse en niet van een instantie van de klasse;
  • regels 7-10: het is in de constructor dat we de aangemaakte objecten gaan tellen, aangezien het aanmaken van elk nieuw object de uitvoering van de constructor veroorzaakt;
  • regel 14: let op de notatie self::$nbObjects om aan te geven dat er wordt verwezen naar een statisch attribuut van de klasse waarin de uitgevoerde code zich bevindt;
  • regels 13-15: de statische methode [say] heeft als taak het aantal aangemaakte objecten weer te geven;
  • regels 20-22: er worden twee objecten aangemaakt en de objectteller wordt weergegeven;

Resultaten


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

5.18. Zichtbaarheid tussen de bovenliggende klasse en de onderliggende klasse

Laten we het volgende script [classes-19.php] eens bekijken:


<?php

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

  // methode
  public function doTest(): void {
    // wie roept dit aan?
    print "parent :\n";
    var_dump($this);
    // bovenliggende weergave
    print "parent : attributeOfParent={$this->attributeOfParent}\n";
    print "parent : attributeOfChild={$this->attributeOfChild}\n";
  }

}

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

  // methode
  public function doTest(): void {
    // onderliggende weergave
    print "child : attributeOfParent={$this->attributeOfParent}\n";
    print "child : attributeOfChild={$this->attributeOfChild}\n";

    // bovenliggend
    parent::doTest();
  }
}

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

Opmerkingen

  • regels 3-17: de klasse [SomeParent];
  • regels 19-32: de dochterklasse [SomeChild]. We zien dat deze de klasse [SomeParent] uitbreidt (regel 19);
  • regel 5: de klasse [SomeParent] heeft slechts één attribuut;
  • regels 8-17: de methode [SomeParent::doTest] is bedoeld om twee attributen weer te geven:
    • [$attributeOfParent], dat behoort tot de klasse [SomeParent];
    • [$attributeOfChild], dat behoort tot de klasse [SomeChild] (regel 21);
  • regels 10-11: de identiteit van de aanroeper wordt weergegeven: de methode wordt namelijk op twee verschillende manieren aangeroepen:
    • vanuit de bovenliggende klasse [SomeParent];
    • vanuit de dochterklasse [SomeChild];
  • regels 13-14: weergave van de twee attributen;
  • regels 19-32: de dochterklasse [SomeChild] die de klasse [SomeParent] uitbreidt (regel 19);
  • regel 21: de klasse [SomeChild] heeft slechts één attribuut;
  • regel 24: de methode [SomeChild::doTest] is bedoeld om twee attributen weer te geven:
    • [$attributeOfParent], dat behoort tot de klasse [SomeParent];
    • [$attributeOfChild], dat tot de klasse [SomeChild] behoort;
  • regels 26-27: weergave van de twee attributen;
  • regel 30: aanroep van de methode [doTest] van de bovenliggende klasse, die op haar beurt de twee attributen weergeeft;
  • regel 36: de methode [SomeParent::doTest] wordt aangeroepen;
  • regel 38: de methode [SomeChild::doTest] wordt aangeroepen;

In de eerste test is de zichtbaarheid van beide attributen [private]. We kunnen dus verwachten dat de dochterklasse het attribuut van haar bovenliggende klasse niet ziet. Dit attribuut zou ten minste de zichtbaarheid [protected] moeten hebben. Maar hoe zit het met het attribuut van de dochterklasse? Is dit zichtbaar in de bovenliggende klasse?

Dit zijn de resultaten van deze eerste test:

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

Opmerkingen

  • regels 1-10: resultaten van de eerste test waarin de methode [SomeParent::doTest] wordt aangeroepen;
  • regels 3-6: we zien dat het object dat de methode aanroept van het type [SomeParent] is;
  • regel 7: weergave van het attribuut [$attributeOfParent];
  • regels 9-10: we zien dat het attribuut [SomeParent::$attributeOfChild] niet bestaat. Het wordt dus niet weergegeven;
  • regels 11-30: resultaten van de tweede test, waarbij de methode [SomeChild::doTest] wordt aangeroepen;
  • regels 13-14: we zien dat het attribuut [SomeChild::$attributeOfParent] niet bestaat. Het wordt dus niet weergegeven. Dit is normaal: het attribuut [SomeParent::$attributeOfParent] is [private] en dus niet bekend in de dochterklasse;
  • regel 15: weergave van het attribuut [$attributeOfChild];
  • regels 16-30: we bevinden ons in de methode [SomeParent::doTest] die door de dochterklasse wordt aangeroepen;
  • regels 17-22: we zien dat [$this] van het type [SomeChild] is met twee privé-attributen;
  • regel 23: heel verrassend is dat [$this] van het type [SomeChild] hier het attribuut van de bovenliggende klasse [$attributeOfParent] ziet;
  • regels 25-30: eveneens verrassend genoeg ziet [$this] van het type [SomeChild] zijn attribuut [$attributeOfChild] niet;

Dit resultaat is zeer verrassend: hoewel de regels 17-21 aangeven dat [$this] van het type [SomeChild] is, gedraagt deze [$this] binnen de methode [SomeParent::doTest] zich alsof het een instantie is van de klasse [SomeParent] en niet van de klasse [SomeChild].

Laten we een nieuwe test uitvoeren met het script [classes-20.php]. Het attribuut [$attributeOfParent] heeft nu de zichtbaarheid [protected] (regel 5):


<?php

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

  // methode
  public function doTest(): void {
    // wie roept het aan?
    print "parent :\n";
    var_dump($this);
    // bovenliggende weergave
    print "parent : attributeOfParent={$this->attributeOfParent}\n";
    print "parent : attributeOfChild={$this->attributeOfChild}\n";
  }

}

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

  // methode
  public function doTest(): void {
    // onderliggende weergave
    print "child : attributeOfParent={$this->attributeOfParent}\n";
    print "child : attributeOfChild={$this->attributeOfChild}\n";

    // bovenliggend
    parent::doTest();
  }

}

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

Resultaten

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

Opmerkingen

  • regel 12: de klasse [SomeChild] heeft nu zicht op het attribuut van zijn bovenliggende klasse [$attributeOfParent]. Dit is normaal, aangezien deze nu het bereik [protected] heeft;
  • in de methode [someParent::doTest] is het object [$this] van het type [SomeChild] (regels 15-20). Het ziet het attribuut van zijn bovenliggende object [$attributeOfparent] (regel 21), maar nog steeds niet zijn eigen attribuut [$attributeOfChild] (regels 23-28);

Bij de derde poging heeft het attribuut [$attributeOfChild] eveneens een bereik [protected]:


<?php

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

  // methode
  public function doTest(): void {
    // wie roept het aan?
    print "parent :\n";
    var_dump($this);
    // bovenliggende weergave
    print "parent : attributeOfParent={$this->attributeOfParent}\n";
    print "parent : attributeOfChild={$this->attributeOfChild}\n";
  }

}

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

  // methode
  public function doTest(): void {
    // onderliggende weergave
    print "child : attributeOfParent={$this->attributeOfParent}\n";
    print "child : attributeOfChild={$this->attributeOfChild}\n";

    // bovenliggend
    parent::doTest();
  }

}

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

De resultaten van de uitvoering zijn als volgt:

---------------------------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
  • regel 22: deze keer, binnen het bovenliggende element, [$this] van het type [SomeChild] (regels 15-20) het beschermde attribuut [$attributeOfChild] van zijn eigen klasse [SomeChild].

Wat kunnen we uit deze tests concluderen?

  • dat de [$this]-instantie van een bovenliggende klasse, die in een methode van de bovenliggende klasse wordt gebruikt, het volgende ziet:
    • de attributen en methoden van de bovenliggende klasse, ongeacht hun zichtbaarheid;
    • niets ziet van de attributen en methoden van zijn onderliggende klassen;

Dit is het verwachte gedrag.

  • dat de [$this]-instantie van een dochterklasse, die wordt gebruikt in een methode van de dochterklasse, het volgende ziet:
    • de attributen en methoden van de bovenliggende klasse, mits deze ten minste de zichtbaarheid [protected] hebben. Degenen met de zichtbaarheid [private] worden niet gezien;
    • de attributen en methoden van de onderliggende klasse, ongeacht hun zichtbaarheid;

Dit is het verwachte gedrag.

  • dat de [$this]-instantie van een dochterklasse, die in een methode van de bovenliggende klasse wordt gebruikt, het volgende ziet:
    • de attributen en methoden van de bovenliggende klasse, ongeacht hun zichtbaarheid;
    • de attributen en methoden van zijn eigen klasse, maar alleen als deze ten minste de zichtbaarheid [protected] hebben. Degenen met de zichtbaarheid [private] worden niet gezien;

Dit is onverwacht gedrag.

5.19. jSON-codering van een klasse

In een klasse is de methode [__toString] vaak aanwezig: deze is bedoeld om een tekenreeks terug te geven die het object vertegenwoordigt dat de methode aanroept. Het kan verleidelijk zijn om deze tekenreeks een jSON-tekenreeks te laten zijn. We gaan dit nu onderzoeken.

Image

We gebruiken de volgende klasse [Personne]:


<?php

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

  // globale setter
  public function setFromArray(array $arrayOfAttributes): Personne {
    // initialisatie van bepaalde attributen van de klasse
    foreach ($arrayOfAttributes as $attribute => $value) {
      $this->$attribute = $value;
    }
    // het object wordt teruggegeven
    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 {
    // het object wordt geïdentificeerd
    var_dump($this);
    // de attributen worden opgehaald
    $attributes = \get_object_vars($this);
    var_dump($attributes);
    // de tekenreeks jSON van de attributen wordt geretourneerd
    return \json_encode($attributes, JSON_UNESCAPED_UNICODE);
  }

}

Opmerkingen

  • regels 5-8: de vier attributen van de klasse;
  • regels 20-35: de getters waarmee de waarde van deze attributen kan worden opgehaald;
  • regels 11-18: een globale setter waarmee de attributen kunnen worden geïnitialiseerd op basis van een associatieve array [$arrayOfAttributes] waarvan de sleutels overeenkomen met de attributen van de klasse;
  • regels 38-46: de methode [__toString] van de klasse;
  • regel 42: de functie PHP [get_object_vars] maakt het mogelijk om de waarden van de attributen van de klasse op te halen in de vorm van een associatieve array [‘naam’=>’naam1’, ‘voornaam’=>’voornaam1’, ‘leeftijd’=>leeftijd1, ‘kinderen’=>[]];
  • regel 45: de tekenreeks jSON van deze attributentabel wordt weergegeven;

Laten we het script [json-01.php] eens bekijken, dat gebruikmaakt van de klasse [Personne]:


<?php

// klasse Persoon
require "Personne.php";

// instantie van de vader
$père = new Personne();
// initialisatie van de vader
$père->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Dieudonné",
  "âge" => 58
]);
// instantie en initialisatie van kind1
$enfant1 = (new Personne())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Sylvain",
  "âge" => 17
  ]);
// instantie en initialisatie van kind2
$enfant2 = (new Personne())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Géraldine",
  "âge" => 12
  ]);
// initialisatie van de kinderen van de vader
$père->setFromArray([
  "enfants" => [$enfant1, $enfant2]
]);

// weergave van de elementen van de vader
$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";

Opmerkingen

  • regels 6-13: we initialiseren een object [Personne] [$père] met de methode [Personne::setFromArray], waarmeeeen object [Personne] te initialiseren met een array waarvan de sleutels identiek zijn aan de attributen van de klasse [Personne];
  • regels 14-19: er wordt op dezelfde manier een object [Personne] [$enfant1] geïnitialiseerd;
  • regels 21-25: we initialiseren een object [Personne] [$enfant2];
  • regels 27-29: het attribuut [$père→enfants] wordt geïnitialiseerd met een array van de twee kinderen;
  • regels 32-33: de twee kinderen worden aan de vader toegewezen;
  • regel 35: de bewerking [print] probeert het object [$enfant1] om te zetten in een tekenreeks. Hiervoor gebruikt ze de methode [__toString] van het object. We verwachten dus de tekenreeks jSON van het object te zien;
  • regels 38-39: we doen hetzelfde met het bovenliggende object;

De resultaten zijn als volgt:


------------------------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":[{},{}]}

Opmerkingen

  • regels 2-11: het object [$enfant1];
  • regels 12-21: de tabel met attributen van het object [$enfant1]. We hebben ze allemaal;
  • regel 22: we hebben inderdaad de tekenreeks jSON van het object [$enfant1];
  • regels 23-44: idem voor het object [$enfant2];
  • regels 45-112: voor de vader is het iets anders, aangezien zijn attribuut [enfants] niet NULL is, zoals bij de kinderen het geval was;
  • regel 112: we zien dat in de tekenreeks jSON van de vader de kinderen ontbreken;
  • regels 79-111: we zien dat in de attributentabel van de vader kind 1 (regels 89-98) een object is gebleven, net als kind 2 (regels 99-110). Kortom, de uitdrukking [\get_object_vars($this)], waarbij [$this] de vader vertegenwoordigt, is niet recursief: als een attribuut van de klasse [Personne] zelf een object is, probeert de uitdrukking [\get_object_vars($this)] niet de attribuutlijst ervan op te halen;

We kunnen dit verbeteren. We wijzigen de klasse [Personne] in de volgende klasse [Personne2]:


<?php

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

  // globale setter
  public function setFromArray(array $arrayOfAttributes): Personne2 {

    // het object wordt teruggegeven
    return $this;
  }

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



  // __toString
  public function __toString(): string {
    // de attributen van het object worden opgehaald
    $attributes = $this->getAttributes($this);
    $enfants = $attributes["enfants"];
    if ($enfants != NULL) {
      $attributes["enfants"] = [$enfants[0]->getAttributes(), $enfants[1]->getAttributes()];
    }
    // de tekenreeks JSON van de attributen wordt weergegeven
    return \json_encode($attributes, JSON_UNESCAPED_UNICODE);
  }

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

}

Opmerkingen

  • regels 36-38: de functie [getAttributes] retourneert de attributenlijst van het object dat de functie aanroept;
  • regels 25-34: de functie [__toString];
  • regel 27: de attributen van de klasse [Personne] worden opgehaald in de array [$attributes];
  • regel 28: uit het vorige voorbeeld weten we dat [$attributes["enfants"]] een array is van twee objecten van het type [Personne];
  • regels 29-31: de twee objecten worden vervangen door hun attributenarray;
  • regel 33: nu hoeft alleen nog maar de opgebouwde array van attributen te worden gecodeerd in jSON;

Het script [json-02.php] maakt als volgt gebruik van de klasse [Personne2]:


<?php

// klasse Personne2
require "Personne2.php";

// instantiëring van de bovenliggende klasse
$père = new Personne2();
// initialisatie
$père->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Dieudonné",
  "âge" => 58
]);
// instantie en initialisatie van kind1
$enfant1 = (new Personne2())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Sylvain",
  "âge" => 17
  ]);
// instantie en initialisatie kind2
$enfant2 = (new Personne2())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Géraldine",
  "âge" => 12
  ]);
// initialisatie van de kinderen van de vader
$père->setFromArray([
  "enfants" => [$enfant1, $enfant2]
]);

// weergave van de vader
print "------------------------père\n";
print "père=$père\n";

Het script [json-02.php] is identiek aan het script [json-01.php], met dien verstande dat de klasse [Personne2] de klasse [Personne] heeft vervangen.

De resultaten van de uitvoering zijn als volgt:

------------------------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}]}

Deze keer zijn de kinderen inderdaad samen met de vader weergegeven.

De vorige oplossing is niet bevredigend, omdat de kinderen zelf ook kinderen kunnen hebben. Dan doen we weer te maken met het vorige probleem.

De klasse [Personne3] lost dit probleem als volgt op:


<?php

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

  // globale setter
  public function setFromArray(array $arrayOfAttributes): Personne3 {

  }

  // getters


  // __toString
  public function __toString(): string {
    // de reeks JSON van de attributen wordt weergegeven
    $attributes = [];
    $this->getRecursiveAttributes($attributes, $this, []);
    // de tekenreeks JSON van de attributen
    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 van de waarde [$value]
    // $keys is een array [key1, key2, .., keyn]
    // $value=$attributesQZXW2HTMLBW2tleTFdZQXQZXW2HTMLBW2tleTJdZQX….[keyn]
    // als [$value] een object is, gebruiken we de methode [getAttributes]
    if (\is_object($value)) {
      // attributen van het object [$value]
      $objectAttributes = $value->getAttributes();
      // wat doen we met het resultaat?
      if ($keys) {
        // in [$attributes] gaan we $value vervangen door de array van zijn attributen
        // we moeten het element $attributesQZXW2HTMLBW2tleTFdZQXQZXW2HTMLBW2tleTJdZQX…[keyn] samenstellen
        // waarbij $keys de array is [key1, key2, .., keyn]
        // neemt men de referentie van de tabel [$attributes]
        $attribute = &$attributes;
        // we scannen de sleuteltabel
        foreach ($keys as $key) {
          // we halen de referentie van het attribuut op
          $attribute = &$attribute[$key];
        }
        // hier zijn $attribut en $attributesQZXW2HTMLBW2tleTFdZQXQZXW2HTMLBW2tleTJdZQX…[key(n)] identiek
        // ze delen dezelfde geheugenlocatie
        // het object [$value] wordt vervangen door zijn attribuutarray;
        // er moet $attributesQZXW2HTMLBW2tleTFdZQXQZXW2HTMLBW2tleTJdZQX… worden geschreven[keyn]=$objectAttributes
        // wat gelijk staat aan $attribute = $objectAttributes
        $attribute = $objectAttributes;
      } else {
        // geen sleutels – we staan aan het begin van de verkenning van het object
        // $objectAttributes vertegenwoordigt de attributen van het eerste niveau van de klasse
        $attributes += $objectAttributes;
      }
      // misschien zitten er in [$objectAttributes] nog objecten
      // we onderzoeken de attributen van [$objectAttributes]
      $this->getRecursiveAttributes($attributes, $objectAttributes, $keys);
    } else {
      if (\is_array($value)) {
        // we hebben een array – we analyseren elk van de elementen
        foreach ($value as $key => $élément) {
          // we voegen de huidige sleutel toe aan de array $keys
          \array_push($keys, $key);
          // we analyseren $élément
          $this->getRecursiveAttributes($attributes, $élément, $keys);
          // we verwijderen de zojuist geanalyseerde sleutel uit de array $keys
          \array_pop($keys);
        }
      }
    }
  }

Opmerkingen

  • regels 21-22: deze keer vraagt de methode [__toString] de attributen van haar klasse op en vraagt dat dit recursief gebeurt: als een attribuut een object of een array van objecten is, dan moet elk object worden vervangen door zijn array van attributen in de uiteindelijke array van attributen van de klasse;
  • regels 31-78: de functie [getRecursiveAttributes] voert deze taak uit. De code is van commentaar voorzien. Het schrijven van een recursieve functie is vaak een complexe aangelegenheid. Dat is hier het geval. De lezer mist niets als hij het niet begrijpt. Er bestaan bibliotheken die dit werk voor je doen. De recursieve aanroep vindt plaats op de regels 64 en 72;
  • het voordeel van deze code is dat deze niet uitsluitend voor de klasse [Personne3] is geschreven. Hij is bruikbaar voor elke klasse met attributen waarvan de waarden verschillende objecttypen zijn, zolang de klassen die door de hoofdklasse worden gebruikt, net als deze zelf de methode [getAttributes] uit de regels 27-29 hebben

Het script [json-03.php] gebruikt de klasse [Personne3] als volgt:


<?php

// klasse Personne3
require "Personne3.php";

// instantie van de vader
$père = new Personne3();
// initialisatie
$père->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Dieudonné",
  "âge" => 58
]);
// instantie en initialisatie van kind1
$enfant1 = (new Personne3())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Sylvain",
  "âge" => 27
  ]);
// instantie en initialisatie van kind2
$enfant2 = (new Personne3())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Géraldine",
  "âge" => 12
  ]);
// initialisatie van de kinderen van de vader
$père->setFromArray([
  "enfants" => [$enfant1, $enfant2]
]);
// instantie en initialisatie kind11
$enfant11 = (new Personne3())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Gaëtan",
  "âge" => 2
  ]);
// instantie en initialisatie van kind12
$enfant12 = (new Personne3())->setFromArray([
  "nom" => "Bertholomé",
  "prénom" => "Mathilde",
  "âge" => 1
  ]);
// initialisatie van de kinderen van kind1
$enfant1->setFromArray([
  "enfants" => [$enfant11, $enfant12]
]);
// weergave van de vader
print "------------------------père\n";
print "père=$père\n";
  • regels 30-45: er worden twee onderliggende klassen toegewezen aan [$enfant1];

De resultaten van de uitvoering zijn als volgt:

------------------------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}]}

Als we dit resultaat opmaken, krijgen we het volgende:


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
        }
    ]
}

We hebben inderdaad de tekenreeks jSON opgehaald van alle objecten [Personne] die het bovenliggende object vormen.