Note

The online documentation is produced by a web publishing technology created by us to read the documents origins in OpenOffice Writer (ODT) and Microsoft Word (docx) formats and produces native web and PDF versions. In this way we maintain Louder project documentation update and in sync on each of its formats.
Select a Language:
Componente Controller
Tabla de Contenido:
Introducción
El componente Controller cumple una importante tarea dentro de la arquitectura MVC de Kumbia. El framework proporciona la integración de este componente con el componente View y ActiveRecord que realiza el papel de los modelos.

La integración de estos componentes proporciona una estructura estable y eficiente para las aplicaciones orientadas a la Web. Además de esto, el componente ofrece un sistema de persistencia transparente al desarrollador que acerca las aplicaciones Web a aplicaciones de escritorio, eliminando la complejidad de administrar el estado y entorno de la lógica de negocios en una sesión. Por medio de plug-ins es posible extender la funcionalidad de este componente.
Como funciona el componente Controller?
Kumbia Enterprise Framework ha implementado una estructura jerárquica de clases que permiten crear diferentes tipos de servicios y desarrollar la lógica de aplicación en diferentes niveles de flexibilidad ó practicidad.

El componente Controller posee la siguiente jerarquía de clases e implementación de servicios:
Tabla: Jerarquia de clases del componente Controller
Clase
UbicaciónDescripción
ControllerBase apps/default/controllers/
application.php
Es la clase padre de todos los controladores, el desarrollador puede agregar métodos que serán heredados por cualquier controlador de la aplicación. Aquí se puede agregar validación de seguridad ó Auditoría de sistemas.
Controller Library/Kumbia/Controller/Controller/Controller.php Es el componente Controller en si, implementa todos los métodos comunes para los tipos de controladores del framework.
ApplicationController Library/Kumbia/Controller/Application/
Application.php
El diseño de este controlador ayuda al programador a interactuar con vistas y modelos de la forma más directa y flexible.
StandardForm Library/Kumbia/Controller/StandardForm/
StandardForm.php
Es una implementación del componente Controller que funciona como Scallfolding (generador de código) dinámico. Busca ayudar al desarrollador a crear capturas de limitada personalización pero que realizan las operaciones de creación, consulta, modificación, reporte y eliminación de los datos de una tabla.
WebServiceController Library/Kumbia/Controller/WebServiceController/
ApplicationController.php
Este tipo de controlador Permite crear Servicios web basados en el estándar SOAP, generar descripciones en WSDL y orquestar el intercambio de datos entre aplicaciones usando este método.
MultiThreadControllerLibrary/Kumbia/Controller/Application/
MultiThreadController.php
Es una sub-implementación de ApplicationController que está diseñada para correr procesos de negocio que requieran seguimiento estilo cross-cutting.


El objetivo de cada controlador es básicamente separar la lógica de la presentación, el componente Controller implementa el patrón Front-Controller en el cual todas las peticiones a la aplicación son atendidas inicialmente por él y luego son enrutadas a controladores de usuario y acciones que atienden cada una.
Crear un Controlador
Los controladores son clases que heredan de las implementaciones de Controladores como ApplicationController ó WebServiceController y que deben ser creados bajo ciertas convenciones en el directorio apps/default/controllers/

Al crear un controlador para la administración de la información de clientes se crea un archivo llamado customer_controller.php, en él una clase CustomerController heredando de alguna de las implementaciones del componente Controller.

La acción por defecto en el controlador debe tener visibilidad pública y llamarse indexAction así:
Ejemplo: Un controlador y su acción por defecto
<?php

class CustomerController extends ApplicationController {

     public function indexAction(){
          $this->renderText("Hola Mundo");
     }

     public function getStatusAction($id){
          $this->renderText("Ver el estado del cliente $id");
     }

}


Para realizar una petición a la aplicación se hace mediante la siguiente URL:
Ejemplo: Acceder al controlador mediante una URL
http://www.example.com/company/customer/index
http://www.example.com/company/customer/ 

Ya que index es la acción por defecto no es necesario indicarla, ya que es implícita. El indicarla produciría el mismo resultado. Para acceder a la acción getStatus se hace de la siguiente forma:
Ejemplo: Acceder a una acción personalizada desde una URL
http://www.example.com/company/customer/getStatus/190

  • El componente Controller esta integrado con el componente View que implementa el patrón Template View. Esta integración permite que en cuanto termina la ejecución de la lógica en la acción automáticamente se renderiza la presentación ó vista correspondiente al controlador y acción solicitados.
  • El funcionamiento del componente Controller se apoya tanto en Dispatcher como en Router para realizar todo el trabajo al atender una petición a la aplicación. No es necesario entender el funcionamiento de estos componentes en detalle aunque si se desea extender la funcionalidad de la arquitectura implementada en Kumbia Enterprise Framework puede resultar útil
  • Primero la clean URL es fragmentada usando el método Router::rewrite aquí se determina que aplicación, controlador y acción se requiere ejecutar. El componente Router es quien realiza la orquestación de todo el flujo de ejecución.
  • El componente Dispatcher recibe los parámetros de controlador y acción y busca el indicado en el directorio de controladores para su procesamiento y delegación a la operación requerida.
  • Antes de ejecutar la petición Dispatcher busca si esta definido el método ó atributo beforeFilter en la clase del controlador ó en su jerarquía y lo ejecuta.
  • Si el flujo de la ejecución no ha sido cambiado mediante el método Controller::routeTo entonces ejecuta la acción solicitada en el controlador. La acción tiene acceso a todo el entorno HTTP e información enviada por métodos POST, GET, PUT, etc.
  • Si no cambia el flujo de ejecución Dispatcher busca si esta definido el método ó atributo afterFilter en la clase controladora en su jerarquía de clases y lo ejecuta.
  • El proceso de enrutamiento es cíclico y termina solo cuando se deja ó no se invoca el método Controller::routeTo.
  • El Compontente View toma el control y recibe lo generado por Controller y visualiza la presentación para éste, en caso de que exista.

El patrón Front-Controller junto con Model-View-Controller funciona como el corazón el framework e integra los componentes Controller, Router, Dispatcher y Core para hacerlo funcionar. Cuando requerimos de entender ó modificar la ejecución del flujo de aplicación nos remitimos a los servicios que estos componentes proporcionan.
Servicios del Componente Router
void Router::rewrite(string $url)
Toma la clean URL y fragmenta cada componente localizando la aplicación, controlador y acción solicitados, este método es llamado automáticamente en el bootstrap del framework ubicado en public/index.php. cedula

void Router::ifRouted()
Antes de la ejecución de cualquier acción busca en la tabla de enrutamiento estático generada a partir de config/routes.ini si se debe enrutar a otra controlador ó acción dinámicamente.

boolean Router::getRouted()
Este método devuelve el estado del router que indica si es necesario hacer una enrutación ó continuar con el flujo normal de la aplicación.

string Router::getApplication()
Devuelve el nombre de la aplicación que fue solicitada en la petición.

string Router::getModule()
Devuelve el nombre del módulo que fue solicitado en la petición.

string Router::getController()
Devuelve el nombre del controlador que fue solicitado en la petición. Esta información adicionalmente se puede obtener usando el método en el controlador llamado getControllerName().

string Router::getAction()
Devuelve el nombre de la acción que fue solicitada en la petición. Esta información adicionalmente se puede obtener usando el método en el controlador llamado getActionName().

string Router::getId()
Devuelve el primer parámetro enviado por URL en la petición.

array Router::getParameters()
Devuelve en un array los parámetros enviados por URL en la petición, estos igualmente se les hace binding a la acción como parámetros del método ejecutado de la clase controladora.


array Router::getAllParameters()
Devuelve un array con todos los fragmentos de la URL solicitada en la petición.

array Router::routeTo(mixed $params)
Permite cambiar el flujo de ejecución de la aplicación transfiriéndoselo a otro controlador y/o acción.

array Router::routeToURI(string $uri)
Permite cambiar el flujo de ejecución de la aplicación transfiriéndoselo a otro controlador y/o acción mediante un Uniform Resource Identifier.

string Router::getActiveApplication() 
Devuelve el nombre de la aplicación actual. Cuando es la aplicación por defecto devuelve la palabra default.

