"Petición para soporte de Name Spaces en PHP5"

Este tema lo comenté por primera vez cuando escribí sobre cómo se representaría una arquitectura de 3 capas en PHP5. Ahora me entero que era más grave de lo que yo pensaba ;-)

Noticia extraida de VivaPHP:
"Petición para soporte de Name Spaces en PHP5 (07.04.2006)

PHP¿Hasta dónde pueden llegar los programadores de PHP en su súplica para que los desarrolladores de Zend vean la luz y agreguen de una soporte de name spaces a este lenguaje? Pues hasta el punto de organizar una petición on-line para implorar que por lo menos incluyan el parche de Jessie Hernandez en la próxima versión de PHP5.

El soporte de name spaces está en seria consideración para PHP6, con una álgida discusión en la lista de correo de php-internals sobre la mejor forma de implementar esta retrasadísima característica."

Antes que saliera la versión final de PHP5 leí la documentación sobre lo nuevo que incorporaba y se hacía referencia constante al concepto de "namespace". Al principio me sorprendió porque PHP5 se basa mucho en la sintaxis que usa Java para implementar los objetos y este usa la sintaxis "package" y solo en .Net se usaba "namespace".

La mayor sorpresa fue que cuando se liberó la versión final de PHP5, este no soportaba los "namespaces" (a pesar de haberlos probado en la beta).

Volviendo al tema, para ubicarnos un poco en el contexto, este tipo de sintaxis nos permite representar el concepto de diseño llamado "arquitectura en capas" o directamente "programación en tres capas" (pueden haber muchas más capas, pero generalmente se habla de que deberían existir por lo menos tres), donde deberíamos separar claramente las distintas responsabilidades básicas de nuestros sistemas.

La primer capa, la presentación, sería la encargada de manejar nuestra "interfaz" con los usuarios (pantallas gráficas, páginas web, etc), la segunda capa, el dominio o lógica, donde deberemos alojar el código que resuelve concretamente la problemática de nuestro sistema, y por último la tercer capa, la persistencia, que tendrá la responsabilidad de "persistir" la información, tanto almacenarla en una base de datos o en un archivo de texto (o en cualquier otro medio), para luego poder recuperarla cuando lo requiera nuestro sistema.

La idea de la "arquitectura orientada a capas" es que tenemos separadas de forma clara las responsabilidades, desacoplando nuestro código.

Si no tuviéramos capas, que es lo mismo que decir que tenemos una sola capa, nuestro código tendría entreveradas todas las responsabilidades. Cualquier cambio que se quiera hacer al código que implementa la interfaz afectaría a todo el resto del código. Si necesitáramos cambiar de interfaz (pasar de un sistema de escritorio de ventanas a un sistema web) deberíamos modificar todas las llamadas en nuestro código que cumplen esa tarea. Pero si tenemos "capas", podemos crear un nuevo "paquete" y sustituirlo por el anterior, y nuestro sistema debería poder seguir funcionando sin problemas. Sería exactamente el mismo caso si tuviéramos que cambiar de motor de bases de datos, solo deberíamos poder cambiar de "capa" y nuestro sistema seguir operativo (o por lo menos, las modificaciones solo afectarían un paquete, no al resto).

En UML cada capa representa un "paquete" (o viceversa), y estos contienen en su interior clases u otros paquetes, y en muchos lenguajes (como Java) esto se traduce físicamente en un directorio.

La idea es que si nuestro Gerente nos entrega el diagrama UML, nosotros deberíamos poder interpretar el diagrama y representarlo en código en alguno de los lenguajes disponibles que cumplan con la Orientación a Objetos.

La desventaja que tiene PHP, al no soportar los namespace, es que no podemos cumplir fielmente los diagramas UML al tener que incluir individualmente los archivos que vamos necesitando, cuando deberíamos poder hacer referencia directa a un "paquete" como una "unidad".

No es lo mismo decir en nuestro código:

namespace dominio;
namespace persistencia;

Que tener que hacer referencia a cada uno de los archivos que vamos a utilizar de nuestro "paquete":

