Skip to content

5. Classi

Vocabolario: una classe è un tipo PHP. Una variabile di questo tipo è chiamata oggetto. Un oggetto è un'istanza (istanza) di una classe.

5.1. La gerarchia degli script

Image

5.2. Qualsiasi variabile può diventare un oggetto con attributi

Lo script [classes-01.php] è il seguente:


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

Risultati:


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

Commenti

  • riga 6: la notazione $obj->attr si riferisce all'attributo attr della variabile $obj. Se non esiste, viene creato, rendendo così la variabile $obj un oggetto con attributi. Abbiamo visto che PHP crea quindi un oggetto stdClass per impostazione predefinita;
  • riga 16: l'espressione $obj2=$obj1, quando $obj1 è un oggetto, è una copia di oggetti per riferimento: $obj2 e $obj1 sono riferimenti (indirizzi) allo stesso oggetto. L'oggetto stesso può essere modificato tramite entrambi i riferimenti;
  • righe 23–27: mirano a mostrare che $obj1 e $obj2 sono due variabili diverse: non si trovano allo stesso indirizzo di memoria:
    • $obj2 = $obj1 copia il valore di $obj1 nella variabile $obj2 (passaggio 1 sopra). Il valore di $obj1 è l'indirizzo di un oggetto. Pertanto, $obj1 e $obj2 puntano allo stesso oggetto. Quando si manipola una variabile $obj che punta a un oggetto, PHP manipola l'oggetto a cui punta la variabile $obj. Secondo il diagramma sottostante, possiamo vedere che l'oggetto a cui si punta può essere modificato sia tramite $obj1 che tramite $obj2. Questo è ciò che mostrano le righe 4 e 5 dei risultati;

Image

  • riga 30: l'espressione $obj3=&$obj2 fa sì che $obj2 e $obj3 abbiano lo stesso indirizzo [1 sotto]. Potremmo dire che le due variabili sono alias per la stessa posizione di memoria. Entrambe puntano a un oggetto, l'Oggetto A qui sotto [2];
    • L'operazione $obj2=new stdClass() crea un nuovo oggetto, Object B [3 sotto], e l'indirizzo di questo nuovo oggetto viene assegnato alla variabile $obj2. Poiché $obj2 e $obj3 sono due alias per la stessa posizione di memoria, anche $obj3 punta al nuovo oggetto, Object B. Ciò è illustrato nelle righe 16–27 e 30–41 dei risultati;

Image

  • righe 52–54: mostrano che un oggetto può essere attraversato come un dizionario. Le chiavi del dizionario sono i nomi degli attributi e i valori del dizionario sono i valori di quegli stessi attributi;
  • riga 51: la funzione count può essere applicata a un oggetto (con un avviso) ma non restituisce, come ci si potrebbe aspettare, il numero di attributi. Pertanto, un oggetto presenta somiglianze con un dizionario ma non lo è;

5.3. Una classe Person senza attributi dichiarati

Lo script [classes-02.php] è il seguente:


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

Risultati:

personne=[Paul,Langevin,48]

Commenti

  • righe 3–13: definiscono una classe Person. Una classe è un modello utilizzato per creare oggetti. Contiene attributi e funzioni chiamate metodi. Gli attributi non devono essere dichiarati;
  • righe 8–11: il metodo identity visualizza i valori di tre attributi non dichiarati nella classe. La parola chiave $this si riferisce all'oggetto a cui viene applicato il metodo;
  • riga 17: creiamo un oggetto $p di tipo Person. La parola chiave new viene utilizzata per creare un nuovo oggetto. L'operazione restituisce un riferimento all'oggetto creato (ovvero un indirizzo). Sono possibili varie notazioni: new Person(), new Person, new person. Il nome della classe non fa distinzione tra maiuscole e minuscole;
  • righe 18–20: i tre attributi richiesti dal metodo identity vengono creati nell'oggetto $p;
  • riga 22: il metodo identity della classe Person viene applicato all'oggetto $p. Nel codice (righe 8–11) del metodo identity, $this si riferisce allo stesso oggetto di $p;

5.4. La classe Person con attributi dichiarati

Lo script [classes-03.php] è il seguente:


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

Risultati:

personne=[Paul,Langevin,48]

Commenti

  • righe 6–8: gli attributi della classe sono dichiarati esplicitamente utilizzando la parola chiave var;

5.5. La classe Person con un costruttore

Gli esempi precedenti mostravano classi Person esotiche come quelle che si potevano trovare in PHP 4. Non è consigliabile seguire questi esempi. Presentiamo ora una classe Person [classes-04.php] che segue le best practice di PHP 7:


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

Risultati:

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