void Router::setApplication(string $name)
Permite establecer dinámicamente el nombre de la aplicación actual.

void Router::setDefaultActionName(string $actionName)
Permite establecer el nombre de la acción por defecto en todos los controladores.

string Router::getDefaultActionName()
Devuelve el nombre de la acción por defecto en todos los controladores.

int Router::getRoutingType()
Devuelve el tipo de enrutamiento producido de acuerdo al origen de la petición. El valor devuelto es la constante Router::ROUTING_NORMAL ó Router::ROUTING_OTHER.
Servicios proporcionados por Dispatcher
void Dispatcher::setControllersDir(string $directory)
Permite establecer el directorio de controladores usado para hacer el lookup de un controlador cuando se realiza una petición.

Controller Dispatcher::getControllerInstance()
Devuelve el objeto controlador instanciado que se está ejecutando.

int Dispatcher:.getDispatchStatus()
Devuelve el estado actual del flujo de ejecución. Puede ser alguna de las constantes de la clase Dispatcher:

  • STATUS_UNINITIALIZED: Indica que no se ha iniciado el proceso de ejecución
  • STATUS_DISPATCHING: Indica que se esta localizando el controlador y su acción solicitada
  • STATUS_RUNNING_BEFORE_FILTERS: Indica que se están localizando los métodos y atributos beforeFilter y se están ejecutando.
  • STATUS_RUNNING_AFTER_FILTERS: Indica que se están localizando los métodos y atributos afterFilter y se están ejecutando.
  • STATUS_RENDER_PRESENTATION: Indica que el control de la aplicación fue transferido al componente View.
  • STATUS_RUNNING_BEFORE_STORE_PERSISTENCE: Indica que se va a realizar el procedimiento de almacenamiento de los datos persistentes del controlador.
  • STATUS_RUNNING_AFTER_STORE_PERSISTENCE: Indica que se ha realizado el procedimiento de almacenamiento de los datos persistentes del controlador.
  • STATUS_RUNNING_CONTROLLER_ACTION: Indica que el control de ejecución lo tiene el controlador como tal y su acción solicitada.
boolean Dispatcher::isRunningController()
Indica si el control de ejecución esta a nivel del controlador y no del framework.

boolean Dispatcher::isRunningUserLevel()
Indica si la ejecución esta a nivel de la lógica del desarrollador y no del framework.

mixed Dispatcher::getValueReturned()
Devuelve el valor que retornó la última acción ejecutada en la petición.
Excepciones Generadas en el Dispatcher
En el proceso del Dispatcher pueden ocurrir excepciones que son enviadas directamente al cliente si no se encuentra definido el método onException ya sea en el controlador ó en ControllerBase. Las excepciones ocurridas en Dispatcher lanzan una excepción de clase DispatcherException en las siguientes circunstancias:
Tabla: Códigos y descripción de excepciones generadas en Dispatcher
Código
Descripción
Dispatcher::NOT_FOUND_ACTIONSe genera cuando no existe un método con el nombre de la acción indicado en la clase controladora y además no existe la acción notFoundAction.
Dispatcher::NOT_FOUND_CONTROLLERSe genera cuando no se encuentra el controlador solicitado pero si existe el archivo correcto.
Dispatcher::NOT_FOUND_FILE_CONTROLLEROcurre cuando no existe el archivo del controlador y por ende la clase del mismo al solicitar la petición a este.
Dispatcher::NOT_FOUND_INIT_ACTIONOcurre cuando se esta tratando de ejecutar el método init en ControllerBase pero este no se ha definido ó su visibilidad no es pública.
Dispacher:: INVALID_METHOD_CALLBACKSe genera cuando se trata de invocar externamente el constructor de la clase controladora ó un método protegido ó privado.
Dispatcher::INVALID_ACTION_VALUE_PARAMETERSe genera cuando no se ha enviado por la URL ó al redireccionar a una determinada acción se ha omitido el valor para un parámetro de una acción en el controlador solicitado.


Como se ha mencionado anteriormente es posible definir un método que actúe como una capa previa al lanzamiento de la excepción al usuario llamada onException. Esta recibe el objeto instanciado de la excepción generada, este método recibe además cualquier excepción independiente de si genera en Dispatcher ó dentro del mismo controlador:
Ejemplo: Definir un método que administre las excepciones en controladores
<?php

class CustomerController extends ApplicationController {

     public function indexAction(){
     
     }

     public function onException($e){
          if($e instanceof DispatcherException){
               if($e->getCode()==Dispatcher::NOT_FOUND_ACTION){
                    Flash::notice("Lo sentimos la página no existe");
               }
          } else {
               //Se relanza la excepción
               throw $e;
          }
          
     }

}

Nota: Una excepción que se presenta frecuentemente es la INVALID_ACTION_VALUE_PARAMETER, que se genera cuando se omite en la URL un parámetro del método de la acción que no es opcional ó que tiene un valor por defecto. Kumbia Enterprise Framework es estricto en este sentido y generará una excepción cuando se omita un valor aunque PHP en sí generaría solo una advertencia. La forma más práctica de evitar esto es asignar valores predeterminados a cada parámetro del método haciendo la lógica de aplicación más consistente evitando mensajes en la pantalla del cliente (explorador, consola, etc). Este tipo de excepciones también se generan al realizar el enrutamiento y omitir el valor de algún parámetro.
Peticiones HTTP a Controladores
Cuando se realiza una petición a un controlador mediante protocolo HTTP es posible crear el objeto de petición ControllerRequest y además utilizar métodos que ayudan a interactuar con los datos enviados a estos como por ejemplo cuando se usan formularios HTML.

La instancia de ControllerRequest se puede obtener en el controlador usando el método Controller::getRequestInstance(), el objeto obtenido encapsula toda la información enviada en la petición HTTP y la información de su entorno para ser utilizada dentro del controlador:
Ejemplo: Obtener un instancia de la clase ControllerRequest
<?php

class CustomerController extends ApplicationController {

     public function indexAction(){
          $request = $this->getRequestInstance();
          if($request->isPost()==true){
               Flash::notice("La petición se ha realizado por método http POST");
          }

     }

     /**
      * Devuelve el nombre del cliente usando JSON solo si la petición
      * fue realizada con AJAX
      */
     public function getCustomerClientAction(){
          $this->setResponse("json");
          $request = $this->getRequestInstance();
          if($request->isAjax()==true){
               $this->renderText($this->jsonEncode(array("name" => "John Smith")));
          }
     }


}


Los valores de las variables superglobales $_GET, $_POST, $_COOKIE, $_REQUEST, $_SERVER Y $_ENV pueden ser accesados usando los métodos de ControllerRequest usando getParamGet, getParamPost, getParamCookie, getParamServer y getParamEnv. De igual forma puede utilizar los métodos del controlador getQueryParam, getPostParam, getRequestParam, getCookieParam, getServerParam y getEnvParam para obtener estos valores sin obtener la instancia de la clase ControllerRequest aunque esto se haga implícitamente.
Administrar archivos adjuntos en una petición
Los archivos adjuntos en una petición pueden ser administrados usando los métodos de ControllerRequest llamados hasFiles, getParamFile y getUploadedFiles.
Ejemplo: Tratamiento de archivos enviados en una petición
<?php

class Movement extends ApplicationController { 

public function loadMovementAction(){

          if($this->getRequestInstance()->hasFiles()==true){
               foreach($this->getRequestInstance()->getUploadedFiles() as $file){
                    echo "Nombre original del archivo: ".$file->getFileName();
                    echo "Tamaño del archivo: ".$file->getFileSize();
                    echo "MIME del archivo: ".$file->getFileType();
                    echo "Nombre Temporal: ".$file->getTempName();
                    $file->moveFileTo('movement/'.$file->getFileName());
               }
          }
     }

}
API de ControllerRequest
Adicional a los métodos mencionados anteriormente la referencia del API de ControllerRequest además tiene:

void setParamRequest(string $index, mixed $value)
Cambia un valor enviado en una petición en la superglobal $_REQUEST. Este método automáticamente actualiza $_POST y $_GET si es necesario.