require_once 'persistencia/BaseDeDatos.class.php';
require_once 'dominio/Clientes.class.php';
require_once 'dominio/Acreedores.class.php';
require_once 'dominio/Factura.class.php';
require_once 'dominio/LineaFactura.class.php';


Lo más grave de todo es que nos debilita el diseño al no permitirnos cumplir con lo básico de los "principios del diseño orientado a objetos". Nosotros deberíamos reusar "paquetes" y no partes concretas de esos paquetes (en este caso, serían las clases).

Esperemos que se implemente en PHP6 y demos un nuevo gran paso.

Programación: "Orientada a la Implementación" versus "Orientada a la Interface" - Parte 1 (revisado 11/12/2008)

Gran dilema les voy a plantear. Acompáñenme al esotérico mundo del Diseño Orientado a Objetos ;-)

Supongamos que tenemos un sistema que hará las funciones de "máquina de escribir", donde obtendrá información a través de un dispositivo de entrada y luego enviará esta información a un dispositivo de salida.

Nuestro diseño estará compuesto por dos paquetes (en el contexto UML) que nos ofrecen los siguientes servicios:

Lógica: donde tendremos la implementación de "alto nivel" de nuestro problema (donde se manipularán los dispositivos).

Dispositivos: donde la implementación de nuestro paquete de "lógica" tendrá acceso a servicios de "bajo nivel" provisto por este paquete.


Entrando en el diseño interno de los paquetes, tendremos:

- Dentro del paquete "Dispositivos": las clases "Teclado", que retornará información en formato de texto, e "Impresora", que en algún momento recibirá un texto para ser impreso.

- Dentro del paquete "Lógica": la clase "MaquinaDeEscribir", que se encargará de crear las instancias correspondientes a los dispositivos de entrada/salida, y pasar la información que devuelve una clase a otra clase.

Imprementación

Seguiremos textualmente los diagramas UML para hacer la codificación de nuestras clases. Empezaremos con los dispositivos.

Archivo: Teclado.php

class Teclado
{
public function leer()
{
// a efectos del ejemplo, solo retornará un
// un texto como representación de una
// entrada de datos
return "texto ingresado";
}
}


Archivo: Impresora.php

class Impresora
{
function escribir($texto)
{
echo $texto;
}
}


Archivo: MaquinaDeEscribir.php


require_once 'dispositivos/Teclado.php';
require_once 'dispositivos/Impresora.php';

abstract class MaquinaDeEscribir
{
static function run()
{
$miTeclado = new Teclado();
$miImpresora = new Impresora();

$miImpresora->escribir($miTeclado->leer());
}
}

¿Cómo se prueba?

Una posible solución creativa, tratando de llevar a PHP hacia un lenguaje 100% Orientado a Objetos (como Java), donde *todo* debería ser un objeto, se usará el "index.php" como una clase "Index".

Nota1: siguiendo el comportamiento estándar de los servidores web, cuando nos posicionemos en la url de nuestro sitio el servidor buscará todos los documentos que inicien con el nombre "index" (minúsculas) y luego probará varias extensiones hasta dar con el archivo inicial (.html, .htm, .php, etc). Hay autores que usan "class.NombreClase.php" o NombreClase.class.php, nosotros usaremos el estádar definido por Zend (NombreClase.php) y para este caso particular simplemente "index.php".

Resumiendo: Solo en este caso, para mantener el estándar web, nuestro archivo se llamará "index.php" y la clase internamente será "Index" (la primer letra en mayúsculas y la palabra en singular).

Nota2: cuando en un diagrama UML tenemos una flecha que apunta hacia otra clase (cualquier flecha), en PHP se traduce *siempre* (o por lo menos hasta la fecha de hoy) en incluir ese fuente con la sentencia include, require, etc. En el caso siguiente, y basado en nuestro diagrama UML, nuestra clase "Index" requerirá *solo* de la clase "MaquinaDeEscribir".

Nota3: como no hace falta instanciar la clase para invocar un método, creo una clase "abstracta" y ejecuto directamente la clase e invoco su método, evitando crear una instancia (menos código, no reservo memoria para la instancia, etc).


