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 desarrolladores OO... 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:

18 comentarios:

Anónimo dijo...

Por ahí dicen que un desarrollador de software siempre debe estar aprendiendo nuevas cosas. Al aprender más lenguajes tu percepción se va a ampliar porque vas a poder comparar las características de unos contra los otros.

Cuando encuentres que cierta cosa no se puede hacer en un lenguaje que tienes que usar pero sí se puede en otro que te gusta mucho que ya usaste es posible que te sea más sencillo imaginar cómo implementar tal cosa.

Creo que eso encierras en esta entrada. El hecho de haber andado en otros lares, programando otras cosas, te ha dado la capacidad de hacer tu "parche".

Buena entrada.

Enrique Place dijo...

En sí lo que más me ha sorprendido es que luego de comentarlo en varios foros o discutirlo con programadores PHP ... ninguno, pero ninguno, entendía lo que estaba queriendo decir con "paquetes" ;-)

Me alegro, muy a pesar de la opinión del autor, que los que están llevando las riendas del desarrollo de las nuevas características de PHP tengan idea de como se trabaja en otros lenguajes "OO".

La intención de Zend, y de muchos colaboradores (Oracle, IBM, etc) es que PHP se vuelva un J2EE más simple, más sencillo, y poder desarrollar un conjunto de clases para todo tipo de usos, como sucede hoy día con el J2SE (el lenguaje incorpora de por sí muchas clases que casi seguro resuelven todos tus problemas habituales).

Lo que sí me estoy convenciendo, es que ya va siendo hora de que los desarrolladores PHP abran sus cabezas a otros conceptos e ideas, sino, los únicos que va a poder aprovechar las nuevas capacidades de la versión 5 y las futuras versiones por venir son los desarrolladores de plataformas como Java y .Net.

Los "viejos programadores del scripting" (también conocidos como los "expertos de la programación estructurada" ;-) no se van a enterar siquiera sobre qué les están hablando.

Anónimo dijo...

Leí el artículo, y estoy de acuerdo con la utilización de Constantes, pero esto no resuelve para nada el tema de la falta de Paquetes en estos lenguajes, es solo una convención distinta a la hora de incluir ciertos archivos.

La opción de Sesiones, no solo no me gustó, sino que la desestimo en lo absoluto, porque puede significar tanto un dolor de cabeza:
- un usuario deja pasar x minutos, la sesión expira, y en la nueva sesión no hay info sobre los directorios, errores de include por todos lados)

...o un problema serio de seguridad:
una persona intercepta las cookies de un usuario, y detalles de tu implementación como lo es la ubicación de tus directorios, queda al descubierto innecesariamente. Eso o intenta de alguna manera sobreescribir los valores en la sesión, en servidores con Register_Globals = on.

Enrique Place dijo...

Si, ese es uno de los puntos, la falta de poder representar sin dificultad el famoso "paquete" en el contexto de UML.

Con respecto a las sesiones, puede ser que tengas razón y tus argumentos tienen mucho asidero. Pero como siempre, todo dependerá del contexto donde debemos aplicarlo (las sesiones existen en PHP, no son lo mejor del mundo, pero están para poder ser usadas ;-).

Cuando me senté a pensar las formas que tenía para resolverlo, partí de un sistema que tenía en producción y que usaba las sesiones para transferir información entre subsistemas.

Luego de seguir probando, terminé simplificando esta alternativa hacia una que usara "constantes" pues me pareció mucho más "limpia".

Esa es una de las razones del orden en los ejemplos, y donde tampoco quise transmitir que era absolutamente necesario crear una sesión para resolver este problema ;-)

Como ya lo dije, cuando surja PHP6 este "post" carecerá completamente de sentido ;-)

Pablo, gracias por los comentarios y tus correcciones.

Anónimo dijo...

Muy bien articulo Enrique, ahora, me invente una forma de llamar a paquetes "inspirada", en el sistema que implementa PRADO

Prado::using('PathAlias.Dir1.Dir2.*');

carpetas, con archivo de configuracion e includes dinamicos, creo q no es muy eficiente, pero si eficaz al menos, solo necesitas usar un include para llamar al sistema de namespace's , referenciarlo y listo

la forma que implementa Prado me parece notable, Que opinas de este frameworks?

Enrique Place dijo...

Estimado Enzo:

> Muy bien articulo Enrique, ahora,
> me invente una forma de llamar a
> paquetes "inspirada", en el
> sistema que implementa PRADO

Y por qué no envías parte del código fundamental de tu implementación así complementamos las opciones posibles para solucionar este inconveniente -esperemos- momentáneo ;-)

Tal vez pueda hacer un segundo artículo explicando al detalle todas las opciones posibles y un ejemplo de uso en un mismo contexto para compararlas.

> Prado::using('PathAlias.Dir1.Dir2.*');

> carpetas, con archivo de
> configuracion e includes
> dinamicos, creo q no es muy
> eficiente, pero si eficaz al
> menos, solo necesitas usar un
> include para llamar al sistema de
> namespace's , referenciarlo y
> listo

Me parece un buena idea... lo de eficiente podrá discutirse, habría que ver que costo tiene hacerlo siempre, pero si compensa todos los problemas puede ser una buena opción.

> la forma que implementa Prado me
> parece notable, Que opinas de
> este frameworks?

Nada, he leído mucha documentación de una gran cantidad de frameworks, pero es imposible evaluar todos de forma seria sin caer en "opinología profesional". Finalmente, por un tema de tiempo he decidido volcar lo poco que dispongo al framework oficial, el Zend Framework.

No sé si será mejor o peor, pero ya marca una diferencia el hecho de ser los mismos que desarrollan PHP, amén de la fuerte tendencia a crecer hacia un J2EE, por lo cual no se van a quedar solo con el framework.