Commenti

  • righe 6–50: la classe Person;
  • righe 7-9: gli attributi privati della classe. Questi attributi sono visibili solo all'interno della classe. Altre parole chiave che possono essere utilizzate sono:
  • public: rende l'attributo pubblico, visibile dall'esterno della classe,
  • protected: rende l'attributo un attributo protetto visibile dall'interno della classe e delle sue classi derivate;
  • poiché gli attributi sono privati, non è possibile accedervi dall'esterno della classe. Pertanto, non è possibile scrivere il seguente codice:
$p=new Personne() ;
$p->nom="Landru" ;

Qui ci troviamo al di fuori della classe Person. Poiché l'attributo name è privato, la riga 2 è errata. Per inizializzare i campi privati dell'oggetto $p, ci sono due modi:

  • utilizzare i metodi pubblici set e get (i nomi di questi metodi possono essere qualsiasi cosa) nelle righe 12–34. Possiamo quindi scrivere:
$p=new Personne() ;
$p->setNom("Landru") ;
  • utilizzare il costruttore nelle righe 37–42. Scriveremmo quindi:
$p=new Personne("Michel","Landru",44) ;

Il codice sopra riportato chiama automaticamente il metodo della classe Person denominato __construct;

  • Riga 59: Questa riga visualizza la persona $p come stringa. Per farlo, viene utilizzato il metodo della classe Person denominato __toString (righe 45–47);
  • Tutti i metodi della classe (funzioni) sono stati preceduti dalla parola chiave public, che indica che la funzione è visibile all'esterno della classe. Le altre parole chiave che possono essere utilizzate sono, come per gli attributi e con lo stesso significato, private e protected. Senza un attributo di visibilità esplicito, la funzione ha una visibilità pubblica implicita;

5.6. La classe Person con controlli di validità nel costruttore

Il costruttore di una classe è il luogo ideale per verificare che i valori di inizializzazione dell'oggetto siano corretti. Tuttavia, un oggetto può essere inizializzato anche tramite i suoi metodi di impostazione o equivalenti. Per evitare di ripetere gli stessi controlli in due punti diversi, questi controlli possono essere inseriti all'interno dei metodi di impostazione. Se si riscontra che il valore di inizializzazione di un oggetto non è corretto, viene generata un'eccezione. Ecco un esempio.

Per prima cosa, spostiamo la definizione della classe Person in un file a sé stante [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]";
  }
 
}

Commenti

  • riga 4: specifica che il tipo dei parametri della funzione deve essere rispettato al momento della dichiarazione;
  • riga 7: definisce uno spazio dei nomi. Il nome completo (o qualificato) della classe Person è quindi \Examples\Person. Si noti il carattere \ all'inizio del nome qualificato: ciò determina un nome qualificato assoluto. Se questo carattere è assente, il nome è relativo (rispetto allo spazio dei nomi corrente). Pertanto, se due classi A e B fanno parte dello stesso spazio dei nomi E, nel codice della classe A è possibile accedere alla classe B utilizzando la notazione relativa B. Se la classe A fa parte dello spazio dei nomi E1 e B fa parte dello spazio dei nomi E2, nel codice di A si accederà a B utilizzando la notazione assoluta \E2\B. La definizione di una classe all'interno di uno spazio dei nomi non è obbligatoria, ma NetBeans emette un avviso se non lo si fa. Pertanto, lo faremo. Inoltre, gli spazi dei nomi dovrebbero corrispondere alla struttura della directory dei file. Pertanto, la classe A nello spazio dei nomi E1 dovrebbe trovarsi in un file denominato E1/A.php. Questo non è obbligatorio, ma, ancora una volta, NetBeans emette un avviso se non lo si fa. Nell'esempio della classe [\Exemples\Personne], NetBeans emette un avviso perché il percorso del file di [Personne.php] è [exemples/classes/Personne.php] e quindi non corrisponde allo spazio dei nomi. Non confondere il percorso del file con lo spazio dei nomi. Il nome completo di una classe utilizza uno spazio dei nomi e non ha nulla a che vedere con il percorso del file PHP della classe. La relazione tra percorso del file e spazio dei nomi è facoltativa e può essere ignorata, come abbiamo fatto qui;
  • righe 12–14: i tre attributi privati della classe;
  • righe 29–37: inizializzazione dell'attributo first_name e verifica del valore di inizializzazione;
  • riga 31: la funzione trim($string) rimuove gli spazi all'inizio e alla fine di $string. Pertanto, trim("abcd") è la stringa "abcd" e trim(" ") è la stringa vuota;
  • riga 32: se il nome è vuoto, viene generata un'eccezione (riga 33); altrimenti, il nome viene memorizzato (riga 35). Per generare un'eccezione, qui abbiamo utilizzato la classe predefinita [Exception]. In questo caso, è necessario utilizzare il suo nome assoluto [\Exception]. Se si utilizza il suo nome relativo [Exception], questa classe verrà cercata nello spazio dei nomi corrente, ovvero lo spazio dei nomi [Examples] della classe Person. Pertanto, l'interprete PHP cercherà una classe con il nome assoluto [\Examples\Exception], che non esiste;