void setParamGet(string $index, mixed $value)
Cambia un valor enviado en una petición en la superglobal $_GET.

void setParamPost(string $index, mixed $value)
Cambia un valor enviado en una petición en la superglobal $_POST.

void setParamCookie(string $index, mixed $value)
Cambia un valor enviado en una petición en la superglobal $_COOKIE.

boolean isSetRequestParam($index)
Indica si existe una llave para un valor en la superglobal $_REQUEST.

boolean isSetQueryParam($index)
Indica si existe una llave para un valor en la superglobal $_GET.

boolean isSetPostParam($index)
Indica si existe una llave para un valor en la superglobal $_POST.

boolean isSetCookieParam($index)
Indica si existe una llave para un valor en la superglobal $_COOKIE.

boolean isSetServerParam($index)
Indica si existe una llave para un valor en la superglobal $_SERVER.

boolean isSetEnvParam($index)
Indica si existe una llave para un valor en la superglobal $_ENV.

boolean isSetRequestParam($index)
Elimina un valor en la superglobal $_REQUEST. Implicitamente elimina el valor de $_GET y $_POST.

boolean isSetQueryParam($index)
Elimina un valor en la superglobal $_GET.

boolean isSetPostParam($index)
Elimina un valor en la superglobal $_POST.

boolean isAjax()
Indica si la petición ha sido realizada usando AJAX. Funciona cuando se utiliza el framework Javascript Prototype ó JQuery.

boolean isFlashRequested()
Indica si la petición ha sido realizada desde un objeto Macromedia Flash incrustado en un sitio Web.

boolean isSoapRequested()
Indica si la petición ha sido realizada desde un cliente SOAP. Al solicitarsen este tipo de peticiones a la aplicación el administrador de presentación y enrutamiento se cambian para crear una comunicación machine-to-machine.

boolean isSecure()
Indica si la petición se realiza bajo una conexión encriptada usando HTTPS.

string getRawBody()
Devuelve el cuerpo de la petición HTTP directamente.

string getHeader(string $name)
Devuelve un encabezado HTTP a partir de su nombre. Funciona anteponiendo el sufijo HTTP_ ó sin él.

string getScheme()
Devuelve el scheme (protocolo) utilizado para realizar la petición HTTP. Devuelve http ó https generalmente.

string getHttpHost()
Devuelve el host y puerto en el que realizo la petición. Generalmente es la ip pública ó privada del servidor donde esta instalado el interprete PHP.

string getMethod()
Devuelve el método HTTP utilizado para hacer la petición POST, GET, PUT, etc.

boolean isGet()
Devuelve true si la petición HTTP fue realizada usando método GET.

boolean isPost()
Devuelve true si la petición HTTP fue realizada usando método POST.

boolean isPut()
Devuelve true si la petición HTTP fue realizada usando método PUT. Es útil cuando se crean aplicaciones REST.

boolean isOptions()
Devuelve true si la petición HTTP fue realizada usando método OPTIONS. Es útil cuando se crean aplicaciones REST.

boolean isHead()
Devuelve true si la petición HTTP fue realizada usando método HEAD. Es útil cuando se crean aplicaciones REST.

boolean isDelete()
Devuelve true si la petición HTTP fue realizada usando método DELETE. Es útil cuando se crean aplicaciones REST.

boolean hasFiles()
Indica si la petición incluye archivos subidos mediante método POST.

array getUploadedFiles()
Obtiene un vector con objetos ControllerUploadFile que encapsulan la información de archivos subidos en la petición.

string getHTTPReferer()
Obtiene el HTTP referer de la petición.

array getAcceptableContent()
Obtiene un vector con los mimes del tipo de contenido aceptado por el cliente HTTP junto con su calidad. Devuelve un array como: Array ( [0] => Array ( [accept] => text/html [quality] => 1 ) [1] => Array ( [accept] => application/xhtml+xml [quality] => 1 ) [2] => Array ( [accept] => application/xml [quality] => 0.9 ) [3] => Array ( [accept] => */* [quality] => 0.8 ) )

array getClientCharsets()
Devuelve la lista de idiomas soportados por el cliente HTTP junto con su calidad. Devuelve un array como: Array ( [0] => Array ( [accept] => ISO-8859-1 [quality] => 1 ) [1] => Array ( [accept] => utf-8 [quality] => 0.7 ) [2] => Array ( [accept] => * [quality] => 0.7 ) )

string getBestQualityCharset()
Obtiene el charset de mejor calidad soportado por el cliente HTTP.

boolean isRequestingStaticContent(array $staticMimes=array())
Devuelve true si se está solicitando contenido estático. Este se determina como estático si el cliente solicita los tipos de archivo image/gif, image/png, image/jpeg, application/x-javascript, text/css y text/javascript. Otros tipos de archivo pueden indicarse usando el parámetro $staticMimes.
Respuestas HTTP de Controladores
La respuesta que generan los controladores en una petición HTTP es encapsulada en el objeto ControllerResponse el cual funciona como un Gateway entre la vista y el controlador. Este objeto esta implementado al igual que ControllerRequest con el patrón Singleton, es decir que por cada petición a la aplicación solo existirá una intancia en todo el contexto de ejecución. Para obtener la instancia de la clase ControllerResponse se usa el método estático getInstance().
Ejemplo: Uso del objeto ControllerResponse
<?php

class DocumentsController extends ApplicationController {

     public function indexAction(){
          $response = ControllerResponse::getInstance();
          $response->setHeader("Content-Type: application/pdf");
          $response->setResponse(ControllerResponse::RESPONSE_OTHER);
          $response->setResponseAdapter('pdf');
     }

}

En el ejemplo anterior la acción index visualiza un archivo en formato PDF que es leído en la presentación, el método setHeader establece un encabezado HTTP apto para la salida y además indica a la aplicación que no debe utilizar el adaptador de presentación por defecto (HTML) sino debe usar 'pdf'.
Establecer el tipo de salida de la Petición
El controlador proporciona el método Controller::setResponse que permite establecer el tipo de salida que se genera en la acción solicitada. Los tipos de salida que admite este método son los siguientes:
Tabla: Tipos de respuesta que acepta el objeto ControllerResponse
Valor
Descripción
viewIndica que solo la vista correspondiente a la acción solicitada será la que se visualizará. Usualmente aplica cuando se renderizan vistas parciales ó fragmentos AJAX.
ajaxRealiza lo mismo que 'view' pero documenta el código más claramente.
jsonPermite producir salidas usando notación JSON (JavaScript Serializable Object Notation).
xmlProduce una salida XML, agrega el encabezado Content-type: text/xml y no muestra ningun layout asociado.
rssProduce una salida XML agrega el encabezado encabezado Content-type: application/rss+xml y no muestra ningun layout asociado.


Los valores de los tipos de salida son internamente convertidos a un tipo de salida soportado por ControllerResponse:
Tabla: Tipos de respuesta avanzados del objeto ControllerResponse
Valor
Descripción
RESPONSE_NORMALEs la respuesta que se genera normalmente. No es necesario establecerla.
RESPONSE_OTHERIndica que no se debe usar el adaptador de visualización por defecto porque se generará una salida en otro formato.


En el siguiente ejemplo se ilustra como generar una salida usando JSON, la acción countItemsAction implementa una salida primero estableciendo el tipo de salida y después haciendo la salida de la misma, en getItemsAction se resumen los dos pasos anteriores en una sola línea:
Ejemplo: Implementar una salida JSON desde un controlador

<?php

class CartController extends ApplicationController {

     public $items = array();

     public function addToListAction($item){
          $this->ítems[] = $item;
     }

     public function countItemsAction(){
          $this->setResponse('json');
          $this->renderText($this->jsonEncode(count($this->ítems)));
     }

     public function getItemsAction(){
          $this->outputJSONResponse($this->ítems);
     } 

}

Nota: El método setResponse tiene una funcionalidad limitada por lo cual ha sido marcado como obsoleto, en vez de este se debe usar View::setRenderLevel en conjunto con Controller::setResponseType.
API de ControllerResponse
El API de la clase ControllerResponse es:

public static ControllerResponse getInstance()
Obtiene la instancia del objeto ControllerResponse.

public void setHeader(string $header, boolean $replace=true)
Permite establecer un encabezado HTTP en la petición actual. El parámetro $replace indica si el encabezado debe reemplazar uno del mismo tipo establecido anteriormente.

public array getHeaders(boolean $process=true)
Obtiene un array con los encabezados HTTP que van a ser enviados en la respuesta de la petición. Si el parámetro $process es true devolvera un array asociativo cuyas claves corresponden a los nombres de los encabezados.

public boolean hasHeader(string $headerName)
Indica si ya se ha definido un encabezado para ser enviado en la respuesta a la petición.

public void setResponseType(int $type)
Permite establecer el tipo de respuesta que debe generar la petición. El parámetro $type recibe los valores de las constantes ControllerResponse::RESPONSE_NORMAL y ControllerResponse:: RESPONSE_OTHER.

public integer getResponseType()
Obtiene el tipo de respuesta que va a generar el controlador. Devuelve el valor de cualquiera de las 3 constantes mencionadas anteriormente.

public integer setResponseAdapter(string $adapter)
Establece el nombre del adaptador utilizado para procesar la salida de la petición. Los posibles valores para $adapter son json, pdf y xml. En la referencia del componente View se explica como utilizar otros adaptadores.

public function getResponseAdapter()
Devuelve el adaptador usado para generar la salida.

public function setContentType($contentType)
Establece el encabezado Content-Type de la respuesta del controlador.
Controlar acciones no encontradas
Cuando se realiza una petición a un controlador y la acción solicitada no esta implementada se genera una excepción que cuando no es controlada presenta información de la excepción al usuario de la aplicación. La información de excepciones presenta información técnica que en manos equivocadas puede permitir facilitar el ataque a la aplicación.

El desarrollador puede implementar la acción notFoundAction ya sea en el controlador ó en la jerarquía de clases lo cuál permitirá presentar al usuario un mensaje personalizado y probablemente almacenar un log de la situación ocurrida.
Ejemplo: Definir una acción que administre las peticiones a controladotes y acciones que no esten definidas
<?php

class ReportsController extends ApplicationController {

     public function notFoundAction($actionName=""){
          $logger = new Logger("File", "notFoundReports.txt");
          $logger->log("No se encontró la acción $actionName");
     }
}
Filtros en controladores
Cada acción ejecutada en cualquier controlador de la aplicación ejecuta, si están presentes filtros antes y después de la ejecución del método solicitado. Los filtros permiten ejecutar tareas de autenticación, politicas de seguridad, validación y enrutamiento, además de acceder por completo al entorno HTTP y modificar la respuesta de la petición.

Los dos tipos de filtros son: beforeFilter que es ejecutado antes de ejecutar la acción solicitada como tal y afterFilter que se ejecuta inmediatamente después.

Los filtros son métodos que se implementan directamente como métodos en la clase controladora ó en la jerarquia de clases del mismo. Usualmente filtros generales para todos controladores se implementan en la clase padre ControllerBase, de esta forma interceptan todas las peticiones a la aplicación.
Ejemplo: Definir filtros a nivel general en la aplicación
<?php

class ControllerBase {

     public function init(){
          Router::routeTo("controller: login");
     }

     public function beforeFilter(){
          $activeRole = Session::getData("activeRole");
if(Router::getController()=="admin"&&$activeRole!="Administradores"){
               Router::routeTo("controller: login", "action: index");
               return false;
          }
     }

}

Se debe devolver false desde beforeFilter cuando se realiza un enrutamiento a otro controlador diferente al activo.

Cuando se implementan múltiples filtros es necesario invocar el filtro padre en el momento indicado:
Ejemplo: Definición de múltiples filtros
<?php

class CustomerController extends ApplicationController {

     public function beforeFilter(){
          /**
           * Algún código de filtro
           */
          parent::beforeFilter();
     }

}
Enrutamiento en controladores
El componente Router ofrece servicios que permiten al desarrollador alterar el flujo de ejecución como sea necesario, ya sea reescribiendo URLs, redireccionando mediante HTTP ó haciendo enrutamientos a nivel de controlador. El proceso de enrutamiento se requiere en una aplicación cuando es necesario llevar al usuario a un controlador y acción sin que sea solicitado propiamente por el mismo.
Ciclo de enrutamiento
Por cada petición que se realiza a la aplicación se inicia un ciclo de enrutamiento que permite ejecutar todas las acciones requeridas en los controladores según la lógica de negocio. Este ciclo ejecuta cada acción enrutada como si se tratara de una petición normal de usuario.