Espero tu aporte de código ;-)

Enrique Place dijo...

Para completar la información sobre Frameworks:

Open Source PHP frameworks


10 PHP Frameworks compared

Unknown dijo...

Felicidades por el post solo tengo una duda soy nuevo en esto pero como lo puedo aplicar si me direcciono a dos niveles
http://www.misitio.com/directorio1/directorio1/index.php

y dentro de ese index, como podre aplicar el script

saludos

Eunate dijo...

Buen articulo!
http://deckerix.tuxfamily.org

Sergabrod dijo...

Muy buen post, la verdad esta es una falencia que noté en php desde los inicios de la versión 4.

Tener que referenciar a varios source desde diferentes lugares y tener en cuenta en cada uno la ubicación desde donde se los llama es como mínimo engorroso y poco escalable.

Particularmente he optado por el popular config.php, no es de lo mejor pero al menos se puede centralizar las referencias a los path que usamos en nuestro desarrollo, espero que en PHP6 ya podamos contar con los packages de Java.

Saludos

Sergio Gabriel Rodriguez
Corrientes - Argentina

Enrique Place dijo...

Que tal Sergio ;-)

> Muy buen post, la verdad esta es
> una falencia que noté en php
> desde los inicios de la versión
> 4.

A veces es difícil darse cuenta de todo lo que carecemos si es que no tenemos una fuente de referencia. En mi caso fue Java y .Net.

> Tener que referenciar a varios
> source desde diferentes lugares
>y tener en cuenta en cada uno la
> ubicación desde donde se los
> llama es como mínimo engorroso y
> poco escalable.

Verdaderamente.

> Particularmente he optado por el
> popular config.php, no es de lo
> mejor pero al menos se puede
> centralizar las referencias a
> los path que usamos en nuestro
> desarrollo, espero que en PHP6
> ya podamos contar con los
> packages de Java.

Una forma es, como hacen muchos frameworks o web's modulares, hacer siempre entrada por el index.php y de ahí tienes un único punto de referencia.

Pero, lo ideal es que este tema lo resuelva directamente el lenguaje, no que nosotros tengamos que estar viendo recursivamente donde buscar los archivos.

Veremos como terminan implementado los namespaces en PHP6.

Saludos ;-)

Sergio Gabriel Rodriguez
Corrientes - Argentina

Sergabrod dijo...

Hola Enrique, muy cierto lo que decis, lo que implementan la mayoría de los frameworks es centralizar todas las llamadas a un bootstrap index.php y desde ahi hacer un dispatch hacia un controller específico.

La manera que lo hace Zend por ej. es con un set_include_path y un autoload para levantar todas las clases del framework:

set_include_path('.' . PATH_SEPARATOR . '../library/'
. PATH_SEPARATOR . '../application/models'
. PATH_SEPARATOR . get_include_path());

include "Zend/Loader.php";
Zend_Loader::registerAutoload();

Estuve viendo la clase Loader y básicamente lo que hace es recorrer el dir de la libray y hacer los includes de todos y cada uno de los php , no se qué tan buena opción es esta en cuanto a rendimiento, pero si es seguro que resuelve el problema de estar pensando en los includes.

Saludos a todos..

Sergio Gabriel Rodriguez
www.3trex.com.ar

Gabriel Fernández dijo...

Hola amigo compatriota:

que pasos le sigo para comprar tu libro?
Muy bueno tu blog, al fin alguien que trata uml en programacion web, y en español!!!

Saludos desde Salto, Uruguay...

Enrique Place dijo...

Que tal Gabriel! ;-)

Toda la información está en surforce.com. ;-)

Para iniciar la compra, debes registrarte primero en registracion.surforce.com, y luego de confirmando tu usuario, podrás ingresar a usuarios.surforce.com e ir a la sección "Comprar" donde te permite seleccionar el producto y forma de pago.

Formas de pago: Western Union, Paypay y transferencia bancaria (este último solo para Argentina).

Cualquier duda que tengas me avisas!

Links de información
http://www.surforce.com/cursos_metodologia.php
http://surforce.com/curso_zend.php

Radamanthys17 dijo...

hola muy buena tu idea de crear un archivo que tenga todas las url absolutas y efectivamente funciona pero fijate q me surge un problema.

estoy armando una aplicacion que es plenamente orientada a objetos asi que uso clases con metodos etc...

el problema surge cuando tengo muchos requires de otras clases que utilizo sus metodos incluso de clases que no uso en todos los metodos sino en algunos.

en si cuando quiero ejecutar por decir asi esa clase que se ayuda de otras clases que tienen metodos que usa la primera , pues no realiza los metodos que tiene que hacer y para sorpresa si quito algunos require once las demas metodos de las otras clases funcionan bien .

yo he llegado a la conclusion de q php no es 100% orientado a objetos lastima , si alguien tiene una idea de que podra ser mi problema pues sera de gran ayuda.

los metodos que uso en la clase principal los he probado en otro archivo php de prueba y funcionan correctamente, pero cuando trato de ejecutar esos metodos dentro de otra clase que tiene mas clases importadas , viene el problema

Gracias

JesusI dijo...

La sentencia $_SERVER[DOCUMENT_ROOT] del ejemplo, debería ser $_SERVER['DOCUMENT_ROOT']

Buen artículo!

Liliana Pleitez dijo...

revisa el diseño, si una clase ocupa tantos metodos de otras clases,no es un buen diseño

Enrique Place dijo...

Que tal Liliana, puedes ampliar tus comentarios, no queda claro sobre qué punto estás tratando, o si es por un ejemplo en particular en el artículo, o por comentarios de algún lector.

Gracias!

Entradas populares