Cómo usar los Namespaces en PHP 5.3
Una de las nuevas incorporaciones y de los cambios más significativos de PHP 5.3 son los Namespaces, nada nuevo para todos aquellos que hayan programado C# o Java, pero un cambio que mejorará la estructura de nuestros programas y que empezaremos a ver sobre todo en todos los grandes Frameworks (las nuevas versiones de Zend Framework, CakePHP, Doctrine y Symfony entre otros requerirán PHP 5.3).
¿Porqué necesitamos los Namespaces?
Según va aumentando el número de librerías escritas en PHP se hace mayor el riesgo de colisión de nombres de clases y funciones entre librerías. En un principio sería totalmente comprensible que diferentes programas implementen por ejemplo una clase llamada Photo que maneje las imágenes en el proyecto, pero eso hasta la llegada de PHP 5.3 estaba mal visto porque había que intentar evitar tener problemas de compatibilidad con otros proyectos. Por eso mismo era más probable que esa clase se llamara por ejemplo Mi_Proyecto_Photo.
Ejemplos de este tipo se pueden ver por ejemplo en WordPress dónde todas las clases empiezan por WP_ o en el framework Zend, donde la política de nombres (muy descriptiva) hace que se le asignen nombres a veces demasiado largos a las funciones: Zend_Search_Lucene_Analysis_Analyzer_Common_Text_CaseInsensitive.
Los Namespaces resuelven este problema porque las constantes, clases y funciones pueden quedar definidas dentro de su Namespace, sin entrar en conflicto con otros homónimos que estén dentro de otros namespaces.
¿Cómo se definen los Namespaces?
Por defecto, y si no se indica lo contrario, todas las funciones, clases y constantes quedarán definidas en el entorno global, como ha sido siempre en PHP. Será lo que pase con todas las librerías que carguemos y que no estén realizadas específicamente para PHP 5.3 o superior, y que por lo tanto no tendrán definido su namespace.
Pero si vamos a correr nuestro programa sobre PHP 5.3 o superior debemos empezar a definir los namespaces en nuestros programas y para ello simplemente hay que hacerlo utilizando la palabra clave namespace seguido del nombre al inicio de nuestro script; siempre debe ser la primera instrucción del fichero sin tener ningún otro código delante, ni espacios, ni HTML (excepto declare):
// Definición del namespace para el código de este script namespace MiProyecto; // A partir de aquí escribe tu código y domina el mundo
Lo lógico y lo recomendado es que utilices un fichero por cada namespace que definas, pero si por alguna extraña razón quisieras hacerlo varias veces en el mismo podrías de la siguiente manera:
// Definición del namespace para el proyecto 1
namespace MiProyecto1;
// Código del Proyecto1
// Definición del namespace para el proyecto 2
namespace MiProyecto2;
// Código del Proyecto2
// Definición del namespace para el proyecto 3 con la sintaxis alternativa
namespace MiProyecto3 {
// Código del Proyecto3
}
Sub-namespaces:
También podemos definir sub-namespaces para tener mejor estructurado nuestro código, o lo que es lo mismo, organizar jerarquicamente nuestras librerías o programas. Como separador a la hora de definir los namespaces y sus sub-namespaces utilizaremos la barra invertida: \. Ejemplos:
- MiProyecto\DB\MySQL
- MiProyecto\DB\Mongo
- MiProyecto\Contenido\Articulos
- OtraCompania\MiProyecto\Plugin\Comentarios
¿Cómo llamar a elementos que se encuentren dentro de un namespace?
Vamos a tomar como ejemplo la siguiente librería definida bajo el namespace Berriart\Sample y que guardaremos como berriart.sample.php:
<?php
// Código de la sección Sample del proyecto Berriart
namespace Berriart\Sample;
const MYCONST = 'Berriart\Sample\MYCONST';
function MyFunction() {
return __FUNCTION__;
}
class MyClass {
static function WhoAmI() {
return __METHOD__;
}
}
Ahora podríamos incluir este código desde otro script y ejecutarlo de la siguiente manera:
<?php
header('Content-type: text/plain');
require_once('berriart.sample.php');
echo \Berriart\Sample\MYCONST . PHP_EOL;
echo \Berriart\Sample\MyFunction() . PHP_EOL;
echo \Berriart\Sample\MyClass::WhoAmI() . PHP_EOL;
En este pequeño script hacemos uso de la función, la clase y la constante definidas en anterior fichero. Como éste no tiene namespace definido se está ejecutando en el entorno global, y por lo tanto para poder hacer uso de los elementos definidos en un namespace determinado los llamamos en este caso haciendo uso de la ‘ruta absoluta’ del namespace. El anterior código nos devolbería lo siguiente:
Berriart\Sample\MYCONST Berriart\Sample\MyFunction Berriart\Sample\MyClass::WhoAmI
Como os podéis imaginar, utilizar el nombre del namespace completo cada vez que llamamos a una función, clase o constante no nos aporta muchos más beneficios que los largos nombres descriptivos, pero no os preocupéis porque no es la única manera de llamarlos, y a continuación podemos ver todas las maneras de hacerlo. Las diferentes nomenclaturas son similares a las de los sistemas de ficheros:
Nombre completamente cualificado (Fully-qualified name) (~ruta absoluta)
Es la manera que hemos visto en el anterior ejemplo y consiste en utilizar el nombre completo del namespace. Para indicar que se trata de un nombre absoluto debemos empezar la llamada con una barra, ej:
$variable = \Berriart\Sample\MyClass::WhoAmI();
De esta manera no hay lugar a ambigüedades. Da igual si este código se encuentra dentro de algún namespace o en el espacio glogal, al estar escrita ‘la ruta completa’ siempre se hará la misma llamada. Pero como se puede apreciar generalmente no sería muy práctico.
Nombre cualificado (Qualified name) (~ruta relativa)
Un identificador con al menos un separador de namespace:
$variable = Sample\MyClass::WhoAmI();
Si el namespace actual fuera Berriart, esto se resolvería con Berriart\Sample\MyClass::WhoAmI():
<?php
namespace Berriart;
header('Content-type: text/plain');
require_once('berriart.sample.php');
echo Sample\MyClass::WhoAmI() . PHP_EOL;
// Devolvería:
// Berriart\Sample\MyClass::WhoAmI
Pero si el código es global, esto se resuelve con Sample\MyClass::WhoAmI() que en el caso del ejemplo al no existir devolvería error:
<?php
header('Content-type: text/plain');
require_once('berriart.sample.php');
echo Sample\MyClass::WhoAmI() . PHP_EOL;
// Devolvería error:
// Fatal error: Class 'Sample\MyClass' not found in...
Nombre no cualificado (Unqualified name) (~fichero relativo)
Nombre de clase, constante o función sin hacer referencia a ningún namespace:
$variable = MyClass::WhoAmI();
Si el código en el que esto se ejecuta es global, no se encuentra en un namespace, esto se resuelve con la clase MyClass que también debería estar definida en el espacio global y no dentro de un namespace. En cambio si la llamásemos dentro de un namespace primero buscaría dentro de ese namespace y luego en el espacio global.
Importando namespaces
Los namespaces se pueden importar mediante el comando use. La utilización del comando use no cambia el espacio en el que trabajamos, si estamos en el espacio global seguiremos en él, y si estamos dentro de un namespace porque estamos usando la palabra clave ‘namespace’ dentro de nuestro script, también seguiremos en él; pero nos permitirá hacer uso de cosas de otros namespaces haciendo uso del nombre cualificado.
<?php
header('Content-type: text/plain');
require_once('berriart.sample.php');
use Berriart\Sample;
echo Sample\MyClass::WhoAmI() . PHP_EOL;
// Devolvería:
// Berriart\Sample\MyClass::WhoAmI
Se pueden importar tantos namespaces como se quieran haciendo uso del comando use en repetidas ocasiones o separandolos con comas en la misma instrucción.
Es importante remarcar que en este ejemplo que hemos puesto seguimos en el espacio global por lo que no se podría llamar directamente a MyClass::WhoAmI() (de manera no cualificada) porque buscaría esa clase también en el espacio global.
Creando Alias para los namespaces
Los alias son un recurso muy útil para poder acortar los nombres de los namespaces a la hora de hacer nuestras llamadas. Se crean utilizando la palabra clave as junto al comando use:
<?php
header('Content-type: text/plain');
require_once('berriart.sample.php');
use Berriart\Sample as BS;
echo BS\MyClass::WhoAmI() . PHP_EOL;
// Devolvería:
// Berriart\Sample\MyClass::WhoAmI
Hay que destacar que también se pueden crear alias directamente para las clases (no en cambio para constantes y funciones):
<?php
header('Content-type: text/plain');
require_once('berriart.sample.php');
use Berriart\Sample\MyClass as Obj;
echo Obj::WhoAmI() . PHP_EOL;
// Devolvería:
// Berriart\Sample\MyClass::WhoAmI
Reglas de resolución de nombres
Las llamadas desde la incorporación de los namespaces se resuelven siguiendo estas reglas:
- Las llamadas a clases, funciones o constantes completamente cualificadas se resuleven en tiempo de compilación. Por ejemplo new \A\B se resuelve con la clase A\B.
- Todos los nombres no cualificados y cualificados (no los completamente cualificados) se traducen durante la compilación según las reglas de importación actuales. Por ejemplo, si el espacio de nombres A\B\C se importa como C, una llamada a C\D\e() se traduce a A\B\C\D\e().
- Dentro de un espacio de nombres, todos los nombres cualificados no traducidos según la reglas de importación tienen añadido al inicio el namespace actual. Por ejemplo, si una llamada a C\D\e() se lleva a cabo dentro del namespace A\B, se traduce a A\B\C\D\e().
- Los nombres de clases no cualificados se traducen durante la compilación según las reglas de importación actuales (el nombre completo sustituido por el nombre abreviado importado). Por ejemplo, si el espacio de nombres A\B\C se importa como C, new C() se traduce a new A\B\C().
- Dentro de un espacio de nombres (digamos A\B), las llamdas a funciones no cualificadas se resuelven en tiempo de ejecución. Aquí se muestra cómo se resuelve una llamada a la función foo():
- Se busca una función desde el espacio de nombres actual: A\B\foo().
- Se intenta encontrar y llamar a la función global foo().
- Dentro de un namespace (digamos A\B), las llamadas a nombres de clases no cualificados o cualificados (no los completamente cualificados) se resuelven en tiempo de ejecución. Aquí se muestra cómo se resuelve una llamada a new C() o a new D\E().
Para new C():
- Se busca una clase desde el espacio de nombres actual: A\B\C.
- Se intenta autocargar A\B\C.
Para new D\E():
- Se busca una clase añadiendo al inicio el espacio de nombres actual: A\B\D\E.
- Se intenta autocargar A\B\D\E.
Para referenciar cualquier clase global en el espacio de nombres global, se debe usar su nombre completamente cualificado new \C().
La constante __NAMESPACE__ y la palabra clave namespace
La constante especial __NAMESPACE__ siempre nos devolverá el namespace actual, en el que nos econtramos. En el caso de estar en el espacio global devolverá una cadena vacia.
<?php namespace Berriart\Sample; echo __NAMESPACE__; // devuelve: Berriart\Sample
Esto tiene una clara aplicación para el debugging, pero también nos sirve para hacer llamadas utilizando el nombre totalmente cualificado:
<?php
namespace Berriart\Sample;
class MyClass {
public function WhoAmI() {
return __METHOD__;
}
}
$c = __NAMESPACE__ . '\\MyClass';
$m = new $c;
echo $m->WhoAmI(); // devuelve: Berriart\Sample\MyClass::WhoAmI
En el caso de este ejemplo como la clase a la que queremos llamar se encuentra en el mismo namespace en el que nos encontramos también podríamos referirnos a la clase utilizando la palabra clave namespace, que utilizada así sería el equivalente a la palabra clave self dentro de las clases:
<?php
namespace Berriart\Sample;
class MyClass {
public function WhoAmI() {
return __METHOD__;
}
}
$m = new namespace\MyClass();
echo $m->WhoAmI(); // devuelve: Berriart\Sample\MyClass::WhoAmI
Autoloading de clases usando Namespaces
Una de las más importantes funcionalidades de PHP 5 a la hora de ahorrar tiempo es la carga automática. Antes, en un programa que solo hiciera uso del espacio global una posible definición de la función especial __autoload sería:
<?php
$obj1 = new MyClass1(); // classes/MyClass1.php is auto-loaded
$obj2 = new MyClass2(); // classes/MyClass2.php is auto-loaded
// autoload function
function __autoload($class_name) {
require_once("classes/$class_name.php");
}
En PHP 5.3, utilizando namespaces, a la función autoload se le pasaría el namespace completo más el nombre de la clase. Por ejemplo, el valor de $class_name podría ser “Berriart\Sample\MyClass”. Por lo tanto, aunque podrías seguir metiendo todas las clases en la misma carpeta y crear una regla de nomenclatura, lo lógico sería que la estructura de ficheros de la librería sea relativa a la estructura de los namespaces y que la función __autoload fuera algo así:
<?php
function __autoload($class_name) {
// convert namespace to full file path
$class_path = 'classes/' . str_replace('\\', '/', $class_name) . '.php';
require_once($class_path);
}
Webgrafía y créditos
Para redacactar este artículo practicamente se ha traducido este tutorial del inglés, y se ha contrastado y completado con el manual de PHP sobre namespaces.
Espero que leer esto os haya ayudado a entender mejor los namespaces, con lo que sea ya sabéis, dejad un comentario.
Tags:
Excelente. Me ha sido muy útil.
#1 - Publicado hace 6 meses y 3 semanas por RodrigoExcelente articulo me vino muy bien.
#2 - Publicado hace 6 meses y 3 semanas por ManuelMuy útil y excelente la explicación. Mil gracias. Saludos
#3 - Publicado hace 5 mesesy 1 semana por AndrésBuenísimo. Gracias y un saludo.
#4 - Publicado hace 2 meses y 3 semanas por Adrián