El psudo-código del ciclo de enrutamiento es el siguiente:

Pseudocódigo: Flujo y orden de ejecución de eventos en ciclo de enrutamiento
Intentar 
InvocarEventoDeControlador("beforeDispatchLoop")
HayEnrutamiento := Verdadero
Mientras HayEnrutamiento = Verdadero Hacer
          HayEnrutamiento := InvocarEventoDeControlador("beforeDispatch")
     HayEnrutamiento := InvocarEventoDeControlador("beforeExecuteRoute")
     EjecutarFiltroBeforeFilter()
          HayEnrutamiento := EjecutarAccionEnControlador()
     EjecutarFiltroAfterFilter()
     HayEnrutamiento := InvocarEventoDeControlador("afterExecuteRoute")
          HayEnrutamiento := InvocarEventoDeControlador("afterDispatch")
ContinuarMientras
InvocarEventoDeControlador("afterDispatchLoop")
CapturarExcepción 
InvocarEventoDeControlador("onExceptions")
FinIntentar

Cuando termina el ciclo de enrutamiento se transfiere el control de la aplicación a la presentación (vista), en donde el compontente View realiza este trabajo.
Enrutar usando Router::routeTo
El Componente Router ofrece el método estático routeTo que realiza las tareas de enrutamiento y valida si existen rutas estáticas predefinidas con la prioridad del caso. Es posible utilizar parámetros con nombre para indicar la ruta requerida donde se desea enrutar. El método routeTo de Controller es un proxy al método estático mencionado.

En el siguiente ejemplo se ilustra la validación de un usuario y su enrutamiento de acuerdo a si la autenticación es exitosa ó falla:
Ejemplo: Validación de usuario y enrutamiento condicional
<?php

class LoginController extends ApplicationController {

     public function startSessionAction(){
          $login = $this->getPostParam('login', 'alpha');
          $pass = $this->getPostParam('pass', 'alpha');
          if($this->Usuarios->findFirst("login = '$login' AND clave = '$pass'")){     
               Flash::success('Bienvenido '.$this->Usuarios->getNombre());
               $this->routeTo('controller: menu');
          } else {
               Flash::error('Permisos Insuficientes/Password Incorrecto');
               $this->routeTo('action: startSession');
          }
     }

}

El método routeTo, gracias a los parámetros por nombre, puede recibir la ruta de enrutamiento en cualquier orden, usando las claves: controller, action e id. Si es necesario pasar más parámetros adicionales puede usar llaves numéricas que coincidan con el orden en que la acción recibe los parámetros requeridos:
Router::routeTo("controller: invoice", "action: setMovementDate", "id: 2008/01/12", "1: 2008/01/17");

Cuando se solicita un enrutamiento con routeTo() solo es efectivo hasta que termina la ejecución de todo el método de la acción, es posible utilizar return para salir del método anticipadamente.
Enrutar usando Router::routeToURI()
También es posible hacer enrutamientos usando un Uniform Resource Indentifier como lo es una clean URL dentro de la misma lógica de aplicación. Para esto se recurre al método estático Router::routeToURI que realiza el papel del mod_rewrite fragmentando la URL y convirtiéndola en los valores internos necesario para hacer el enrutamiento. El siguiente ejemplo ilustra la reescritura de una fecha pasada por la URL estilo Blogger:
Ejemplo: Reescritura de URL mediante Router::routeToURI
<?php

class BloggerController extends ApplicationController {

     public function showPostAction ($year, $month, $date, $name){
          $this->routeToURI("/blogger/showPostByDate/$year-$month-$date/$name");
     }

