Categorie
PHP

PHP – I namespaces

I namespaces, sono un’importante novità, da tempo agognata e promessa, e finalmente disponibile da PHP 5.3, colmando così una della lacune che vengono ancora rinfacciate al linguaggio.
Anche se questa caratteristica è ormai disponibile da qualche tempo, probabilmente ancora non tutti ne hanno capito a pieno l’importanza e magari, continuano ad utilizzare le varie soluzioni alternative che erano nate in attesa della loro implementazione.

Cosa sono i namespaces?

I namespaces, forniscono un modo in cui raggruppare classi, funzioni e costanti correlate tra loro al fine di evitare una collisione di denominazione. Si potrà così avere due classi con lo stesso nome in due posti diversi se vengono incapsulate all’interno dei namespaces. La documentazione ufficiale li definisce infatti così:

What are namespaces? In the broadest definition namespaces are a way of encapsulating items.

Il loro scopo principale è quello di risolvere il problema degli autori di librerie o applicazioni quando creano elementi riutilizzabili come classi o funzioni. Cosa succederebbe se aggiungessimo una libreria esterna ad un nostro progetto, e la libreria avesse all’interno classi denominate allo stesso modo delle nostre? Stesso discorso per progetti di grosse dimensioni, sviluppati da più persone e composti da centinaia se non migliaia di componenti. La possibilità di scontri di denominazione sarebbe molto alta. Bene i nemespaces servono proprio ad evitare questo problema.

Alternative

Prima della loro implementazione, gli sviluppatori hanno adottato tecniche di emulazione, soprattutto riguardo la programmazione object-oriented. Una standard piuttosto comune, per esempio, è quello adottato da Zend Framework 1 in cui il problema della collisione di denominazione viene risolto attraverso nomi di classe extra lunghi, in cui viene anteposto al nome della classe i nomi delle directories in qui essa si trova, separate da un underscore es: My_Controller_Helper_Acl (in My\Controller\Helper\Acl.php). Utilizzando questa tecnica naturalmente i nomi di classe sono univoci, ma a volte tendono a risultare esageratamente lunghi.

Difinizione

I namespaces sono definiti attraverso la parola chiave namespace seguita da un nome.

<?php
namespace Test; 
//...

Un file che ne contiene uno deve dichiararlo in cima alla pagina, prima di qualsiasi altra riga di codice, ad eccezione dei commenti e della parola chiave declare:

<?php
/**
 * Questa è una dichiarazione di namespace con commento,  
 * spazi bianchi e declare per stabilire la codifica del file
 */

declare(encoding='UTF-8');
namespace Test; 
//...

All’interno di un namespace possiamo raggruppare classi, interfacce, funzioni e costanti.
Una volta dichiarato, l’ambito del namespace è applicato all’intero file.

<?php
namespace Test; 

const TEST_CON = '1';
function testFun () {}
class testClass {} 
?>

I Namespaces che contengono parole chiave PHP genereranno un errore di analisi:

<?php
/**
 * Class e Function sono parole chiave PHP e non possono essere 
 * utilizzati per nominare un namespace
 */
namespace Function; // errore di analisi
namespace Project\Class; //errore di analisi

Infine i namespaces non possono iniziare con un numero:

<?php
namespace Test\Sub\1Level; // errore
namespace Test\Sub\Level1; // namespace valido

Sub-namespace

Come per directories e files, i namespaces possono essere definiti con sotto-livelli. Il seguente codice produrrà la costante Test\Sub\Level\TEST_CON, la funzione Test\Sub\Level\testFun e la classe Test\Sub\Level\testClass.

<?php
namespace Test\Sub\Level; 

const TEST_CON = '1';
function testFun () {}
class testClass {} 
?>

Namespaces nello stesso file

Anche se sconsigliato è possibile dichiarare più namespace nello stesso file. In questo caso è buona norma utilizzare la sintassi a parentesi graffe:

<?php
namespace Test {
    const TEST_CON = '1';
    function testFun () {}
    class testClass {}
}

namespace Test2 {
    const TEST_CON = '2';
    function testFun () {}
    class testClass {}
}
?>
Nota: Non è possibile mischiare namespace dichiarati con sintassi senza parentesi con namestace dichiarati con sintassi a parentesi graffe.

Per combinare, nello stesso file, codice di uno spazio di nomi e codice globale dobbiamo utilizzare la sintassi con parentesi. Il codice globale, dovrebbe essere racchiuso in una dichiarazione namespace senza nome:

<?php
namespace Test {
    const TEST_CON = '1';
    function testFun () {}
    class testClass {}
}

