martes, 20 de agosto de 2013

Espacio de nombres (namespaces) en PHP

En un proyecto grande, el número de clases aumenta con facilidad. Y según se van definiendo más clases, y sobre todo si hay diferentes programadores en el desarrollo, es mas probable que se produzcan colisiones entre nombres de dichas clases o funciones. Hasta PHP 5.3 una solución empleada en muchos proyectos era utilizar nombres de clases muy largos para evitar conflictos. Con la llegada de los espacios de nombres, en PHP 5.3, podemos agrupar clases y funciones en contenedores con identificadores. De forma que no haya conflictos con clases y funciones que se llamen igual en los diferentes contenedores (espacios de nombres).

Definir un espacio de nombres

Si no se indica lo contrario, todas las clases y funciones serán definidas en un entorno global. Con la posibilidad de que haya las colisiones de nombres comentadas anteriormente. Así que vamos a empezar a definir un espacio de nombres. Para definir uno, simplemente tenemos que utilizar la palabra reservada namespace seguida del nombre identificador del espacio de nombres. Además la definición de un espacio de nombres es lo primero que tiene que aparecer en el script. Posteriormente ya se pueden hacer los 'includes' necesarios.

Lo habitual es definir un espacio de nombres por script, declarándolo al principio del mismo. Sin embargo es posible definir varios de ellos en un mismo script.

 namespace primer;  
 class Test{  
 }  
 class Prueba{  
 }  
 namespace segundo;  
 class Test{  
 }  

Hay que tener en cuenta que el contenido de los diferentes espacios de nombres, diferentes definidos en un mismo script, se puede introducir entre corchetes. Pero no se pueden mezclar espacios de nombres con corchetes y otros sin ellos. En caso contrario se mostrará el siguiente error:
Fatal error: Cannot mix bracketed namespace declarations with unbracketed namespace declarations in...

 namespace Test{   
   class Prueba {  
     public function __construct() {  
     }  
   }  
 }  
 namespace Test2 {  
   class Prueba {  
     public function __construct() {  
     }  
   }  
 }  

Si queremos una mejor organización de las clases, podemos crear una organización jerárquica con los nombres de espacios. Ya que podemos anidar espacios de nombres. Para ello simplemente utilizaremos la barra invertida '\' para separar sub-espacios de nombres.

 <?php  
 namespace MyProyecto\Espacio1;  
 class Database{  
 }  
 class Usuario{  
 }  
 ?>  

 <?php  
 namespace MyProyecto\Espacio2;  
 class Articulo{  
 }  
 ?>  

 <?php  
 namespace MyProyecto\Utiles;  
 class Debug{  
 }  
 ?>  

De la misma forma que se ha visto anteriormente, podemos definir sub-espacios de nombres en un mismo script.

Hemos comentado, que si no indicamos ningún espacio de nombres, el código pertenecerá al espacio de nombres global. Ahora bien, tenemos la posibilidad de que una parte del script definido con un espacio de nombres concreto pertenezca al espacio de nombres global. Para ello usaremos la palabra reservada namespace sin nombre identificador. Y encerraremos el código de cada espacio de nombres, el definido y el global, entre corchetes:

 <?php  
 namespace Prueba\Ejemplo{  
   class Test{  
   }  
   function prueba(){  
   }  
 }  
 namespace{  
   class Coche{  
   }  
 }  

Acceder a un espacio de nombres

Dependiendo de nuestra posición (espacio de nombres actual) en la aplicación, hay 3 formas diferentes de acceder al nombre de una clase o función definida en un espacio de nombres. Imaginemos que en el espacio de nombres Proyecto\Blog\Admin esta definida la clase Usuario.

ejemplo.php
 <?php  
 namespace Proyecto\Blog\Admin;  
 class Usuario{ 
    private $nombre;
    function getNombre(){
        return $this->nombre;
    }  
    function setNombre($name){
        $this->nombre = $name;
    }
 }  
 ?>  