require_once "logica/MaquinaDeEscribir.php";

abstract class Index
{
static public function run()
{
echo 'Ejecuto máquina de escribir->';
MaquinaDeEscribir::run();
}
}

Index::run();

Ok, nuestro ejemplo está funcionando.

Ahora la pregunta es: ¿existe alguna otra mejor forma de hacerlo?

Programación Orientada a la Interface

Por suerte sí, existe otra forma: usando interfaces. Para poder aplicar esta nueva forma de solucionar el problema, debemos "dar vuelta" la forma que usamos los objetos de nuestro ejemplo. En el caso anterior, la clase de "alto nivel" dependía de dos clases de "bajo nivel", lo que significa algo muy grave: "la clase de alto nivel es *dependiente* de los cambios que puedan sucederle a las clases de bajo nivel, es decir, nuestro alto nivel depende de los detalles de implementación del bajo nivel".

El problema de nuestro diseño es que estamos programando "orientado a la implementación".

¿El diseño anterior, puede considerarse un buen diseño?

  • ¿Cuan mantenible será nuestro sistema si elementos de bajo nivel pueden generar cambios en todo el modelo?
  • ¿Es correcto que nuestra MaquinaDeEscribir dependa de cómo se toman los datos y de cómo se imprimen?
  • ¿Qué es lo más importante en nuestro diseño? ¿la Lógica o los Dispositivos?
  • ¿Este diseño, es reutilizable? ¿podemos reutilizar el MaquinaDeEscribir en otros contextos?
  • ¿Su costo de mantenimiento es alto o bajo? ¿cuanto código hay que agregar para extender su comportamiento (para cubrir un nuevo requerimiento)?
  • ¿el código presentado se mantiene, o hay que adaptarlo cada vez que queramos extender su funcionamiento?

Todas estas preguntas serán desveladas en el próximo capítulo ;-)

El 24 de Abril inicia el curso: "Programación PHP5"

Luego de muchos preparativos se abre en la Universidad ORT el período de inscripción para el curso que dictaré sobre "Programación PHP5" en el marco del "Ciclo de Especialización dirigido a profesionales de Sistemas y Electrónica".

Por las características del temario del curso, este el primero y el único del mercado que contempla los últimos avances en la programación web con el lenguaje de programación PHP en su versión 5.

Cualquier duda o consulta, pueden enviarme un correo que ampliaré toda la información que soliciten.

El detalle completo se encuentra en el Blog de Enrique Place

(PHP5) Patrón de Diseño: Template Method (revisado: 7/11/2006)

La intención general de muchos patrones es contener el "foco de cambio", el lugar donde podrían originarse cambios en nuestro sistema, lo que requeriría tener que modificar código del mismo. Si "controlamos" ese foco, lograremos cumplir el principio "Open/Closed" (Abierto/Cerrado) que dice que: "nuestros sistemas deberían ser cerrados al cambio pero abiertos a la extensión". En otras palabras, nuestro diseño debería proveer la posibilidad de que nuestro sistema pueda crecer agregando nuevo código sin alterar el código existente.

Cuando nos acostumbremos a implementar patrones, nos daremos cuenta que la mayoría de los diseños cumplen con este principio.

En el caso concreto del patrón "Template Method", su intención es controlar el esqueleto de un algoritmo, permitiendo ofrecer un comportamiento base que pueda ser modificado al redefinir los métodos en las clases derivadas de la "Clase Padre" en el contexto de la herencia.

El gran beneficio de este patrón es la flexibilidad para aplicar cambios y para reusar código, facilitando además la alteración del "algoritmo base" de la solución que estamos implementando. El algoritmo está implementado en un único método y si debemos modificarlo este cambio impactará en todo el sistema de forma controlada, sin tener que modificar individualmente a las clases concretas (donde se encuentra la implementación de bajo nivel). Y sobre el caso de reuso, la "clase padre" de la herencia puede contener un "comportamiento por defecto" que todas las "clases hijas" van a heredar, y solo sobreescribirán el método correspondiente cuando necesiten cambiar su comportamiento según nuestros requerimientos.

