Come estendere una classe in PHP ed il concetto di ereditarietà.
In un articolo precedente ci siamo già occupati di come estendere una classe e quindi della visibilità delle proprietà e metodi di una classe (public, private e protected), premessa fondamentale di questo argomento.
Con questo nuovo articolo ho voluto integrare questo argomento con nuovi esempi per spiegare ancora meglio come estendere una classe tenendo conto dei vincoli legati alla visibilità delle proprietà e dei metodi.
Partiamo da questo:
<?php
class ParentClass{
public $proprieta1 = 1;
private $proprieta2 = 2;
protected $proprieta3 = 3;
}
class ChildClass extends ParentClass{
}
$obj = new ChildClass();
echo $obj->proprieta1;
?>
Estendere una classe significa acquisire metodi e proprietà di un'altra classe padre, estendone le funzionalità.
Nel nostro esempio, inizializziamo la classe ChildClass, e lo script renderà il numero 1: questo perchè la classe child non sta ancora implementando nulla.
Mentre nell'esempio successivo, la ChildClass sovrascive (facciamo un OVERRIDE) il valore della proprietà $proprieta1, definita nella classe padre, per cui lo script renderà il numero 16
<?php
class ParentClass{
public $proprieta1 = 1;
private $proprieta2 = 2;
protected $proprieta3 = 3;
}
class ChildClass extends ParentClass{
public $proprieta1 = 16;
}
$obj = new ChildClass();
echo $obj->proprieta1;
?>
Nel nostro esempio abbiamo sovrascritto una proprietà di tipo public.
Ordine di visibilità di proprietà e metodi
Una classe child può accedere (e modificare) le proprietà della classe parent che hanno visibilità di tipo public e di tipo protected, ma NON alle proprietà con visibilità private.
Inoltre nella classe child la proprietà dovrà avere visibità superiore o uguale a quella della classe parent, per cui $proprieta1 nella child non potrà essere protected.
Ricordiamo che l'ordine di visibilità, dal maggiore al minore, è PUBLIC, PROTECTED, PRIVATE.
Vediamo un altro esempio in cui la classe parent ha un metodo "getProperty3" che rende il valore della proprietà "$proprietà3"
<?php
class ParentClass{
public $proprieta1 = 1;
private $proprieta2 = 2;
protected $proprieta3 = 3;
public function getProperty3() {
return $this->proprieta3;
}
}
class ChildClass extends ParentClass{
public $proprieta1 = 16;
}
$obj = new ChildClass();
echo $obj->getProperty3();
?>
Lo script restituirà il numero 3. Adesso facciamo un override della proprietà "$proprieta3", che ha visibilità protected e per questo possiamo farlo.
<?php
class ParentClass{
public $proprieta1 = 1;
private $proprieta2 = 2;
protected $proprieta3 = 3;
public function getProperty3() {
return $this->proprieta3;
}
}
class ChildClass extends ParentClass{
public $proprieta1 = 16;
protected $proprieta3 = 24;
}
$obj = new ChildClass();
echo $obj->getProperty3();
?>
Il risultato dello script è il numero 24: la classe child ha infatti sovrascritto il valore di $proprieta3 per cui il metodo parent getProperty3 renderà quel valore.
Quindi, ricapitolando, se hai una proprietà con visibilità public o protected, puoi sovrascriverne il valore dalla classe child, mentre non puoi fare un override della proprietà $proprieta2 perchè questa proprietà è di tipo private.
Procediamo con un'altro esempio, in cui la classe child contiene lo stesso metodo getProperty3 presente nella classe parent.
<?php
class ParentClass{
public $proprieta1 = 1;
private $proprieta2 = 2;
protected $proprieta3 = 3;
public function getProperty3() {
return $this->proprieta3;
}
}
class ChildClass extends ParentClass{
public $proprieta1 = 16;
protected $proprieta3 = 24;
public function getProperty3() {
return parent::getProperty3();
}
}
$obj = new ChildClass();
echo $obj->getProperty3();
?>
Dopo aver instanziato l'oggetto, quando chiamiamo getProperty3 contenuto nella classe child, che a sua volta richiama l'analogo metodo getProperty3 presente nella classe parent: non viene quindi aggiunta alcuna funzionalità alla classe parent. Il risultato sarà sempre 24.
Vediamo adesso di estenderne le funzionalità: facciamo in modo che la getProperty3 risponda con una frase
<?php
class ParentClass{
public $proprieta1 = 1;
private $proprieta2 = 2;
protected $proprieta3 = 3;
public function getProperty3() {
return $this->proprieta3;
}
}
class ChildClass extends ParentClass{
public $proprieta1 = 16;
protected $proprieta3 = 24;
public function getProperty3() {
$result = parent::getProperty3();
return "il risultato è ".$result;
}
}
$obj = new ChildClass();
echo $obj->getProperty3();
?>
Lo script adesso renderà "il risultato è 24": abbiamo così esteso il metodo getProperty3.
Così come per le proprietà, una classe child può accedere (e modificare) i metodi della classe parent che hanno visibilità di tipo public e di tipo protected, ma NON ai metodi con visibilità private. E non è neanche possibile nella classe child utilizzare una visibilità inferiore. Per cui nella child il medoto getProperty3 non potrà essere protected: otterremo un errore.
Type hinting di metodi e proprietà
Se utilizziamo il type hinting, occorrerà estendere un metodo o proprietà usando lo stesso type hinting.
Ad esempio questo script renderebbe errore
<?php
class ParentClass{
public $proprieta1 = 1;
private $proprieta2 = 2;
protected $proprieta3 = 3;
public function getProperty3(): string {
return $this->proprieta3;
}
}
class ChildClass extends ParentClass{
public $proprieta1 = 16;
protected $proprieta3 = 24;
public function getProperty3() {
$result = parent::getProperty3();
return "il risultato è ".$result;
}
}
$obj = new ChildClass();
echo $obj->getProperty3();
?>
Lo script, per essere corretto, devi indicare "string" come type hinting per entrambi i metodi
<?php
class ParentClass{
public $proprieta1 = 1;
private $proprieta2 = 2;
protected $proprieta3 = 3;
public function getProperty3(): string {
return $this->proprieta3;
}
}
class ChildClass extends ParentClass{
public $proprieta1 = 16;
protected $proprieta3 = 24;
public function getProperty3(): string {
$result = parent::getProperty3();
return "il risultato è ".$result;
}
}
$obj = new ChildClass();
echo $obj->getProperty3();
?>
Lo stesso vale anche per gli argomenti dei metodi. Ad esempio se nella classe parent abbiamo un metodo "printText" con un argomento con type hinting "string", per estendere questo metodo nella classe child, devo obbligatoriamente accettare un argomento di tipo "string".
<?php
class ParentClass{
public $proprieta1 = 1;
private $proprieta2 = 2;
protected $proprieta3 = 3;
public function getProperty3(): string {
return $this->proprieta3;
}
public function printText(string $text) {
echo $text;
}
}
class ChildClass extends ParentClass{
public $proprieta1 = 16;
protected $proprieta3 = 24;
public function getProperty3(): string {
$result = parent::getProperty3();
return "il risultato è ".$result;
}
public function printText(string $text) {
echo "ciao da ".$text;
}
}
$obj = new ChildClass();
echo $obj->printText("Giulio");
?>
Quindi ricapitolando, il metodo dichiarato in una classe child (il numero di argomenti, il tipo di argomenti, ...) deve essere dichiarato nello stesso modo con cui è dichiato nella classe parent.
Come sovrascrivere una costante
Anche le costanti, di cui ci siamo già occupati in un articolo precedente, possono essere sovrascritte.
Ad esempio, se abbiamo una costante nella classe parent, possiamo ridichiarla senza problemi nella classe child
<?php
class ParentClass{
const MIA_COSTANTE = '3';
}
class ChildClass extends ParentClass{
const MIA_COSTANTE = '6';
}
$obj = new ChildClass();
echo ChildClass::MIA_COSTANTE;
?>
Facciamo attenzione al livello di visibilità della costante.
Se ad esempio nel parent la costante ha visibilità private, nella child potrà essere solo public, perchè protected è un livello inferiore rispetto a private.
In questo esempio, lo script è corretto perchè la funzione getConstant della classe child fa rifrimento alla costante presente nel parent che ha visibilità protected
<?php
class ParentClass{
protected const MIA_COSTANTE = '3';
}
class ChildClass extends ParentClass{
public function getConstant(){
return self::MIA_COSTANTE;
}
}
$obj = new ChildClass();
echo $obj->getConstant();
?>
Questo script invece produce un errore, perchè la costante nel parent ha visibilità private per cui non è accessibile dal child
<?php
class ParentClass{
private const MIA_COSTANTE = '3';
}
class ChildClass extends ParentClass{
public function getConstant(){
return self::MIA_COSTANTE;
}
}
$obj = new ChildClass();
echo $obj->getConstant();
?>
Come impedire di estendere un classe: final class
Se non vogliamo che un classe possa essere estesa dobbiamo precederne il nome con la chiave final.
Questo esempio renderà un errore perchè stiamo cercando di estendere la classe ParentClass che è dichiarata come "final" per cui non estendibile.
<?php
final class ParentClass{
private const MIA_COSTANTE = '3';
}
class ChildClass extends ParentClass{
public function getConstant(){
return self::MIA_COSTANTE;
}
}
$obj = new ChildClass();
echo $obj->getConstant();
?>
Questo final si può applicare anche ai metodi, come in questo esempio che produrrà un errore perchè abbiamo dichiarato il metodo hello del parent come final, ed abbiamo tentanto di estenderlo dalla classe child.
<?php
class ParentClass{
final public function hello() {
echo "ciao da Giulio";
}
}
class ChildClass extends ParentClass{
final public function hello() {
echo "ciao da Elisa";
}
}
$obj = new ChildClass();
echo $obj->hello();
?>
Potrebbe interessarti
- Le interfacce in PHP
- Le classi astratte e i metodi astratti, in PHP
- Le costanti di una classe in PHP
- Le proprietà e i metodi STATICI in PHP
- Il metodo costruttore in PHP
- Autoloading classi in PHP, con un occhio ai namespace ed allo standard di programmazione psr-4.
- Cos'è il type hinting, la sua evoluzione nelle varie versioni di PHP, ed il controllo sulla tipologia di dato con la modalità strict_mode.
- I metodi magici in PHP.
- Cosa sono i namespace in PHP. La guida completa, con esempi.
- Cosa sono i trait in PHP. La guida completa con esempi.
- Le classi in PHP. introduzione alla programmazione in stile OOP, metodi e proprietà di una classe.