1. Si queremos utilizar las clase Usuario y si estamos en un espacio de nombres que no está dentro de la jerarquía que contiene dicha clase, necesitamos utilizar la jerarquía completa para poder acceder a ella. A esta forma se le llama  nombre completamente cualificado. Y es análogo a utilizar una ruta absoluta en un sistema de ficheros.

 <?php 
 namespace Prueba;
 include './ejemplo.php';
 $usuario = new \Proyecto\Blog\Admin\Usuario();
 var_dump($usuario);
 ?>  

Recuerda que si estamos en un espacio de nombres particular pero utilizamos namespace sin nombre identificador seguido de corchetes, estamos accediendo al espacio de nombres global.

 <?php  
 namespace Prueba{  
   include './ejemplo.php';
   //...  
 }  
 namespace{  
   $usuario = new \Proyecto\Blog\Admin\Usuario();  
 }  
 ?>  

Si queremos  utilizar una función del espacio de nombres global y esta se llama del mismo modo que una de las definidas en el espacio de nombres actual, usaremos un nombre completamente cualificado. Recuerda que el primer '\' representa el inicio en la jerarquía.

 namespace Prueba;  
 function strlen($cadena) {  
   echo $cadena . "<br/>";  
 }  
 echo strlen('hola'); // llama a la función strlen del namespace actual  
 echo \strlen('hola'); // llama a la función global strlen  

Si no hay conflictos de nombres, para acceder a la función del espacio de nombres global, no será necesario ninguna referencia a ningún espacio de nombres.

 namespace Prueba;  
 function strlen_prueba($cadena) {  
   echo $cadena . "<br/>";  
 }  
 echo strlen('hola');   

2. Si estamos dentro de algún nivel en la jerarquía que contiene a la clase solicitada, no necesitamos utilizar la ruta completa si no que utilizaremos una ruta relativa al espacio de nombres en el que nos encontremos. A esta forma de acceso se le llama nombre cualificado.

  namespace Proyecto\Blog;   
  include './namespaces.php';  
  $usuario = new Admin\Usuario();   
  var_dump($usuario);  

3. Si estamos en el espacio de nombres actual, utilizamos la clase como siempre la habíamos utilizado hasta ahora, sin hacer referencia a ningún espacio de nombres. A esta forma de acceso se le llama nombre no cualificado.

  namespace Proyecto\Blog\Admin;   
  include './namespaces.php';  
  $usuario = new Usuario();   
  var_dump($usuario);  

Importando un espacio de nombres

Como hemos explicado anteriormente, si estamos en un 'namespace' ajeno jerárquicamente al
al que necesitamos, podemos utilizar un nombre completamente cualificado para acceder a una clase de dicho espacio de nombres.

 <?php  
  namespace Prueba;   
  require './ejemplo.php';//script con definición de clase Usuario   
  $usuario = new \Proyecto\Blog\Admin\Usuario();   
  var_dump($usuario);  
 ?>  

Pero de esta forma, si en nuestro ejemplo tenemos que instanciar numerosas veces a la clase Usuario, tenemos que escribir toda la jerarquía: new \Proyecto\Blog\Admin\Usuario() numerosas veces. Esto es muy incomodo y por eso los espacios de nombres permiten su importación.
Para importar un espacio de nombres se utiliza la palabra reservada use, seguida del espacio de nombres a importar.

 <?php  
  namespace Prueba;   
  require './ejemplo.php';  
  use Proyecto\Blog\Admin;  
  $usuario = Admin\Usuario();   
  var_dump($usuario);  
 ?>  

La utilización del comando use no cambia el espacio en el que trabajamos. Simplemente nos permitirá utilizar implementaciones de otros espacios de nombres haciendo uso del nombre cualificado (ruta relativa). Sin importar un espacio de nombre, utilizábamos un nombre completamente cualificado (ruta absoluta).
En un script se pueden importar tantos espacios de nombres como se quieran.

Alias de un espacio de nombres

Además de importar un espacio de nombres, podemos utilizar un alias del mismo para acortar las llamadas a los recursos. Para ello utilizaremos la palabra reservada as tras la importanción del espacio de nombres:

 use espacioDeNombresImportar as alias;  

  namespace Prueba;   
  require './namespaces.php';  
  use Proyecto\Blog\Admin as Ad;  
  $usuario = new Ad\Usuario();   
  var_dump($usuario);  