     public function showPostByDateAction($date, $name) {
          // alguna lógica aqui
     }

}
Inicialización de Controladores
Debido a que el componente Controller proporciona un entorno de estado de persistencia, los objetos instanciados de las clases controladoras no son destruidos al terminar la sesión y son persistidos durante toda la sesión de usuario. Según lo anterior los constructores de las clases controladoras no son confiables para inicializar las condiciones de ejecución cada vez que se genera una petición a los mismos. Por esta razón se proporciona la posibilidad de definir el método protegido 'initialize' con el fin de inicializar el objeto desde la persistencia en cada petición al controlador.
Ejemplo: Inicializar un controlador mediante initialize()
<?php

class UsuariosController extends StandardForm {

     public $scaffold = true;
     public $template = "admin_menu";

     public function beforeInsert(){
          $clave = sprintf("%04s", mt_rand(0, 9999));
          $this->Usuarios->clave = sha1($clave);
          Flash::success('La clave del Usuario es "'.$clave.'", por favor no la olvide');
     }

     public function beforeUpdate(){
          $usuario = new Usuarios();
          $usuario->find($this->getRequestParam("fl_id", "int"));
          $this->Usuarios->clave = $usuario->clave;
     }

     public function initialize(){

          $this->ignore('clave');
          $this->setTextUpper("nombre");
          $this->setComboStatic('perfil', array(
               array('Administradores', 'ADMINISTRADOR'),
               array('Cajeros', 'CAJERO'),
               array('Meseros', 'MESERO')
          ));

          $this->setComboStatic("estado", array(
               array("A", "ACTIVO"),
               array("I", "INACTIVO")
          ));

     }

}

Este método es invocado tanto en el constructor del controlador como en el evento __wakeup que ocurre al deserializar el objeto desde la persistencia de sesión.
Estado de persistencia de Controladores
Como se menciono anteriormente Kumbia Enterprise Framework implementa persistencia del estado de los controladores buscando aproximar el desarrollo Web a un comportamiento de aplicación de escritorio. Para activar/desactivar la persistencia es necesario invocar el método setPersistance(bool $persitence), en el método inicializador el controlador.

Gracias a la implementación del patrón base Registry en el componente Dispatcher, cada objeto de una clase controladora instanciada se almacena temporalmente y si se realizan enrutamientos a controladores previamente accedidos estos no son llevados a persistencia por lo que el estado de sesión del controlador se mantiene intacto como la primera vez que se invocó un método de la misma.

Al activar la persistencia el valor del atributo name se mantiene entre peticiones al controlador:
Ejemplo: Implementar persistencia en el controlador
<?php

class ProductsController extends ApplicationController {

     public $name;

     public function indexAction(){
          $this->name = "TV 22'";
     }

     public function showProductAction(){
          Flash::notice($this->name);
     }


     public function initialize(){
          $this->setPersistance(true);
     }

}
Ventajas del Estado de Persistencia
El estado de persistencia ó contexto de conversación permite que:

  • Implementar atributos modulares en los controladores de tal forma que se haga un uso más intuitivo de los datos de sesión.
  • Implementar interfaces y componentes que "recuerden" el estado que se le dio la última vez que se accedió a ellos.
  • Crear conversaciones con el usuario de la aplicación de tal forma que contextualmente cada módulo mantenga un flujo de trabajo ó proceso como parte de su naturaleza de negocio misma.
Consideraciones del estado de Persistencia
El estado de persistencia esta pensando en dar la posibilidad de mantener los valores de los atributos de los controladores con el fin de establecer un contexto de conversación entre el cliente y la aplicación.

Los controladores en un "estado" persistente almacenan los datos en el administrador de sesiones, si el volumen de datos almacenado es elevado el rendimiento de la aplicación podría disminuir. Instancias de entidades y otros objetos complejos que hagan parte de los datos de persistencia son excluidos de los datos almacenados debido a la cantidad de recursos que consumen. Puede utilizar el componente Cache para almacenar este tipo de objetos usando diversos backends.

En el capítulo de optimización y rendimiento se presenta el componente GarbageCollector que permite de forma controlada comprimir y/o eliminar datos de estado de persistencia que sean utilizados por la aplicación.
Transacciones y Concurrencia del Estado de Persistencia
Los controladores con estado de persistencia activo generan una transacción de los atributos de al iniciar la ejecución de cualquier acción en el. Mientras se ejecuta la petición en el controlador la transacción permanece en un nivel de isolación en el cuál no realiza ningún bloqueo sobre los datos de sesión. Se puede entender que se realizan transacciones atómicas con estos datos persistentes. Peticiones con un alto grado de concurrencia en la misma sesión de usuario y al mismo controlador podrían generar inconsistencias.

Cuando se genera una excepción no controlada la transacción realiza un rollback haciendo que los atributos del controlador permanezcan intactos a su estado en la última petición exitosa.
Eventos del estado de Persistencia
Un controlador con el contexto de persistencia activado puede implementar eventos que permitan modificar la estado inicial al restaurar el objeto de su estado "dormido" y también cual será su estado final antes de ser almacenado "dormir":
Tabla: Tipos de respuesta que acepta el objeto ControllerResponse
Evento
Descripción
afterRestorePersistenceSe genera cuando despierta el controlador de su estado "dormido" en la persistencia. Esto solo ocurre cuando se realiza una petición al controlador.
beforeStorePersistenceSe genera cuando el objeto controlador se esta preparando para ir a la persistencia.


En el siguiente ejemplo se implementa el evento beforeStorePersitence para evitar que un dato de gran tamaño no relevante se lleve a la persistencia:
Ejemplo: Implementar un evento del estado de persistencia
<?php

class ProductsController extends ApplicationController {

     public $name;
     public $veryLongData;

     public function indexAction(){
          $this->name = "TV 22'";
     }

     public function showProductAction(){
          Flash::notice($this->name);
     }


     public function initialize(){
          $this->setPersistance(true);
     }

     public function beforeStorePersitence(){
          $this->veryLongData = "";
     }

}
La Clase ControllerBase
Esta clase es la padre de todos los controladores, el concepto de esta clase esta inspirado en el patrón "ApplicationController" (no esta relacionado con la clase del mismo nombre), que es una extensión al MVC permitiendo que parte de la lógica de negocio sea compartida por un clase de la cual heredan las demás. Esta clase además sirve de punto de intersección y permite ajustar el flujo de ejecución de acuerdo al contexto en el que se invoque.

El desarrollador puede implementar métodos que serán heredados por cualquier controlador de la aplicación. Es importante proteger la visibilidad de la lógica compartida en esta clase de tal forma que solo sea utilizada internamente por los controladores.
La Clase Controller
La clase Controller implementa funcionalidad compartida a todas las implementaciones de controladores. Esta clase actúa como el corazón de la capa de lógica de dominio.
API de Controller
Controller implementa principalmente métodos proxy a otros componentes facilitando su uso inmediato en un proceso de negocio.

function void routeTo()
Este método es implementado por conveniencia al desarrollador y realiza un proxy al método Route::routeTo.

function void routeToURI()
Este método es implementado por conveniencia al desarrollador y realiza un proxy al método Route::routeToURI.

function mixed getPostParam(string $paramName)
Este método es implementado por conveniencia al desarrollador y realiza un proxy al método ControllerRequest::getParamPost.

function mixed getQueryParam(string $paramName)
Este método es implementado por conveniencia al desarrollador y realiza un proxy al método ControllerRequest::getParamQuery.

function mixed getRequestParam(string $paramName)
Este método es implementado por conveniencia al desarrollador y realiza un proxy al método ControllerRequest::getParamRequest.

function mixed getServer(string $paramName)
Este método es implementado por conveniencia al desarrollador y realiza un proxy al método ControllerRequest::getParamServer.

function mixed getEnvironment(string $paramName)
Este método es implementado por conveniencia al desarrollador y realiza un proxy al método ControllerRequest::getParamEnvironment.

function mixed filter(string $paramValue)
Este método es implementado por conveniencia al desarrollador y permite aplicar un filtro usando el componente Filter .

function void setRequestParam(mixed $index, mixed $value)
Este método es implementado por conveniencia al desarrollador y realiza un proxy al método ControllerRequest::setParamRequest.

