Cosa sono i namespace in PHP. La guida completa, con esempi.
I namespaces sono stati introdotti in PHP nella versione 5.3, e permettono di raggruppare classi, interfacce, funzioni e costanti correlate tra loro al fine di evitare una collisione di denominazione. In questo modo potremo far coesistere classi con lo stesso nome inserite ("incapsulate") in namespace differenti, esattamente come avviene per file con lo stesso nome posti in cartelle diverse del filesystem.
Ad esempio, in una directory "/var/www/html/test" abbiamo il file "prova1.php" che contiene una classe "Prova"
<?php
class Prova{
public function m1(){
echo "ciao dal metodo ".__METHOD__." della classe <b>".__CLASS__;
}
}
?>
ed un file "prova2.php" che contiene anch'esso una classe "Prova" analoga alla precedente
Abbiamo inoltre una index.php, in cui includiamo entrambi i file.
<?php
include "prova1.php";
include "prova2.php";
?>
Il risultato sarà un fatal error, perchè le due classi presentano lo stesso nome.
Fatal error: Cannot declare class Prova, because the name is already in use in /var/www/html/test/prova2.php on line 2
Pensiamo per un istante alle due classi "Prova" come se fossero due file di un filesystem: due file con lo stesso nome non possono coesistere all'interno della stessa directory, ma devo essere localizzati in directory differenti per mantenere lo stesso nome.
I namespace ci consentono di superare problema di onomimie fra classi introducendo un sistema simile a quanto faremmo in un filesystem. Sono una sorta di filesystem "virtuale".
All'interno dei file "prova1.php" e "prova2.php" che contengono le classi, dobbiamo indicare dove le classi contenute al loro interno sono localizzate in questo filesystem virtuale.
E lo facciamo inserendo la parola chiave namespace seguita dal nome, arbitrario, che assegnato a questa directory (spazio).
Modifichiamo il file "prova1.php" in questo modo, inserendo il namespace "Spazio1". La classe Prova sarà quindi definita all'interno del namespace "Spazio1".
<?php
namespace Spazio1;
class Prova{
public function m1(){
echo "ciao dal metodo ".__METHOD__." della classe <b>".__CLASS__;
}
}
?>
Nel file "prova2.php" inseriamo un namespace "Spazio2". La classe Prova, di questo file, sarà quindi definita all'interno del namespace "Spazio2".
<?php
namespace Spazio2;
class Prova(){
public function m1(){
echo "ciao dal metodo ".__METHOD__." della classe <b>".__CLASS__;
}
}
?>
Rilanciamo la "index" e.... l'errore sarà sparito!
Adesso, all'interno della "index" proviamo a creare una istanza della classe "Prova"
<?php
include "prova1.php";
include "prova2.php";
$obj=new Prova;
?>
Lanciamo la "index" e .... errore fatale!!
Fatal error: Uncaught Error: Class 'Prova' not found in /var/www/html/test/index.php:5 Stack trace: #0 {main} thrown in /var/www/html/test/index.php on line 5
La classe non viene trovata, e questo perchè nel file delle classe ho dichiarato che le stesse "risiedono" all'interno di uno specifico namespace, una "directory virtuale": devo indicare in quale "directory virtuale" si trova la classe "Prova".
Se voglio istanziare un oggetto della classe "Prova" localizzata nel namespace "Spazio1" scriverò
$obj=new Spazio1\Prova;
Mentre per istanziare un oggetto della classe "Prova" localizzata nel namespace "Spazio2" scriverò
$obj=new Spazio2\Prova;
Proviamolo nella nostra "index", andando ad instanziare due oggetti delle due classi, specificando il rispettivo namespace
<?php
include "prova1.php";
include "prova2.php";
$obj=new Spazio1\Prova;
$obj2=new Spazio2\Prova;
$obj->m1();
echo "<br>";
$obj2->m1();
?>
Lanciamo la "index" ed ecco il risultato
ciao dal metodo Spazio1\Prova::m1 della classe Spazio1\Prova
ciao dal metodo Spazio2\Prova::m1 della classe Spazio2\Prova
Abbiamo potuto utilizzare due classi omonime!
Pensiamo quindi a tutti quei casi in cui il nostro sito ha già delle librerie con classi, ed aggiungiamo una nuova libreria che contiene classi con nomi già presenti nelle nostre librerie: i namespace ci consentono si superare i conflitti di denominazione.
I namespace consentono di scrivere codice ordinato ed organizzato, e di far lavorare più persone allo stesso progetto senza il rischio che si dichiarino classi con lo stesso nome.
Il namespace non è sempre e solo un nome come "Spazio1". Abbiamo equiparato i namespace a directory per cui anche per questi possiamo avere una alberatura.
Ad esempio, abbiamo definito uno spazio "App\Backend\User\Sicurezza\Controlli" con all'interno la classe "Password" ed il metodo "randomPassword" che genera un numero randomico
<?php
namespace App\Backend\User\Sicurezza\Controlli;
class Passwd{
public static function randomPassword(int $lunghezza){
// creo stringa casuale
$rnd=md5(time());
// mescolo casualmente i caratteri
$rnd=str_shuffle($rnd);
// ritorno la pwd
return substr($rnd,0,$lunghezza);
}
}
?>
Nel file "index", che include questo file, creiamo un oggetto di classe Passwd, indicando il suo namespace completo, e poi chiamiamo il metodo randomPassword.
<?php
include "prova3.php";
$obj=new App\Backend\User\Sicurezza\Controlli\Passwd;
echo $obj->randomPassword(12);
?>
Gli alias
Nell'esempio precedente, il namespace ha una lunghezza notevole. PHP consente di definire degli alias e renderne più pratico il loro utilizzo.
Un alias si definisce con la parola chiave use seguito dal namespace e dal nome scelto per l'alias. Così facendo, quando istanzio un oggetto della classe farò riferimento non al namespace completo ma solo al suo alias, come in questo esempio
<?php
include "prova3.php";
use App\Backend\User\Sicurezza\Controlli as MyAlias;
$obj=new MyAlias\Passwd;
echo $obj->randomPassword(10)."<br>";
echo $obj->randomPassword(7)."<br>";
echo $obj->randomPassword(20)."<br>";
echo $obj->randomPassword(3);
?>
Abbiamo richiamiamo il metodo randomPassword più volte, ed il risultato è stato:
41b504102b
4a2db25
7661026f85724d2a1708
d24
Possiamo utilizzare l'alias in modo ancora più semplice, senza utilizzare la parola chiave as
<?php
include "prova3.php";
use App\Backend\User\Sicurezza\Controlli;
$obj=new Controlli\Passwd;
echo $obj->randomPassword(10)."<br>";
echo $obj->randomPassword(7)."<br>";
echo $obj->randomPassword(20)."<br>";
echo $obj->randomPassword(3);
?>
Vincoli nell'utilizzo dei namespace
I namespace vanno inseriti immediatamente dopo l'apertura del codice php, nessun codice deve precederlo, tranne il caso di commenti ed eventuali istruzioni di tipo declare come in questo esempio.
<?php
declare(encoding='UTF-8');
namespace Spazio1;
class Prova(){
public function m1(){
echo "ciao dal metodo ".__METHOD__." della classe <b>".__CLASS__;
}
}
?>
Inoltre i nomi dei namespace non possono iniziare con un numero, ad esempio questo il namespace "1Spazio" non è consentito:
namespace 1Spazio;
Non possono contenere keywords del linguaggio php, come ad esempio "Class" o "Function":
namespace Spazio1\Class;
Global Space ed utilizzo dei namespace
Soffermiamoci adesso sull'utilizzo dei namespace e ripartiamo dalla similitudine tra namespace e filesystem.
Abbiamo un file "statistiche.php" dove abbiamo definito un classe "Statistiche". Al suo interno abbiamo anche istanzito un oggetto di classe "Statistiche"
<?php
class Statistiche{
public function myStat() {
echo "ciao";
}
}
$obj = new Statistiche;
?>
In questo file non abbiamo indicato il namespace. Quando non viene indicato un namespace, si dice che questa classe è inserita in uno spazio globale, o Global Space: è come se ci trovassimo nella root (radice) del nostro filesystem virtuale.
Creiamo un secondo file "index.php", dove indichiamo il namespace "App\Backend", che include il file precedente, e istanziamo un oggetto di classe "Statistiche".
<?php
namespace App\Backend;
require_once('statistiche.php');
$obj = new Statistiche;
$obj->myStat();
?>
Proviamo a lanciare questo file ed apparirà questo errore fatale:
Fatal error: Class 'App\Backend\Statistiche' not found in /var/www/html/test/index.php on line 5
La classe "Statistiche" non è stata trovata perchè in questo file ci troviamo nello spazio "App\Backend" mentre la classe "Statistiche" si trova nella root "\": ci troviamo quindi in una "cartella" diversa!
Per accedere ad una classe definita in uno spazio globale dobbiamo indicare un namespace separator prima della classe, cioè una backslash, cioè dobbiamo entrare nella root.
Aggiungiamo il namespace separator nella nostra "index.php" e così siamo nella root e possiamo accedere alla classe "Statistiche":
<?php
namespace App\Backend;
require_once('statistiche.php');
$obj = new \Statistiche;
$obj->myStat();
?>
Rilanciamo il file e non avremo errori, bensì la risposta "ciao" del metodo "myStat".
In questa situazione, abbiamo fatto riferimento alla classe "Statistiche" utilizzando un FULLY QUALIFIED NAME, cioè utilizzando all'inizio un namespace separator (una backslash) seguito dal nome della classe.
E' come se utilizzassi un percorso assoluto, per cui saranno FULLY QUALIFIED NAME tutti questi esempi:
\Esempio
\Esempio\Prova
\Esempio\Prova\Livello
L'importante è che si parta dal Global Space, cioè dalla root "\"
Esistono anche i QUALIFIED NAME e gli UNQUALIFIED NAME, e li vediamo subito.
All'interno del file "statistiche.php" avevamo instanziato un oggetto di classe "Statistiche" in questo modo:
$obj = new Statistiche;
In questo caso parliamo di UNQUALIFIED NAME, in quanto non abbiamo inserito alcun namespace separator (nessuna backslash).
Per vedere i QUALIFIED NAME modifichiamo i nostri script.
Nel file "statistiche.php" indichiamo il namespace "App\Backend\Info": la classe "Statistiche" adesso sarà all'interno di questo namespace
<?php
namespace App\Backend\Info;
class Statistiche{
public function myStat() {
echo "ciao";
}
}
?>
Se rilanciamo la "index" riceveremo un fatal error in quando la classe "Statistiche" non viene giustamente trovata, perchè è in un namespace differente.
La classe "Statistiche" è in "App\Backend\Info", mentre io sto richiamando la classe da "App\Backend", per cui dovrò procedere ragionando come per i percorsi relativi: sono già nella cartella "App\Backend", mi basta entrare (in modo quindi relativo) alla cartella "Info" e così potrò utilizzare la classe.
<?php
namespace App\Backend;
require_once('statistiche.php');
$obj = new Info\Statistiche;
$obj->myStat();
?>
In questo caso abbiamo fatto riferimento alla classe "Statistiche" utilizzando un QUALIFIED NAME, perchè contiene dei namespace separator, ma non inizia con un namespace separator.
Si potrebbe anche indicare quando segue
$obj = new namespace\Info\Statistiche;
e, con questa scrittura, la parola chiave "namespace" indica il namespace in cui ci troviamo, e cioè "App\Backend"
Avrei potuto anche inserire un FULLY QUALIFIED NAME ma sarebbe stato inutile, a meno che, nella mia incertezza su cosa usare, avessi voluto andare sul sicuro, e cioè tutto il percorso assoluto.
<?php
namespace App\Backend;
require_once('statistiche.php');
$obj = new \App\Backend\Info\Statistiche;
$obj->myStat();
?>
Ricapitolando, è possibile fare riferimento al nome di una classe in tre modi:
- Con un Qualified Name (nomi qualificati): è paragonabile al percorso relativo, rispetto al namespace in cui mi trovo, e contiene almeno un namespace, ma non all'inizio.
- Con un Fully-qualified Name (nomi completamente qualificati): inizia sempre con un backslash ed è paragonabile ad un percorso assoluto, quindi più lungo di un percorso relativo, ma siamo certi di non commettere errori relativamente ai percorsi
- Con un Unqualified Name (nomi non qualificati): non contiene namespace
Vantaggi nell'utilizzo dei namespace
Ricapitoliamo i principali motivi per i quali si utilizzano i namespace:
- Permettono di raggruppare classi, interfacce, funzioni e costanti per evitare una collisione di denominazione, cioè problemi di omonimia (detta "collisione di denominazione").
- Permetteno di utilizzare degli alias, facilitando così la lettura del codice sorgente.
Potrebbe interessarti
- Le interfacce in PHP
- Le classi astratte e i metodi astratti, in PHP
- Le costanti di una classe in PHP
- Come estendere una classe in PHP ed il concetto di ereditarietà.
- 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 trait in PHP. La guida completa con esempi.
- Le classi in PHP. introduzione alla programmazione in stile OOP, metodi e proprietà di una classe.