La classe [Person] viene utilizzata dal seguente script [classes-05.php]:


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

Commenti

  • riga 7: lo script utilizzerà la classe [Person]. Dobbiamo quindi indicare all'interprete PHP dove può trovare la definizione di questa classe. Questo è il ruolo delle istruzioni [include file] e [require file]. Qui abbiamo utilizzato l'istruzione [include]. La differenza tra le due istruzioni è la seguente: se l'istruzione [include file] incontra degli errori durante il caricamento di [file], viene generato un errore [E_WARNING] ma l'esecuzione continua, mentre [require] nello stesso caso genera un errore fatale e l'esecuzione dello script si interrompe. Ciascuna di queste due istruzioni ha una variante: [include_once] e [require_once]. Queste due varianti consentono di gestire i casi in cui lo stesso file viene incluso più volte. Si consideri un progetto costituito da diversi script PHP, alcuni dei quali fanno riferimento alla classe [Person]. La loro esecuzione causerà l'inclusione del file [Person.php] più volte, provocando un errore poiché una classe non può essere definita due volte. La soluzione consiste nell'utilizzare le varianti [_once], che assicurano che il file venga incluso una sola volta nello script complessivo del progetto;
  • riga 7: la costante [__DIR__] è una costante PHP che fa riferimento al percorso completo della directory contenente lo script con la costante [__DIR__]. Pertanto, l'espressione alla riga 17:

require_once __DIR__."/Personne.php";

sarà equivalente a qualcosa del tipo:


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

Nel percorso del file, puoi usare sia / che \;

  • Riga 14: utilizziamo la classe [Person] che abbiamo appena definita. Lo script [classes-05.php] non ha spazi dei nomi. La riga 14 utilizza il nome relativo della classe [Person] senza namespace. Poiché la classe [Person] non ha un namespace, viene cercata all'interno dello script [classes-05.php] stesso e quindi non verrà trovata. Esistono due soluzioni a questo problema:
    • utilizzare il nome completo della classe [\Examples\Person];
    • utilizzare l'istruzione `use` alla riga 10. Ciò indica che il codice che segue utilizza la classe `[Examples\Person]`;
  • Riga 10: L'istruzione use fa sapere all'interprete che la classe [Person] a cui si fa riferimento alla riga 14 è in realtà la classe [\Examples\Person]. Detto questo, dove troverà l'interprete il codice per questa classe? Glielo dice la riga 7. Questa riga indica che per eseguire lo script corrente, deve essere caricato anche lo script [Person.php]. Qui viene utilizzato il nome relativo del file. Verrà quindi cercato nella cartella contenente lo script [classes-05.php]. Gli script [Person.php] e [classes-05.php] devono quindi trovarsi nella stessa cartella. È il caso qui, dove entrambi si trovano nella cartella [examples/classes]. L'istruzione alla riga 10 è equivalente a:

use \Exemples\Personne as Personne;

L'istruzione [use] sopra specifica che l'alias [Person] si riferisce alla classe [\Exemples\Personne];

  • riga 14: viene creato un oggetto [Person]. Il metodo [__construct] della classe [Person] verrà qui eseguito implicitamente;
  • riga 16: visualizza la Person $p. Per essere visualizzato, il valore della variabile $p deve essere convertito in una stringa. Implicitamente, viene quindi eseguito il metodo [Person.__toString]. Questo metodo deve quindi restituire una stringa;
  • Abbiamo visto che il costruttore della classe [Person] può generare un'eccezione di tipo [\Exception]. È quindi necessario gestirla. Di conseguenza, il codice alla riga 14 è incompleto. Dobbiamo utilizzare il codice nelle righe 18–24 per gestire correttamente l'eccezione che potrebbe verificarsi. Qui, ne generiamo intenzionalmente una passando un'età che non è un numero intero. In questo caso specifico, l'eccezione generata — — viene generata dall'interprete PHP e non dal codice della classe [Person]. Infatti, la firma del metodo [Person.__construct] è la seguente:

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

Pertanto, il parametro [age] passato al costruttore deve essere di tipo intero. Se così non fosse, l'interprete PHP genera un [TypeError]. Inoltre, i metodi [set] della classe [Person] generano un [\Exception]. Poiché il costruttore che li chiama non ha un blocco try/catch, l'eccezione si propaga di un livello verso l'alto al codice che ha chiamato il costruttore, ovvero il codice nello script [classes-05.php]. In definitiva, lo script [classes-05.php] può ricevere due tipi di eccezioni: \Exception o \TypeError. Si noti che quando lo sviluppatore è certo che determinate eccezioni non possano verificarsi, non utilizzerà i blocchi catch corrispondenti. In questo caso, essi vengono utilizzati sistematicamente esclusivamente a scopo dimostrativo. Tuttavia, i blocchi catch verranno utilizzati per ogni possibile eccezione, anche se improbabile;

