PHP5: Diseño en 3 capas y problemas con subdirectorios

PHP5: Diseño en 3 capas y problemas con subdirectorios

Uno de los problemas que me he encontrado con la versión 5 de PHP es la falta de la representación de los "paquetes" desde el propio lenguaje de programación. Lamentablemente la versión beta de PHP5 incluía el soporte a este "concepto" pero aparentemente los desarrolladores no pudieron completar esta característica a tiempo y tuvieron que quitar el soporte en la versión final y prometerlo para la próxima versión 6.

En el contexto de UML, un "paquete" es un "agrupador" de elementos de la Programación Orientada a Objetos. Estos elementos pueden ser clases, interfaces, otros paquetes, etc.

En la mayoría de las tecnologías que usan la OO la representación "física" corresponde directamente con un "subdirectorio" de nuestro sistema de archivos (filesystem). Es decir, cada paquete es una carpeta más que agrupa otros archivos (que representarán a su vez clases, interfaces, etc).

Cuando hablamos de un diseño en "3 capas", nos referimos a que conceptualmente tendremos separado nuestro sistema en "3 zonas" con responsabilidades o tareas distintas a cumplir.

¿Cual es el beneficio de esta separación?

La división en capas reduce la complejidad, facilita la reutilización y acelera el proceso de ensamblar o desensamblar alguna capa, o sustituirla por otra distinta (pero con la misma responsabilidad).

Lo tradicional es que tendremos la capa de "Presentación" (o GUI, Interfaz, etc), la capa de "Dominio" (donde verdaderamente se resuelve el "problema", también llamada "Lógica de Negocio") y la capa de "Persistencia" (o cualquier otro nombre que se nos ocurra: "Capa de Acceso a Datos", etc) donde se resuelve el almacenamiento o recuperación de la información (puede ser una Base de Datos, un archivo XML, un Webservices, etc).

Si volvemos a UML, la forma que tenemos de representar esta separación en capas es a través de "paquetes". Pasando en limpio, una "capa de responsabilidad" es un "paquete" que por principio de diseño *siempre* tiene una única responsabilidad (y su nombre la describe).

¿Cómo se hace en otras tecnologías?

En tecnologías como .Net o Java, cuando se crea un "paquete" nuestro entorno de desarrollo genera la sintaxis de forma automática y comienza a agrupar de forma gráfica todas las clases que vayamos a agregar dentro.

En Java se usa la palabra "package", en .Net la palabra "namespace", y en PHP6 (siguiendo la filosofía de copiar todo de Java) se usará .... "namespace" X-)))

La gran ventaja "no aparente" es que nos evita tener que hacer referencias físicas a los subdirectorios correspondientes. Este problema surge cuando usamos otro tipo de lenguaje que no implementa este concepto, y ahí aparecen los primeros problemas.

¿Cómo hacemos en PHP5?

Tanta publicidad de nuestra parte para apoyar a esta nueva versión que incorpora las nuevas características de la OO, que lo hace subir de nivel, obtener más respeto de los desarrolladoresOO... y no soporta el concepto de "paquetes"!!! ;-)

Intentaremos una solución. Si conceptualmente separamos nuestro sistema en 3 capas, nuestro problema se debería resumir en un manejo de subdirectorios: debo crear 3 subdirectorios y estos deben poder ser referenciados desde cualquier lugar sin mayor dificultad. 

Hago hincapié en esto porque podemos cometer varios errores si subestimamos el problema.

Si lo solucionamos con "referencias absolutas", nuestro sistema queda "hardcoded" y dificultamos el cambio de lugar físico a nuestra aplicación. Si intentamos hacer todas "referencias relativas", como dice su palabra, dependerán de donde son invocadas estas referencias para que funcionen o no (por eso son "relativas").

Ejemplos
// Ejemplo de "referencia absoluta"
require_once '/var/www/aplicacion/persistencia';

// Ejemplo de "referencia relativa"
require_once 'persistencia/BD.class.php';

Este último caso funcionará solo si el fuente que invoca esta sentencia se encuentra posicionado en la raíz del proyecto.

Si estamos dentro del subdirectorio "dominio", deberíamos bajar un nivel y subir al nivel del subdirectorio.
require_once '../persistencia/BD.class.php';

Una solución que he llegado a hacer (sin tener que implementar un sitio modular) es que exista un archivo configuración.php con referencias a todos los "paquetes" del sistema e incluirlo en todos los fuentes que necesiten usarlos.

Una opción puede ser crear una sesión y luego variables de sesión con las referencias a los directorios, y la segunda, sin usar una sesión, cargando la información en constantes.

Otra solución teórica que veo a este problema es invocar el archivo de configuración definiéndolo en la propia configuración de PHP (php.ini) para que lo incluya en todos los fuentes sin necesidad de escribir línea alguna. Lo negativo es que ahorramos una línea de invocación pero obligamos a todos los fuentes tener acceso a esta información ... lo que hace que conozcan más de lo que deberían (otro principio de diseño).

Pero bueno, "la peor gestión es la que no se realiza" ... entonces, continuemos ;-)

Ejemplos

Código de configuración.php con uso de sesiones
$_SESSION['HOME']= $_SERVER[DOCUMENT_ROOT];
$_SESSION['APLICA']= $_SESSION['HOME']."/aplicacion";

$_SESSION['DOM']= $_SESSION['APLICA']."/dominio";
$_SESSION['PRE']= $_SESSION['APLICA']."/presentacion";
$_SESSION['PER']= $_SESSION['APLICA']."/persistencia";

Forma de invocarlo desde el index.php
require_once ($_SERVER[DOCUMENT_ROOT]."/aplicacion/configuracion.php");
// Forma de usarlo
require_once($_SESSION['PRE'].'/class.Template.php');

Alternativa usando constantes (evitando las sesiones)
define('HOME',$_SERVER[DOCUMENT_ROOT]);
define('APLICA',HOME."/aplicacion");

define('DOM',APLICA."/dominio");
define('PRE',APLICA."/presentacion");
define('PER',APLICA."/persistencia");

Forma de invocarlo desde el index.php
// No cambia
require_once ($_SERVER[DOCUMENT_ROOT]."/aplicacion/configuracion.php");

// Forma de usarlo
require_once(PRE.'/class.Template.php');

Conclusión final

Es una gran ventaja haber conocido otras plataformas, lo que te permite tener un punto de vista más objetivo. Muchas veces creemos que no se puede hacer o que está mal porque nuestro conocimiento y experiencia es muy limitado.

Si no hubiera sabido como se hacía en otras plataformas nunca me hubiera dado cuenta que me estaba faltando algo. Cuantos habrán dicho a Colón que estaba loco y que la Tierra no podía ser redonda ;-)

Espero que los programadores que nunca han visto programación "OO" y menos una separación en 3 capas les despierte la curiosidad.

Lo lamentable de todo este "parche" es que en la versión 6 este tipo de problemas debería estar resuelto simplemente como en el siguiente ejemplo en Java:

Comentarios