Namespace (packages) para PHP 5

Paquete

Saludos espartanos,

Hoy hablaremos del namespace y de su situación actual en php.

Namespace o espacio de nombres es un conjunto de nombres (identificadores) en el cual todos ellos son únicos. Por ejemplo, podemos agrupar en un namespace “Número”, “Letra”, “A”, “3″… y añadir cuanto queramos sin repetir ninguno de los anteriores.

¿Para qué sirve esto? En programación, nos permite dar nombres a las diferentes funciones/clases/variables que definine el sistema, otros programadores y nosotros mismos.

Todo lenguaje tiene su propio namespace, la diferencia reside en cómo se divide/organiza:

  • Java lo organiza en paquetes.
    • Un paquete se puede ver como un sub-namespace del namespace raíz (que englobaría todo): Un puñado de nombres del total.
    • Los paquetes se organizan en jerarquías. EJ: tenemos el paquete raíz, que incluye al “Casas” y al “Coches”. Éste último a su vez incluye al “Primera mano”, “Segunda mano”, etc.
    • Así, si por ejemplo queremos trabajar con “Casas”, importamos el paquete con todo lo necesario para interactuar con ellas. Esto es, no nos hará falta importar otros paquetes que interactuen con “otras cosas”, optimizando una vez ejecutada la aplicación: memoria, tiempo de CPU, tiempo de ejecución, ancho de banda… etc.
    • La orden para dicha importación es: import nombre_paquete.* (el asterisco significa “todo lo que contiene”).
    • Si queremos importar la clase “Casas amplias” haríamos algo así como: import Casas.Casas_amplias.
  • En php versiones 5 y anteriores, sin embargo, esto de los paquetes como que no se implementó, quedando un namespace único.
    • Esto nos obliga a tener mucho cuidado con qué nombre le damos a nuestras variables/funciones/clases. Por ejemplo, no podemos crearnos una función llamada print, ya que se encuentra declarada por defecto en el sistema.
    • Si organizamos nuestras clases/funciones en archivos, deberemos importarlos uno a uno mediante include (o require). Si tenemos un directorio llamado “Casas” que contiene las clases “CasaVacia”, “CasaLlena” y muchas otras, como necesitemos todas ellas para procesar información, php nos obliga a hacer un include de todas ellas (imaginaros 20 o 30 includes). Ni que decir tiene que, si posteriormente añadimos una nueva clase (por ejemplo, una subclase de CasaVacia, deberemos recorrernos tooodos nuestros archivos para añadir un nuevo include. Total, un rollo no escalable y mal organizado.

Los responsables de php 6 están estudiando, al fin y a petición de la comunidad de desarrolladores, incluir su propio sistema de namespace. Hasta entonces, yo, que me encuentro desarrollando un framework para php 5, he tomado las ideas de java para crear el mío propio.

El objetivo:

  • Simular, en la medida de lo posible, al sistema de paquetes de java (quien lo conozca sabrá mejor de lo que hablo).
  • Sencillo de utilizar.
  • Fácil de integrar en frameworks y demás.

La idea:

  • Todos los paquetes se almacenan en un mismo directorio (que sería algo así como el raíz del namespace).
  • Los paquetes son directorios.
  • Las clases/funciones son archivos php.

El uso:

  • import (’*') incluiría todos los paquetes.
  • import (’Casas.*’) incluiría todos los paquetes/funciones/clases del directorio ‘/Casas’.
  • import (’Casas.CasaNueva’) incluiría la clase contenida en ‘/Casas/CasaNueva.php’;
  • Igual con subpaquetes.

El código:

/**
*
* @package activaFramework
* @version $Id: .namespace.php,v 0.01 2007/08/01 19:13:09 mon Leader $
* @copyright (c) 2007 Activa Projects
* @license Creative Commons Reconocimiento-CompartirIgual 2.5 España
* @license http://creativecommons.org/licenses/by-sa/2.5/es/
*
*/

/**
 * Implementa sistema de paquetes en php 5.
 *
 * @version $Id: .namespace.php,v 0.01 2007/08/01 19:13:09 mon Leader $
 *
 * @example import('*');
 * @example import('Integer');
 * @example import('Arrays.*');
 * @example import('Arrays.DisordenArray);
 *
 * @param String $importPath - Path del paquete a importar.
 * @throws Exception
 */
function import ($importPath){
	//CONFIG
		$modelPath = './models';//ojo! No asignar última barra del path del dir
		$extension = '.php'; 	//extensiones de los archivos php

	//Funciones auxiliares
		function importD($dirPath){
			if (!file_exists($dirPath))
				throw new Exception('"'.$modelPath.$extension.'" no existe como paquete.');
			elseif (!is_readable($dirPath))
				throw new Exception('"'.$modelPath.$extension.'" no es un paquete accesible.');
			$dir = opendir($dirPath);
			while ($entrada = readdir($dir)){
				if ($entrada == '.' OR $entrada == '..'){
					//void
				}elseif (is_dir($dirPath .'/'. $entrada)){
					importD($dirPath .'/'. $entrada);
				}else{//is_file
					if (is_readable($dirPath .'/'. $entrada))
						require_once($dirPath .'/'. $entrada);
					else
						throw new Exception('"'.$dirPath.'/'.$entrada.'" no es un paquete accesible.');
				}
			}
			return true;
		}

	//Chequeo: Se indicó un string que importar.
	if (!is_string($importPath))
		throw new Exception('El nombre del paquete debe ser un string, no un "'.gettype($importPath).'".');
	if (empty($importPath))
		throw new Exception('No se indicó nombre de paquete.');	

	//Chequeo: importPath no contiene ni '/' ni caracteres prohibidos
		//TODO realizar chequeo caracteres prohibidos de importPath.

	//Troceamos el importPath
	$modelPathParts = explode('.', $importPath);

	//Generamos la ruta (hasta encontrar *)
	foreach ($modelPathParts as $pos=>$part) {
		if (($part == '') OR ($part == '*' AND isset($modelPathParts[$pos+1])))
			throw new Exception('"'.$importPath.'" no es un nombre de paquete válido.');
		elseif ($part == '*') {
			$modelPath .= '/';
			$importDir = true;
		} else
			$modelPath .= '/'.$part;
	}

	//Caso de ser modelo concreto, lo incluimos
	if (!$importDir) {
		if (!file_exists($modelPath . $extension))
			throw new Exception('"'.$modelPath.$extension.'" no existe como modelo.');
		elseif (!is_readable($modelPath . $extension))
			throw new Exception('"'.$modelPath.$extension.'" no es accesible.');
		else
			require_once($modelPath . $extension);
			return;
	}

	//Caso de ser directorio.
	importD($modelPath);
}

Acerca del código:

  • En la línea 27, ‘modelPath’ indica el directorio donde se guardan los paquetes.
  • En caso de error, lanza una excepción. Tratarlas sería una buena idea.
  • Este código está pensado para un framework, por ello:
    • La configuración del mismo, al entenderse que solo se cambia si acaso al introducirlo en el proyecto, es con variables internas de la función.
    • La función ‘importD’ se incluye dentro para dar sensación de una única función.
    • Si las peticiones al framework pasan siempre por un mismo archivo (controlador principal), se puede añadir el código directamente en él para evitar el include(’namespace.php’).

Bueno, espero que os sirva de ayuda. Se ha intentado redactar el artículo para que cualquier persona aun sin saber de programación pueda entenderlo (aunque pocos habrán llegado hasta aquí con lo largo que és :D). Este código si está bien testeado, aun así se agradecen comentarios/críticas/ruegos/preguntas… como siempre ;).

Un saludo,

Mon (por un php con packages).

3 Responses

  1. Luke Says:

    Tu manera de transmitir es mucho mas densa, para aliviar el dolor agradeceriamos que Jas te hiciese alguna viñeta explicativa de las suyas para añadir a tu post.

    PD: Luke tomando nota y aprendiendo PHP

  2. somosespartanos.com » Blog Archive » Namespaces en PHP 5.3 (aleluya) Says:

    […] De hecho, hace unos meses comentamos aquí una opción que, incluso con cambios mínimos, podría valer hasta para php 4: Namespace (packages) para PHP 5. […]

  3. xzlhtuiotr Says:

    Hello! Good Site! Thanks you! ojegaickxzoc

Leave a Comment

Please note: Comment moderation is enabled and may delay your comment. There is no need to resubmit your comment.