function void setPostParam(mixed $index, mixed $value)
Este método es implementado por conveniencia al desarrollador y realiza un proxy al método ControllerRequest::setParamPost.

function void setQueryParam(mixed $index, mixed $value)
Este método es implementado por conveniencia al desarrollador y realiza un proxy al método ControllerRequest::setParamQuery.

function void setCookie(mixed $index, mixed $value)
Este método es implementado por conveniencia al desarrollador y realiza un proxy al método ControllerRequest::setParamCookie.

function void setPersistance(boolean $persistance)
Establece el estado conversacional del controlador. Cuando $persistance es true el estado del controlador, (los valores de sus atributos) se mantienen entre una petición y otra a la aplicación.

function boolean getPersistance()
Devuelve un valor booleano que indica si el estado conversacional del controlador está activo ó no.

function void redirect(string $uri)
Permite realizar una redirección HTTP a una determinada URI dentro de la aplicación actual.

function void setResponse(string $type)
Establece el tipo de respuesta que debe generar la petición. Consulte la referencia de ControllerResponse para obtener más información sobre el uso de este método.

function void exceptions(Exception $exception)
Al reescribir este método es possible administrar las excepciones producidas en el controlador.

function string jsonEncode(mixed $data)
Codifica una variable $data usando notación JSON para luego ser enviada al cliente.

function void outputJSONResponse(mixed $data)
Codifica una variable $data usando notación JSON y la envia directamente al cliente.

function string getControllerName()
Obtiene el nombre del controlador actualmente en ejecución.

function string getActionName()
Obtiene el nombre de la acción actualmente en ejecución

function void getId()
Obtiene el valor del primer parámetro adicional pasado por la URL después del nombre de la acción.

function void getViewHandler()
Al reescribir este método es posible transferir el control de la presentación a un componente de usuario ó según sea requerido por la aplicación.

function array getViewExceptionHandler()
Al reescribir este método es posible transferir el control de la presentación a un componente de usuario ó según sea requerido por la aplicación cuando halla una excepción sin capturar.

function void setTemplateBefore(string|array $template)
Permite definir uno ó mas layouts que se deben incluir en la jerarquía de renderización de la presentación. Este/estos layout(s) son visualizados antes del layout del controlador. Consulte la referencia del componente View para obtener más información sobre el uso de este método.

function void cleanTemplateBefore()
Resetea la lista de layouts definidos a visualizar antes del layout del controlador.

function void setTemplateAfter(string|array $template)
Permite definir uno ó mas layouts que se deben incluir en la jerarquía de renderización de la presentación. Este/estos layout(s) son visualizados antes del layout del controlador. Consulte la referencia del componente View para obtener más información sobre el uso de este método.

function void cleanTemplateAfter()
Resetea la lista de layouts definidos a visualizar después del layout del controlador.

function string|array getTemplateBefore()
Obtiene el ó los layouts a renderizar antes del layout del controlador definidos hasta el momento.

function string|array getTemplateAfter()
Obtiene el ó los layouts a renderizar después del layout del controlador definidos hasta el momento.

function ControllerRequest getRequestInstance()
Obtiene la instancia del objeto ControllerRequest para su uso en el controlador.

function ControllerResponse getResponseInstance()
Obtiene la instancia del objeto ControllerResponse para su uso en el controlador.

function void setParamToView(string $index, string $value)
Transfiere un valor a la vista. El valor $value puede ser utilizado en la vista por medio de una variable local de la vista llamada $index.

function WebServiceClient getService(string $serviceName)
Obtiene una instancia de un servicio del contenedor de servicios para ser usada en el controlador. El servicio $serviceName puede también ser un nombre del Naming Directory ó un valor obtenido de usando un UDDI. Consulte el capítulo del contenedor de servicios para obtener más información de este método.
Controladores usando ApplicationController
La implementación de controladores ApplicationController es la principal que debe ser usada cuando se requiere dar respuestas a peticiones solicitadas por los usuarios finales de las aplicaciones.
API de ApplicationController
function void renderText(string $text)
Envia un texto a la presentación

function boolean validateRequired(string $rules, string $base='', string $getMode='')
Valida la presencia obligatoria de determinados campos recibidos de la entrada con base a unas reglas definidas por el desarrollador. Consulte el componente Validator y Filter para obtener más información.

function void cleanValidationMessages()
Resetea los mensajes de validación producidos hasta el momento.

function void addValidationMessage(string $message, string $fieldName='')
Agrega un mensaje a la cola de validación. El Segundo parámetro opcional $fieldName permite establecer un campo de la interfaz de usuario al que está relacionado el mensaje.

function array getValidationMessages()
Obtiene los mensajes de validación generados hasta el momento en la cola de mensajes.

function boolean isExportable()
Este método puede ser reescrito y debe devolver un valor booleano indicando si los atributos publicos del controlador deben transferirse a la presentación como variables locales.
Controladores usando MultiThreadController
Este tipo de controlador es una sub-implementación de ApplicationController cuyo objetivo es poder implementar procesamiento asimetrico para grandes procesos en controladores. De esta forma es posible ejecutar en forma simultanea tareas de seguimiento y monitorización para procesos de negocio.

Los procesos se ejecutan de forma asincrónica y en un orden que no puede ser denominado seguro por lo tanto son aptos para procesos de debug ó monitorización y no para implementar otros procesos de negocio buscando su ejecución simultánea.

En el siguiente ejemplo se simula la implementación de un proceso de negocio que debido a la cantidad de datos que se deben procesar puede tardar un tiempo prolongado en procesarse, si el proceso fuese más grande y complejo hacer un seguimiento y monitorización detallado puede degradar progresivamente la lógica misma.

El controlador contiene una acción llamada hugeProcess que contiene el proceso a ejecutar, la anotación @synchronized en el método logProcessState indica que este método debe ser ejecutado simultáneamente con cualquier acción ejecutada en el controlador:
class MyProcessController extends MultiThreadController {

     private $_percent;
     private $_customerName;

     /**
      * Realiza el log del proceso
      *
      * @synchronized
      */
     public function logProcessState(){
          if($this->_logger==null){
               $this->_logger = new Logger('File', 'processLog.txt');
          }
          $this->_logger('El cliente actual es: '.$this->_customerName.
' '.$this->_percent);
     }

     public function hugeProcessAction(){
          $totalQuantity = 0;
          foreach($this->Movement->find() as $movement){
               $customer = $movement->getCustomer();
               $this->_customerName = $customer->getName();
               $this->_percent = $movement->getQuantity()/
$customer->getCreditAmmount();
               $product = $movement->getProduct();
               if($product->getType()=='A'){
                    $totalQuantity+=$movement->getQuantity();
               }
          }
          $this->setParamToView('totalQuantity', $totalQuantity);
     }

}

Múltiples métodos del controlador pueden tener la anotación @synchronized de tal forma que sean ejecutados de manera simúltanea con cualquier acción del controlador.
Servicios Web usando WebServiceController
Qué es un Web Service?
Según la definición de la arquitectura de Servicios Web del W3C, un servicio Web es un sistema de software diseñado para soportar interacción interoperable maquina a maquina sobre una red de computación. Cada servicio Web contiene una descripción que otra maquina puede procesar (usando WSDL probablemente). Algunos servicios Web pueden interactuar directamente usando mensajes SOAP apoyándose en serialización XML y el protocolo HTTP en sí.

Con Servicios Web es posible dotar a las aplicaciones de interoperatividad estándar permitiendo su comunicación de forma independiente a su plataforma tecnológica. Kumbia Enterprise Framework permite al desarrollador implementar controladores como Servicios Web automatizando los procesos de publicación del servicio, administración y persistencia en forma transparente.

Un controlador WebServiceController funciona como un Service Provider el cual proporciona un contrato de servicio que lo conforman las acciones del mismo y que son expuestas públicamente como operaciones del mismo de tal forma que clientes externos puedan utilizarlos de manera controlada.

Los Service Customers (clientes de servicios) y Services Providers ofrecen funcionalidad concreta para el envío y recepción de mensajes en una comunicación maquina a maquina. Esto es transparente para el desarrollador aunque debe ser mencionado de manera informativa.

