Introduciamo le notazioni più importanti della programmazione ad oggetti in PHP. Lasciamo ogni considerazione sulla OOP e la sua necessità di fronte a progetti complessi. A livello scolastico, la OOP in PHP raramente viene affrontata, anche dai manuali scolastici, poiché ci si concentra sulla regole di base dello scripting, della connessione col db e query varie.
In realtà, anche scomodando le ultime versioni di PHP 8, la notazione di una classe è molto semplice se si è già studiato uno o più linguaggi di programmazione OOP come C++ o Java. Diciamo che PHP ha sintassi molto simile e, spesso, regole meno stringenti dei fratelli compilati.
Diciamo che didatticamente è relativamente interessante studiare quindi la versione ad oggetti di PHP se non per introdurre uno dei framework citati anche nei nostri precedenti articoli (qui), come Symphony o Laravel che fanno di PHP un uso massiccio in OOP e pattern MVC.
Come dichiarare una classe
Non perdiamoci ulteriormente in chiacchiere e vediamo subito un esempio. Creiamo un file persona.php. Qui diversamente da Java, non è vincolante inserire una classe per file, rispettando il nome del file e della classe con le medesime maiuscole. Qualche manuale o software suggerisce di creare i file delle classi, diversamente da quelli di sopo/funzioni, con una sintassi tipo class.nomeclasse.php per identificare meglio il contenuto. Ovviamente sta al lettore e al contesto di uso cosa scegliere.
<?php
class Persona
{
private $nome;
private $cognome;
function setNome($_nome)
{
$this->nome = $_nome;
}
function getNome()
{
return $this->nome;
}
}
Come notiamo, esistono ancora attributi che possono essere pubblici, privati o protetti come Java, le funzioni di accesso e l’uso della parola $this che agisce un po’ come il this di C++/Java anche se con una sintassi leggermente diversa per colpa della freccia e della possibile confusione del $ che si mette davanti al this ma non all’attributo puntato. Per testarla possiamo fare subito una riga di codice sotto la classe, in PHP non esiste di fatto un main eseguibile, ma è buona prassi separare le responsabilità.
Ancora, agli attributi è possibile assegnare un valore sin da subito nella classe, ma è sempre sconveniente farlo.
<?php
include "persona.php";
$pippo = new Persona();
$pippo->setNome("Alfredo");
echo $pippo->getNome();
Contrariamente a molti manuali online, preferisco sempre delimitare bene l’uso di pubblico sugli attributi e rispettare sempre e comunque le regole di buon senso dell’incapsulamento.
Il costruttore
Facciamo un passetto in più e vediamo un costruttore e un metodo accessorio. In PHP esiste il costruttore vuoto ma solo finché non ne viene definito uno parametrico. Se è presente uno costruttori con parametri, quello vuoto non può essere ridichiarato se non con uno stratagemma farraginoso quanto inutile, soprattutto didatticamente ( c’è la funzione func_num_args() che conteggia i parametri passabili ad una funzione ad esempio). Stessa identica cosa, contrariamente a Java e C++, non possiamo definire overloading di più costruttori con parametri differenti e neanche di metodi! Vale anche per i metodi accessori: se ho bisogno di definire funzionalità simili ma con parametri differenti, devo necessariamente definire funzioni diverse. Sembra un limite forte, pur sempre aggirabile, usando parametri come vettori, ma tutto sommato accettabile. Il metodo stampa è stato realizzato in modo che restituisca un insieme di tag html da inserire nel punto giusto eventualmente.
<?php
class Persona
{
private $nome;
private $cognome;
private $sesso;
function setNome($_nome)
{
$this->nome = $_nome;
}
function getNome()
{
return $this->nome;
}
public function __construct($_nome, $_cognome)
{
$this->nome = $_nome;
$this->cognome = $_cognome;
$this->sesso = 'M';
}
public function stampa()
{
$stampa = "<label>Nome: </label> $this->nome <br>";
$stampa .= "<label>Cognome: </label> $this->cognome <br>";
return $stampa;
}
}
inseriamo tutto nella classe main, con tanto di diciture per effettuare il debug
<?php
ini_set ( 'display_errors', 1 );
error_reporting ( E_ALL );
include "persona.php";
$pluto = new Persona("Mario", "Rossi");
echo $pluto->stampa();
Ereditarietà e polimorfismo
Abbiamo già visto che il PHP non sia così esaltante come linguaggio ad oggetti, soprattutto se il programmatore viene da un’esperienza con linguaggi OOP decisamente strutturati come Java, C# o C++ ad esempio. Consoliamo subito il lettore:Java, C++, C# sono linguaggi basati sulle classi, richiedono una sintassi più stringente. PHP, come i citati JS e Python, è un linguaggio basato su prototipi, decisamente meno vincolante. Non è un caso se gli ultimi citati sono linguaggi di scripting mentre i precedenti sono compilati o giù di li.
Riproponiamo la classe Persona leggermente diversa per permetterci alcune considerazioni. A seguire la classe Professore che estende Persona.
<?php
class Persona
{
private $nome;
protected $cognome;
public $sesso;
public function setNome($_nome)
{
$this->nome = $_nome;
}
public function getNome()
{
return $this->nome;
}
public function setCognome($_cognome)
{
$this->nome = $_cognome;
}
public function getCognome()
{
return $this->nome;
}
public function setSesso($_sesso)
{
$this->nome = $_sesso;
}
public function getSesso()
{
return $this->nome;
}
public function __construct($_nome, $_cognome, $_sesso = 'M')
{
$this->nome = $_nome;
$this->cognome = $_cognome;
$this->sesso = $_sesso;
}
public function stampa()
{
$stampa = "<h1>".__CLASS__."</h1>";
$stampa .= "<label>Nome: </label> $this->nome <br>";
$stampa .= "<label>Cognome: </label> $this->cognome <br>";
$stampa .= "<label>Sesso: </label> $this->sesso <br>";
return $stampa;
}
}
E quindi vediamo la classe figlia. Come nei linguaggi più blasonati, la parola chiave extends ci consente di effettuare la magia dell’ereditarietà.
<?php
include "persona.php";
class Professore extends Persona
{
private $materia = "Informatica";
public function __construct($_nome, $_cognome, $_sesso, $_materia)
{
parent::__construct($_nome, $_cognome, $_sesso);
$this->materia = $_materia;
}
function setMateria($_materia)
{
$this->nome = $_materia;
}
function getMateria()
{
return $this->materia;
}
public function stampa()
{
$stampa = "<h1>"__CLASS__."</h1>";
$stampa .= "<label>Nome: </label> ".parent::getNome()." <br>"; //private
$stampa .= "<label>Cognome: </label> $this->cognome <br>"; //è protected
$stampa .= "<label>Sesso: </label> $this->sesso <br>"; //è public
$stampa .= "<label>Materia: </label> $this->materia <br>"; //è public
return $stampa;
}
}
Intanto se le classi fossero dichiarate nello stesso file, sarebbero immediatamente visibili, mentre se le realizziamo in file separati bisogna ricordarsi di fare un include/require.
Concentriamoci sul costruttore con il nome chiave __construct. Come abbiamo già citato, continua a non essere ammissibile l’overloading nella stessa classe, ma possiamo ridefinirlo in funzione degli attributi aggiuntivi. Occhio quindi al costruttore e che parametri fargli passare. All’interno troviamo la dicitura parent:: che il lettore più attento avrà già capito che richiama il costruttore del padre per la componente di sottoparametri già previsti nella porzione gerarchica superiore.
I metodi di accesso dei nuovi attributi non hanno nessuna conseguenza ovviamente mentre il metodo stampa è decisamente molto interessante per comprendere le dinamiche dell’ereditarità. Innazi tutto, il nome: possiamo fare l’overriding padre figlio ma se senza cambiare il numero di parametri: non è concesso. Se avessimo bisogno di un numero di parametri differenti dovremmo prevedere un nuvo metodo, ad esempio non più stampa($parametro) ma in caso una stampa_aggiuntivo($parametro) a fianco dello stampa() originario.
I codici del metodo stampa vediamo essere quindi diversi. Mentre nel padre possiamo accedere direttamente ai propri attributi, nel figlio dobbiamo tener conto delle diciture private/protected/public. Tutto quello che è public è ereditato e utilizzabile dai figli ed esternamente alla classe. Va utilizzato con parsimonia per preservare l’incapsulamento. Protected ha analogia con public ma non è esposto all’etesrno uiqndi, gli attributi possono essere utilzzati direttamente nei fili. Una pratica comada e spesso utilizzata soprattutto se i lcodice delle classi gerarchiche è realizzato dallo stesso programamtore/team. Qui lo vediamo con $this->cognome. L’attributo privato viene ripescato col parent perchè deve essere ripescato col metodo di accesso ma presente nella classe parent.
Abbiamo aggiunto la dicitura con la variabile __CLASS__ che ci indica quale classe nella gerarchia sta chiamando il metodo stampa(). Mancano all’appello metodi e attributi statici che vedremo in altri articoli per comodità.
Una classe main per provare le nostre classi potebbe essere la seguente:
<?php
include "professore.php";
$pippo = new Persona("Mario","Rossi","M");
echo $pippo->stampa();
echo "<hr>";
$sofia = new Professore("Sofia","Centnaro","F", "Storia");
echo $sofia->stampa();
L’esempio visto, ci tengo a precisare, essere molto didattico. Un metodo stampa() simile non tiene conto della separazione MVC dove la visualizzazione non dovrebbe essere “innestata” nel codice backend, soaprattutto interno alla classe. Vedemo assieme alle query come sia più opportuno esporre i dati con array o JSON per lasciare la stampa al codice Javascript o altro sistema template.
Ultima modifica 29 Marzo 2023