Pero si queremos evitar tener que llamar la ruta relativa desde el alias, podemos optar por importar directamente la clase.  Y si queremos  le aplicaremos un alias.

  namespace Prueba;   
  require './namespaces.php';  
  use Proyecto\Blog\Admin\Usuario;  
  $usuario = new Usuario();   
  var_dump($usuario);  

  namespace Prueba;   
  require './namespaces.php';  
  use Proyecto\Blog\Admin\Usuario as clase;  
  $usuario = new clase();   
  var_dump($usuario);  

__NAMESPACE__ y namespace

La constante __NAMESPACE__ contiene el nombre del espacio de nombres actual. Si el espacio de nombres actual es el global, la constante devolverá una cadena vacía.

 namespace Prueba {  
   require './namespaces.php';  
   use Proyecto\Blog\Admin\Usuario as clase;  
   echo __NAMESPACE__ . "<br/>";  //Prueba
 }  
 namespace {  
   if (__NAMESPACE__ == "") echo "Espacio de nombres global";  
 }  

Los principales usos:
- Tareas de depuración de los scripts.
- Construcción de nombres completamente cualificados de forma dinámica.

La palabra reservada namespace, aparte de usarse para definir espacios de nombres, también puede ser usada para solicitar un recurso, de forma explicita, del espacio de nombres actual o de un espacio de nombres hijo.

 namespace Test {  
   class Prueba {  
     public function __construct() {  
       echo __NAMESPACE__;  
     }  
   }  
 }  
 namespace Test2 {  
   class Prueba {  
     public function __construct() {  
       echo __NAMESPACE__;  
     }  
   }  
   $p = new namespace\Prueba();//Test2  
 }  


Espacios de nombres y la autocarga

Como explicamos en el tutorial de la función mágica de autocarga, para evitar tener que hacer todas las inclusiones de los scripts para poder hacer uso de los recursos definidos en ellos, podemos hacer uso de esta función que intentará realizar la inclusión del recurso que necesitemos.

$obj1 = new Usuario(); // class/Usuario.php autocargada
$obj2 = new Usuario2(); // class/Usuario2.php autocargada
function __autoload($nombre_clase)   
 {  
   require_once 'class/' . $nombre_clase. '.php';  
 }  

Usar la función de autocarga de este modo implica colocar todas las clases en un directorio. A no ser que se quiera complicar mucho su implementación. Pero gracias a los espacios de nombres se podría optar por organizar la estructura de ficheros (scripts) de la misma forma que la estructura de espacios de nombres de la aplicación. Ya que mediante los espacios de nombres podemos pasar a la función mágica un nombre completamente cualificado y el nombre de una clase. Por ejemplo, en nuestro anterior función mágica $nombre_clase podría ser 'Proyecto\Blog\Admin\Usuario. Y dentro de la función convertir la ruta del espacio de nombres en una ruta de ficheros. Vamos a ver un ejemplo.

class/Proyecto/Blog/Admin/Usuario.php
 namespace Proyecto\Blog\Admin;  
 class Usuario{  
   private $nombre;  
   function getNombre(){  
     return $this->nombre;  
   }  
   function setNombre($name){  
     $this->nombre = $name;  
   }  
 }  

app.php
 use Proyecto\Blog\Admin\Usuario as usr;  
 $usuario = new usr();  
 $usuario->setNombre('Jose');  
 echo $usuario->getNombre() . "<br/>";  
 function __autoload($nombre_clase) {  
     echo $nombre_clase;//Proyecto\Blog\Admin\Usuario
      // convertimos espacio de nombres en ruta completa de ficheros       
     $clase = 'class/' . str_replace('\\', '/', $nombre_clase) . '.php';  
     echo "ruta clase: " . $clase . "<br/>";// class/Proyecto/Blog/Admin/Usuario.php  
     require_once($clase);  
 }  


Entradas relacionadas

PHP orientado a objetos - Introducción

No hay comentarios:

Publicar un comentario