Per questo motivo, il blocco try nelle righe 18–24 ha due blocchi catch per gestire separatamente i due tipi di eccezione;

  • Riga 20: È possibile scrivere sia [Exception] che [\Exception]:
    • La prima versione utilizza il nome relativo della classe, relativo allo spazio dei nomi dello script. Lo script non ha uno spazio dei nomi. Il suo spazio dei nomi è quindi la radice di tutti gli spazi dei nomi: \. Pertanto, scrivere [Exception] qui equivale a scrivere [\Exception]. E la classe [Exception] si trova effettivamente nello spazio dei nomi [\];

È preferibile utilizzare i nomi assoluti delle eccezioni predefinite di PHP in uno script che non ha uno spazio dei nomi proprio. Pertanto, se si decide di assegnare uno spazio dei nomi a questo script, scrivere i nomi assoluti delle classi rimane valido, mentre nell’altro caso, la modifica dello spazio dei nomi causerà errori con i nomi relativi delle classi;

  • Riga 21: Quando si verifica un'eccezione, il metodo [Exception→getMessage] recupera il messaggio di errore dell'eccezione. Lo stesso vale per un [TypeError]. Nel metodo [Person.setFirstName], abbiamo scritto:

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

Alla riga 5 viene generata un'eccezione con il messaggio di errore [Il nome non deve essere vuoto]. Questo è ciò che il metodo [Exception→getMessage] alla riga 29 dello script [classes-05.php] recupererà.

Risultati:


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. Aggiunta di un metodo che funge da costruttore secondario

In PHP 7 non è possibile avere più costruttori con parametri diversi che consentano di costruire un oggetto in vari modi. Possiamo quindi utilizzare metodi che fungono da costruttori:


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

Commenti

  • righe 2-5: il metodo initWithPerson permette di assegnare all'oggetto corrente i valori degli attributi di un altro oggetto Person. Qui, chiama il costruttore __construct, ma questo non è obbligatorio. Potrebbe inizializzare gli attributi della classe [Person] stessa;

La nuova classe [Person] viene utilizzata dal seguente script [classes-06.php]:


<?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;
  • Righe 14, 23, 30: È normale che, in seguito a un'eccezione, si debba interrompere l'esecuzione di uno script da console se l'errore riscontrato è irreversibile. Questo non vale per uno script web: non interrompiamo l'esecuzione dello script, ma visualizziamo una pagina di errore. Se ci troviamo all'interno di una funzione, non usiamo l'istruzione exit ma return: non interrompiamo l'esecuzione dello script (exit) ma usciamo dalla funzione (return) dopo aver generato un errore;

Risultati:

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

5.8. Un array di oggetti [Person]

L'esempio seguente [classes-07.php] mostra che è possibile avere array di oggetti.


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

Risultati:

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

Commenti

  • riga 9: creazione di un array di 2 persone;
  • riga 12: iterazione sull'array;
  • riga 13: $group[$i] è un oggetto di tipo Person. Il metodo [Person.__toString] viene utilizzato per visualizzarlo;

5.9. Creazione di una classe derivata dalla classe Person

Creiamo la seguente classe [Teacher] in un file denominato [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]";
  }
 
}

Commenti

  • riga 7: anche la classe [Teacher] fa parte dello spazio dei nomi [Examples];
  • riga 10: la classe Teacher estende la classe Person. La classe derivata Teacher eredita gli attributi e i metodi della sua classe padre;
  • riga 12: la classe Teacher aggiunge un nuovo attributo, subject, che le è specifico;
  • riga 25: il costruttore della classe Teacher accetta 4 parametri:
    • 3 per inizializzare la sua classe padre (first_name, last_name, age), riga 27;
    • 1 per la propria inizializzazione (subject), riga 29;
  • riga 27: la classe derivata ha accesso ai metodi e ai costruttori della sua classe padre tramite la parola chiave parent::. Qui, passiamo i parametri (first_name, last_name, age) al costruttore della classe padre;
  • righe 33–35: il metodo __toString della classe derivata utilizza il metodo __toString della classe padre;

La classe [Teacher] viene utilizzata dal seguente script [classes-08.php]:


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

Commenti

  • righe 4-5: dobbiamo indicare all'interprete PHP dove si trovano le due classi [Teacher, Person];
  • righe 7-8: dichiarazione dei nomi completi delle due classi. Questo ci permetterà di farvi riferimento nel codice semplicemente con i loro nomi senza il suffisso del namespace;
  • riga 13: creiamo un array contenente un tipo [Person] e un tipo [Teacher];
  • righe 16-18: visualizziamo gli elementi dell'array;
  • riga 17: verrà chiamato il metodo __toString di ciascun elemento $group[$i]. La classe Person ha un metodo __toString. La classe Teacher ne ha due: quello della sua classe padre e il proprio. Ci si potrebbe chiedere quale verrà chiamato. L'esecuzione mostra che è stato chiamato quello della classe Teacher. È sempre così: quando viene chiamato un metodo su un oggetto, viene cercato nel seguente ordine: nell'oggetto stesso, nella sua classe padre se ne ha una, poi nella classe padre della classe padre, e così via… La ricerca si interrompe non appena viene trovato il metodo.