namespace {
    session_start();
    Test\Bis\testFun();
}
?>
Nota: In caso di multi-namespaces nello stesso file l’ambito di ogni namespace termina alla dichiarazione del successivo.
Nota:La definizione di sub-namespaces con blocchi di codice annidati non è supportata. Il seguente codice genererà un errore fatale: “Namespace declarations cannot be nested”.

<?php
namespace Test {
    namespace Sub {
        class TestClass { }
    }
}

Utilizzo dei namespace

Dopo averli definiti dobbiamo capire come utilizzarli realmente. Questa è probabilmente la parte più importante da capire. In questo ci può aiutare il filesystem, difatti la logica è praticamente la stessa.
Prendiamo come esempio il seguente file test\sub\level\myFile.php.

  • Posso accedervi attraverso il nome, se la directory corrente fosse level allora potrei accedere al file semplicemente attraverso il nome: myFile.php.
  • Posso accedervi attraverso percorso relativo, se mi trovassi dentro sub potrei accedervi con: level\myFile.php.
  • Posso accedervi attraverso percorso assoluto, qualunque sia la directory corrente, con: \test\sub\level\myFile.php.

Lo stesso principio lo possiamo applicare con i namespaces, avremo quindi le seguenti tre possibilità:

  • Unqualified name: (nomi non qualificati) si riferiscono solo al namespace corrente, se per esempio mi trovo nel namespace Test, allora testFun() sarà risolto con Test\testFun(). In pratica sono privi di un namespace separator (\).
  • Qualified name: (nomi qualificati) paragonabile al percorso relativo del filesystem. Per esempio se il namespace corrente fosse Test\Sub, Level\testFun() sarebbe risolto con: Test\Sub\Level\testFun().
  • Fully-qualified name: (nomi completamente qualificati) paragonabile al percorso assoluto, è probabilmente il modo più sicuro ed inequivocabile anche se più prolisso. Per esempio \Test\Sub\Level\testFun() cercherà la funzione testFun() nel namespace Test\Sub\Level. In pratica iniziano sempre con un namespace separator.

Facciamo un esempio, consideriamo il file file1.php:

<?php
//file1.php
namespace Test\Sub\Level; 

const TEST_CON = '1';
function testFun () {}
class testClass {}
?>

Qua abbiamo definito una costante un funzione ed una classe nel namespace Test\Sub\Level.
Ora consideriamo il file file2.php:

<?php
//file2.php
namespace Test\Sub;
require_once 'file1.php';
const TEST_CON = '2';
function testFun () {}
class testClass {}

// Unqualified name
echo TEST_CON;      // risolto in Test\Sub\TEST_CON
testFun();      // risolto in Test\Sub\testFun
$a = new testClass();     // risolto in Test\Sub\testClass

// Qualified name
Level\TEST_CON;     // risolto in Test\Sub\Level\TEST_CON
Level\testFun();      // risolto in Test\Sub\Level\testFun

// Fully-qualified names
\Test\Sub\testFun();        // risolto in Test\Sub\testFun
\Test\Sub\Level\testFun();  // risolto in Test\Sub\Level\testFun
?>

Riferirsi all’ambito globale

Una volta dichiarato un namespace, se si ha la necessita di riferirsi a classi o inerfacce globali bisogna anteporre al nome un backslash. Questo invece non è necessario per le funzioni e le costanti, sempre che queste non siano state dichiarate a loro volta nel namespace. Es:

<?php
namespace Test\Sub;

function strtoupper ($string) {
    return $string;
}

/* Risolto in Test\Sub\strtoupper() */
echo strtoupper('ciao'); //ciao
/* Risolto nella funzione nativa */
echo \strtoupper('ciao'); //CIAO
/* Risolto nella funzione nativa visto che Test\Sub\strtolower() non esiste */
echo strtolower('CIAO'); //ciao

//Fatal error: Class 'Test\Sub\mysqli' not found in...
$mysqli = new mysqli("host", "user", "psw", "db");

//Ok
$mysqli = new \mysqli("host", "user", "psw", "db");
?>

Importazione di namespaces

Attraverso la parola chiave use, possiamo importare un namespace in un file diverso.
Questa è una caratteristica che ci permetterà poi di far riferimento al namespace importato attraverso un alias anzichè con suo nome completo, rendendoci la vita più facile soprattutto per namespace profondamente nidificati.

<?php
namespace Test\Sub;

use Test\Sub\Level;  //identico a scrivere Test\Sub\Level as Level

Level\testFun(); //chiama la funzione Test\Sub\Level\testFun
$a = new Level\testClass(); //instanzia un oggetto della classe Test\Sub\Level\testClass
?>

E’ possibile importare namespace, classi ed interfacce, ma non funzioni o costanti.

<?php
use Test\Sub\Level\testClass;

$a = new testClass();
?>

Naturalmente una situazione del genere non è valida…

