5. Le classi
Vocabolario: Una classe (class) è un tipo PHP. Una variabile di questo tipo è chiamata oggetto. Un oggetto è un’istanza (esemplare) di una classe.
5.1. L’albero degli script

5.2. Qualsiasi variabile può diventare un oggetto dotato di attributi
Lo script [classes-01.php] è il seguente:
<?php
// un oggetto generico
// $obj1=new stdClass();
// ogni variabile può avere attributi per definizione
$obj1->attr1 = "un";
$obj1->attr2 = 100;
// visualizza l'oggetto
print "objet1=[$obj1->attr1,$obj1->attr2]\n";
// modifica l'oggetto
$obj1->attr2 += 100;
// visualizza l'oggetto
print "objet1=[$obj1->attr1,$obj1->attr2]\n";
// copia il valore di oggetto1 (indirizzo dell'oggetto puntato) in oggetto2
// le due variabili sono quindi diverse ma puntano allo stesso oggetto
$obj2 = $obj1;
// modifica oggetto2
$obj2->attr2 = 0;
// visualizza i due oggetti
print "objet1=[$obj1->attr1,$obj1->attr2]\n";
print "objet2=[$obj2->attr1,$obj2->attr2]\n";
// modifica l'oggetto a cui punta oggetto1
$obj1 = new stdClass();
print "obj1 :\n";
print_r($obj1);
print "obj2 :\n";
print_r($obj2);
// assegna il riferimento (l'indirizzo) di oggetto2 a oggetto3
// $obj2 e $obj3 sono quindi la stessa identica variabile
$obj3 = &$obj2;
print "obj2 :\n";
print_r($obj2);
print "obj3 :\n";
print_r($obj3);
// modifica obj3
$obj3->attr2 = 10;
// visualizza entrambi gli oggetti
print "objet2=[$obj2->attr1,$obj2->attr2]\n";
print "objet3=[$obj3->attr1,$obj3->attr2]\n";
// modifica l'oggetto a cui punta obj2
$obj2 = new stdClass();
$obj2->attr3 = "deux";
$obj2->attr4 = 20;
// visualizza i due oggetti $obj2 e $obj3
print "obj2 :\n";
print_r($obj2);
print "obj3 :\n";
print_r($obj3);
// un oggetto è un dizionario?
print count($obj3) . "\n";
while (list($attribut, $valeur) = each($obj3)) {
print "obj3[$attribut]=$valeur\n";
}
// fine
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 indica l'attributo attr della variabile $obj. Se non esiste, viene creato, trasformando così la variabile $obj in un oggetto con attributi. Abbiamo visto che PHP crea quindi per impostazione predefinita un oggetto di tipo stdClass;
- riga 16: l’espressione $obj2=$obj1, quando $obj1 è un oggetto, rappresenta una copia di oggetti per riferimento: $obj2 e $obj1 sono riferimenti (indirizzi) allo stesso oggetto. L'oggetto stesso può essere modificato da uno qualsiasi dei due riferimenti;
- righe 23-27: servono a dimostrare che $obj1 e $obj2 sono due variabili diverse: non si trovano allo stesso indirizzo di memoria:
- $obj2=$obj1 ha copiato il valore di $obj1 nella variabile $obj2 (operazione 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 e questa punta a un oggetto, PHP manipola l’oggetto a cui punta la variabile $obj. Come si evince dallo schema riportato di seguito, è possibile modificare l’oggetto puntato sia tramite $obj1 sia tramite $obj2. È quanto mostrano le righe 4 e 5 dei risultati;

- riga 30: l’espressione $obj3=&$obj2 fa sì che $obj2 e $obj3 si trovino allo stesso indirizzo [1 ci-dessous]. Si potrebbe dire che le due variabili sono alias della stessa posizione di memoria. Entrambe puntano a un oggetto, l’Oggetto A riportato di seguito [2];
- l’operazione $obj2=new stdClass() fa sì che venga creato un nuovo oggetto, Oggetto B ([3 ci-dessous]), e l’indirizzo di questo nuovo oggetto viene assegnato alla variabile $obj2. Poiché $obj2 e $obj3 sono due alias della stessa posizione di memoria, anche $obj3 punta al nuovo oggetto «Oggetto B». È quanto mostrano le righe 16-27 e 30-41 dei risultati;

- righe 52-54: mostrano che un oggetto può essere iterato come un dizionario. Le chiavi del dizionario sono i nomi degli attributi e i valori del dizionario sono i valori di questi stessi attributi;
- riga 51: la funzione count può essere applicata a un oggetto (con un avviso), ma non restituisce, come ci si sarebbe potuto aspettare, il numero di attributi. Pertanto, un oggetto presenta delle somiglianze con un dizionario, ma non ne è uno;
5.3. Una classe Persona senza attributi dichiarati
Lo script [classes-02.php] è il seguente:
<?php
class Personne {
// attributi della classe
// non dichiarati - possono essere creati dinamicamente
// metodo
function identite() {
// a priori, utilizza attributi inesistenti
return "[$this->prenom,$this->nom,$this->age]";
}
}
// test
// gli attributi sono pubblici e possono essere creati dinamicamente
$p = new Personne();
$p->prenom = "Paul";
$p->nom = "Langevin";
$p->age = 48;
// chiamata di un metodo
print "personne=" . $p->identite() . "\n";
// fine
exit;
Risultati:
Commenti
- righe 3-13: definiscono una classe Personne. Una classe è uno stampo a partire dal quale si creano oggetti. Essa raggruppa attributi e funzioni chiamate metodi. Non è obbligatorio dichiarare gli attributi;
- righe 8-11: il metodo identité visualizza il valore di tre attributi non dichiarati nella classe. La parola chiave $this indica l’oggetto a cui viene applicato il metodo;
- riga 17: si crea un oggetto $p di tipo Personne. La parola chiave new serve a creare un nuovo oggetto. L'operazione restituisce un riferimento all'oggetto creato (quindi un indirizzo). Sono possibili diverse sintassi: new Personne(), new Personne, new personne. Il nome della classe non fa distinzione tra maiuscole e minuscole;
- righe 18-20: i tre attributi necessari al metodo identité vengono creati nell’oggetto $p;
- riga 22: il metodo identité della classe Personne viene applicato all’oggetto $p. Nel codice (righe 8-11) del metodo identité, $this fa riferimento allo stesso oggetto di $p;
5.4. La classe Persona con attributi dichiarati
Lo script [classes-03.php] è il seguente:
<?php
class Personne {
// attributi della classe
var $prenom;
var $nom;
var $age;
// metodo
function identite() {
return "[$this->prenom,$this->nom,$this->age]";
}
}
// test
// gli attributi sono pubblici
$p = new Personne();
$p->prenom = "Paul";
$p->nom = "Langevin";
$p->age = 48;
// chiamata di un metodo
print "personne=" . $p->identite() . "\n";
// fine
exit;
Risultati:
Commenti
- righe 6-8: gli attributi della classe sono dichiarati esplicitamente con la parola chiave var;
5.5. La classe Personne con un costruttore
Gli esempi precedenti mostravano classi Personne insolite, come quelle che si potevano trovare in PHP 4. Non è consigliabile seguire questi esempi. Presentiamo ora una classe Personne [classes-04.php] conforme alle buone pratiche di PHP 7:
<?php
// rigoroso rispetto dei tipi dichiarati dei parametri delle funzioni
declare (strict_types=1);
class Personne {
// attributi della classe
private $prenom;
private $nom;
private $age;
// getter e setter
public function getPrenom(): string {
return $this->prenom;
}
public function getNom(): string {
return $this->nom;
}
public function getAge(): int {
return $this->age;
}
public function setPrenom(string $prenom): void {
$this->prenom = $prenom;
}
public function setNom(string $nom): void {
$this->nom = $nom;
}
public function setAge(int $age): void {
$this->age = $age;
}
// costruttore
public function __construct(string $prenom, string $nom, int $age) {
// si utilizzano i set
$this->setPrenom($prenom);
$this->setNom($nom);
$this->setAge($age);
}
// metodo toString
public function __toString(): string {
return "[$this->prenom,$this->nom,$this->age]";
}
}
// test
// creazione di un oggetto Persona
$p = new Personne("Paul", "Langevin", 48);
// identità di questa persona
print "personne=$p\n";
// si modifica l'età
$p->setAge(14);
// identità della persona
print "personne=$p\n";
// fine
exit;
Risultati:
Commenti
- righe 6-50: la classe Personne;
- righe 7-9: gli attributi privati (private) della classe. Questi attributi sono visibili solo all'interno della classe. Le altre parole chiave utilizzabili sono:
- public: rende l'attributo visibile dall'esterno della classe,
- protected: rende l'attributo protégé visibile dall'interno della classe e dalle classi da essa derivate;
- poiché gli attributi sono privati, non è possibile accedervi dall’esterno della classe. Non è quindi possibile scrivere il seguente codice:
Qui ci si trova all'esterno della classe Personne. Poiché l'attributo nom è privato, la riga 2 è errata. Per inizializzare i campi privati dell'oggetto $p, ci sono due modi:
- utilizzare i metodi pubblici set e get (il nome di questi metodi può essere qualsiasi) delle righe 12-34. Si potrà quindi scrivere:
- utilizzare il costruttore delle righe 37-42. Si scriverà quindi:
La sintassi sopra riportata richiama automaticamente il metodo della classe Personne denominato __construct;
- riga 59: questa riga visualizza la persona $p sotto forma di stringa di caratteri. A tal fine, viene utilizzato il metodo della classe Personne denominato __toString (righe 45-47);
- tutti i metodi della classe (funzioni) sono stati preceduti dalla parola chiave public, che indica che la funzione è visibile al di fuori della classe. Le altre parole chiave utilizzabili sono, come per gli attributi e con lo stesso significato, private e protected. In assenza di un attributo di visibilità esplicito, la funzione ha una visibilità implicita public;
5.6. La classe Personne 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 set o equivalenti. Per evitare di inserire le stesse verifiche in due punti diversi, è possibile inserirle nei metodi set. Se un valore di inizializzazione di un oggetto risulta errato, verrà generata un’eccezione. Ecco un esempio.
Per prima cosa si sposta la definizione della classe Personne nel proprio file [Personne.php]:
<?php
// rigoroso rispetto dei tipi dichiarati dei parametri delle funzioni
declare (strict_types=1);
// spazio dei nomi;
namespace Exemples;
// classe Persona
class Personne {
// attributi della classe
private $prenom;
private $nom;
private $age;
// getter e setter
public function getPrenom(): string {
return $this->prenom;
}
public function getNom(): string {
return $this->nom;
}
public function getAge(): int {
return $this->age;
}
public function setPrenom(string $prénom): void {
// il nome deve essere non vuoto
$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 {
// il cognome deve essere non vuoto
$nom = trim($nom);
if ($nom === "") {
throw new \Exception("Le nom doit être non vide");
} else {
$this->nom = $nom;
}
}
public function setAge(int $âge): void {
// l'età deve essere valida
if ($âge < 0) {
throw new \Exception("L'âge doit être un entier positif ou nul");
} else {
$this->age = $âge;
}
}
// costruttore
public function __construct(string $prenom, string $nom, int $age) {
// si utilizzano i set
$this->setPrenom($prenom);
$this->setNom($nom);
$this->setAge($age);
}
// metodo
public function initWithPersonne(Personne $p): void {
// inizializza l'oggetto corrente con una persona $p
$this->__construct($p->prenom, $p->nom, $p->age);
}
// metodo toString
function __toString(): string {
return "[$this->prenom,$this->nom,$this->age]";
}
}
Commenti
- riga 4: si richiede che il tipo dei parametri delle funzioni sia rispettato quando viene dichiarato;
- riga 7: definisce uno spazio dei nomi (namespace). Il nome completo (detto qualifié) della classe Personne è quindi \Exemples\Personne. Si noti il carattere \ che introduce il nome qualificato: si ottiene quindi un nome qualificato absolu. Se questo carattere è assente, si ottiene un nome qualificato relatif (relativo allo spazio dei nomi corrente). Pertanto, se due classi A e B appartengono allo stesso spazio dei nomi E, nel codice della classe A sarà possibile accedere alla classe B utilizzando la notazione relative B. Se la classe A fa parte dello spazio dei nomi E1 e B dello spazio dei nomi E2, nel codice di A, B sarà raggiungibile tramite la notazione absolue \E2\B. Definire una classe all’interno di uno spazio dei nomi non è obbligatorio, ma NetBeans emette un avviso se non lo si fa. Quindi lo faremo. Inoltre, gli spazi dei nomi dovrebbero corrispondere alla struttura ad albero dei file. Pertanto, la classe A in uno spazio dei nomi E1 dovrebbe trovarsi in un file E1/A.php. Non è obbligatorio, ma anche in questo caso NetBeans emette un avviso se non lo si fa. Nell’esempio della classe [\Exemples\Personne], NetBeans emette un avviso perché la struttura del file [Personne.php] è [exemples/classes/Personne.php] e quindi non corrisponde allo spazio dei nomi. Non bisogna confondere la struttura ad albero con lo spazio dei nomi. Il nome completo di una classe utilizza uno spazio dei nomi e non ha nulla a che vedere con la struttura ad albero del file PHP della classe. Il collegamento tra struttura ad albero e spazio dei nomi è facoltativo e può non essere rispettato, come abbiamo fatto qui;
- righe 12-14: i tre attributi privati della classe;
- righe 29-37: inizializzazione dell’attributo prenom e verifica del valore di inizializzazione;
- riga 31: la funzione trim($chaine) elimina gli spazi presenti all’inizio e alla fine di $chaine. Pertanto, trim(«abcd ») restituisce la stringa «abcd», mentre trim(« ») restituisce 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 è stata utilizzata qui la classe predefinita [Exception]. In questo caso è necessario utilizzare il suo nome assoluto [\Exception]. Se si utilizza il suo nome relativo [Exception], allora questa classe verrà cercata nello spazio dei nomi corrente, ovvero lo spazio dei nomi [Exemples] della classe Personne. Pertanto, l’interprete PHP cercherà una classe con nome assoluto [\Exemples\Exception] che non esiste;
La classe [Personne] viene utilizzata dal seguente script [classes-05.php]:
<?php
// rigoroso rispetto del tipo dei parametri delle funzioni
declare(strict_types=1);
// inclusione della definizione della classe Persona
require_once __DIR__."/Personne.php";
// nome qualificato della classe Persona
use \Exemples\Personne;
// test
// creazione di un oggetto Persona
$p = new Personne("Paul", "Langevin", 48);
// identità di questa persona
print "Exemple1, personne=$p\n";
// creazione di un oggetto Persona errato
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";
}
// creazione di un oggetto Persona errato
try {
$p = new Personne("", "yy", 10);
} catch (\Exception $e1) {
print "Exemple3, erreur : " . $e1->getMessage() . "\n";
} catch (\TypeError $e2) {
print "Exemple3, erreur : " . $e2->getMessage() . "\n";
}
// fine
exit;
Commenti
- riga 7: lo script utilizzerà la classe [Personne]. È quindi necessario indicare all’interprete PHP dove può trovare la definizione di questa classe. Questo è il ruolo delle istruzioni [include fichier] e [require fichier]. In questo caso è stata utilizzata l’istruzione [include]. La differenza tra le due istruzioni è la seguente: se l’istruzione [include fichier] riscontra degli errori durante il caricamento di [fichier], viene generato un errore di livello [E_WARNING], ma l’l’esecuzione prosegue, mentre [require], nello stesso caso, genera un errore fatale e l’esecuzione dello script si interrompe. Ciascuna delle due istruzioni ha una variante: [include_once] e [require_once]. Queste due varianti consentono di gestire il caso di inclusioni multiple dello stesso file. Si può immaginare, ad esempio, un progetto costituito da diversi script PHP, molti dei quali fanno riferimento alla classe [Personne]. La loro esecuzione provocherà quindi l’inclusione più volte del file [Personne.php] e genererà un errore, poiché una classe non può essere definita due volte. La soluzione consiste nell’utilizzare le varianti [_once], che garantiscono che il file venga incluso una sola volta nello script globale del progetto;
- riga 7: la costante [__DIR__] è una costante PHP che indica il nome completo della cartella in cui si trova lo script contenente 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 è possibile utilizzare indifferentemente i caratteri / e \;
- riga 14: si utilizza la classe [Personne] che abbiamo appena definito. Lo script [classes-05.php] non ha spazi dei nomi. La riga 14 utilizza il nome relativo della classe [Personne] senza spazio dei nomi. In assenza di uno spazio dei nomi per la classe [Personne], questa viene cercata all’interno dello stesso script [classes-05.php] e quindi non verrà trovata. Esistono due soluzioni a questo problema:
- utilizzare il nome completo della classe [\Exemples\Personne];
- utilizzare l’istruzione `use` alla riga 10. Questa indica che il codice che segue utilizza la classe [\Exemples\Personne];
- riga 10: l’istruzione `use` permette all’interprete di capire che la classe `[Personne]`, a cui si fa riferimento alla riga 14, è in realtà la classe `[\Exemples\Personne]`. Detto questo, dove troverà l’interprete il codice di questa classe? È la riga 7 a indicarglielo. Questa riga specifica che per eseguire lo script corrente è necessario caricare anche lo script [Personne.php]. Qui è stato utilizzato il nome relativo del file. Verrà quindi cercato nella cartella che contiene lo script [classes-05.php]. È quindi necessario che gli script [Personne.php] e [classes-05.php] si trovino nella stessa cartella. È proprio questo il caso, poiché entrambi si trovano nella cartella [exemples/classes]. L’istruzione della riga 10 è equivalente a:
use \Exemples\Personne as Personne;
L’istruzione [use] sopra riportata indica che l’alias [Personne] rimanda alla classe [\Exemples\Personne];
- riga 14: viene creato un oggetto [Personne]. In questo caso verrà implicitamente eseguito il metodo [__construct] della classe [Personne];
- riga 16: visualizza la Persona $p. Per essere visualizzato, il valore della variabile $p deve essere convertito in una stringa di caratteri. Implicitamente, viene quindi eseguito il metodo [Personne.__toString]. Quest’ultimo deve quindi restituire una stringa di caratteri;
- abbiamo visto che il costruttore della classe [Personne] può generare un'eccezione di tipo [\Exception]. È quindi necessario gestirla. Pertanto, il codice della riga 14 è incompleto. È necessario utilizzare quello delle righe 18-24 per gestire correttamente l’eccezione che può verificarsi. In questo caso ne generiamo volontariamente una passando un’età che non è un numero intero. In questo caso specifico, l’eccezione che si verifica viene generata dall’interprete PHP e non dal codice della classe [Personne]. Infatti, la firma del metodo [Personne.__construct] è la seguente:
function __construct(string $prenom, string $nom, int $age)
È quindi necessario che il parametro [age] passato al costruttore sia di tipo intero. In caso contrario, l’interprete PHP genera un errore di tipo [TypeError]. Inoltre, i metodi [set] della classe [Personne] generano un’eccezione di tipo [\Exception]. Poiché il costruttore che li chiama non dispone di una struttura try/catch, l’eccezione risale di un livello, fino al codice che ha chiamato il costruttore, ovvero il codice dello script [classes-05.php]. Infine, lo script [classes-05.php] può ricevere due tipi di eccezione: \Exception o \TypeError. Si noti che quando lo sviluppatore è certo che determinate eccezioni non possano verificarsi, non utilizzerà le opzioni catch corrispondenti. In questo caso vengono utilizzate sistematicamente solo a scopo dimostrativo. Le opzioni catch saranno tuttavia utilizzate per qualsiasi eccezione possibile, anche se improbabile;
Per questo motivo, la struttura try delle righe 18-24 presenta due catch per gestire separatamente i due tipi di eccezione;
- riga 20: si può scrivere indifferentemente [Exception] o [\Exception]:
- la prima versione utilizza il nome relativo della classe, riferito allo spazio dei nomi dello script. Lo script non ne possiede uno. Il suo spazio dei nomi è quindi la radice degli spazi dei nomi: \. Pertanto, scrivere qui [Exception] equivale a scrivere [\Exception]. Tuttavia, la classe [Exception] si trova effettivamente nello spazio dei nomi [\];
È preferibile utilizzare il nome assoluto delle eccezioni predefinite di PHP in uno script che non abbia a sua volta spazi dei nomi. Pertanto, se si decide di assegnare uno spazio dei nomi a questo script, la scrittura dei nomi assoluti delle classi rimane valida, mentre nell’altro caso la modifica dello spazio dei nomi causerà errori sui nomi relativi delle classi;
- riga 21: in caso di eccezione, il metodo [Exception→getMessage] consente di ottenere il messaggio di errore dell’eccezione. Lo stesso vale per un errore di tipo [TypeError]. Nel metodo [Personne.setPrenom] è stato scritto:
public function setPrenom(string $prénom) {
// il nome deve essere diverso da vuoto
$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 [Le prénom doit être non vide]. Questo è ciò che verrà recuperato dal metodo [Exception→getMessage] alla riga 29 dello script [classes-05.php].
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 secondo costruttore
In PHP 7 non è possibile avere più costruttori con parametri diversi che consentano di creare un oggetto in vari modi. È quindi possibile utilizzare metodi che fungono da costruttori:
// metodo
public function initWithPersonne(Personne $p) {
// inizializza l'oggetto corrente con una persona $p
$this->__construct($p->prenom, $p->nom, $p->age);
}
Commenti
- righe 2-5: il metodo initWithPersonne consente di assegnare all'oggetto corrente i valori degli attributi di un altro oggetto Personne. In questo caso, richiama il costruttore __construct, ma ciò non è obbligatorio. Potrebbe inizializzare autonomamente gli attributi della classe [Personne];
La nuova classe [Personne] viene utilizzata dal seguente script [classes-06.php]:
<?php
// inclusione della definizione della classe Persona
require_once __DIR__."/Personne.php";
// dichiarazione del nome qualificato della classe Persona
use \Exemples\Personne;
// test
// creazione di un oggetto Persona
try {
$p = new Personne("Paul", "Langevin", 48);
} catch (\Exception $e) {
print "erreur : " . $e->getMessage();
exit;
}
// identità di questa persona
print "personne=$p\n";
// creazione di una seconda persona
try {
$p2 = new Personne("Laure", "Adeline", 67);
} catch (\Exception $e) {
print "erreur : " . $e->getMessage();
exit;
}
// inizializzazione della prima con i valori della seconda
try {
$p->initWithPersonne($p2);
} catch (\Exception $e) {
print "erreur : " . $e->getMessage();
exit;
}
// verifica
print "personne=$p\n";
// fine
exit;
- righe 14, 23, 30: capita spesso che, a seguito di un'eccezione, sia necessario interrompere l'esecuzione di uno script da console se l'errore riscontrato è irreversibile. Questo non avviene in uno script web: non si interrompe l’esecuzione dello script, ma viene visualizzata una pagina di errore. Se ci si trova all’interno di una funzione, non verrà utilizzata l’istruzione exit, bensì return: non si interrompe l’esecuzione dello script (exit), ma si esce dalla funzione (return) dopo aver generato un errore;
Risultati:
5.8. Un array di oggetti [Personne]
L'esempio seguente [classes-07.php] mostra che è possibile avere array di oggetti.
<?php
require_once __DIR__."/Personne.php";
use \Exemples\Personne;
// test
// creazione di un array di oggetti Persona
// per facilitare la comprensione del codice, non si gestisce l'eventuale eccezione
$groupe = [new Personne("Paul", "Langevin", 48), new Personne("Sylvie", "Lefur", 70)];
// identità di queste persone
for ($i = 0; $i < count($groupe); $i++) {
print "groupe[$i]=$groupe[$i]\n";
}
// fine
exit;
Risultati:
Commenti
- riga 9: creazione di un array di 2 persone;
- riga 12: iterazione sull'array;
- riga 13: $groupe[$i] è un oggetto di tipo Personne. Per visualizzarlo viene utilizzato il metodo [Personne.__toString];
5.9. Creazione di una classe derivata dalla classe Persona
Si crea in un file [Enseignant.php] la seguente classe [Enseignant]:
<?php
// rigoroso rispetto dei tipi dichiarati dei parametri delle funzioni
declare (strict_types=1);
// spazio dei nomi
namespace Exemples;
// una classe derivata da "persona"
class Enseignant extends Personne {
// attributi
private $discipline; // materia insegnata
// getter e setter
public function getDiscipline(): string {
return $this->discipline;
}
public function setDiscipline(string $discipline): void {
$this->discipline = $discipline;
}
// costruttore
public function __construct(string $prénom, string $nom, int $âge, string $discipline) {
// attributi del genitore
parent::__construct($prénom, $nom, $âge);
// altri attributi
$this->setDiscipline($discipline);
}
// sovraccarico della funzione __toString della classe padre
public function __toString(): string {
return "[" . parent::__toString() . ",$this->discipline]";
}
}
Commenti
- riga 7: anche la classe [Enseignant] fa parte dello spazio dei nomi [Exemples];
- riga 10: la classe Enseignant deriva (extends) dalla classe Personne.. La classe derivata Enseignant eredita gli attributi e i metodi dalla sua classe madre;
- riga 12: la classe Enseignant aggiunge un nuovo attributo, discipline, che le è proprio;
- riga 25: il costruttore della classe Enseignant riceve 4 parametri:
- 3 per inizializzare la sua classe padre (nome, cognome, età), riga 27;
- 1 per la propria inizializzazione (materia), riga 29;
- riga 27: la classe derivata ha accesso ai metodi e ai costruttori della sua classe padre tramite la parola chiave parent ::. Qui si passano i parametri (nome, cognome, età) al costruttore della classe padre;
- righe 33-35: il metodo __toString della classe derivata utilizza il metodo __toString della classe padre;
La classe [Enseignant] viene utilizzata dal seguente script [classes-08.php]:
<?php
// inclusione della definizione delle due classi
require_once __DIR__."/Personne.php";
require_once __DIR__."/Enseignant.php";
// dichiarazione delle due classi utilizzate
use \Exemples\Personne;
use \Exemples\Enseignant;
// test
// creazione di un array di oggetti Personne e derivati
// per semplificare l'esempio, non si gestiscono le eccezioni
$groupe = array(new Enseignant("Paul", "Langevin", 48, "anglais"), new Personne("Sylvie", "Lefur", 70));
// identità di queste persone
for ($i = 0; $i < count($groupe); $i++) {
print "groupe[$i]=$groupe[$i]\n";
}
// fine
exit;
Commenti
- righe 4-5: dobbiamo indicare all’interprete PHP dove si trovano le due classi [Enseignant, Personne];
- righe 7-8: dichiarazione dei nomi completi delle due classi. Questo ci consentirà di riferirci ad esse nel codice semplicemente con il loro nome, senza il suffisso dei rispettivi spazi dei nomi;
- riga 13: si crea un array contenente un tipo [Personne] e un tipo [Enseignant];
- righe 16-18: visualizzano gli elementi dell’array;
- riga 17: verrà chiamato il metodo __toString di ciascun elemento $groupe[$i]. La classe Personne ha un metodo __toString. La classe Enseignant ne ha due: quella della sua classe padre e la propria. Ci si potrebbe chiedere quale verrà chiamata. L'esecuzione mostra che è stata chiamata quella della classe Enseignant. Funziona sempre così: quando viene chiamato un metodo su un oggetto, questo viene cercato nel seguente ordine: nell’oggetto stesso, nella sua classe padre se ne ha una, poi nella classe padre della classe padre, ecc… La ricerca si interrompe non appena il metodo viene trovato.
Risultati:
5.10. Creazione di una seconda classe derivata dalla classe Persona
L'esempio seguente crea una classe Etudiant derivata dalla classe Personne, nel file [Etudiant.php]:
<?php
// rigoroso rispetto dei tipi dichiarati dei parametri delle funzioni
declare (strict_types=1);
// spazio dei nomi
namespace Exemples;
class Etudiant extends Personne {
// attributi
private $formation; // formazione seguita
// getter e setter
public function getFormation(): string {
return $this->formation;
}
public function setFormation(string $formation): void {
$this->formation = $formation;
}
// costruttore
public function __construct(string $prénom, string $nom, int $âge, string $formation) {
// attributi del genitore
parent::__construct($prénom, $nom, $âge);
// altri attributi
$this->setFormation($formation);
}
// sovraccarico della funzione __toString della classe padre
public function __toString(): string {
return "[" . parent::__toString() . ",$this->formation]]";
}
}
Questa classe viene utilizzata dal seguente script [classes-09.php]:
<?php
// inclusione e definizione delle classi utilizzate dallo 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
// creazione di un array di oggetti «persona» e derivati
// per facilitare la comprensione dell'esempio, non si gestiscono le eccezioni
$groupe = array(new Enseignant("Paul", "Langevin", 48, "anglais"), new Personne("Sylvie", "Lefur", 70), new Etudiant("Steve", "Boer", 23, "iup2 qualité"));
// identità di queste persone
for ($i = 0; $i < count($groupe); $i++) {
print "groupe[$i]=$groupe[$i]\n";
}
// fine
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 con PHP 7 non è così:
<?php
class Classe1 {
// costruttore
public function __construct() {
print "constructeur de la classe Classe1\n";
}
}
class Classe2 extends Classe1 {
// costruttore
public function __construct() {
// il costruttore della classe padre non viene chiamato implicitamente
print "constructeur de la classe Classe2\n";
}
}
class Classe3 extends Classe1 {
// costruttore
public function __construct() {
// chiamata esplicita del costruttore della classe padre
parent::__construct();
// codice specifico della Classe3
print "constructeur de la classe Classe3\n";
}
}
// test
print "test1---------\n";
new Classe2();
print "test2---------\n";
new Classe3();
Risultati
5.12. Ridefinizione di un metodo della classe padre
Abbiamo già visto che un metodo della classe padre può essere ridefinito in una classe figlia. Pertanto, il metodo [__toString] della classe [Personne] (vedi link) è stato ridefinito nelle classi figlie [Enseignant] (vedi link) e [Etudiant] (vedi link). Lo script [classes-13.php] illustra nuovamente il concetto:
<?php
// rigoroso rispetto del tipo dei parametri delle funzioni
declare(strict_types=1);
// classe principale
class Classe1 {
public function f(): int {
return 1;
}
function g(): int {
return 2;
}
}
// classe derivata
class Classe2 extends Classe1 {
// si ridefinisce la funzione f della classe padre
public function f(): int {
return parent::f() + 10;
}
}
// codice
$c2 = new Classe2();
print $c2->f() . "\n";
print $c2->g() . "\n";
$c1=new Classe1();
print $c1->f()."\n";
Commenti
- righe 7-17: la classe [Classe1] definisce due metodi f e g;
- righe 20-27: la classe [Classe2] estende la classe [Classe1] e ridefinisce il metodo f di quest’ultima;
Risultati
Commenti
- la riga 30 del codice crea un oggetto $c2 di tipo [Classe2];
- la riga 31 del codice chiama il metodo f dell’oggetto $c2. Poiché questo esiste, viene eseguito;
- la riga 32 del codice chiama il metodo g dell’oggetto $c2. Poiché questo non esiste, viene cercato nella classe padre, dove viene trovato ed eseguito;
- la riga 33 del codice crea un oggetto $c1 di tipo [Classe1];
- 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
// rigoroso rispetto del tipo dei parametri delle funzioni
declare(strict_types=1);
// classe principale
class Classe1 {
public function f(): int {
return 1;
}
function g(): int {
return 2;
}
}
// classe derivata
class Classe2 extends Classe1 {
// si ridefinisce la funzione f della classe padre
public function f(): int {
return parent::f() + 10;
}
}
// il parametro della funzione è di tipo Classe1 o derivato
function doSomething(Classe1 $c1): void {
print $c1->f() + $c1->g() . "\n";
}
// codice
// si crea un oggetto di tipo Classe2 derivato da Classe1
$c2 = new Classe2();
// si chiama doSomething con
doSomething($c2);
Commenti
- righe 7-17: la classe [Classe1];
- righe 20-27: una classe [Classe2] derivata da [Classe1];
- riga 30: una funzione che richiede un parametro di tipo [Classe1]. Quando il tipo richiesto è una classe, il parametro effettivo può essere un oggetto del tipo richiesto o di un tipo derivato;
- righe 35-38: la funzione [doSomething] viene chiamata con un parametro di tipo [Classe2], mentre il tipo previsto è [Classe1];
Risultati
5.14. Classi astratte
Una classe astratta è una classe incompleta che non può essere istanziata. Per poter essere utilizzata, deve necessariamente essere derivata.
A cosa serve una classe astratta? A volte si hanno classi che condividono uno o più metodi ma che si differenziano per altri metodi o altri attributi. È quindi auspicabile raggruppare tutto ciò che è comune in una classe padre. Per il momento non abbiamo bisogno di una classe astratta. Ma supponiamo che le classi figlie differiscano solo per un metodo M: la firma del metodo sarebbe la stessa in tutte le classi figlie, ma la sua implementazione sarebbe diversa. Per imporre alle classi figlie di implementare il metodo M:
- dichiareremo la firma del metodo M nella classe padre. Poiché quest’ultima 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, anch’essa viene dichiarata astratta con la stessa parola chiave `abstract`. Ciò fa sì che la classe non possa più essere istanziata. È necessario creare una classe figlia che definisca l’implementazione del metodo M, affinché il corpo della classe padre sia utilizzabile;
Ecco un esempio [classes-15.php]:
<?php
// rispettando rigorosamente il tipo dei parametri delle funzioni
declare(strict_types=1);
// classe principale astratta
abstract Class Classe1 {
// metodo noto a tutte le classi derivate
public function f(): int {
return 1;
}
// metodo g astratto - sarà definito dalle classi derivate
abstract function g(): int;
}
// classe derivata
Class Classe2 extends Classe1 {
// il metodo g della classe padre deve essere definito
public function g(): int {
return parent::f() + 10;
}
}
// classe derivata
Class Classe3 extends Classe1 {
// il metodo g della classe padre deve essere definito
public function g(): int {
return parent::f() + 20;
}
// è possibile ridefinire il metodo f della classe padre
public function f(): int {
return 2;
}
}
// codice
$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 [Classe1] è astratta (riga 7) poiché non è in grado di implementare il metodo g della riga 15. Deve quindi essere obbligatoriamente derivata per poter essere utilizzata;
- righe 19-26: la classe [Classe2] estende la classe [Classe1] e ridefinisce il metodo g della sua classe padre (righe 22-24);
- righe 29-41: la classe [Classe3] estende la classe [Classe1] e ridefinisce il metodo g della sua classe padre (righe 32-34);
- righe 37-39: la classe [Classe3] ridefinisce il metodo f della sua classe padre;
- righe 44-49: si creano due oggetti di tipo [Classe2] e [Classe3] e si chiamano i loro metodi f e g;
Risultati
5.15. Classi finali
Una classe finale è una classe da cui non è possibile derivare altre classi. Consideriamo il seguente script [classes-11.php]:
<?php
// spazio dei nomi
namespace Exemples;
// classe non derivabile
final Class Classe1 {
}
// classe derivata
Class Classe2 extends Classe1 {
}
// codice - deve generare un errore
new Classe2();
Commenti
- righe 7-9: la parola chiave final rende la classe [Classe1] una classe finale da cui non è possibile derivare;
- righe 12-14: la classe [Classe2] estende la classe finale [Classe1], il che costituisce un errore;
- riga 17: l’errore verrà segnalato solo durante l’esecuzione dello script, quando si tenterà di manipolare un oggetto di tipo [Classe2];
Risultati
5.16. Metodi finali
Un metodo finale è un metodo che non può essere ridefinito tramite derivazione. Ecco un esempio [classes-12.php]:
<?php
// rigoroso rispetto del tipo dei parametri delle funzioni
declare(strict_types=1);
// spazio dei nomi
namespace Exemples;
// classe principale
Class Classe1 {
// questo metodo non può essere ridefinito in una classe derivata
public final function f(): int {
return 1;
}
}
// classe derivata
Class Classe2 extends Classe1 {
public function f(): int {
return 2;
}
}
// codice - deve generare un errore
new Classe2();
Commenti
- riga 13: il metodo f della classe [Classe1] è dichiarato finale tramite la parola chiave final;
- riga 20: la classe [Classe2] estende la classe [Classe1];
- righe 22-23: si ridefinisce la funzione f della classe padre [Classe1]. Ciò dovrebbe causare un errore;
- riga 29: si crea un oggetto di tipo [Classe2] per costringere l’interprete PHP a ispezionare la classe [Classe2];
Risultati
5.17. Metodi e attributi statici
Un metodo statico è un metodo associato alla classe in cui è definito e non agli oggetti istanza della classe. Pertanto, se la classe C dichiara un metodo statico M, per utilizzarlo si scriverà:
- 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 {
// metodo statico
static function say(string $message): void {
print "$message\n";
}
}
// test -------------------
Classe1::say("hello");
Commenti
- riga 6: il metodo [say] è dichiarato statico con la parola chiave static;
- riga 13: chiamata del metodo statico [say] con la sintassi: Classe1::say;
Risultati
Consideriamo ora il seguente codice [classes-18.php]:
<?php
class Classe1 {
// attributo statico
private static $nbObjects = 0;
public function __construct() {
print "constructeur Classe1\n";
self::$nbObjects++;
}
// metodo statico
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: viene dichiarato un attributo statico che conterà il numero di istanze della classe [Classe1] create. Non si tratta di 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 nell’istanza non ha senso: quando viene creato un nuovo oggetto, in quale istanza si incrementerà il contatore? Si sarebbe costretti a incrementare il contatore di un oggetto specifico, tralasciando i contatori delle altre istanze. Un attributo statico è un attributo della classe e non dell’istanza della classe;
- righe 7-10: è nel costruttore che si contano gli oggetti creati, poiché la creazione di ogni nuovo oggetto provoca l’esecuzione del costruttore;
- riga 14: si noti la notazione self::$nbObjects per indicare che si fa riferimento a un attributo statico della classe in cui si trova il codice eseguito;
- righe 13-15: il metodo statico [say] ha il compito di visualizzare il numero di oggetti creati;
- righe 20-22: si creano due oggetti e si visualizza 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 {
// attributo
private $attributeOfParent = 4;
// metodo
public function doTest(): void {
// chi lo chiama?
print "parent :\n";
var_dump($this);
// visualizzazione padre
print "parent : attributeOfParent={$this->attributeOfParent}\n";
print "parent : attributeOfChild={$this->attributeOfChild}\n";
}
}
class SomeChild extends SomeParent {
// attributo
private $attributeOfChild = 14;
// metodo
public function doTest(): void {
// visualizzazione figlio
print "child : attributeOfParent={$this->attributeOfParent}\n";
print "child : attributeOfChild={$this->attributeOfChild}\n";
// genitore
parent::doTest();
}
}
// script principale
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]. Si nota che essa 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à infatti 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: chiamata al metodo [doTest] della classe padre, che a sua volta visualizzerà i due attributi;
- riga 36: viene chiamato il metodo [SomeParent::doTest];
- riga 38: viene chiamato il metodo [SomeChild::doTest];
Nel primo test, la visibilità dei due attributi è [private]. Ci si può quindi aspettare che la classe figlia non veda l’attributo della sua classe padre. Quest’ultimo dovrebbe avere almeno la visibilità [protected]. Ma che dire dell’attributo della classe figlia? È visibile nella classe padre?
Ecco i risultati di questo primo test:
Commenti
- righe 1-10: risultati del primo test in cui viene chiamato il metodo [SomeParent::doTest];
- righe 3-6: si vede che l’oggetto che chiama il metodo è di tipo [SomeParent];
- riga 7: visualizzazione dell'attributo [$attributeOfParent];
- righe 9-10: si vede che l'attributo [SomeParent::$attributeOfChild] non esiste. Non viene quindi visualizzato;
- righe 11-30: risultati del secondo test in cui viene richiamato il metodo [SomeChild::doTest];
- righe 13-14: si nota che l’attributo [SomeChild::$attributeOfParent] non esiste. Pertanto non viene visualizzato. È normale: l’attributo [SomeParent::$attributeOfParent] è [private] e quindi non è riconosciuto nella classe figlia;
- riga 15: visualizzazione dell’attributo [$attributeOfChild];
- righe 16-30: ci si trova nel metodo [SomeParent::doTest] chiamato dalla classe figlia;
- righe 17-22: si vede che [$this] è di tipo [SomeChild] con due attributi privati;
- riga 23: cosa davvero sorprendente, [$this], di tipo [SomeChild], vede qui l’attributo del genitore [$attributeOfParent];
- righe 25-30: altrettanto sorprendentemente, [$this], di tipo [SomeChild], non vede il proprio attributo [$attributeOfChild];
Questo risultato è davvero 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 una visibilità [protected] (riga 5):
<?php
class SomeParent {
// attributo
protected $attributeOfParent = 4;
// metodo
public function doTest(): void {
// chi lo chiama?
print "parent :\n";
var_dump($this);
// visualizzazione padre
print "parent : attributeOfParent={$this->attributeOfParent}\n";
print "parent : attributeOfChild={$this->attributeOfChild}\n";
}
}
class SomeChild extends SomeParent {
// attributo
private $attributeOfChild = 14;
// metodo
public function doTest(): void {
// visualizzazione figlio
print "child : attributeOfParent={$this->attributeOfParent}\n";
print "child : attributeOfChild={$this->attributeOfChild}\n";
// genitore
parent::doTest();
}
}
// script principale
print "---------------------------test1\n";
(new SomeParent())->doTest();
print "---------------------------test2\n";
(new SomeChild())->doTest();
Risultati
Commenti
- riga 12: la classe [SomeChild] ora vede l’attributo del suo genitore [$attributeOfParent]. Ciò è normale poiché quest’ultimo ha ora un ambito [protected];
- nel metodo [someParent::doTest], l’oggetto [$this] è di tipo [SomeChild] (righe 15-20). Vede l’attributo del suo genitore [$attributeOfparent] (riga 21) ma non vede ancora il proprio attributo [$attributeOfChild] (righe 23-28);
Nel terzo tentativo, anche l’attributo [$attributeOfChild] ha un ambito [protected]:
<?php
class SomeParent {
// attributo
protected $attributeOfParent = 4;
// metodo
public function doTest(): void {
// chi lo chiama?
print "parent :\n";
var_dump($this);
// visualizzazione padre
print "parent : attributeOfParent={$this->attributeOfParent}\n";
print "parent : attributeOfChild={$this->attributeOfChild}\n";
}
}
class SomeChild extends SomeParent {
// attributo
protected $attributeOfChild = 14;
// metodo
public function doTest(): void {
// visualizzazione figlio
print "child : attributeOfParent={$this->attributeOfParent}\n";
print "child : attributeOfChild={$this->attributeOfChild}\n";
// genitore
parent::doTest();
}
}
// script principale
print "---------------------------test1\n";
(new SomeParent())->doTest();
print "---------------------------test2\n";
(new SomeChild())->doTest();
I risultati dell’esecuzione sono i seguenti:
- 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 si può dedurre 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 nulla 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, vede:
- gli attributi e i metodi della classe padre se hanno almeno la 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.
- L’istanza [$this] di una classe figlia, utilizzata in un metodo della classe padre, vede:
- gli attributi e i metodi della classe padre, indipendentemente dalla loro visibilità;
- gli attributi e i metodi della propria classe solo se hanno almeno la visibilità [protected]. Quelli con visibilità [private] non vengono visualizzati;
Si tratta di un comportamento inaspettato.
5.19. Codifica jSON di una classe
In una classe è spesso presente il metodo [__toString]: dovrebbe restituire una stringa di caratteri che rappresenta l’oggetto che lo chiama. Si potrebbe essere tentati di far sì che questa stringa sia una stringa jSON. Esploriamo ora questa possibilità.

Utilizzeremo la seguente classe [Personne]:
<?php
class Personne {
// attributi
private $nom;
private $prénom;
private $âge;
private $enfants;
// setter globale
public function setFromArray(array $arrayOfAttributes): Personne {
// inizializzazione di alcuni attributi della classe
foreach ($arrayOfAttributes as $attribute => $value) {
$this->$attribute = $value;
}
// si restituisce l'oggetto
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 {
// si identifica l'oggetto
var_dump($this);
// si recuperano i suoi attributi
$attributes = \get_object_vars($this);
var_dump($attributes);
// si restituisce la stringa jSON degli attributi
return \json_encode($attributes, JSON_UNESCAPED_UNICODE);
}
}
Commenti
- righe 5-8: i quattro attributi della classe;
- righe 20-35: i getter che consentono di ottenere il valore di questi attributi;
- righe 11-18: un setter globale che consente di inizializzare gli attributi a partire da un array associativo [$arrayOfAttributes] con chiavi identiche agli attributi della classe;
- righe 38-46: il metodo [__toString] della classe;
- riga 42: la funzione PHP [get_object_vars] consente di ottenere il valore degli attributi della classe sotto forma di un array associativo [‘nome’=>’nome1’, ‘cognome’=>’cognome1’, ‘età’=>’età1’, ‘figli’=>[]];
- riga 45: si restituisce la stringa jSON di questo array di attributi;
Esaminiamo lo script [json-01.php] che utilizza la classe [Personne]:
<?php
// classe Persona
require "Personne.php";
// istanziazione del padre
$père = new Personne();
// inizializzazione del padre
$père->setFromArray([
"nom" => "Bertholomé",
"prénom" => "Dieudonné",
"âge" => 58
]);
// istanziazione e inizializzazione di figlio1
$enfant1 = (new Personne())->setFromArray([
"nom" => "Bertholomé",
"prénom" => "Sylvain",
"âge" => 17
]);
// istanza e inizializzazione del figlio2
$enfant2 = (new Personne())->setFromArray([
"nom" => "Bertholomé",
"prénom" => "Géraldine",
"âge" => 12
]);
// inizializzazione dei figli del padre
$père->setFromArray([
"enfants" => [$enfant1, $enfant2]
]);
// visualizzazione degli elementi del padre
$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: si inizializza un oggetto [Personne] [$père] con il metodo [Personne::setFromArray] che consente diinizializzare un oggetto [Personne] con un array le cui chiavi corrispondono agli attributi della classe [Personne];
- righe 14-19: si inizializza un oggetto [Personne] [$enfant1] allo stesso modo;
- righe 21-25: si inizializza un oggetto [Personne] [$enfant2];
- righe 27-29: si inizializza l'attributo [$père→enfants] con un array contenente i due figli;
- righe 32-33: si assegnano i due figli al padre;
- riga 35: l’operazione [print] cercherà di trasformare l’oggetto [$enfant1] in una stringa di caratteri. A tal fine, utilizza il metodo [__toString] dell’oggetto. Ci si aspetta quindi di vedere la stringa jSON dell’oggetto;
- righe 38-39: si procede allo stesso modo con il genitore;
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 [$enfant1];
- righe 12-21: la tabella degli attributi dell’oggetto [$enfant1]. Sono presenti tutti;
- riga 22: è presente la stringa jSON dell’oggetto [$enfant1];
- righe 23-44: lo stesso vale per l’oggetto [$enfant2];
- righe 45-112: per il padre è leggermente diverso, poiché il suo attributo [enfants] non è NULL come lo era per i figli;
- riga 112: si nota che nella stringa jSON del padre mancano i figli;
- righe 79-111: si nota che nella tabella degli attributi del padre, il figlio 1 (righe 89-98) è rimasto un oggetto, così come il figlio 2 (righe 99-110). In altre parole, l’espressione [\get_object_vars($this)], dove [$this] rappresenta il padre, non è ricorsiva: se un attributo della classe [Personne] è a sua volta un oggetto, l’espressione [\get_object_vars($this)] non tenta di ottenere il suo array di attributi;
È possibile migliorare la situazione. Modifichiamo la classe [Personne] nella seguente classe [Personne2]:
<?php
class Personne2 {
// attributi
private $nom;
private $prénom;
private $âge;
private $enfants;
// setter globale
public function setFromArray(array $arrayOfAttributes): Personne2 {
…
// si restituisce l'oggetto
return $this;
}
// getters
public function getNom() {
return $this->nom;
}
…
// __toString
public function __toString(): string {
// si recuperano gli attributi dell'oggetto
$attributes = $this->getAttributes($this);
$enfants = $attributes["enfants"];
if ($enfants != NULL) {
$attributes["enfants"] = [$enfants[0]->getAttributes(), $enfants[1]->getAttributes()];
}
// si restituisce la stringa JSON degli attributi
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: si recuperano gli attributi della classe [Personne] nell’array [$attributes];
- riga 28: dall’esempio precedente si sa che [$attributes["enfants"]] è un array di due oggetti di tipo [Personne];
- righe 29-31: i due oggetti vengono sostituiti dal loro array di attributi;
- riga 33: non resta che codificare in jSON l’array di attributi costruito;
Lo script [json-02.php] utilizza la classe [Personne2] nel modo seguente:
<?php
// classe Persona2
require "Personne2.php";
// istanziazione del padre
$père = new Personne2();
// inizializzazione
$père->setFromArray([
"nom" => "Bertholomé",
"prénom" => "Dieudonné",
"âge" => 58
]);
// istanziazione e inizializzazione di figlio1
$enfant1 = (new Personne2())->setFromArray([
"nom" => "Bertholomé",
"prénom" => "Sylvain",
"âge" => 17
]);
// istanza e inizializzazione del figlio2
$enfant2 = (new Personne2())->setFromArray([
"nom" => "Bertholomé",
"prénom" => "Géraldine",
"âge" => 12
]);
// inizializzazione dei figli del padre
$père->setFromArray([
"enfants" => [$enfant1, $enfant2]
]);
// visualizzazione del padre
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 [Personne2] ha sostituito la classe [Personne].
I risultati dell'esecuzione sono i seguenti:
Questa volta sono stati correttamente visualizzati i figli insieme al padre.
La soluzione precedente non è soddisfacente perché i figli possono a loro volta avere dei figli. Ci si ritrova quindi al problema precedente.
La classe [Personne3] risolve questo problema nel modo seguente:
<?php
class Personne3 {
// attributi
private $nom;
private $prénom;
private $âge;
private $enfants;
// setter globale
public function setFromArray(array $arrayOfAttributes): Personne3 {
…
}
// getter
…
// __toString
public function __toString(): string {
// si restituisce la stringa JSON degli attributi
$attributes = [];
$this->getRecursiveAttributes($attributes, $this, []);
// stringa JSON degli attributi
return \json_encode($attributes, JSON_UNESCAPED_UNICODE);
}
public function getAttributes(): array {
return \get_object_vars($this);
}
private function getRecursiveAttributes(array &$attributes, $value, $keys): void {
// analisi del valore [$value]
// $keys è un array [key1, key2, .., keyn]
// $value=$attributesQZXW2HTMLBW2tleTFdZQXQZXW2HTMLBW2tleTJdZQX….[keyn]
// se [$value] è un oggetto, si utilizza il suo metodo [getAttributes]
if (\is_object($value)) {
// attributi dell'oggetto [$value]
$objectAttributes = $value->getAttributes();
// Cosa si fa con il risultato?
if ($keys) {
// in [$attributes], si sostituirà $value con l’array dei suoi attributi
// bisogna costruire l'elemento $attributesQZXW2HTMLBW2tleTFdZQXQZXW2HTMLBW2tleTJdZQX…[keyn]
// dove $keys è l’array [key1, key2, .., keyn]
// si prende il riferimento della tabella [$attributes]
$attribute = &$attributes;
// si esegue la scansione della tabella delle chiavi
foreach ($keys as $key) {
// si prende il riferimento dell'attributo
$attribute = &$attribute[$key];
}
// qui $attribut e $attributesQZXW2HTMLBW2tleTFdZQXQZXW2HTMLBW2tleTJdZQX…[key(n)] sono identici
// e condividono la stessa posizione di memoria
// l’oggetto [$value] viene sostituito dal suo array di attributi;
// è necessario scrivere $attributesQZXW2HTMLBW2tleTFdZQXQZXW2HTMLBW2tleTJdZQX…[keyn]=$objectAttributes
// il che equivale a $attribute = $objectAttributes
$attribute = $objectAttributes;
} else {
// nessuna chiave - siamo all'inizio dell'esplorazione dell'oggetto
// $objectAttributes rappresenta gli attributi di primo livello della classe
$attributes += $objectAttributes;
}
// forse in [$objectAttributes] ci sono ancora degli oggetti
// si esaminano gli attributi di [$objectAttributes]
$this->getRecursiveAttributes($attributes, $objectAttributes, $keys);
} else {
if (\is_array($value)) {
// si ha un array: si analizzano tutti i suoi elementi
foreach ($value as $key => $élément) {
// si aggiunge la chiave corrente all'array $keys
\array_push($keys, $key);
// si analizza $élément
$this->getRecursiveAttributes($attributes, $élément, $keys);
// si rimuove dall'array $keys la chiave appena analizzata
\array_pop($keys);
}
}
}
}
Commenti
- righe 21-22: questa volta, il metodo [__toString] richiede gli attributi della propria classe e chiede che l’operazione venga eseguita 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 degli attributi della classe;
- righe 31-78: la funzione [getRecursiveAttributes] svolge questo compito. Il codice è stato commentato. Scrivere una funzione ricorsiva è spesso un’operazione complessa. È il caso anche qui. Il lettore non perderà nulla se non la capisce. Esistono librerie che svolgono questo lavoro. La chiamata ricorsiva avviene alle righe 64 e 72;
- il vantaggio di questo codice è che non è stato scritto esclusivamente per la classe [Personne3]. È valido per qualsiasi classe che abbia attributi con valori di tipi di oggetti diversi, purché le classi utilizzate dalla classe principale dispongano, come essa, del metodo [getAttributes] delle righe 27-29
Lo script [json-03.php] utilizza la classe [Personne3] nel modo seguente:
<?php
// classe Persona3
require "Personne3.php";
// istanziazione del padre
$père = new Personne3();
// inizializzazione
$père->setFromArray([
"nom" => "Bertholomé",
"prénom" => "Dieudonné",
"âge" => 58
]);
// istanziazione e inizializzazione di figlio1
$enfant1 = (new Personne3())->setFromArray([
"nom" => "Bertholomé",
"prénom" => "Sylvain",
"âge" => 27
]);
// istanziazione e inizializzazione di figlio2
$enfant2 = (new Personne3())->setFromArray([
"nom" => "Bertholomé",
"prénom" => "Géraldine",
"âge" => 12
]);
// inizializzazione dei figli del padre
$père->setFromArray([
"enfants" => [$enfant1, $enfant2]
]);
// istanziazione e inizializzazione del figlio11
$enfant11 = (new Personne3())->setFromArray([
"nom" => "Bertholomé",
"prénom" => "Gaëtan",
"âge" => 2
]);
// istanziazione e inizializzazione del figlio12
$enfant12 = (new Personne3())->setFromArray([
"nom" => "Bertholomé",
"prénom" => "Mathilde",
"âge" => 1
]);
// inizializzazione dei figli di figlio1
$enfant1->setFromArray([
"enfants" => [$enfant11, $enfant12]
]);
// visualizzazione del padre
print "------------------------père\n";
print "père=$père\n";
- righe 30-45: si assegnano due figli a [$enfant1];
I risultati dell’esecuzione sono i seguenti:
Se si formatta questo risultato, si ottiene 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 correttamente le stringhe jSON da tutti gli oggetti [Personne] che costituiscono il genitore.