Risultati:

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

5.10. Creazione di una seconda classe derivata dalla classe Person

L'esempio seguente crea una classe Student derivata dalla classe Person nel file [Student.php]:


<?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]]";
  }
 
}

Questa classe viene utilizzata dal seguente script [classes-09.php]:


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

Risultati:


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

5.11. Relazione tra il costruttore di una classe derivata e quello della classe padre

In alcuni linguaggi orientati agli oggetti, il costruttore di una classe derivata chiama automaticamente quello della sua classe padre. Il codice seguente [classes-16.php] mostra che questo non è il caso con PHP 7:


<?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();

Risultati

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

5.12. Sovrascrivere un metodo della classe padre

Abbiamo già visto che un metodo della classe padre può essere sovrascritto in una classe figlia. Pertanto, il metodo [__toString] della classe [Person] (vedi link) è stato sovrascritto nelle classi figlie [Teacher] (vedi link) e [Student] (vedi link). Lo script [classes-13.php] illustra nuovamente il concetto:


<?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";

Commenti

  • righe 7–17: la classe [Class1] definisce due metodi, f e g;
  • righe 20–27: la classe [Class2] estende la classe [Class1] e ridefinisce il suo metodo f;

Risultati

1
2
3
11
2
1

Commenti

  • La riga 30 del codice crea un oggetto $c2 di tipo [Class2];
  • La riga 31 del codice chiama il metodo f dell'oggetto $c2. Poiché questo metodo esiste, viene eseguito;
  • La riga 32 del codice chiama il metodo g dell'oggetto $c2. Poiché questo metodo non esiste, viene cercato nella classe padre, dove viene trovato ed eseguito;
  • La riga 33 del codice crea un oggetto $c1 di tipo [Class1];
  • La riga 34 del codice chiama il metodo f dell'oggetto $c1. Poiché questo metodo esiste, viene eseguito;

5.13. Passaggio di un oggetto come parametro di una funzione

Consideriamo il seguente script [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);

Commenti

  • righe 7–17: la classe [Class1];
  • righe 20–27: una classe [Class2] derivata da [Class1];
  • riga 30: una funzione che si aspetta un parametro di tipo [Class1]. Quando il tipo atteso è una classe, il parametro effettivo può essere un oggetto del tipo atteso o di un tipo derivato;
  • righe 35–38: la funzione [doSomething] viene chiamata con un parametro di tipo [Class2], anche se il tipo previsto è [Class1];

Risultati

13

5.14. Classi astratte

Una classe astratta è una classe incompleta che non può essere istanziata. Per poter essere utilizzata, deve essere derivata da un'altra classe.

A cosa serve una classe astratta? A volte abbiamo classi che condividono uno o più metodi ma differiscono in altri metodi o attributi. È quindi auspicabile raggruppare tutto ciò che è comune in una classe padre. Per ora, non abbiamo bisogno di una classe astratta. Ma supponiamo che le classi figlie differiscano solo in un metodo, M: la firma del metodo sarebbe la stessa in tutte le classi figlie, ma la sua implementazione sarebbe diversa. Per richiedere alle classi figlie di implementare il metodo M:

  • Dichiareremo la firma del metodo M nella classe padre. Poiché la classe padre non sa come implementarlo, anteponiamo al metodo la parola chiave abstract: ciò significa che l'implementazione del metodo M viene rimandata alle classi figlie;
  • poiché la classe padre non è completamente implementata, viene anch'essa dichiarata abstract utilizzando la stessa parola chiave abstract. Ciò significa che la classe non può più essere istanziata. È necessario creare una classe figlia per definire l'implementazione del metodo M, in modo che il corpo della classe padre possa essere utilizzato;