<?php
use Test\Sub\Level\testClass;
use Test2\Sub2\Level2\testClass;

$a = new testClass();
$b = new testClass();
?>

…in quanto non è chiaro quale classe testClass deve essere istanziata:
Fatal error: Cannot use Test2\Sub2\Level2\testClass as testClass because the name is already in use in…
In questi casi (ma anche in altri) può tornarci comodo fornire noi stessi un alias al namespace:

<?php
use Test\Sub\Level\testClass as classe1;
use Test2\Sub2\Level2\testClass as classe2;

$a = new classe1;
$b = new classe2;
?>

E’ supportata anche questa sintassi di dichiarazione multipla:

<?php
use Test\Sub\Level\testClass as classe1, 
        Test2\Sub2\Level2\testClass as classe2;
//..
Nota: Non è possibile utilizzare Fully-qualified nameper riferirsi a degli alias. Il seguente codice produrrà un errore fatale in quanto come detto cercherà la classe nell’ambito globale: 

<?php
use Test\Sub\Level\testClass;

$a = new \testClass();
?>

Namespaces dinamici

PHP è un linguaggi dinamico per esempio ci permette di istanziare classi o chiamare funzioni dinamicamente:

<?php
class test {
    public function __construct() {
        echo  __CLASS__;
    }
}

$class = 'test';
$a = new $class; //test
?>

I namespaces non ne fanno eccezione possiamo utilizzare indifferentemente sia qualified che fully-qualified name, non vi è nessuna differenza:

<?php
namespace Test\Sub\Level;

const TEST_CON = '1';
function testFun () {
    return __FUNCTION__;
}
class testClass {
    public function __construct() {
        echo  __METHOD__;
    }
}

//utilizzo qualified name
echo constant('Test\Sub\Level\TEST_CON'); //1

// utilizzo fully-qualified name
$ns    = 'Test\Sub\Level';
$class = $ns . '\testClass';
$fun   = $ns . '\testFun';

echo $fun(); //Test\Sub\Level\testFun
$a = new $class; //Test\Sub\Level\testClass::__construct
?>

Dobbiamo ricordarci tuttavia che possiamo utilizzare soltanto nomi completi:

<?php
//file1.php
namespace Test\Sub\Level;

const TEST_CON = '1';
?>
<?php
//file2.php
namespace Test\Sub;
require_once 'file1.php';

echo Level\TEST_CON; //1
echo constant('Level\TEST_CON'); //errore
echo constant('Test\Sub\Level\TEST_CON'); //1
?>

Sono inoltre supportati altri due metodi per accedere agli elementi del namespace corrente in maniera astratta. Questi sono la parola chiave namespace e la costante magica __NAMESPACE__.

<?php
namespace Test\Sub\Level;
const TEST_CON = '1';
class testClass {

    function __construct() {
        echo __CLASS__;
    }

}

echo namespace\TEST_CON; //1

echo __NAMESPACE__; //Test\Sub\Level

$class = __NAMESPACE__ . '\testClass';
$a = new $class; //Test\Sub\Level\testClass
?>

La parola chiave namespace è un equivalente dell’operatore self delle classi, mentre la costante __NAMESPACE__ è una stringa contenente il nome del namespace corrente.
In ambito globale conterrà una stringa vuota.

Riepilogo

I namespace sono quindi un’importante caratteristica di PHP. Essi ci permettono di:

  1. Raggruppare classi, funzioni e costanti al fine di evitare una collisione di denominazione.
  2. Evitare nomi di classi molto lunghi, facilitando la lettura del codice sorgente.

Sono contemplate tre modalità di risoluzione:

  1. Unqualified name: Un identificatore senza nessun separatore namespace, es: test.
  2. Qualified name: Un identificatore con almeno un separatore namespace che non sia la prima lettera es: test\sub.
  3. Fully qualified name: Un identificatore che inizia con un separatore namespace, es \test\sub (anche namespace\test è un fully qualified name).

Questo è in definitiva tutto quello che è necessario sapere per iniziare ad utilizzarli nelle proprie applicazioni.

Risorse

www.php.net/manual/en/language.namespaces.php

8 risposte su “PHP – I namespaces”

Infatti, è davvero l’unico articolo chiaro e completo. Ora ho imparato cosa sono i namespace, e grazie a questo posso cominciare a studiare Symfony 🙂

E a distanza di altri 3 anni confermo quanto detto sopra: continua ad essere il più chiaro e completo.

Ma esiste un modo del tipo:
use Namespace\*
per includere tutti i componenti del namespace con il loro effettivo nome (senza specificare Namespace\…) ???

Articolo ben fatto e completo, finalmente non solo un copia e incolla di “sono introdotti con il  php 5.3” e basta come fanno tutti

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.