Libro de GluePHP.¶
Introducción.¶
Bienvenido a GluePHP, un framework para el desarrollo de aplicaciones web de una sola página empleando el paradigma de la programación dirigida por eventos.
Requerimientos.¶
- PHP 7.1
Licencia.¶
- MIT
Instalación.¶
Tenga en cuenta que el proyecto se encuentra en una fase inestable.
La instalación de GluePHP se realiza mediante composer. Para esto es necesario declarar las siguientes dependencias en el archivo composer.json.
{
"require": {
"glueapps/composed-views": "dev-0.1a",
"glueapps/glue-php": "dev-0.1a"
}
}
Seguidamente se debe ejecutar el comando:
$ composer update
Terminología.¶
Las aplicaciones hechas con GluePHP las definimos como glue apps. Una de las características de estas aplicaciones es que son compuestas, por lo que a sus componentes les llamamos glue components. Éstos componentes presentan datos compartidos y sincronizados entre el navegador y el servidor a los cuales definimos como glue attributes. Por otra parte, definimos como glue kit a un conjunto de glue components reutilizables.
Durante el desarrollo práctico del libro podrá comprender a fondo estos conceptos. No obstante, queremos aclarar que éstos solo tienen un significado local a nuestro proyecto y no guardan relación con otros ya bien conocidos en la informática como glue code, glue framework, etc.
Por razones de simplicidad, a lo largo del libro usaremos de manera equivalente los términos app, componente y kit, para referirnos a glue app, glue component y glue kit respectivamente.
Filosofía.¶
Desarrollar una glue app es muy similar a desarrollar una aplicación con interfaz gráfica para el desktop o para móviles. En esos casos se cuenta con una serie de componentes gráficos que son añadidos al diseño de la interfaz donde más tarde se les programa sus eventos. Esta filosofía de desarrollo se corresponde con el paradigma de la programación dirigida por eventos y es la empleada en el desarrollo con GluePHP.
Dada la gran diversidad que existe en el aspecto de las aplicaciones web, a la hora de desarrollar una glue app suele ser común tener que crear primeramente los componentes gráficos denominados glue componentes según nuestra terminología. No obstante, gracias a las facilidades que brinda GluePHP esta tarea se realiza de una manera muy sencilla.
Ventajas y desventajas.¶
GluePHP facilita muchos de los esfuerzos necesarios para el desarrollo de aplicaciones web de una sola página en PHP. Es por esto que reduce considerablemente los tiempos de desarrollo sobre todo en las aplicaciones basadas en kits de componentes reutilizables como son las administraciones o aplicaciones de gestión en general.
No obstante, en aplicaciones con gran cantidad de componentes y/o alta concurrencia de eventos puede verse afectado el rendimiento.
Contribuyendo.¶
Capítulo 1. Creando una glue app.¶
La mejor forma de comprender la filosofía de trabajo de GluePHP es usándolo en la práctica. Para esto vamos a desarrollar una aplicación muy sencilla pero que servirá para mostrar muy bien los diferentes aspectos del proceso.
La aplicación estará compuesta por una caja de texto, una etiqueta y un botón. La lógica consistirá en que al hacer clic sobre el botón la etiqueta muestre un saludo con el nombre introducido por el usuario en el campo de texto.
En el archivo app1.zip encontrará resuelto el ejercicio de este capítulo.
Introducción.¶
En el desarrollo con GluePHP existen identificados dos roles principales llamados desarrollador de aplicación y desarrollador de componentes respectivamente. Es bastante común que los desarrolladores se desempeñen en ambos roles de manera indistinta, pero en cada caso se tienen diferentes tipos de responsabilidades.
El desarrollador de aplicación, como su nombre lo indica, se encarga de realizar las partes del desarrollo relacionadas específicamente con las funcionalidades propias de la aplicación. Este rol se encarga de atender la estructura del proyecto, los controladores, la composición, los eventos y la lógica en general.
Por otra parte, el desarrollador de componentes, se encarga de desarrollar los componentes y otros recursos que serán usados por el desarrollador de aplicación.
Durante el desarrollo de este capítulo y con el objetivo de lograr la mejor explicación posible, primeramente nos desempeñaremos con el rol del desarrollador de aplicación y seguidamente con el de componentes.
Ninguno de los aspectos mostrados en los siguientes pasos tiene un carácter absoluto por lo que al realizar sus propios proyectos usted debe ser capaz de aplicarlos de acuerdo a sus necesidades.
1. Instalando GluePHP.¶
Recomendamos trabajar sobre un directorio vacío para tener un proyecto organizado.
$ mkdir app1
$ cd app1
Cree un archivo de nombre composer.json con el siguiente contenido:
{
"require": {
"glueapps/composed-views": "dev-0.1a",
"glueapps/glue-php": "dev-0.1a"
}
}
Seguidamente ejecute el comando:
$ composer update
Una vez que halla finalizado la instalación cree un archivo de nombre bootstrap.php con el siguiente contenido:
<?php
require_once 'vendor/autoload.php';
En este archivo vamos a definir las clases y funciones de la aplicación a medida que avancemos en el capítulo.
2. Controladores.¶
Una glue app necesita dos controladores web para su funcionamiento.
El primero de ellos se denomina controlador de carga ya que es el responsable de entregarle al navegador todo el código HTML de la página única, mientras que el segundo se denomina controlador de procesamiento y se encarga de procesar todas las solicitudes asíncronas(ajax) generadas desde el navegador.
La lógica de estos controladores es definida por el usuario, no obstante, es obligatorio que realicen ciertas operaciones para el correcto funcionamiento de la app.
Si para el desarrollo de la app se está usando algún framework PHP de prósito general, estos controladores se deben crear de la misma manera en la que se crean controladores con dicho framework.
Creando el controlador de carga.¶
Cree un archivo con nombre index.php con el siguiente contenido:
<?php
require_once 'bootstrap.php';
// Se instancia la app con sus componentes y eventos.
$app = require_once 'app.php';
// Se persiste la instancia de la app donde en este caso la persistencia se hace
// mediante la sesión.
session_start();
$_SESSION['app'] = $app;
// Se imprime en el navegador el código HTML de la página.
$app->print();
Los comentarios del código muestran las respectivas funcionalidades. Como puede ver, la instancia de la app se obtiene del archivo app.php por lo que será en este donde estará definida toda la app.
Creando el controlador de procesamiento.¶
Cree un archivo de nombre process.php con el siguiente contenido:
<?php
require_once 'bootstrap.php';
use GlueApps\GluePHP\Request\Request;
// Obtiene la instancia de la app persistida por el controlador de carga o por
// el procesamiento anterior.
session_start();
$app = $_SESSION['app'];
// La app procesa la solicitud y devuelve una respuesta.
$request = Request::createFromJSON($_REQUEST['glue_request']);
$response = $app->handle($request);
// Vuelve a persistir la app.
$_SESSION['app'] = $app;
// Envía al navegador la respuesta en formato JSON.
echo $response->toJSON();
die();
Puede ver que el código también está comentado.
3. Definiendo la app.¶
A partir de este paso es donde realmente comienza el desarrollo de nuestra app, pues crear los controladores fué crear la plataforma básica de ejecución y será siempre un paso obligatorio cuando se desarrolle cualquier glue app independientemente de su tamaño.
Cree un archivo de nombre app.php con el siguiente contenido:
<?php
/////////////////
// Composición //
/////////////////
// Instancia la app. El primer argumento especifica la ruta del controlador
// de procesamiento.
$app = new App('process.php');
$app->setDebug(true);
// Instancia los componentes. El primer argumento especifica el identificador del componente.
$input = new Input('input');
$label = new Label('label');
$button = new Button('button');
// Inserta los componentes en la sección 'body' de la app.
$app->appendComponent('body', $input);
$app->appendComponent('body', $label);
$app->appendComponent('body', $button);
////////////////////////////
// Vinculación de eventos //
////////////////////////////
// Declara que el evento 'click' del botón será manejado por la función 'clickButton'.
$button->on('click', 'clickButton');
return $app;
Primeramente se instancia la app donde el primer argumento se corresponde con la ruta del controlador de procesamiento. Como puede ver seguidamente existen dos bloques de comentarios llamados “Composición” y “Vinculación de eventos” respectivamente.
La composición no es más que insertar en la instancia de la app las instancias de los componentes que estarán presentes en la misma, mientras que la vinculación de eventos consiste en especificar las funciones encargadas de manejar los respectivos eventos de los componentes.
Como manejador de eventos se acepta cualquier tipo de callback pero el uso de closures puede requerir un tratamiento especial. En el próximo capítulo será abordado este tema.
La composición también puede efectuarse mediante código XML lo que reduce considerablemente la tarea, pero esto solo será posible si se está usando algún kit preparado para esto. Este tema será abordado en próximos capítulos.
Como se comenta en el código, lo que se hace para nuestra app es crear tres componentes e insertarlos en la sección “body”. Además, se registra la función “clickButton” como la encargada de manejar el evento “click” del botón.
La vinculación de eventos también se puede realizar sobre la instancia de la app donde el nombre del evento se conforma por el identificador del componente, un punto, y el nombre del evento. En este caso sería$app->on('button.click', 'clickButton');
.
Cada componente posee un identificador con el objetivo de que el mismo pueda ser referenciado desde cualquier parte del código. Por lo general este identificador es especificado por el usuario a través del constructor de la clase aunque esto depende del diseño de la misma, no obstante, si no se especifica ningún valor entonces el componente obtendrá un identificador por defecto.
El modo debug.¶
Como puede ver, después de instanciar la app se ha hecho una llamada al método $app->setDebug(true);
. Tal y como su nombre lo indica, de esta manera se activa el modo debug, el cuál tiene por objetivo mostrar la mayor cantidad de información posible durante el funcionamiento de la app.
Cuando este modo está activo, los scripts de GluePHP se imprimirán en la página sin compresión ya que por defecto estos se muestran comprimidos. De esta manera, se podrá hacer una depuración en el navegador si se desea.
Otra de las características de este modo, es que en la consola del navegador se va a imprimir mucha información del funcionamiento interno de la app a medida que las operaciones vallan teniendo lugar. La siguiente imagen muestra un ejemplo de esta información:
Usar el modo debug ayuda a comprender el funcionamiento de una glue app.
4. Definiendo la lógica de los eventos.¶
En el próximo capítulo podrá ver cómo usar closures para definir los eventos en el mismo lugar en el que se vinculan.
Una vez que se han vinculado los eventos será necesario definir la lógica de los mismos. En el caso de nuestra app hemos declarado que el evento “click” del botón será manejado por la función “clickButton” y para definir la lógica del mismo procedemos a crear dicha función.
Edite el archivo bootstrap.php y añada el siguiente código:
function clickButton($event)
{
// Referenciando los componentes de la app.
$label = $event->app->label;
$input = $event->app->input;
$label->setText('Hola ' . $input->getText());
}
Toda función manejadora de eventos recibirá un argumento con información del evento en curso. Tal y como puede ver en el código, mediante este argumento se podrán referenciar los componentes dentro de la función.
En este caso la lógica es bien sencilla. La línea $label->setText('Hola ' . $input->getText());
modifica el valor del texto del componente “label” con la palabra “Hola” más el valor del texto del componente “input” y de esta forma se implementa el requisito de la app.
5. Creando la clase App.¶
El código HTML de la página estará especificado en la clase “App”. Como requisito obligatorio esta tiene que ser hija de la clase GlueApps\GluePHP\AbstractApp
.
Añada la declaración de la clase “AbstractApp” al inicio del archivo bootstrap.php:
<?php
require_once 'vendor/autoload.php';
use GlueApps\GluePHP\AbstractApp;
// ...
Seguidamente añada el código de la clase al final del archivo:
class App extends AbstractApp
{
public function html(): ?string
{
return <<<HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My glue app</title>
</head>
<body>
{$this->renderSidebar('body')}
{$this->renderAssets('scripts')}
</body>
</html>
HTML;
}
}
Puede notar que el método App::html()
devuelve un valor de tipo string que se corresponde con el código HTML o vista de la página.
El significado de la línea {$this->renderSidebar('body')}
consiste en que en esa posición serán mostrados los componentes de la sección “body” de la página.
En la claseGlueApps\GluePHP\AbstractApp
existe registrado por defecto la sección “body” pero en próximos capítulos se mostrará la forma de especificar las secciones.
Por otra parte hay que destacar también el significado de la línea {$this->renderAssets('scripts')}
la cual provocará que en esa posición se muestren los recursos de tipos scripts de la página. Si esta línea es omitida el código HTML no contendrá las fuentes JavaScript necesarias para que la app pueda funcionar correctamente, por lo que mostrar los assets de este tipo tiene un carácter obligatorio. También más adelante profundizaremos en el tema de los assets.
Una vez llegado a este punto habremos terminado de desempeñarnos con el rol del desarrollador de aplicación. Como habrá podido notar, se han usado clases inexistentes para los componentes. Los siguientes pasos consisten en la creación de esas clases por lo que comenzaremos a desempeñarnos con el rol del desarrollador de componentes.
6. Creando las clases de los componentes.¶
Es en la creación de componentes donde GluePHP ofrece sus mayores bondades ya que el proceso de desarrollo se hace mayormente de manera declarativa.
Para crear un componente es necesario crear una clase descendiente de GlueApps\GluePHP\Component\AbstractComponent
donde al igual que en la clase “App”, el código HTML o vista se especifica a través de su método html()
. En el caso de los componentes, su código HTML soporta cierto marcado especial para especificar el comportamiento y/o función de ciertos elementos de la vista.
Por cada instancia de componente presente en la app existirá una instancia equivalente en el frontend. Todos los atributos de la clase marcados con la anotación @Glue
serán interpretados como glue attributes independientemente del tipo de visibilidad que posean. De esta forma se le indica a GluePHP que el atributo debe existir también en la instancia frontend y que, entre ambas, mantendrán un Double Binding sobre dicho atributo. Esto quiere decir que al modificarse el valor en alguna de las instancias, ya sea frontend o backend, la otra será actualizada automáticamente.
Por cada glue attribute la clase contendrá dos métodos dinámicos para las operaciones tipo getters y setters sobre el mismo. Los nombres de estos métodos cumplirán con el formato camel case y estarán constituidos por el propio nombre del atributo precedido por la palabra “get” o “set” según sea el caso. Por ejemplo, para un glue attribute llamado “name” su método getter se llamará getName mientras que su setter será setName.
Declare el uso de la clase GlueApps\GluePHP\Component\AbstractComponent
al inicio del archivo bootstrap.php:
<?php
require_once 'vendor/autoload.php';
use GlueApps\GluePHP\AbstractApp;
use GlueApps\GluePHP\Component\AbstractComponent;
// ...
Creando la clase Input.¶
Añada el siguiente código de la clase “Input” al final del archivo bootstrap.php:
class Input extends AbstractComponent
{
/**
* @Glue
*/
protected $text;
public function html(): ?string
{
return '<input type="text" gphp-bind-value="text">';
}
}
Puede ver que la clase “Input” contiene un único glue attribute llamado “text” y que su vista se corresponde con un único elemento tipo “input”. El atributo gphp-bind-value="text"
forma parte del marcado especial comentado anteriormente y su existencia le dice a GluePHP que cree un Double Binding entre el valor del glue attribute “text” y el valor de la propiedad “value” del elemento “input”. De esta forma, cuando se introduzca en la caja de texto la palabra “abcd”(por ejemplo), en el servidor la llamada al método $input->getText();
devolverá “abcd”. En sentido contrario, si en el servidor se efectúa la llamada $input->setText('dcba');
en el navegador la caja de texto mostrará “dcba”. No olvide que la comunicación entre el navegador y el servidor se realiza de forma asíncrona(ajax) por lo que todas estas operaciones se efectúan sin tener que recargar la página.
Creando la clase Label.¶
Añada el siguiente código al archivo bootstrap.php:
class Label extends AbstractComponent
{
/**
* @Glue
*/
protected $text;
public function html(): ?string
{
return '<label gphp-bind-html="text"></label>';
}
}
Como puede ver la clase Label es muy similar a la clase Input. Ambas cuentan con un glue attribute llamado “text” y solo difieren en la vista. En este caso, esta no es más que un elemento “label” con el atributo gphp-bind-html="text"
. El significado de este atributo es muy similar al anterior. La diferencia está en que en este caso el Binding solo se va a producir desde el glue attribute hacia el HTML interno del elemento. Es decir que la llamada en el servidor $label->setText('abc');
producirá que en el navegador el label muestre el texto “abc”.
Creando la clase Button.¶
Añada el siguiente código al archivo bootstrap.php:
class Button extends AbstractComponent
{
/**
* @Glue
*/
protected $text = 'Click Me!';
public function html(): ?string
{
return '<button gphp-bind-html="text" gphp-bind-events="click"></button>';
}
}
Como puede ver, la clase Button cuenta también con un glue attribute “text” donde en este caso presenta un valor por defecto. En el caso de la vista no es más que un elemento tipo “button” que presenta dos atributos especiales.
El significado del atributo gphp-bind-html="text"
fué explicado anteriormente por lo que la única novedad la constituye el atributo gphp-bind-events="click"
. De esta forma se le dice a GluePHP que el evento “click” del elemento “button” producirá en el servidor el evento “click” del componente. De esta manera hemos implementado que cuando en el navegador pulsemos el botón se invoque en el servidor la función “clickButton”.
Ejecutando la app.¶
Como toda aplicación web, toda glue app necesita ser ejecutada por un servidor web. Por razones de simplicidad recomendamos usar el servidor web interno de PHP.
Ejecute el siguiente comando:
$ php -S localhost:8080
Puede cambiar el puerto según su conveniencia.
Ahora accedemos a la URI http://localhost:8080/ y comprobamos que la app funciona de la siguiente manera:
Capítulo 2. Usando closures para la definición de eventos.¶
Actualmente este capítulo se encuentra incompleto pues depende de la realización de otro proyecto. De cualquier manera recomendamos su lectura ya que el uso de closures para la vinculación de eventos constituye la manera más natural de definirlos.
Las funciones anónimas, más conocidas en PHP como closures, constituyen un potente recurso de programación presente hoy en día en la mayoría de los lenguajes modernos, donde una de sus aplicaciones más típicas se encuentra precisamente en la vinculación a eventos.
En el capítulo anterior comentábamos que se aceptaba cualquier tipo de callback como función manejadora de eventos, pero para el caso de los closures podía ser necesario un tratamiento especial. Durante este capítulo vamos a profundizar en el tema teniendo en cuenta las consideraciones necesarias al respecto.
1. Vinculando y definiendo eventos en el mismo lugar.¶
En la vinculación de eventos anterior, declaramos que la función “clickButton” sería la responsable de manejar el evento “click” del botón. Para su implementación, tuvimos que crear dicha función dentro de la cual fué necesario referenciar nuevamente a los componentes requeridos para su lógica.
Dado que por lo general, las funciones manejadoras de eventos solo son llamadas cuando se disparan sus respectivos eventos, no tiene mucho sentido darles un nombre para que puedan ser llamadas desde otras partes del código. Gracias a que en PHP existen las funciones anónimas o closures, emplearlas para la vinculación de los eventos resulta la forma más natural de hacerlo ya que además, pueden heredar muy fácilmente variables desde su ámbito padre.
Modifique la vinculación de eventos del archivo app.php de la siguiente forma:
// ...
////////////////////////////
// Vinculación de eventos //
////////////////////////////
// Vincula y define en el mismo lugar el evento 'click' del botón.
$button->on('click', function () use ($input, $label) {
$label->setText('Hola ' . $input->getText());
});
return $app;
La función “clickButton” existente en el archivo bootstrap.php ya no es necesaria. Si lo desea puede eliminarla o simplemente ignorarla.
Como puede notar, de esta forma, hemos implementado la misma lógica pero de una forma mucho más compacta y natural.
Si tras esta modificación procedemos a ejecutar la app nuevamente, el resultado obtenido no será el esperado ya que se habrá producido un error del tipo «Serialization of “Closure” is not allowed». Esta es una desventaja que presentan los closures en PHP y es que de manera nativa no se permite su serialización.
En este punto, usted se puede preguntar por qué la existencia de este error si hasta el momento no hemos ejecutado ninguna serialización de manera directa. Recordemos que en nuestros controladores realizamos la persistencia de la aplicación a través de la sesión($_SESSION['app'] = $app;
). Como lo que se intenta guardar es un objeto, el motor de PHP intentará serializarlo para después guardarlo como cadena, y este es, precisamente la causa del error.
Afortunadamente existen algunas técnicas para resolver este problema. Actualmente estamos desarrollando un componente independiente que podrá ser empleado en las operaciones de persistencia. Dicho componente solucionará este error y al emplear un algoritmo más personalizado esperamos obtener una mejora en el rendimiento.
Por el momento basta con que sepa que el uso de closures lo tenemos muy presente para el desarrollo con GluePHP.
Capítulo 3. Uso dinámico de componentes.¶
La composición de las aplicaciones hechas con GluePHP tienen un comportamiento dinámico. Esto quiere decir, que en cualquier momento se pueden insertar, eliminar y/o modificar componentes independientemente de si la aplicación ha sido cargada ya o no.
Para mostrarle este comportamiento vamos a desarrollar una aplicación muy similar a la anterior solo que en este caso estará compuesta solo por una caja de texto y un botón. La lógica de la misma consistirá en que al presionar el botón se insertará un nuevo botón con el texto introducido por el usuario en la caja de texto. Por otra parte, cuando este nuevo botón sea presionado deberá ser eliminado completamente.
En el archivo app3.zip encontrará resuelto el ejercicio de este capítulo.
1. Definiendo la app.¶
Edite el archivo app.php de la siguiente manera:
<?php
/////////////////
// Composición //
/////////////////
$app = new App('process.php');
$input = new Input('input');
$button = new Button('button');
$app->appendComponent('body', $input);
$app->appendComponent('body', $button);
////////////////////////////
// Vinculación de eventos //
////////////////////////////
$button->on('click', 'clickButton');
return $app;
2. Definiendo la lógica de los eventos.¶
Modifique la función “clickButton” de la siguiente forma:
function clickButton($e)
{
$newButton = new Button;
$newButton->setText($e->app->input->getText());
$newButton->on('click', 'clickNewButton');
$e->app->appendComponent('body', $newButton);
}
Como puede verse primeramente se crea la nueva instancia de la clase Button, se le asigna el texto introducido por el usuario en la caja de texto y se declara que su evento “click” será manejado por la función “clickNewButton”. Más tarde se añade el componente a la sección “body” de la página.
El siguiente paso consiste en definir la función “clickNewButton”. Para ello añada el siguiente código al archivo bootstrap.php:
function clickNewButton($e)
{
$button = $e->component;
$button->detach();
}
Como puede ver en el código anterior, mediante el argumento pasado a la función manejadora de eventos se puede obtener también el componente causante del evento en curso. Esta referencia se obtiene a través del atributo especial “component” de dicho argumento. Cuando se ejecuta la sentencia $button->detach();
el componente quedará totalmente separado de la instancia de la app lo que provocará su eliminación.
Los glue components tienen una estructura jerárquica. Esto quiere decir que pueden contener un padre y varios hijos. Cuando se invoca el método$component->detach()
se le indica al componente padre(en caso de que exista) que debe eliminar al actual como uno de sus hijos. De manera equivalente, si sobre un componente se ejecuta el método$component->dropChild('child')
se eliminará del componente con identificador “child” la referencia al componente actual como su padre.
3. Ejecutando la aplicación.¶
De esta forma habrá quedado implementada la app y para comprobar su funcionamiento procedemos a su ejecución.
Capítulo 4. Acciones.¶
Como la interfaz de una glue app se ejecuta en un navegador web y la lógica de sus eventos se procesa en un servidor, existe un mecanismo de envío de mensajes instantáneos desde el servidor al navegador, con el objetivo de que el navegador reaccione instantáneamente ante las órdenes emitidas por el servidor. A estos mensajes se les conoce como acciones en GluePHP y constituyen el pilar fundamental de la comunicación en ese sentido.
Cuando en una glue app se dispara un evento en alguno de sus componentes, se envía al controlador de procesamiento una solicitud ajax que entre otra información contiene el nombre del evento que se debe procesar en el servidor. Si durante la ejecución de la lógica del respectivo evento se envía alguna acción, lo que internamente sucede es que se escribe en la salida al navegador los datos de la respectiva acción sin que esto termine la solicitud ajax actual, es decir que dicha solicitud pasa a ser de tipo streaming. De esta forma se garantiza que el navegador sea notificado de la ocurrencia de la acción casi inmediatamente en que esta es emitida por el servidor.
Las acciones tienen un protagonismo esencial en el funcionamiento interno de las glue apps y muchas de las características que hemos visto hasta el momento hacen uso de las mismas. Por ejemplo cuando el valor de un glue attribute cambia en el servidor, GluePHP envía una acción para que su equivalente sea actualizado en el navegador instantáneamente. Otro caso en el que está presente su uso es en la inserción y eliminación dinámica de componentes mostrada en el capítulo anterior.
En este capítulo mostraremos la creación de acciones personalizadas y su forma de envío. Para ello vamos a desarrollar una aplicación compuesta por dos botones donde el primero mostrará el texto “Alerta” mientras que el segundo “Confirmación”. El funcionamiento consistirá en que cuando se haga clic en el botón “Alerta” se muestre en el navegador un mensaje de alerta nativo y de igual forma, cuando se presione el botón “Confirmación” el navegador mostrará un mensaje de confirmación nativo donde los textos de los mensajes en todos los casos serán aleatorios.
En el archivo app4.zip encontrará resuelto el ejercicio de este capítulo.
1. Definiendo la app.¶
Edite el archivo app.php de la siguiente manera:
<?php
/////////////////
// Composición //
/////////////////
$app = new App('process.php');
$alertButton = new Button('alert_button');
$alertButton->setText('Alerta');
$confirmationButton = new Button('confirmation_button');
$confirmationButton->setText('Confirmación');
$app->appendComponent('body', $alertButton);
$app->appendComponent('body', $confirmationButton);
////////////////////////////
// Vinculación de eventos //
////////////////////////////
$alertButton->on('click', 'clickAlertButton');
$confirmationButton->on('click', 'clickConfirmationButton');
return $app;
2. Definiendo la lógica de los eventos.¶
Añada las siguientes funciones al archivo bootstrap.php:
function clickAlertButton($e)
{
$text = uniqid();
$action = new CustomAction($text, 'alert');
$e->app->act($action);
}
function clickConfirmationButton($e)
{
$text = uniqid();
$action = new CustomAction($text, 'confirmation');
$e->app->act($action);
}
Para enviar una acción primeramente es necesario crear una instancia del tipo de acción a enviar y especificarle sus datos según corresponda. Seguidamente se debe invocar sobre la instancia de la app la llamada al método act()
con la instancia de la acción a enviar como argumento.
Como puede ver, en las funciones anteriores se hace uso de la clase CustomAction la cual no ha sido definida aún, por lo que nuestro próximo paso consiste en crear dicha clase.
2. Creando la clase de la acción.¶
Para crear un nuevo tipo de acción es necesario crear una clase que descienda de GlueApps\GluePHP\Action\AbstractAction
por lo que primeramente debemos declarar el uso de la misma al inicio del archivo bootstrap.php:
// ...
use GlueApps\GluePHP\Action\AbstractAction;
Añada la siguiente clase al archivo bootstrap.php que se corresponde con la implementación de nuestra acción:
class CustomAction extends AbstractAction
{
public function __construct(string $text, string $type)
{
parent::__construct([
'text' => $text,
'type' => $type,
]);
}
public static function handlerScript(): string
{
return <<<JAVASCRIPT
if (data.type == 'alert') {
alert(data.text);
} else if (data.type == 'confirmation') {
confirm(data.text);
}
JAVASCRIPT;
}
}
En la clase GlueApps\GluePHP\Action\AbstractAction
existe un método estático y abstracto de nombre handlerScript()
por lo que toda clase heredera está obligado a implementarlo. Este método debe devolver el fragmento de código JavaScript que será ejecutado en el navegador una vez que se reciba una acción de este tipo.
Al mirar el código JavaScript de nuestra clase puede notar que se hace uso de una variable de nombre data
y puede notar también que sobre la misma se hace referencia a las propiedades text
y type
. Por otra parte, si mira el constructor de la clase puede comprobar que el constructor de la clase padre es invocado con un array que contiene dos valores indexados por las claves text
y type
. En la clase GlueApps\GluePHP\Action\AbstractAction
existe un atributo especial y protegido de nombre $data
donde su valor se especifica a través del constructor de la misma. Cuando una acción es enviada, GluePHP hace una conversión automática del valor de este atributo y lo asigna a la variable data
del código JavaScript donde la conversión tiene en cuenta los siguientes casos:
- Cuando el tipo del atributo sea escalar la variable tomará un tipo similar pues los tipos escalares de PHP existen también en JavaScript.
- Cuando el tipo del atributo sea un array tipo vector(solo índices numéricos y ordenados) el tipo de la variable será una instancia de la clase
Array
. - Cuando el tipo del atributo sea un array tipo hash(índices de cadenas de caracteres) el tipo de la variable será un objeto con propiedades equivalentes a los índices del array.
- Cuando el tipo del atributo sea un objeto el tipo de la variable será también un objeto.
De esta forma GluePHP le hace muy sencillo al programador implementar la comunicación en el sentido del servidor al navegador ya que el trabajo se reduce a especificar los datos a enviar y a escribir el código JavaScript.
Es importante mencionar que el código JavaScript recibe también una variable de nombreapp
que contiene la instancia de la app en el navegador.
Como puede ver la acción que hemos programado requiere dos valores de tipo string donde el primero de ellos se corresponde con el texto del mensaje mientras que el segundo constituye el tipo de alerta. La lógica del código JavaScript consiste en mostrar un mensaje tipo “alert” si el valor de la propiedad data.type
es igual “alert” y tipo “confirm” en el caso de que data.type
sea igual a “confirm”. Tenga en cuenta que la desición del tipo de mensaje a mostrar se toma en el navegador.
3. Ejecutando la aplicación.¶
Si procedemos a ejecutar la aplicación esta debe funcionar de la siguiente manera:
Capítulo 5. Trabajando con datos de los eventos frontend.¶
Resulta bastante común que la lógica de un evento dependa de cierta información del evento en curso. Como en una glue app su interfaz se ejecuta en el navegador y la lógica en el servidor, cuando se produce un evento es necesario enviar al servidor la información necesaria para el funcionamiento del mismo.
En este capítulo vamos a desarrollar una aplicación compuesta solo por una caja de texto donde su funcionamiento consistirá en que cuando se introduzca un caracter, se muestre un mensaje de alerta indicando que se ha introducido una letra o un número según sea el caso.
En el archivo app5.zip encontrará resuelto el ejercicio de este capítulo.
Cuando se produce un evento en el navegador, este proporciona un objeto que contiene una gran cantidad de información al respecto. Teniendo en cuenta que generalmente son solo unos pocos datos los que se necesitan en la lógica, GluePHP da la posibilidad al usuario de especificar por cada evento la información necesaria a enviar.
1. Editando la clase del componente Input.¶
Cuando anteriormente definimos la clase del componente Input no declaramos que este tipo de componente iba a lanzar algún tipo de eventos. Para implementar nuestra app necesitamos que este tipo de componente sea capaz de lanzar un evento relacionado con la pulsación de teclas.
En el archivo bootstrap.php edite la clase Input de la siguiente manera:
class Input extends AbstractComponent
{
/**
* @Glue
*/
protected $text;
public function html(): ?string
{
return '<input type="text" gphp-bind-value="text" gphp-bind-events="keypress">';
}
}
Al especificar el atributo gphp-bind-events="keypress"
sobre el elemento declaramos que el evento “keypress” del elemento input
producirá el evento de igual nombre en el componente.
2. Definiendo la app.¶
Edite el archivo app.php de la siguiente manera:
<?php
/////////////////
// Composición //
/////////////////
$app = new App('process.php');
$input = new Input('input');
$app->appendComponent('body', $input);
////////////////////////////
// Vinculación de eventos //
////////////////////////////
$input->on('keypress', 'onKeyPress', ['key', 'charCode']);
return $app;
Puede notar que en la vinculación del evento “keypress” del componente “input” se especifica un array como tercer argumento. De esta forma se declara que cuando se produzca el evento “keypress” del componente se envíe al servidor los datos “key” y “charCode” del objeto del evento proporcionado por el navegador.
En la siguiente imagen se ha impreso en la consola del navegador, el objeto del evento “keypress” obtenido al producirse dicho evento en un elemento “input”. De esta forma puede comprobar la disponibilidad de los datos “key” y “charCode” para ser enviados al servidor.
3. Definiendo la lógica del evento.¶
Añada la siguiente función al archivo bootstrap.php
function onKeyPress($e)
{
$data = $e->getData();
if ($data['charCode'] >= 97 && $data['charCode'] <= 122) {
$alert = new CustomAction("Es la letra {$data['key']}", 'alert');
$e->app->act($alert);
} elseif ($data['charCode'] >= 48 && $data['charCode'] <= 57) {
$alert = new CustomAction("Es el número {$data['key']}", 'alert');
$e->app->act($alert);
}
}
Como puede ver, a través del método $e->getData();
se obtienen los datos del evento en curso enviado desde el navegador. El valor devuelto es un array asociativo donde sus claves se corresponden con los datos enviados.
4. Ejecutando la aplicación.¶
Capítulo 6. Trabajando con sidebars.¶
Como vimos en el capítulo 1, un sidebar no es más que una sección dentro del código HTML o vista de la página donde se muestran las vistas de los componentes insertados en la misma. Cuando creamos la clase App mostramos que la forma de mostrar un sidebar era a través del método renderSidebar(string $id)
. En aquel momento comentamos que en la clase GlueApps\GluePHP\AbstractApp
existía por defecto un único sidebar de nombre “body” y que más adelante se mostraría la forma de crearlos.
La manera de crear los sidebars de una página es muy sencilla. Para ello solo hay que crear un método público llamado “sidebars” y hacerlo devolver un array donde sus elementos se corresponderá con los nombres de los sidebars que existirán en la página.
En el siguiente fragmento de código se ha declarado la existencia de los sidebars “header”, “body” y “footer” en la página.
class App extends AbstractApp
{
public function sidebars(): array
{
return ['header', 'body', 'footer'];
}
// ...
}
Adicionalmente existe una manera de insertar componentes en los sidebars a la vez que estos se declaran. En el siguiente ejemplo se inserta un componente de tipo Logo en el sidebar “header”.
class App extends AbstractApp
{
public function sidebars(): array
{
return [
'header' => [
new Logo,
],
'body', 'footer'
];
}
// ...
}
Capítulo 7. Trabajando con assets.¶
En el desarrollo web se les conoce como assets a todos los recursos referenciados desde el código HTML de las páginas como son las hojas de estilo, scripts, imágenes, etc. Dado que la carga de las páginas implica también la carga de sus recursos, es muy importante tener en cuenta la cantidad y tamaño de los mismos para garantizar que el proceso esté lo más optimizado posible.
En las aplicaciones GluePHP los assets se definen tanto en la clase de la aplicación como en la de los componentes, y aunque en ambos casos se hace de manera similar su tratamiento es diferente. Cuando estos son declarados en la clase de la aplicación significa que los mismos siempre se incluirán en la página, en cambio, cuando se declaran en la clase de un componente solo se incluirán si existe en la aplicación al menos un componente de ese tipo. De esta manera se garantiza que las aplicaciones contengan solo los assets que necesitan.
En esta versión todavía no se encuentra soportada la gestión dinámica de assets lo que quiere decir que después de la carga de la página no se añadirá ningún nuevo asset aunque se inserte en la misma algún nuevo componente con dependencias a assets que no se encuentren cargados.
GluePHP trata a los assets como objetos donde sus respectivas clases serán hijas de la clase GlueApps\ComposedViews\Asset\AbstractAsset
. De esta manera, todas las instancias contarán con un identificador y un mecanismo para soportar dependencias y grupos. Además, teniendo en cuenta que los assets se necesitan imprimir en la página y que por lo general su vista solo se compone de un único elemento HTML, esta clase desciende de la clase GlueApps\ComposedViews\HtmlElement\HtmlElement
para generar la vista del asset. De esta manera el usuario podrá editar los atributos del elemento si lo desea. Teniendo en cuenta además que por lo general cada asset solo se imprime una única vez en la página, contarán con una bandera para indicar si el mismo ya ha sido impreso o no.
1. Imprimiendo los assets.¶
Como los assets forman parte del código HTML o vistas de las páginas, será en las clases de las aplicaciones donde se deberán mostrarán.
Para imprimir todos los assets de un grupo existe el método renderAssets(?string $groups = null, bool $filterUnused = true, bool $markUsage = true): string
. Como puede ver, el método acepta varios argumentos opcionales ya que presentan valores por defecto. Con el primer argumento $groups
indica que se deben imprimir solo los assets que pertenezcan a los grupos separados por espacios. En el caso de que su valor sea nulo se imprimirán todos los assets sin importan los grupos a los que pertenezcan. Cuando el argumento $filterUnused
sea verdadero se indicará que solo se imprimirán los assets que no hayan sido impresos todavía y en el caso de que su valor sea falso significa que su uso no se tendrá en cuenta. Cuando el argumento $markUsage
sea verdadero todos los assets que se impriman serán marcados como usados. Es muy importante destacar que esta función imprimirá los assets de forma ordenada por lo que tendrá en cuenta sus dependencias. Cuando un asset depende de otro será impreso después de su dependencia.
Otro método del que se dispone para la impresión de assets es renderAsset(string $assetId, bool $required = true, bool $markUsage = true): string
. El mismo sirve para imprimir un único asset especificando su identificador. Cuando el argumento $required
sea verdadero se lanzará una excepción del tipo GlueApps\ComposedViews\Exception\AssetNotFoundException
en el caso de que no exista ningún asset con el identificador especificado. De igual forma el argumento $markUsage
sirve para marcar o no su uso después de su impresión.
2. Declarando assets.¶
Para declarar assets tanto en la clase de la aplicación como en la de los componentes, es necesario crear un método público de nombre “assets” que devuelva un array con las instancias de los mismos.
Para soportar los tipos de assets más comunes existen predefinidas una serie de clases que han sido diseñadas de forma tal que a través de sus constructores se les pueda proporcionar los datos necesarios por orden de importancia. El primer argumento se va a corresponder siempre con el identificador del asset. El segundo con los datos del tipo correspondiente ya sea una URI o un fragmento de código JavaScript o CSS. El tercero y cuarto serán opcionales y especificarán las dependencias y grupos respectivamente mediante un string
donde se interpretarán múltiples valores separados por espacios. Es importante mencionar que todas las clases predefinidas pertenecen a uno o varios grupos por defecto.
En el siguiente ejemplo se han declarado assets de los tipos GlueApps\ComposedViews\Asset\{StyleAsset, ScriptAsset}
.
use GlueApps\ComposedViews\Asset\{StyleAsset, ScriptAsset};
class App extends AbstractApp
{
public function assets(): array
{
return [
new StyleAsset('bootstrap-css', 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css'),
new ScriptAsset('jquery', 'https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js'),
new ScriptAsset('bootstrap-js', 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js', 'jquery'),
];
}
// ...
}
class DatePicker extends AbstractComponent
{
public function assets(): array
{
return [
'plugins' => [
new StyleAsset('datepicker-css', 'https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.7.1/css/bootstrap-datepicker.min.css', 'bootstrap-css'),
new ScriptAsset('datepicker-js', 'https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.7.1/js/bootstrap-datepicker.min.js', 'jquery bootstrap-js'),
],
];
}
// ...
}
En la clase App se han declarado los assets “jquery”, “bootstrap-css” y “bootstrap-js”. Tenga en cuenta que con los segundos argumentos se hace referencia a sus respectivas URIs y en el caso del asset “bootstrap-js” con un tercer argumento se ha declarado que depende del asset “jquery”.
En la clase DatePicker se han declarado los assets “datepicker-css” y “datepicker-js” donde en ambos casos pertenecen al grupo “plugins”. En el caso de “datepicker-css” se ha declarado que depende solo de “bootstrap-css” mientras en el caso de “datepicker-js” dependerá de “jquery” y “bootstrap-js”.
Como se comentó anteriormente, con un cuarto argumento de tipo string se podrán especificar uno o varios grupos separando sus nombres por espacios.
3. Conociendo los tipos de assets existentes.¶
La siguiente tabla muestra las clases existentes en GluePHP para trabajar con los tipos de assets más comunes. Todas se encuentran definidas en el espacio de nombres GlueApps\ComposedViews\Asset
. La tabla muestra el tipo de elemento HTML que representa el asset, los grupos y atributos predefinidos además de un ejemplo de su vista. El símbolo %data%
se corresponde con el valor especificado por el usuario como segundo argumento del constructor.
Clase | Elemento | Atributos | Grupos | Vista
– | – | – | – | –
ScriptAsset | script
| src=»%data%» | scripts, uri | <script src="%data%"></script>
ContentScriptAsset | script
| | scripts, content | <script>%data%</script>
StyleAsset | link
| href=»%data%», rel=»stylesheet» | styles, uri | <link href="%data%" rel="stylesheet">
ContentStyleAsset | style
| | styles, content | <style>%data%</style>
ImportAsset | link
| href=»%data%», rel=»import» | imports, uri | <link href="%data%" rel="import">
4. Editando los assets.¶
La clase GlueApps\ComposedViews\HtmlElement\HtmlElement
se usa para generar la vista de un único elemento HTML. La misma solo se compone de cuatro datos que se corresponden con el tipo de elemento, los atributos, el contenido y la etiqueta de cierre. Todos esos se pueden editar a través de sus respectivos métodos de lectura y escritura. Dado que los assets son también instancias de esta clase su vista puede ser editada.
El siguiente ejemplo muestra como se añade un nuevo atributo a un asset.
$bootstrapCss = new StyleAsset('bootstrap-css', 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css');
$bootstrapCss->setAttribute('integrity', 'sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u');
echo $bootstrapCss->html();
El resultado de ese script será:
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u">
5. Trabajando con rutas relativas.¶
Como ha podido ver hasta este momento solo hemos usados rutas absolutas para los assets. Dado que generalmente toda aplicación web usa assets alojados en su propio servidor, y teniendo en cuenta que la estructura de archivos es definida por el usuario, será necesario contar con una manera de trabajar con con las rutas relativas.
En la clase de la aplicación es posible especificar una ruta base como segundo argumento del constructor.
$app = new App('process.php', 'http://127.0.0.1/public/');
Cuando se indica una ruta base en una aplicación lo más conveniente suele ser que la vista incluya una etiqueta <base>
de HTML. De esta forma será el navegador quien combine de forma automática todas las rutas relativas con la ruta base. No obstante si se necesita generar manualmente una ruta que combine la ruta base con una ruta relativa se puede usar la función basePath(string $assetUri = ''): string
como se muestra en el siguiente ejemplo:
class App extends AbstractApp
{
public function html(): ?string
{
return <<<HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My glue app</title>
<base href="{$this->basePath}">
</head>
<body>
<img src="{$this->basePath('logo.png')}">
{$this->renderSidebar('body')}
{$this->renderAssets('scripts')}
</body>
</html>
HTML;
}
}
6. Conociendo la superposición de assets.¶
Cuando una aplicación y uno o varios componentes definen un asset con un mismo identificador, será el de la aplicación el que prevalezca. Conociendo esto, cuando se necesite redefinir un asset definido en la clase de un componente que no se pueda editar, la solución puede ser declarar el asset en la aplicación como se ha mostrado anteriormente.
No obstante, dado que por lo general solo es necesario redefinir los assets para modificar su URI, existe una manera muy simplificada de hacerlo. La solución consiste en crear un método en la página de nombre rewriteUri()
y hacerlo devolver un array asociativo donde sus llaves se corresponderán con el identificador del asset a modificar y su valor será la nueva URI.
En el ejemplo siguiente se ha reescrito la URI del asset “jquery” para referirse a la versión 3.3.1. Esto quiere decir que cuando en la página se valla a imprimir este asset será esta URI la que se usará independientemente de los componentes que la definan de otra manera.
class App extends AbstractApp
{
public function rewriteUri(): array
{
return [
'jquery' => 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js',
];
}
// ...
}
7. Filtrando assets.¶
Siempre que en las páginas se produzca alguna consulta de assets, antes de su devolución final se disparará internamente un evento que permitirá su filtrado. Gracias a este evento el usuario puede realizar operaciones personalizadas sobre los assets.
El siguiente ejemplo provocará que en la app nunca se usen los assets que posean una URI.
use GlueApps\ComposedViews\PageEvents;
use GlueApps\ComposedViews\Event\FilterAssetsEvent;
use GlueApps\ComposedViews\Asset\UriInterface;
$app->on(PageEvents::FILTER_ASSETS, 'filterAssets');
function filterAssets(FilterAssetsEvent $event)
{
$assets = $event->getAssets();
foreach ($assets as $id => $asset) {
if ($asset instanceof UriInterface) {
unset($assets[$id]);
}
}
// Se establecen los assets que serán usados.
$event->setAssets($assets);
}
Capítulo 8. Estructura jerárquica de los componentes.¶
En GluePHP todos los componentes presentan una estructura jerárquica ya que pueden contener un padre y múltiples hijos. Esta estructura representa además una estructura de árbol y este capítulo tiene como objetivo mostrar todas las operaciones relacionadas con la misma.
Es importante mencionar que todas las operaciones mostradas en el capítulo son aplicables también sobre las instancias de las aplicaciones.
1. El componente padre.¶
Respecto al componente padre solo van a existir operaciones tipo getter y setter. Para obtenerlo basta con llamar a $component->getParent();
mientras que para establecerlo se debe invocar al método $component->setParent($parent);
. Cuando a un componente se le establece un padre, por defecto el padre lo añade como su hijo ya que es algo totalmente lógico. No obstante, si por algún motivo no se desea ese comportamiento adicional, la operación se debe realizar de la siguiente manera: $component->setParent($parent, false);
.
La manera más sencilla de borrar el padre de un componente es a través de la función $component->detach();
.
2. Los componentes hijos.¶
Para añadir un hijo a un componente se debe llamar a la función $component->addChild($child);
. De manera equivalente, cuando un componente añade un hijo, sobre el segundo se va a asignar al primero como su padre. Si esto no se desea se debe realizar la llamada de la siguiente manera: $component->addChild($child, false);
.
Para buscar hijos se usa la función $component->getComponent('id');
donde el argumento representa el identificador del componente a buscar. Si el componente es encontrado se devolverá la instancia del mismo mientras que en caso contrario se devolverá NULL
. Es muy importante destacar que esta función hará una búsqueda en profundidad sobre todo el árbol, es decir, si el componente no presenta ningún hijo directo con el identificador buscado, la búsqueda continuará sobre sus hijos y los hijos de sus hijos respectivamente hasta hallar alguna coincidencia.
Para obtener todos los hijos directos se debe llamar a la función $component->getChildren()
la cual devolverá un array asociativo donde las claves serán los identificadores mientras que los valores serán las instancias. Si se desea consultar si un componente es hijo directo de otro, se puede hacer la llamada $component->hasRootChild('id');
la cual devolverá un valor booleano según el caso.
Para eliminar un hijo existe la función $component->dropChild('id');
. Esta función solo trabaja sobre los hijos directos y cuando uno es encontrado con igual identificador, a este se le establecerá su padre a NULL
. Tal y como se ha mostrado hasta ahora, si ese comportamiento adicional se desea deshabilitar se debe hacer especificando “false” como segundo argumento: $component->dropChild('id', false);
.
3. Iterando sobre el árbol.¶
En algunas ocasiones se necesitará recorrer en profundidad todos los componentes de un árbol. En ese caso debe emplear la función traverse()
la cual devuelve un iterador comenzando desde el primer hijo directo hasta el último elemento del árbol.
El siguiente ejemplo ayuda a comprender el funcionamiento:
parent
|
|____ comp1
| |
| |____ comp2
| |
| |____ comp3
| |
| |____ comp4
|
|___ comp5
foreach ($parent->traverse() as $comp) {
echo $comp->getId() . PHP_EOL;
}
El script anterior imprimirá en pantalla los nombres de los componentes en el orden mostrado, es decir:
comp1
comp2
comp3
comp4
comp5
4. Clonando un árbol de componentes.¶
Cuando necesite clonar un componente se tiene que hacer de la siguiente manera: $component2 = $component1->copy();
.
Es muy importante aclarar que no se recomienda emplear la clonación nativa de PHP ya que el resultado no se ajusta a los requisitos de GluePHP.
5. Eventos en el árbol de componentes de las páginas.¶
En una glue app, el árbol de componentes principal lo constituye el de las páginas ya que son estas quienes contienen a los componentes. Cuando en cualquier parte del mismo, se produzca tanto una inserción como alguna eliminación, se producirá sobre la página un evento antes y otro después de la respectiva operación. Estos eventos son usados internamente por GluePHP, pero adicionalmente pueden ser usados por los usuarios para incluir cualquier tipo de lógica.
use GlueApps\ComposedViews\PageEvents;
use GlueApps\ComposedViews\Event\{BeforeInsertionEvent, AfterInsertionEvent, BeforeDeletionEvent, AfterDeletionEvent};
$app->on(PageEvents::BEFORE_INSERTION, 'beforeInsertionHandler');
$app->on(PageEvents::AFTER_INSERTION, 'afterInsertionHandler');
$app->on(PageEvents::BEFORE_DELETION, 'beforeDeletionHandler');
$app->on(PageEvents::AFTER_DELETION, 'afterDeletionHandler');
function beforeInsertionHandler(BeforeInsertionEvent $event)
{
$child = $event->getChild();
$parent = $event->getParent();
$event->cancel();
}
function afterInsertionHandler(AfterInsertionEvent $event)
{
}
function beforeDeletionHandler(BeforeDeletionEvent $event)
{
}
function afterDeletionHandler(AfterDeletionEvent $event)
{
}
El fragmento anterior muestra la forma de usar esos eventos así como las clases a usar según el tipo. En todos los casos se contará con información sobre el componente padre e hijo pero solo los de tipo antes podrán ser cancelados.
Capítulo 9. Profundizando en la creación de componentes.¶
En el capítulo 1 se mostró la forma de crear tipos de componentes para GluePHP. Recordemos que la tarea consiste de los siguientes pasos:
- Crear una clase descendiente de
GlueApps\GluePHP\Component\AbstractComponent
. - Declarar los glue attributes especificando la anotación
@Glue
sobre los respectivos atributos de la clase. - Declarar funcionalidades especiales(binding y emisión de eventos) de ciertos elementos de la vista.
En este capítulo se mostrará de manera profunda, ciertas características adicionales que presentan los componentes y que se deben de tener en cuenta a la hora crear sus tipos.
1. Personalizando los getters y los setters de los glue attributes.¶
Tal y como se explicó en el capítulo 1, por cada glue attribute existente en la clase, existirán dos métodos para las operaciones getters y setters, donde sus nombres cumplirán con el formato camelCase y se compondrán de las palabras get o set según sea el caso, seguido del nombre del glue attribute. Por defecto estos métodos poseen cierta lógica interna pero pueden ser redefinidos si se desea añadirles alguna lógica personalizada.
En el siguiente ejemplo se han definidos ambos métodos con el objetivo de soportar solo el tipo de datos string. En el caso del método setter es de destacar la presencia del segundo argumento bool $sendAction = true
y de la sentencia $this->_set('text', $text, $sendAction);
. La existencia de ambos tiene un carácter obligatorio en todo método setter redefinido ya que forman parte del uso interno que GluePHP les da a los componentes.
class MyComponent extends AbstractComponent
{
/**
* @Glue
*/
protected $text;
public function setText(string $text, bool $sendAction = true)
{
$this->_set('text', $text, $sendAction);
return $this;
}
public function getText(): string
{
return $this->text;
}
}
2. Eventos antes y después de las actualizaciones en el servidor.¶
Cuando en una etapa de procesamiento se produce una actualización de un componente en el servidor, lo que se hace es invocar a los respectivos métodos setters con los nuevos valores. Estas invocaciones se hacen siguiendo el orden en el que se produjeron los cambios de los respectivos glue attributes en el navegador. Dado que por lo general, este orden de ocurrencia no sigue un patrón determinado, existirá una limitación si se necesita redefinir un método setter donde su lógica necesite ser ejecutada siempre después de la actualización de otro glue attribute.
Para solucionar esta limitación existen los eventos “beforeUpdate” y “afterUpdate” de los componentes. Como lo describen sus nombres, el primero se produce antes de realizar la actualización, mientras que el segundo después de efectuar la misma. Para ello, se deben definir métodos con igual nombre en la clase del componente donde se puede especificar la lógica deseada. Como puede ver en el siguiente fragmento, ambos métodos recibirán un objeto del tipo GlueApps\GluePHP\Update\UpdateInterface
a través del cuál se podrá conocer toda la información de la actualización.
use GlueApps\GluePHP\Update\UpdateInterface;
class MyComponent extends AbstractComponent
{
// ...
public function beforeUpdate(UpdateInterface $update)
{
}
public function afterUpdate(UpdateInterface $update)
{
}
}
3. Mostrando los componentes hijos.¶
Generalmente cuando se tiene un componente padre se necesita que la vista del mismo incluya también todas las vistas de sus hijos. Para hacer sencilla esta tarea, existe en la clase base de los componentes una función llamada renderizeChildren()
que devuelve todas las vistas de sus hijos.
El siguiente ejemplo muestra su uso:
class OrderedList extends AbstractComponent
{
public function html(): ?string
{
return "<ol>{$this->renderizeChildren()}</ol>";
}
}
class Item extends AbstractComponent
{
protected $text;
public function getText(): string
{
return $this->text;
}
public function setText(string $text)
{
$this->text = $text;
}
public function html(): ?string
{
return "<li>{$this->text}</li>";
}
}
$item1 = new Item('li1');
$item1->setText('Item 1');
$item2 = new Item('li2');
$item2->setText('Item 2');
$list = new OrderedList('ol');
$list->addChild($item1);
$list->addChild($item2);
echo $list->html();
El resultado del script anterior imprimirá la siguiente estructura HTML:
<ol>
<div class="gphp-children gphp-ol-children">
<div class="gphp-component gphp-li1" id="gphp-li1">
<li>Item 1</li>
</div>
<div class="gphp-component gphp-li2" id="gphp-li2">
<li>Item 2</li>
</div>
</div>
</ol>
Como puede ver el resultado incluye ciertos elementos que no pertenecen propiamente ni a la vista del padre ni a la de sus hijos. Estos elementos son añadidos por GluePHP para identificar en el DOM donde se almacenan los componentes hijos.
4. Uso de rutas relativas en el componente.¶
Gracias a la función basePath(string $assetUrl = ''): string
presente en la clase base de los componentes, es posible combinar muy fácilmente la ruta pasada a su argumento con la ruta base de la aplicación del componente en caso de que exista.
En el siguiente ejemplo se ha creado una aplicación donde se le ha especificado que su ruta base es http://localhost/
. Además se ha creado e insertado en la misma un componente con el que se muestra como al llamar a la función basePath()
sobre el mismo, se devuelve un resultado que combina la ruta base de la aplicación con la ruta pasada como argumento.
$app = new App('http://localhost/controller.php', 'http://localhost/');
$component = new MyComponent;
$app->appendComponent('body', $component);
echo $component->basePath('img/logo.png'); // http://localhost/img/logo.png
5. Extendiendo las clases JavaScript de los componentes.¶
Tal y como se ha comentado varias veces a lo largo del libro, cada glue component se forma de dos instancias completamente sincronizadas, una en el servidor y otra en el navegador. Hasta este momento, solo hemos mostrado el trabajo desde el backend, y es que GluePHP se enfoca en el desarrollo con PHP, lo que significa que la mayoría de sus funcionalidades están pensadas para este. No obstante, dada la naturaleza dual de este tipo de aplicaciones, es necesario tener en cuenta también ciertas necesidades que pueden existir relacionadas con el frontend.
Una de esas necesidades suele ser el tener que extender la clase JavaScript de algún tipo de componente. Esto se puede hacer muy fácilmente desde la propia clase PHP implementando un método estático de nombre extendClassScript(): ?string
.
En el siguiente ejemplo se ha añadido el método showAlert(text)
a la clase JavaScript del componente MyComponent. Es importante destacar que en el contexto de ese código JavaScript, CClass se refiere a la clase del componente.
class MyComponent extends AbstractComponent
{
// ...
public static function extendClassScript(): ?string
{
return <<<JAVASCRIPT
CClass.prototype.showAlert = function(text) {
alert(text);
};
JAVASCRIPT;
}
}
De esta manera, cuando en la aplicación exista un componente de este tipo, si sobre la instancia del navegador se invoca la llamada a la función component.showAlert('Hola');
se mostrará una alerta nativa con la palabra “Hola”.
Tenga en cuenta que hasta este momento no hemos explicado como trabajar con el frontend por lo que usted aún no conoce como obtener un componente en el navegador. Esto y mucho más lo conocerá más adelante cuando abordemos el capítulo dedicado completamente a trabajar con el frontend.
6. Inyectando código en la construcción de instancias JavaScript.¶
Otra de las necesidades que se puede tener relacionada con la parte frontend de los componentes es la de personalizar de forma individual la construcción de las instancias. Para ello se debe crear en la clase PHP del componente un método llamado constructorScript(): ?string
el cual debe devolver el código JavaScript deseado.
El siguiente ejemplo muestra como implementar esta función. En el mismo se ha definido un tipo de componente llamado MyComponent y se han creado dos instancias de este tipo.
use function GlueApps\GluePHP\jsVal;
class MyComponent extends AbstractComponent
{
public $data;
public function constructorScript(): ?string
{
$value = jsVal($this->data);
if (is_string($this->data)) {
return "this.text = {$value};";
} elseif (is_numeric($this->data)) {
return "this.number = {$value};";
}
}
}
$component1 = new MyComponent;
$component1->data = 'Hello World';
$component2 = new MyComponent;
$component2->data = 12345;
Si ambos componentes se insertan en una aplicación, en el navegador la instancia del componente 1 tomará la propiedad “text” con el texto “Hello World”, mientras que la instancia del componente 2 tomará una propiedad llamada “number” y su valor será 12345.
Es muy importante destacar el uso de la función jsVal()
. La misma es un helper para la conversión de valores desde PHP a JavaScript. Esta función recibe un argumento que puede ser de los tipos string, integer, double, boolean, NULL, array, object, y devuelve una cadena con el valor preparado para ser combinado con código JavaScript. El siguiente ejemplo muestra los diferentes casos:
jsVal('Hi Andy'); // 'Hi Andy'
jsVal(29); // 29
jsVal(12.345); // 12.345
jsVal(true); // true
jsVal(null); // null
jsVal(['name' => 'Andy']); // {name: 'Andy'}
jsVal([1, 2, 3, 4, 5]); // [1, 2, 3, 4, 5]
7. Introducción a los procesadores.¶
Cuando en el capítulo 1 nos desempeñamos con el rol de desarrollador de componentes, especificamos algunos atributos especiales sobre los elementos de las vistas para indicar ciertas funcionalidades como el Data Binding o la fuente de los eventos. Toda esa magia y mucho más se logra gracias a los procesadores, los cuales constituyen una de las piedras angulares más importantes en el funcionamiento de GluePHP.
Cuando en el frontend se instancia un componente, inmediatamente se aplican sobre el mismo varios tipos de procesadores. Un procesador no es más que una determinada lógica JavaScript que se aplica sobre un componente frontend recién creado.
No todos los componentes se procesan de la misma manera, y es en su clase PHP donde se especifica cuales son los procesadores que se aplican para un determinado tipo. Esta especificación se hace a través del método processors(): array
el cual devuelve un array con los nombres de los mismos. En la clase GlueApps\GluePHP\Component\AbstractComponent
existen predefinidos una serie de procesadores, y es gracias a esto, que hasta el momento no hemos tenido que especificar ningún procesador para los tipos de componentes que hemos creado. Seguidamente se muestra un fragmento de la clase GlueApps\GluePHP\Component\AbstractComponent
para mostrarle todos los procesadores registrados por defecto.
namespace GlueApps\GluePHP\Component;
// ...
use GlueApps\GluePHP\Processor\BindValueProcessor;
use GlueApps\GluePHP\Processor\BindEventsProcessor;
use GlueApps\GluePHP\Processor\BindAttributesProcessor;
use GlueApps\GluePHP\Processor\BindHtmlProcessor;
use GlueApps\GluePHP\Processor\ShortEventsProcessor;
// ...
abstract class AbstractComponent extends AbstractViewComponent
{
// ...
public function processors(): array
{
return [
BindValueProcessor::class,
BindEventsProcessor::class,
BindAttributesProcessor::class,
BindHtmlProcessor::class,
ShortEventsProcessor::class,
];
}
// ...
}
Como puede ver, también los procesadores se definen con clases PHP. En el próximo capítulo se abordará el tema acerca de su creación, pero de momento, explicaremos con profundidad todos los que vienen incluidos por defecto en GluePHP, donde muchos de ellos, ya fueron usados en el ejercicio del capítulo 1.
7.1. BindValueProcessor.¶
Este procesador se usa para crear un double binding entre un glue attribute y la propiedad “value” de un elemento HTML. Se debe emplear sobre elementos que hagan uso de esta propiedad como por ejemplo input
o select
, ya que en otros casos no tendría sentido.
Este procesador, recorrerá todos los elementos de la vista del componente que se esté procesando. Cuando sea encontrado un elemento que disponga del atributo gphp-bind-value="%G_ATTR%"
o data-gphp-bind-value="%G_ATTR%"
, será sobre dicho elemento donde se cree el double binding desde su propiedad “value” con la del glue attribute de nombre “%G_ATTR%”.
Tal y como usted supone, %G_ATTR% representa el nombre de un glue attribute.
7.2. BindHtmlProcessor.¶
Este procesador se usa para crear un binding simple desde un glue attribute hasta el HTML interno del elemento. El mismo se especifica con el atributo gphp-bind-html="%G_ATTR%"
o data-gphp-bind-html="%G_ATTR%"
.
En el ejercicio del capítulo 1 se empleó este procesador en el componente Label.
7.3. BindAttributesProcessor.¶
Este procesador sirve para crear un binding simple desde un glue attribute hasta un atributo del elemento HTML que lo contenga. El mismo se especifica con el atributo gphp-bind-attr-%ATTR%="%G_ATTR%"
o data-gphp-bind-attr-%ATTR%="%G_ATTR%"
.
Por ejemplo, en el siguiente caso, el atributo “class” del elemento “div” mantendrá el binding con el glue attribute “class”:
<div gphp-bind-attr-class="class"></div>
7.4. BindEventsProcessor.¶
Este procesador se usa para indicar eventos del componente desde elementos de la vista. Se indica con el atributo gphp-bind-events="%EV_1% %EV_2% ..."
o
data-gphp-bind-events="%EV_1% %EV_2% ..."
.
Es de destacar que se pueden indicar varios eventos si los nombres se separan por espacios. En el siguiente ejemplo, los eventos “click” y “mousedown” del elemento <button>
producirán eventos de igual nombre en el componente.
<button gphp-bind-events="click mousedown">Click Me!</button>
7.5. ShortEventsProcessor.¶
Este procesador también sirve para especificar un único evento del componente desde un elemento de la vista. Se especifica con dos arrobas seguido del nombre del evento.
En el siguiente ejemplo se ha especificado que el evento “click” del elemento <button>
producirá el evento de igual nombre en el componente:
<button @@click>Click Me!</button>
Capítulo 10. Procesadores.¶
Capítulo 11. Integración con VueJS.¶
Capítulo 12. Integración con Polymer.¶
Capítulo 13. Creando kits de componentes.¶
Capítulo 14. Trabajando con el frontend.¶
Capítulo 15. Funcionamiento de una glue app¶
El funcionamiento de una glue app sucede en una etapa inicial llamada carga y en muchas otras donde cada una de ellas se nombra procesamiento.
Carga:¶
La carga se produce cuando el navegador accede a la URI que contiene la vista(HTML) de la página única. Durante esta etapa se produce el siguiente detalle de operaciones entre el navegador web y el servidor:
- Navegador: Accede a la URI de la app.
- Servidor: Instancia la app con sus componentes.
- Servidor: Define la lógica de los eventos.
- Servidor: Persiste la instancia de la app.
- Servidor: Responde al navegador enviando el código HTML de la app.
- Navegador: Instancia la app con sus componentes.
- Navegador: Procesa los componentes.
En esta etapa es necesario que el servidor persista la instancia de la app ya que esta será usada nuevamente durante la próxima etapa de procesamiento. Como las instancias de los componentes forman parte de la instancia de la app, estas también son persistidas.
Por otra parte, puede notar que se crean instancias de la app y sus componentes primero en el servidor y después en el navegador. Antes habíamos mencionado que las glue apps mantenían datos compartidos y sincronizados entre el navegador y el servidor, y este es, precisamente el objetivo de crear una instancia equivalente por cada entorno. Cuando en el paso 4 se envía el código HTML al navegador, embedido con este, existe además un fragmento de código JavaScript que entre otras funcionalidades define las clases de los componentes, crea las instancias y las inicializa.
El último paso de la etapa consiste en procesar los componentes en el navegador. Esto no es más que aplicarle al componente frontend recién creado un determinado algoritmo, pero sobre este tema profundizaremos más adelante.
Procesamiento:¶
Una vez que la carga ha finalizado la app está totalmente operativa por lo que el usuario comenzará a interactuar con la misma. Esto provocará que se disparen ciertos eventos en determinados componentes como por ejemplo, el hacer clic en un botón, modificar un campo de texto, etc. Como la lógica de los eventos fue programada en el servidor(Paso 3 de la etapa de carga), será necesario llevar a este tanto los datos del evento en curso, como los cambios producidos hasta el momento en los componentes frontend. El detalle de operaciones de esta etapa es el siguiente:
- Navegador: Envía una solicitud ajax al servidor con los datos del evento y de los nuevos cambios en los componentes frontend.
- Servidor: Obtiene la instancia de la app persistida anteriormente.
- Servidor: Actualiza los componentes backend con los cambios recibidos en la solicitud actual.
- Servidor: Procesa el evento. Generalmente provoca envío de acciones al navegador.
- Servidor: Persiste la instancia de la app con sus componentes.
- Servidor: Responde al navegador.
- Navegador: Procesa la respuesta.
Una vez que el servidor recibe una solicitud de que se debe procesar un determinado evento, este obtiene inmediatamente la instancia backend de la app persistida durante la etapa de carga o durante la etapa de procesamiento anterior. Como los componentes backend y frontend tienen que estar sincronizados la primera tarea que se realiza es la actualización de los componentes backend con los cambios que se produjeron en el frontend.
Seguidamente se procede a ejecutar la lógica del evento lo que generalmente ocasiona modificaciones en determinados componentes backend. Si durante el procesamiento de un evento, se produce alguna modificación de algún componente, inmediatamente se envía una acción al navegador con información del cambio para que este actualice lo antes posible dicho componente frontend. Enviar una acción no es más que escribir en la salida al navegador datos en formato JSON, lo que provocará que la actual solicitud ajax pase a ser de tipo streaming ya que se comienza a enviar datos de respuesta sin que esto finalice la misma.
Después de que se ha ejecutado la lógica del evento, se tiene que volver a persistir la app con sus componentes para que la próxima etapa de procesamiento cuente con las modificaciones que se produjeron durante la actual etapa.
Por último el servidor termina la solicitud actual enviando cierta información del proceso que termina siendo interpretada y procesada en el navegador.