Ecco un esempio [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";

Commenti

  • righe 7–16: la classe [Class1] è astratta (riga 7) perché non sa come implementare il metodo g alla riga 15. Deve quindi essere derivata per essere utilizzabile;
  • righe 19–26: la classe [Class2] estende la classe [Class1] e ridefinisce il metodo g della sua classe padre (righe 22–24);
  • righe 29–41: la classe [Class3] estende la classe [Class1] e ridefinisce il metodo g della sua classe padre (righe 32–34);
  • righe 37–39: la classe [Class3] ridefinisce il metodo f della sua classe padre;
  • righe 44–49: vengono creati due oggetti dei tipi [Class2] e [Class3] e vengono chiamati i loro metodi f e g;

Risultati

1
2
3
4
1
11
2
21

5.15. Classi finali

Una classe finale è una classe da cui non è possibile derivare altre classi. Si consideri il seguente script [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();

Commenti

  • righe 7–9: la parola chiave final rende la classe [Class1] una classe finale da cui non è possibile derivare;
  • righe 12-14: la classe [Class2] estende la classe finale [Class1], il che costituisce un errore;
  • riga 17: l'errore verrà segnalato solo quando lo script verrà eseguito e si tenterà di manipolare un oggetto di tipo [Class2];

Risultati

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. Metodi finali

Un metodo finale è un metodo che non può essere sovrascritto dalle sottoclassi. Ecco un esempio [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();

Commenti

  • riga 13: il metodo f della classe [Class1] è dichiarato final utilizzando la parola chiave final;
  • riga 20: la classe [Class2] estende la classe [Class1];
  • righe 22-23: la funzione f della classe padre [Class1] viene ridefinita. Ciò dovrebbe causare un errore;
  • riga 29: viene creato un oggetto di tipo [Class2] per costringere l'interprete PHP a ispezionare la classe [Class2];

Risultati

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. Metodi e attributi statici

Un metodo statico è un metodo associato alla classe in cui è definito, non agli oggetti istanza della classe. Pertanto, se la classe C dichiara un metodo statico M, per utilizzarlo dovremmo scrivere:

  • C::M se ci si trova all'esterno della classe;
  • self::M se ci si trova all'interno della classe;

Ecco un esempio [classes-17.php]:


<?php
 
class Classe1 {
 
  // static method
  static function say(string $message): void {
    print "$message\n";
  }
 
}
 
// test -------------------
Classe1::say("hello");

Commenti

  • riga 6: il metodo [say] viene dichiarato statico utilizzando la parola chiave static;
  • riga 13: chiamata al metodo statico [say] utilizzando la sintassi: Class1::say;

Risultati

hello

Ora considera il seguente codice [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();

Commenti

  • Riga 5: Dichiaramo un attributo statico che conterà il numero di istanze della classe [Class1] che sono state create. Questo non è un attributo che può appartenere a un'istanza della classe. Infatti, se vengono creati due oggetti, O1 e O2, nessuno dei due è a conoscenza dell'altro. Avere un contatore all'interno dell'istanza non ha senso: quando viene creato un nuovo oggetto, in quale istanza dovremmo incrementare il contatore? Saremmo costretti a incrementare il contatore di un oggetto specifico, ignorando i contatori delle altre istanze. Un attributo statico è un attributo della classe, non un attributo di istanza della classe;
  • righe 7–10: è nel costruttore che conteremo gli oggetti creati, poiché la creazione di ogni nuovo oggetto fa scattare l'esecuzione del costruttore;
  • riga 14: Notate la notazione `self::$nbObjects` per indicare che ci stiamo riferendo a un attributo statico della classe in cui risiede il codice eseguito;
  • righe 13–15: il metodo statico [say] è responsabile della visualizzazione del numero di oggetti creati;
  • righe 20–22: creiamo due oggetti e visualizziamo il contatore degli oggetti;

Risultati


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

5.18. Visibilità tra classe padre e classe figlia

Esaminiamo il seguente script [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();

Commenti

  • righe 3–17: la classe [SomeParent];
  • righe 19–32: la classe figlia [SomeChild]. Possiamo vedere che estende la classe [SomeParent] (riga 19);
  • riga 5: la classe [SomeParent] ha un solo attributo;
  • righe 8–17: il metodo [SomeParent::doTest] ha lo scopo di visualizzare due attributi:
    • [$attributeOfParent], che appartiene alla classe [SomeParent];
    • [$attributeOfChild], che appartiene alla classe [SomeChild] (riga 21);
  • righe 10–11: viene visualizzata l'identità del chiamante; il metodo verrà chiamato in due modi diversi:
    • dalla classe padre [SomeParent];
    • dalla classe figlia [SomeChild];
  • righe 13–14: visualizzazione dei due attributi;
  • righe 19–32: la classe figlia [SomeChild] che estende la classe [SomeParent] (riga 19);
  • riga 21: la classe [SomeChild] ha un solo attributo;
  • riga 24: il metodo [SomeChild::doTest] ha lo scopo di visualizzare due attributi:
    • [$attributeOfParent], che appartiene alla classe [SomeParent];
    • [$attributeOfChild], che appartiene alla classe [SomeChild];
  • righe 26–27: visualizzazione dei due attributi;
  • riga 30: chiama il metodo [doTest] della classe padre, che a sua volta visualizza i due attributi;
  • riga 36: viene chiamato il metodo [SomeParent::doTest];
  • riga 38: viene chiamato il metodo [SomeChild::doTest];

Nel primo test, la visibilità di entrambi gli attributi è [private]. Possiamo quindi aspettarci che la classe figlia non veda l'attributo della sua classe padre. Questo attributo dovrebbe avere almeno una visibilità [protected]. Ma che dire dell'attributo della classe figlia? È visibile nella classe padre?

Ecco i risultati di questo primo 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

Commenti

  • righe 1-10: risultati del primo test in cui viene chiamato il metodo [SomeParent::doTest];
  • righe 3-6: vediamo che l'oggetto che chiama il metodo è di tipo [SomeParent];
  • riga 7: visualizzazione dell'attributo [$attributeOfParent];
  • righe 9-10: vediamo che l'attributo [SomeParent::$attributeOfChild] non esiste. Non viene quindi visualizzato;
  • righe 11–30: risultati del secondo test in cui viene chiamato il metodo [SomeChild::doTest];
  • righe 13–14: vediamo che l'attributo [SomeChild::$attributeOfParent] non esiste. Non viene quindi visualizzato. Questo è normale: l'attributo [SomeParent::$attributeOfParent] è [private] e quindi non è conosciuto nella classe figlia;
  • riga 15: visualizzazione dell'attributo [$attributeOfChild];
  • righe 16–30: ci troviamo nel metodo [SomeParent::doTest] chiamato dalla classe figlia;
  • righe 17–22: vediamo che [$this] è di tipo [SomeChild] con due attributi privati;
  • riga 23: sorprendentemente, [$this] di tipo [SomeChild] può vedere qui l'attributo del genitore [$attributeOfParent];
  • righe 25–30: altrettanto sorprendentemente, [$this] di tipo [SomeChild] non vede il proprio attributo [$attributeOfChild];

Questo risultato è molto sorprendente: sebbene le righe 17–21 indichino che [$this] è di tipo [SomeChild], questo [$this] all'interno del metodo [SomeParent::doTest] si comporta come se fosse un'istanza della classe [SomeParent] e non della classe [SomeChild].

Eseguiamo un nuovo test con lo script [classes-20.php]. L'attributo [$attributeOfParent] ha ora visibilità [protected] (riga 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();

Risultati

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

Commenti

  • riga 12: la classe [SomeChild] ora vede l'attributo del proprio genitore [$attributeOfParent]. Ciò è normale poiché l'attributo ha ora un ambito [protected];
  • nel metodo [someParent::doTest], l'oggetto [$this] è di tipo [SomeChild] (righe 15–20). Vede l'attributo del genitore [$attributeOfParent] (riga 21) ma non ancora il proprio attributo [$attributeOfChild] (righe 23–28);

Nel terzo test, anche l'attributo [$attributeOfChild] ha un ambito [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();

I risultati dell'esecuzione sono i seguenti:

---------------------------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
  • Riga 22: questa volta, all'interno del genitore, [$this] di tipo [SomeChild] (righe 15–20) vede l'attributo protetto [$attributeOfChild] della propria classe [SomeChild].

Cosa possiamo imparare da questi test?

  • che l'istanza [$this] di una classe padre, utilizzata in un metodo della classe padre, vede:
    • gli attributi e i metodi della classe padre indipendentemente dalla loro visibilità;
    • non vede nessuno degli attributi e dei metodi delle sue classi figlie;

Questo è il comportamento previsto.

  • che l'istanza [$this] di una classe figlia, utilizzata in un metodo della classe figlia, veda:
    • gli attributi e i metodi della classe padre se hanno almeno visibilità [protected]. Quelli con visibilità [private] non sono visibili;
    • gli attributi e i metodi della classe figlia, indipendentemente dalla loro visibilità;

Questo è il comportamento previsto.

  • che l'istanza [$this] di una classe figlia, utilizzata in un metodo della classe padre, veda:
    • gli attributi e i metodi della classe padre, indipendentemente dalla loro visibilità;
    • gli attributi e i metodi della propria classe solo se hanno almeno visibilità [protected]. Quelli con visibilità [private] non sono visibili;

Questo è un comportamento inaspettato.

5.19. Codifica JSON di una classe

In una classe, il metodo [__toString] è spesso presente: dovrebbe restituire una stringa che rappresenta l'oggetto che lo chiama. Si potrebbe essere tentati di far sì che questa stringa sia una stringa JSON. Esploreremo ora questa strada.

Image

Useremo la seguente classe [Person]:


<?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);
  }
 
}

Commenti

  • righe 5–8: i quattro attributi della classe;
  • righe 20–35: i metodi getter che consentono di recuperare i valori di questi attributi;
  • righe 11–18: un setter globale che inizializza gli attributi a partire da un array associativo [$arrayOfAttributes] le cui chiavi corrispondono agli attributi della classe;
  • righe 38–46: il metodo [__toString] della classe;
  • riga 42: la funzione PHP [get_object_vars] recupera i valori degli attributi della classe come array associativo [‘name’=>’name1’, ‘first_name’=>’first_name1’, ‘age’=>’age1’, ‘children’=>[]];
  • riga 45: restituiamo la stringa JSON di questo array di attributi;

Esaminiamo lo script [json-01.php] che utilizza la classe [Person]:


<?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";

Commenti

  • righe 6-13: inizializziamo un oggetto [Person] [$father] utilizzando il metodo [Person::setFromArray], che ci permette di inizializzare un oggetto [Person] con un array le cui chiavi corrispondono agli attributi della classe [Person];
  • righe 14-19: inizializziamo un oggetto [Person] [$child1] allo stesso modo;
  • righe 21–25: inizializziamo un oggetto [Person] [$child2];
  • righe 27–29: l'attributo [$father→children] viene inizializzato con un array dei due figli;
  • righe 32–33: assegniamo i due figli al padre;
  • riga 35: l'operazione [print] tenta di convertire l'oggetto [$child1] in una stringa. Per farlo, utilizza il metodo [__toString] dell'oggetto. Ci aspettiamo quindi di vedere la stringa JSON dell'oggetto;
  • righe 38–39: facciamo lo stesso con il padre;

I risultati sono i seguenti:


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

Commenti

  • righe 2–11: l'oggetto [$child1];
  • righe 12-21: l'array di attributi per l'oggetto [$child1]. Li abbiamo tutti;
  • riga 22: abbiamo la stringa JSON per l'oggetto [$child1];
  • righe 23-44: lo stesso vale per l'oggetto [$child2];
  • Righe 45–112: per il genitore è leggermente diverso perché il suo attributo [children] non è NULL, come lo era per i figli;
  • riga 112: vediamo che i figli mancano dalla stringa JSON del padre;
  • righe 79–111: vediamo che nell'array degli attributi del padre, il figlio 1 (righe 89–98) rimane un oggetto, così come il figlio 2 (righe 99–110). In breve, l'espressione [\get_object_vars($this)], dove [$this] rappresenta il genitore, non è ricorsiva: se un attributo della classe [Person] è esso stesso un oggetto, l'espressione [\get_object_vars($this)] non tenta di recuperare il suo array di attributi;

Possiamo migliorare questo codice. Modifichiamo la classe [Person] nella seguente classe [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);
  }
 
}