Kumbia Enterprise Framework proporciona capas de Software que permiten implementar ya sea Service Customers ó Services Providers en aplicaciones desarrolladas con él. De esta forma usted puede preocuparse por desarrollar la lógica de negocio sin preocuparse por la forma en que los servicios Web vayan a interactuar a más bajo nivel.
Para que son necesarios los Web Services
Las aplicaciones empresariales se pueden ver muy beneficiadas con el uso de Web Services. Crear contratos de servicio adecuados permite a los desarrolladores extraer en forma abstracta servicios que permitan el intercambio de información con aplicaciones de terceros, encapsulando detalles como la conexión a bases de datos, credenciales de seguridad, dominio y control de la información entregada bajo las condiciones pactadas.

Sistemas empresariales ganan nuevos sub-sistemas de comunicación estandarizada entre aplicaciones de tal forma que su composición sea reutilizable y componible. Mediante el uso de Web Services se eliminan dependencias del pasado como el traspaso de información mediante archivos planos, vistas en las bases de datos, conexiones por FTP ó directamente a los RBDMs que carecían de un control adecuado y obligaban a implementaciones rígidas y complicadas además de exigir la integración de otros componentes en su aplicación.
Arquitectura Orientada a Servicios (SOA)
Las organizaciones empresariales de hoy en dia requieren interconectar procesos e información entre los departamentos de las mismas, sus provedores, clientes y socios comerciales extendiendo sus fronteras y aumentando la competitividad y capacidad de crecimiento.

La Arquitectura Orientada a Servicios proporciona elementos que permiten que un conjunto de aplicaciones distribuidas se convierta en conjunto de recursos que puedan ser utilizados tanto interna como externamente y como resultado se obtiene mayor agilidad y productividad en la organización.

Kumbia Enterprise Framework ofrece herramientas para la construcción de una Arquitectura Orientada a Servicios, sin embargo, debe ser el arquitecto ó desarrollador quien debe determinar si es realmente necesario implementar esta estrategia de acuerdo a la complejidad y objetivos del negocio.

Transformar la lógica de negocios empresarial a una arquitectura orientada a servicios incluye implementar contratos de servicio, reusabilidad, abstracción, loose coupling (acoplamiento débil) y composibilidad. SOA es un modelo arquitectacional para plataformas de tecnología y cada empresa puede definir los objetivos asociados a computación orientada a servicios usando diferentes tecnologías.

Usar Web Services es probablemente uno de las mejores opciones para aplicar los principios SOA y empezar a usar esta arquitectura.
Crear un Web Service
Como se mencionó anteriormente es posible crear servidores para servicios Web usando la implementación de Controller llamada WebServiceController. Este tipo de Controlador permite crear servicios web XML y encapsula toda la comunicación basada en el estándar SOAP. Las comunicaciones soportan las especificaciones SOAP 1.1 y 1.2 así como el uso de descriptores WSDL.

A pesar que los mensajes SOAP son complejos; todos estos detalles son transparentemente implementados en una capa funcional apta al desarrollo rápido y efectivo. Del lado del servidor el desarrollador puede implementar servicios usando lenguaje PHP nativo y la arquitectura y servicios del framework sin demasiados cambios. Del lado del cliente es posible utilizar servicios Web invocando el nombre de los métodos/acciones implementados en el servicio servidor.

Gracias a que se cumple con la especificaciones definidas por el W3C (Word Wide Web Consortium) es posible tener independencia del lenguaje PHP tanto del lado del cliente como del servidor. Lo anterior permite acceder a servicios Web creados en otras plataformas y lenguajes y que están accedan a otros creados usando PHP.

El ejemplo a continuación muestra la definición de un servicio Web simplificado que permite chequear la disponibilidad de fechas para realizar una reserva en un sistema ficticio. En el primer método startSession se recibe como parámetros el login y password y usando el componente Auth se autentica contra un servidor ActiveDirectory usando el adaptador LDAP, si estas credenciales son validas entonces el resultado devuelto es devuelto y posteriormente permite validar que el usuario este autenticado. Notese que el segundo método getAvalability lanza una excepción WebServiceControllerException cuando no se cumple una validación la cual se notifica al cliente de igual manera.

Los valores escalares devueltos por cada método se transfieren transparentemente entre el servidor y el cliente, datos más complejos exigen distribuir componente compartidos de tal manera que no haya perdida de información entre el Service Provider (servidor) y el Customer (cliente).

Nótese que todos los métodos de la clase que vayan a ser accedidos remotamente deben tener el sufijo Action.
Ejemplo: Servicio web con WebServiceController
<?php

class ReservationController extends WebServiceController {

     public function startSessionAction($login, $password){
          $password = $this->filter($password, "alpha");
          $login = $this->filter($login, "alpha");
          $auth = new Auth("ldap", "server: ldap.server.local", 
               "accountDomainName: server.local",
               "username: CN=$login,DC=server,DC=local",
               "password: $password");
          if($auth->autentícate()){
               return true;
          } else {
               sleep(2);
               return false;
          }
     }

     public function getAvalabilityAction($initialDate, $finalDate){
          if(Auth::isValid()==false){
               return false;
          }
          if(Date::compareDates($initialDate, $finalDate)==-1){
               throw new WebServiceControllerException("Fechas incorrectas");     
          } else {     
               $reserva = Reserva($initialDate, $finalDate);
               return $reserva->checkAvalability();
          }

     }

     public function getTicketNumberAction(){
          if(Auth::isValid()==false){
               return false;
          }
          return "AE1872";
     }

}

En el cliente se utiliza la clase SoapClient estándar para acceder al servicio Web Basado en Soap:
Ejemplo: Acceder a un servicio web usando SoapClient
<?php

class VerifierController extends ApplicationController {
     
     public function validateAction(){
          $initialDate = $this->getPostParam("initialDate", "date");
          $finalDate = $this->getPostParam("finalDate", "date");
          $reservationService = new SoapClient(null, array(
               'location' => 'http://web.server.local/w/reservation',
               'uri' => 'http://app-services'
          ));
          try {
               if($reservationService->startSession("webserviceUser", "2fe05187a")==true) {
                    if($reservationService->getAvailability($initialDate, $finalDate)==true) {     
                         $ticketNumber = $reservationService->getTicketNumber();
                         return $this->routeTo("action: successChecking", 
"id: $ticketNumber");
                    }
               }
          }
          catch(SoapFault $e) {
               Flash::error("Ha ocurrido un error");
          }
     }

     public function successCheckingAction($ticketNumber){
          Flash::success("Felicidades, esta disponible el ticket $ticketNumber");
     }

}
WebServiceClient
Kumbia Enterprise Framework también proporciona la clase WebServiceClient que extiende el cliente SOAP SoapClient y pretende dar más facilidades al trabajar con servicios Web con servidores en este framework. El primer parámetro del constructor puede ser la ubicación del servicio (location) cuyo Uniform Resource Identificador (uri) es http://app-services:

Ejemplo: Crear un servicio web con WebServiceClient
$webService = new WebServiceClient('http://web.server.local/w/reservation');     

Es posible definir los parámetros del cliente mediante un array de esta forma:
Ejemplo: Definir parámetros extra en el servicio web con WebServiceClient
$webService = new WebServiceClient(array(
     'wdsl' => 'http://www.example.com/external-services/reservation.wsdl',
     'location' => 'http://web.server.local/w/reservation',
     'uri' => 'http://www.example.com/external-services',
     'encoding' => 'ISO-8859-1'
));     

El constructor establece los siguientes parámetros por defecto para el cliente SoapClient:
Tabla: Parámetros que recibe el contructor de WebServiceClient
Parámetro
Valor
wsdlnull
urihttp://app-services
encodingUTF-8
compressionSOAP_COMPRESSION_ACCEPT | SOAP_COMPRESSION_GZIP


La extensión Soap de PHP es requerida para usar este cliente.
Obteniendo el WSDL de un Servicio Web
Kumbia Enterprise Framework puede hacer AutoDiscover de servicios Web basados en WebServiceController, en caso que no se defina un WSDL manualmente para el servicio. Para generar un descriptor WSDL correcto es necesario agregar PHPDocs a cada acción del controlador y así poder determinar el tipo de dato devuelto por cada operación pública del Service Provider, sino se puede determinar se genera un empty type.