Contexto del ejemplo

Necesitamos una estructura que nos permita generar listados que cumplen el siguiente algoritmo: imprimir primero la información del cabezal, luego el cuerpo y finalmente el pie del documento. Cada clase derivada vuelve a implementar el método que hereda de la "clase padre" cada vez que necesite cambiar el comportamiento base. De la misma forma, si deseamos agregar un nuevo tipo de listado, no necesitaremos modificar el código de nuestra solución, solo crear una clase y heredar de la clase padre ("abierto a la extensión").

Por demás está decir que el "foco de cambio" es claro: si tenemos un sistema que provee listados de distinto tipo, es muy probable que con el tiempo surjan modificaciones en el comportamiento general (cambia la estructura o el diseño de nuestra hoja de impresión) o se agregan nuevos casos a los existentes (un nuevo tipo de listado).

Se creó una clase Listado que será la única estructura que contendrá verdaderamente el algoritmo de la solución (dentro del método imprimir) y una serie de métodos que pertenecen *solo* a la lógica interna del algoritmo.


// Archivo: Listado.class.php

class Listado {

// Algoritmo base
public function imprimir(){
$this->cabezal();
$this->cuerpo();
$this->pie();
}
// Contenido inicial para cada parte del
// algoritmo

protected function cabezal(){
echo
"Listado - Cabezal";
}
protected function cuerpo(){
echo
"Listado - Cuerpo";
}
protected function pie(){
echo
"Listado - Pie";
}
}
?>

// Archivo: Comun.class.php

require_once 'Listado.class.php';

class
Comun extends Listado{

// Altero el comportamiento base
// de una parte del algoritmo, el "pié"

protected function pie(){
echo
"Comun - pie";
}

}
?>

// Archivo: Avanzado.class.php

require_once 'Listado.class.php';

class
Avanzado extends Listado{

// En este caso, modifico completamente
// el contenido de los elementos que
// comprenden el algoritmo, pero no
// quiere decir que pueda cambiar
// el orden del mismo (esto se mantiene
// controlado en la clase base "Listado")

protected function cabezal(){
echo
"Avanzado - cabezal";
}
protected function cuerpo(){
echo
"Avanzado - cuerpo";
}
protected function pie(){
echo
"Avanzado - pie";
}
}

?>

// Archivo: Estandar.class.php

require_once 'Listado.class.php';

class
Estandar extends Listado {
// En este caso, no hacemos nada
// y se reaprovecha todo el contenido
// de la "clase base"
}
?>

// Archivo: index.php

require_once 'Listado.class.php';
require_once
'Comun.class.php';
require_once
'Avanzado.class.php';
require_once
'Estandar.class.php';

abstract class Index {
public static function run(){
self::imprimir(new Comun());

self::imprimir(new Avanzado());

self::imprimir(new Estandar());
}
// Solo pueden pasar como parámetros
// objetos que sean del tipo "clase base",
// logrando efectuar un "contrato de diseño"
// (para poder imprimir debes heredar de la
// clase "Listado").
private static function imprimir(Listado $b){
$b->imprimir();
}
}

Index::run();
?>



Detalles a tener en cuenta

La definición del diseño del patrón dice que los métodos que se utilizan son parte del algoritmo y están con la visibilidad "protegida" y no "privada". Si fuera así, los métodos no podrían ser accesibles desde otras clase de la jerarquía, impidiendo que puedan sobreescribirse.

Si fuera "públicos" los métodos, podrían ser accedidos directamente desde la clase "cliente" (en este caso "Index") y acceder a partes del algoritmo, lo cual solo nos interesa que lo ejecuten de forma integra. Para este último caso está disponible el método "imprimir" de la clase "Listado".

Y como último detalle, no hace falta instanciar clases que no se necesitan o no deban hacerlo ... para eso se define clases "abstractas" que impiden la creación de instancias y a los métodos como "estáticos", lo que impide que puedan ser usados en el contexto de una instancia.

Dudas, sugerencias, correcciones, insultos, en los comentarios, plis ;-)

Más información

Wikipedia

Do Factory

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:

Entradas populares