Commenti

  • righe 36–38: la funzione [getAttributes] restituisce l'array degli attributi dell'oggetto che la chiama;
  • righe 25–34: la funzione [__toString];
  • Riga 27: recuperiamo gli attributi della classe [Person] nell'array [$attributes];
  • riga 28: in base all'esempio precedente, sappiamo che [$attributes["children"]] è un array di due oggetti di tipo [Person];
  • righe 29–31: i due oggetti vengono sostituiti dal loro array di attributi;
  • riga 33: non resta che codificare l'array di attributi costruito in JSON;

Lo script [json-02.php] utilizza la classe [Person2] come segue:


<?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";

Lo script [json-02.php] è identico allo script [json-01.php], tranne per il fatto che la classe [Person2] ha sostituito la classe [Person].

I risultati dell'esecuzione sono i seguenti:

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

Questa volta siamo riusciti a ottenere i figli insieme al padre.

La soluzione precedente non è soddisfacente perché i figli potrebbero avere a loro volta dei figli. Ci si imbatte quindi nel problema precedente.

La classe [Person3] risolve questo problema come segue:


<?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);
        }
      }
    }
  }

Commenti

  • righe 21–22: questa volta, il metodo [__toString] recupera gli attributi della propria classe e richiede che ciò avvenga in modo ricorsivo: se un attributo è un oggetto o un array di oggetti, allora ogni oggetto deve essere sostituito dal proprio array di attributi nell'array finale di attributi della classe;
  • righe 31-78: la funzione [getRecursiveAttributes] svolge questo lavoro. Abbiamo commentato il codice. Scrivere una funzione ricorsiva è spesso complesso. È il caso qui. Il lettore non perderà nulla se non la capisce. Esistono librerie che gestiscono questo compito. Le chiamate ricorsive avvengono alle righe 64 e 72;
  • il vantaggio di questo codice è che non è stato scritto esclusivamente per la classe [Person3]. Si applica a qualsiasi classe con attributi i cui valori sono di tipi di oggetti diversi, purché le classi utilizzate dalla classe principale abbiano, come essa, il metodo [getAttributes] alle righe 27–29

Lo script [json-03.php] utilizza la classe [Person3] come segue:


<?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";
  • righe 30-45: assegniamo due figli a [$child1];

I risultati dell'esecuzione sono i seguenti:

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

Se formattiamo questo risultato, otteniamo quanto segue:


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

Abbiamo recuperato con successo le stringhe JSON per tutti gli oggetti [Person] che compongono il genitore.