El mapeo de los valores escalares tanto los que se reciben como los que se envían en PHP a su correspondiente xsd ó soap-enc, es el siguiente:

String : <xsd:string>, Integer : <xsd:integer>, Float y Doubles : <xsd:float>, Boolean: <xsd:bool>, Array : <soap-enc:Array> y objetos <xsd:struct>.

Los espacios de nombres XML usados son:

xsd: "http://www.w3.org/2001/XMLSchema"
soap-enc: "http://schemas.xmlsoap.org/soap/encoding/"

Es posible invocar la acción getWSDLDescriptor en el controlador para obtener una descripción WSDL del servicio.
Orquestación de servicios
La lógica de negocio de los servicios web es implementada usando controladores que hacen parte de la arquitectura MVC, tanto la capa de lógica de dominio como la de datos tienen sentido para el desarrollador pero la presentación no existe ó no es claro su implementación ya que en servicios web la comunicación se realiza entre maquinas y no existe intervención humana donde se requiera.

Al recibir una petición desde un cliente Soap la instancia del framework es capaz de enrutar a la acción en el controlador adecuada examinando el mensaje SOAP enviado a la aplicación. Luego de ejecutar la lógica de dominio en la acción el valor devuelto por esta es tratado y convertido en un mensaje SOAP que se envia al cliente.

La manipulación a bajo nivel de los mensajes SOAP se le encarga al componente Soap que implementa los recursos necesarios para recibir y enviar comunicaciones de servicios Web y hacerlo transparente al usuario.

En el siguiente ejemplo se ilustra como la comunicación de servicios Web es transparente al desarrollador y como el entorno MVC facilita la orquestación de servicios:

En la maquina servidor1 se implementa el siguiente servicio web:
Ejemplo: Servicio web aritmético
<?php

class AritmethicController extends WebServiceController {

     public function addAction($a, $b){
          return $a + $b;
     }
}
En la maquina servidor2 se implementa un cliente del Servicio Web:
Ejemplo: Acceso al servicio web aritmético
<?php

class CalculatorController extends WebServiceController {

     public function addAction($x, $y){
          $service = new WebServiceClient("http://server1/ins/aritmethic/");
          $result = $service->add($x, $y); //Realiza la suma en el servidor1
     }
}

Al invocarse la acción calculador/add en servidor 2 se mapean automáticamente los valores de $x y $y a la acción add a $a y $b respectivamente en servidor1 al invocar este método en el objeto de servicio creado.

El mismo ejemplo se puede implementar utilizando un Proxy a un tercer servidor quien finalmente realiza la operación.

El servicio web en el servidor2 queda así implementado:
Ejemplo: Definir un Proxy a servicios web
<?php

class AritmethicController extends WebServiceController {

     public function addAction($a, $b){
          $service = new WebServiceClient("http://server3/ins/aritmethicserver/");
          return $service->add($x, $y); //Devuelve la suma en el servidor3
     }
}

El servicio Web AritmethicServer implementado en el servidor3:
Ejemplo: Servicio web aritmético en el tercer servidor
<?php

class AritmethicServerController extends WebServiceController {

     public function addAction($c, $d){
          return $c + $d;
     }

}

Aunque no es necesario implementar este ejemplo usando 3 maquinas, permite ilustrar como se puede abstraer la lógica de aplicación en forma distribuida de tal forma que múltiples aplicaciones accedan a servicios reutilizables que aumentan la escalabilidad de los sistemas desarrollados.
Características de Servicios Web en Kumbia Enterprise
Al igual que los otras implementaciones de controladores, en los servicios web usando WebServiceController se usa de forma transparente toda la funcionalidad del framework que se usa en los controladores de aplicación, algunas de estas son:

  • Es posible acceder a los modelos como propiedades de la clase controladora
  • Se puede implementar persistencia del estado del controlador entre sesiones y peticiones
  • Se puede enrutar el flujo de ejecución a otros servicios Web dentro de la misma aplicación
  • Seguridad declarativa y programacional se puede implementar en este tipo de controladores
Controladores StandardForm
La implementación de controladores StandardForm es una característica heredada de Kumbia PHP Framework. Por motivos de compatibilidad con aplicaciones existentes en la versión Enterprise su funcionalidad se mantiene intacta. El objetivo de esta implementación es la generación dinámica de formularios que permitan administrar la información de tablas en la conexión pretederminada del entorno actual.

El desarrollador debe evaluar si las limitantes de esta implementación no afectarán en un futuro los objetivos de negocio de la aplicación.

Nota: Esta implementación esta agendada para ser marcada como obsoleta en versiones posteriores del framework. LouderTechnology está activamente desarrollado Louder Application Forms el cuál genera componentes de aplicación robustos que aprovechan de manera más adecuada y flexible las posibilidades del framework.
Características de StandardForm

  • Generación dinámica de un CRUD (Create, Read, Update, Delete) con posibilidad de utilizar parte de ellas ó todos.
  • De acuerdo a la información de los meta-datos de una tabla en la base de datos generando componentes de interfaz de usuario adecuados para cada campo.
  • Es posible realizar validación del lado del cliente y del servidor.
  • Es posible implementar eventos de acuerdo a las acciones ejecutadas por el usuario en el controlador.
  • La información de la tabla creada puede ser exportada a PDF, HTML, Microsoft Word y Excel.
Limitantes de StandardForm

  • La especificación del modelo entidad-relación esta limitada a tablas y campos definidos mediante las convenciones del framework
  • La funcionalidad de los formularios generados sobre todo la visual puede ser difícil de personalizar con respecto a los objetivos del desarrollador.
Plugins de Controladores
En ciertas ocasiones cuando las aplicaciones corren en múltiples entornos con diferentes características, la funcionalidad y alcance de la misma aplicación no es suficiente y se debe extender automáticamente durante el runtime. La arquitectura de plugins de Controladores permite observar, extender y manipular el comportamiento de las clases controladoras de la aplicación según las condiciones lo exijan.

Los plugins permiten interceptar eventos de los controladores de tal forma que estos sean observables y además ejecutárse uno tras otro de manera centralizada sin requerir refactorización ó reintegración en la aplicación.
Crear un Plugin de Controlador
Los plugins de controlador son clases que implementan eventos que son invocados a medida que avanza la ejecución de una petición en cualquier controlador. Estas clases deben cumplir con los siguientes requerimientos:

  • Deben estar ubicados en el directorio de plugins usualmente apps/app-name/plugins
  • El nombre del archivo que implementa la clase debe ser el nombre del plugin
  • El nombre de la clase debe tener la extensión Plugin
  • Los plugins de controlador deben heredar de la clase ControllerPlugin ó ser subclase de ella

Las clases pueden implementar métodos públicos que referencian los eventos ocurridos en los controladores, la lista de ellos es la siguiente:
Tabla: Eventos que se pueden implementar en plugins de Controlador
Nombre Evento
Descripción
beforeDispatchLoopOcurre antes de iniciar el ciclo de enrutamiento, en este punto no se ha cargado el controlador, aunque se encuentra disponible todo el entorno de la aplicación.
beforeDispatchOcurre dentro del ciclo de enrutamiento y justo antes de invocar el controlador solicitado. Este evento se invoca por cada controlador usado en el flujo de ejecución.
beforeExecuteRouteOcurre antes de ejecutar la acción solicitada en el controlador, en este punto se puede obtener los datos de la petición, controlador, acción y parámetros.
afterExecuteRouteOcurre después de ejecutar la acción solicitada en el controlador.
afterDispatchOcurre después del proceso de ejecución de la acción por controlador.
afterDispatchLoopOcurre cuando termina el ciclo de enrutamiento.
beforeNotFoundActionOcurre cuando la acción ó controlador ejecutado solicitado no esta implementado. Es independiente de si la acción notFoundAction está implementada.
beforeUncaugthExceptionOcurre antes de lanzarse una excepción que no fue capturada por la aplicación.
onControllerExceptionOcurre cuando se genera una excepción dentro del controlador. Se ejecuta antes de beforeUncaughException