====== 04 - PHP POO II: Namespaces ====== Los espacios de nombres (**namespaces**) son una manera de encapsular elementos. Se pueden ver como un concepto abstracto en muchos aspectos. Por ejemplo, en cualquier sistema operativo, los directorios sirven para agrupar ficheros relacionados, actuando así como espacios de nombres para los ficheros que contienen. Como ejemplo, el fichero //foo.txt// puede existir en los directorios ///home/greg// y ///home/otro//, pero no pueden coexistir dos copias de //foo.txt// en el mismo directorio. Además, para acceder al fichero //foo.txt// fuera del directorio ///home/greg//, se debe anteponer el nombre del directorio al nombre del fichero, empleando el separador de directorios (/) para así obtener ///home/greg/foo.txt//. Este mismo principio se extiende a los espacios de nombres en el mundo de la programación. En el mundo de PHP, los espacios de nombres están diseñados para solucionar dos problemas con los que se encuentran los autores de bibliotecas y de aplicaciones al crear elementos de código reusable, tales como clases o funciones: * El conflicto de nombres entre el código que se crea y las clases/funciones/constantes internas de PHP o las clases/funciones/constantes de terceros. * La capacidad de apodar (o abreviar) Nombres_Extra_Largos diseñada para aliviar el primer problema, mejorando la legibilidad del código fuente. ===== Definir namespaces ===== Los espacios de nombres se declaran utilizando la palabra reservada **namespace**. Un fichero que contenga un espacio de nombres debe declararlo al inicio del mismo, antes que cualquier otro código, con una excepción: la palabra reservada //declare//. Además, todo lo que no sea código de PHP no puede preceder a la declaración del espacio de nombres, incluyendo espacios en blanco extra. También, a diferencia de otras construcciones de PHP, se puede definir el mismo espacio de nombres en varios ficheros, permitiendo la separación del contenido de un espacio de nombres a través del sistema de ficheros. Al igual que los directorios y ficheros, los espacios de nombres de PHP también tienen la capacidad de especificar una jerarquía de nombres de espacios de nombres. Así, un nombre de un espacio de nombres se puede definir con subniveles: namespace miProyecto\Sub\Nivel; ===== Uso de namespaces ===== Se puede hacer una simple analogía entre los espacios de nombres de PHP y el sistema de ficheros. Existen tres maneras de acceder a un fichero en el sistema de ficheros: * Nombre de fichero relativo como //foo.txt//. Se resuelve con //directorio_actual/foo.txt// donde directorio_actual es el directorio actualmente ocupado. Así, si el directorio actual es ///home/foo//, el nombre se resuelve con ///home/foo/foo.txt//. * Nombre de ruta relativa como //subdirectorio/foo.txt//. Se resuelve con //directorio_actual/subdirectorio/foo.txt//. * Nombre de ruta absoluta como ///main/foo.txt//. Se resuelve con ///main/foo.txt//. Se puede aplicar el mismo principio a los elementos del espacio de nombres de PHP. Por ejemplo, supongamos que tenemos dos carpetas en llamadas //carpeta1// y //carpeta2//. Dentro de carpeta1 tenemos dos archivos (//archivo1.php// y //archivo2.php//) y una carpeta llamada //subcarpeta1//. Ésta última carpeta (//subcarpeta1//) tendrá otro archivo llamado //archivo3.php//. Por último, tenemos el archivo //archivo4.php// dentro de la carpeta //carpeta2//. En principio, todos los archivos tendrán el mismo contenido: el //namespace// al que pertenece y una función llamada //hola()//. Para simplificar, el //namespace// será el nombre de la carpeta donde está ubicado el archivo: //carpeta1// para los archivos 1 y 2, //carpeta1\subcarpeta1// para el archivo 3 y //carpeta2// para el archivo 4. La función hola mostrará la frase “Hola mundo del nombre_archivo”. Por ejemplo, el código de //archivo1.php// será: namespace carpeta1; function hola(){ echo "Hola mundo de archivo 1
"; }
Vamos a incluir //archivo2.php// en //archivo1.php// y ejecutar la función //hola()//: namespace carpeta1; include "./archivo2.php"; function hola(){ echo "Hola mundo de archivo 1
"; } hola();
Fatal error: Cannot redeclare carpeta1\hola() (previously declared in /var/www/html/ejemplos/T10/carpeta1/archivo1.php:8) in /var/www/html/ejemplos/T10/carpeta1/archivo2.php on line 5 Obviamente, nos da un error, ya que estamos definiendo dos veces la misma función: //hola()//. Si te das cuenta, el error nos dice que no puede redeclarar la función //**carpeta1\**hola()//, con lo que, si estuvieran en espacios de nombre diferentes no tendríamos ningún problema. Cambiamos el nombre de la función de //archivo2.php// a //hola2()//, añadimos //archivo3.php// y ejecutamos las 3 funciones: namespace carpeta1; include "./archivo2.php"; include "./subcarpeta1/archivo3.php"; function hola(){ echo "Hola mundo de archivo 1
"; } hola(); hola2(); subcarpeta1\hola();
Hola mundo de archivo 1 Hola mundo de archivo 2 Hola mundo de archivo 3 Igual que en los sistemas de ficheros, PHP entenderá que lo que ejecutamos en la última línea pertenece al //namespace// //carpeta1\subcarpeta1// (es similar a utilizar rutas relativas en los sistemas de ficheros). Por último, vamos a añadir //archivo4// y ejecutar su función. En este caso tendremos que indicar el //namespace// completo, ya que //carpeta2// no es un subnivel del //namaspace carpeta1// (sería como utilizar rutas absolutas en un sistema de ficheros). Para hacerlo, debemos empezar el //namespace// de la función a ejecutar con una barra invertida. namespace carpeta1; include "./archivo2.php"; include "./subcarpeta1/archivo3.php"; include "../carpeta2/archivo4.php"; function hola(){ echo "Hola mundo de archivo 1
"; } hola(); hola2(); subcarpeta1\hola(); \carpeta2\hola();
Hola mundo de archivo 1 Hola mundo de archivo 2 Hola mundo de archivo 3 Hola mundo de archivo 4 ===== Importar namespaces ===== Uno de los usos más habituales de los //namespaces// es junto a la POO, para utilizar clases definidas en otros archivos. El problema es que tienes que anteponer el //namespace// al que pertenece esa clase siempre que quieras utilizarla. Por ejemplo, supongamos que tenemos 1 archivo en //carpeta2// un archivo llamado //ClaseA.php// con una clase definida con solo su constructor: namespace carpeta2; class ClaseA { function __construct() { echo "objeto de la clase miClase creado"; } } Si queremos utilizar esa clase en //archivo1.php// tenemos que incluir el archivo donde está definida y usar //\carpeta2\ClaseA// cada vez que la referenciemos. namespace carpeta1; include "./archivo2.php"; include "./subcarpeta1/archivo3.php"; include "../carpeta2/archivo4.php"; include "../carpeta2/ClaseA.php"; function hola(){ echo "Hola mundo de archivo 1
"; } hola(); hola2(); subcarpeta1\hola(); \carpeta2\hola(); $objetoA = new \carpeta2\ClaseA();
Podemos simplificar el proceso importando la clase con su espacio de nombre usando la palabra reservada **use**. De esta forma, ya no hará falta anteponer el //namespace// al que pertenece esa clase: namespace carpeta1; use carpeta2\ClaseA; include "./archivo2.php"; include "./subcarpeta1/archivo3.php"; include "../carpeta2/archivo4.php"; include "../carpeta2/ClaseA.php"; function hola(){ echo "Hola mundo de archivo 1
"; } hola(); hola2(); subcarpeta1\hola(); \carpeta2\hola(); $objetoA = new ClaseA();
También podemos utilizar un alias para la clase importada usando la palabra reservada **as**. namespace carpeta1; use carpeta2\ClaseA as OtraClase; include "./archivo2.php"; include "./subcarpeta1/archivo3.php"; include "../carpeta2/archivo4.php"; include "../carpeta2/ClaseA.php"; function hola(){ echo "Hola mundo de archivo 1
"; } hola(); hola2(); subcarpeta1\hola(); \carpeta2\hola(); $objetoA = new OtraClase();
Como veremos en las prácticas, podemos combinar un autocargador de clases con los //namespaces// para no tener que incluir los archivos manualmente. Ya se encarga el //autoload// de cargarlos según su //namespace//.