1. Yupp PHP Framework
Tutorial “manos a la obra”
Autor: Ing. Pablo Pazos Gutiérrez
<pablo.swp@gmail.com>
Fecha: Mayo 2011
Versión: 1.1
2. Índice
1. Fundamentos de patrones MVC y ORM...........................................................................................4
1.1 Model-View-Controller (MVC).....................................................................................................4
1.1.1 Algunos conceptos a importantes de recordar:....................................................................5
1.2 Object-Relational Mapping (ORM)..............................................................................................6
1.2.1 Mapeo objeto-relacional.......................................................................................................6
1.2.1.1 Mapeo de clase..............................................................................................................6
1.2.1.2 Mapeo de relaciones......................................................................................................7
1.2.1.3 Mapeos de herencia......................................................................................................8
2. Fundamentos de programación ágil y aplicaciones web.................................................................10
3. Instalación y configuración del framework......................................................................................12
3.1 Descargar la liberación.............................................................................................................12
3.2 Descargar del servidor de desarrollo........................................................................................13
3.3 Configuración del framework....................................................................................................14
4. Estructura de Yupp Framework......................................................................................................15
5. URLs en Yupp................................................................................................................................18
6. Creando una aplicación simple.......................................................................................................19
7. Estructura de las aplicaciones Yupp...............................................................................................24
7.1 Configuración de la base de datos por aplicación.....................................................................25
7.1.1 Creando la configuración....................................................................................................25
7.2 Script de bootstrap....................................................................................................................26
7.3 Scripts de testing......................................................................................................................26
8. Model: funcionalidades avanzadas.................................................................................................28
8.1 Gestionando relaciones hasOne...............................................................................................28
8.2 Gestionando relaciones hasMany.............................................................................................30
8.2.1 Tipos de relaciones hasMany.............................................................................................31
8.3 Convenciones sobre nombrado de tablas.................................................................................32
8.3.1 Nombrado implícito de tablas.............................................................................................32
8.3.2 Nombrado explícito de tablas.............................................................................................32
8.3.3 Nombrado de tablas de join................................................................................................33
8.4 Creando restricciones sobre campos y relaciones....................................................................33
8.4.1 Definiendo restricciones.....................................................................................................33
8.4.2 Verificando restricciones....................................................................................................35
8.4.2.1 Validando datos mediante restricciones.......................................................................35
8.4.2.2 Validando datos previa a la persistencia......................................................................35
8.5 Definiendo lógica pre-validación...............................................................................................36
8.6 Usando belongsTo....................................................................................................................37
8.7 Definiendo relaciones de muchos a muchos.............................................................................40
8.8 Eliminación física vs. eliminación lógica....................................................................................40
9. Controladores.................................................................................................................................42
9.1 Convenciones...........................................................................................................................42
9.2 Acciones de infraestructura (scaffolded)...................................................................................44
9.3 Recibiendo archivos..................................................................................................................44
9.4 Devolviendo XML o JSON........................................................................................................46
9.4.1 Programando una acción que devuelve JSON...................................................................46
9.4.2 Programando una acción que devuelve XML.....................................................................47
10. Vistas...........................................................................................................................................48
10.1 Fundamentos para la implementación de vistas.....................................................................48
3. 10.2 Uso de helpers........................................................................................................................49
10.2.1 Helper layout....................................................................................................................49
10.2.2 Helper template................................................................................................................50
10.2.3 Helper javascript...............................................................................................................52
10.2.4 Helper ajax link.................................................................................................................52
10.3 Vistas de infraestructura (scaffolded)......................................................................................53
11. Estado actual del proyecto...........................................................................................................54
11.1 Hoja de ruta del proyecto........................................................................................................54
11.2 Información administrativa......................................................................................................54
4. 1. Fundamentos de patrones MVC y ORM
1.1 Model-View-Controller (MVC)
MVC es un patrón arquitectónico, que determina grandes componentes de software, a modo de
capas, que se dividen las responsabilidades funcionales de un sistema informático. Es frecuente la
asociación de MVC al modelo de 3 capas: interfaz de usuario, lógica de negocio y acceso a datos,
donde “view” se asocia a la interfaz de usuario, “controller” a la lógica de negocio, y “model” al
acceso a datos.
Existen múltiples implementaciones de MVC, en esta sección veremos la alternativa de
implementación elegida para Yupp Framework. En el siguiente diagrama se muestra un ciclo de
pedido, donde se pasa por los 3 componentes de MVC.
En el diagrama se muestra un modelo simplificado del MVC de Yupp Framewrok (YMVC), donde un
usuario que realiza un pedido desde su navegador web, y este es atendido por el componente
Controller. En Controller se ejecuta la lógica de negocio, se accede a los datos mediante el
componente Model, donde se hacen consultas y actualizaciones. Luego el componente View es el
que determina que respuesta se le dará al usuario. View también puede acceder a Model pero en
general no accede directamente, sino que usa el resultado de la ejecución de la lógica, provisto por
Controller. Luego Controller entrega la respuesta al usuario, en general es una página web con datos
de las entidades del Model.
5. 1.1.1 Algunos conceptos a importantes de recordar:
● Un controlador implementa cierta lógica que es ejecutada dependiendo del pedido recibido.
● La lógica se implementa en forma de acciones, cada acción es un método dentro del
controlador. Un controlador puede tener varias acciones.
● Cada acción determina que vista se va a mostrar al usuario, lo que depende de los
parámetros de entrada y la lógica que se ejecute.
● Desde una acción, se pueden mostrar varias vistas distintas, solo una a la vez.
● Cada acción determina que modelo pasarle a cada vista.
A continuación se muestra un diagrama más detallado que se aproxima mejor a la implementación
de YMVC.
1.1.2 Descripción de los componentes:
Routing: se encarga de recibir un pedido del usuario y determinar qué controller lo debe atender.
Una instancia de Yupp puede contener múltiples aplicaciones, cada una con múltiples controllers.
Filter: es un componente que sirve para realizar acciones previas a la ejecución de la lógica del
controller seleccionado. Es útil para realizar verificaciones de seguridad, por ejemplo verificar si el
usuario tiene permisos para ejecutar la lógica del controller o no.
Data Access Layer (DAL): componente que abstrae el acceso a distintos DBMS, de modo que el
framework pueda trabajar, con MySQL o Postgres de forma transparente para el usuario.
6. La clase RequestManager es la que se encarga de recibir los pedidos, procesar los parámetros,
utilizar routing para determinar el controller y acción a ejecutar, procesar el resultado, y devolver una
vista. También se encarga de procesar errores y mostrarlos de forma amigable al usuario.
1.2 Object-Relational Mapping (ORM)
ORM es un mecanismo que permite trabajar con objetos en lugar de registros, en el acceso a bases
de datos relacionales. Esto es de especial interés para quienes programamos usando Orientación a
Objetos, porque evita la complejidad de tener que trabajar con estructuras de registros relacionales.
Para ORM también existen múltiples alternativas de implementación, en esta sección nos
concentraremos en la implementación de ORM elegida para Yupp (YORM).
Objetivos principales de YORM:
● Definir modelos de datos complejos, completamente orientados a objetos
● Evitar al máximo trabajar con SQL
● Soportar múltiples gestores de bases de datos
El primer objetivo implica que se deben soportar el mapeo de clases a tablas, el mapeo de
relaciones entre clases, y el mapeo de herencia entre clases. El segundo objetivo implica que se
utilizarán estructuras de clases para crear las consultas a las bases de datos, en lugar de utilizar
SQL. Y el tercer objetivo implica que el framework se debe abstraer del gestor de bases datos. Como
vimos en el MVC, esto se hace con el componente DAL.
1.2.1 Mapeo objeto-relacional
1.2.1.1 Mapeo de clase
El primer problema a resolver es el de mapear una clase simple a una tabla en la base de datos
relacional. Esto es relativamente sencillo de hacer en general, pero con PHP existe un problema
agregado: PHP es dinámico y débilmente tipado, y las bases de datos utilizan registros fuértemente
tipados, por lo que para cada campo de una clase programada en PHP, es necesario contar con un
mecanismo de especificación del tipo, ya que PHP no lo tiene.
El siguiente diagrama muestra la correspondencia entre la definición de una clase y su
correspondiente mapeo a una tabla.
7. La implementación de esta clase en Yupp es similar a este código:
// Todas clas clases persistentes heredan de PersistentObject
class Usuario extends PersistentObject
{
// Los campos se definen en el constructor
function __construct($args = array (), $isSimpleInstance = false)
{
// Determina el nombre de la tabla
$this->setWithTable("usuarios");
// Campos de la clase
$this->addAttribute("nombre", Datatypes :: TEXT);
$this->addAttribute("email", Datatypes :: TEXT);
$this->addAttribute("edad", Datatypes :: INT_NUMBER);
$this->addAttribute("fechaNacimiento", Datatypes :: DATE);
// Llamada al constructor de la superclase
// Inyecta atributos útiles para ORM (id, class, deleted)
parent :: __construct($args, $isSimpleInstance);
}
// Métodos estáticos omitidos
}
1.2.1.2 Mapeo de relaciones
Habiendo definido 2 clases, estas pueden relacionarse de múltiples formas. Las relaciones entre
clases pueden ser unidireccionales o bidireccionales, y pueden tener distintas cardinalidades. Para
las cardinalidades, diferenciaremos 2 casos: 1 y N. A continuación se muestra un diagrama con
todas las posibilidades de definición de relaciones entre 2 clases:
Para las relaciones con un lado N, aparte de las tablas que el YORM creará para las clases A y B,
creará una tabla intermedia (tabla de join) para mantener las relaciones. A continuación se muestra
un ejemplo. Si a es una instancia de A y b1, b2, b3 son instancias de B, y A tiene varios B (en este
caso b1, b2 y b3), las relaciones se mantienen de la siguiente forma:
8. tabla_a tabla_a_b tabla_b
id id_a id_a id_b1 id id_b1
id_a id_b2 id id_b2
id_a id_b3 id id_b3
A continuación se muestra el código PHP de la clase A con una relación unidireccional a un B:
class A extends PersistentObject
{
function __construct($args = array (), $isSimpleInstance = false)
{
$this->setWithTable("tabla_a");
// Declaración de campos de la clase A omitidos ..
$this->addHasOne("b", "B"); // Declaración de relación a un B
// Llamada al constructor de la superclase
// Inyecta atributos útiles para ORM (id, class, deleted)
parent :: __construct($args, $isSimpleInstance);
}
// Métodos estáticos omitidos
}
En el caso de que la clase A tuviera relacionados varios B, la definición de la clase sería:
class A extends PersistentObject
{
function __construct($args = array (), $isSimpleInstance = false)
{
$this->setWithTable("tabla_a");
// Declaración de campos de la clase A omitidos ..
$this->addHasMany("b", "B"); // Declaración de relación a muchos B
// Llamada al constructor de la superclase
// Inyecta atributos útiles para ORM (id, class, deleted)
parent :: __construct($args, $isSimpleInstance);
}
// Métodos estáticos omitidos
}
1.2.1.3 Mapeos de herencia
9. Las clases persistentes soportan la definición de herencia (especialización) como en cualquier
modelo orientado a objetos. Al conjunto de una clase y todas las clases que heredan de esta, directa
o indirectamente, se le llama “estructura de herencia” o “árbol de herencia”.
Por ejemplo, si se tienen 2 clases C y D, donde D hereda de C, hay dos opciones de ORM:
● mapear las dos clases a la misma tabla, ó
● que cada clase tenga su propia tabla.
Caso 1: D hereda de C y se mapean en la misma tabla.
Caso 2: D hereda de C y se mapean en tablas distintas:
En el caso 2 se puede apreciar que en la tabla_d se inyecta un nuevo atributo “super_id_c”, que
indica cual es el identificador de la parte de la clase D que se hereda de C, y sirve para hacer un join
entre las tablas, de modo de obtener una instancia completa de D, que incluye los valores de los
atributos declarados tanto en la clase C como en la clase D.
También para el caso 2 se debe notar que el identificador de las instancias de D será en que se
guarda en la tabla “tabla_d”, ya que es la tabla que corresponde con la clase concreta. Este
identificador puede ser distinto al que se guarda en la tabla “tabla_c” (por eso es necesaria la
columna “super_id_c”). Para una instancia de D persistita, en la columna “class” de ambas tablas
estará el valor “D”. Las instancias de C persistidas, no tendrán ningún registro ingresado en la tabla
“tabla_d”.
10. 2. Fundamentos de programación ágil y
aplicaciones web
En general, el desarrollo ágil refiere a una característica de algunos procesos de desarrollo de
software, que permiten que un proyecto tenga:
● Productos tangibles y funcionales en períodos de tiempo cortos
● Mayor visibilidad del avance del proyecto para el cliente
● Capacidad de adaptación rápida a cambios en los requerimientos
Que esto sea posible, también depende de las tecnologías que se empleen para los proyectos. En
particular los “frameworks ágiles”, como lo es Yupp, han propuesto tecnologías para acompañar y
ayudar a los procesos de desarrollo ágil de software. En este sentido, los objetivos de Yupp son:
● Quitar de manos del programador las tareas repetitivas que agregan poco valor al proyecto
(consultas a la BD, validación de datos, internacionalización, ajax, etc)
● Seguir el paradigma “convención sobre configuración”, que marca reglas básicas o
convenciones, que hacen que no se necesiten grandes y dificultosas configuraciones por
cada proyecto.
● Fácil de instalar y usar.
● Minimalista, todo lo necesario, pero no más.
● Permitir generar aplicaciones web funcionales sin necesidad de realizar una programación
completa.
● Curva de aprendizaje reducida.
Aplicaciones web
Principales características de las aplicaciones web:
● Instaladas en la nube
● No necesitan actualizaciones
● Accedidas desde web browsers
● Ejecución mixta, parte en el cliente, parte en el servidor (puede ser más de uno)
Las tendencias actuales muestran que en el futuro las aplicaciones basadas en web serán las únicas
aplicaciones, y que las aplicaciones de escritorio poco a poco van a desaparecer. Además existe una
tendencia de empresas como Google y Mozilla al desarrollo de sistemas operativos y aplicaciones
todo basado en web.
Por otro lado, con el desarrollo del nuevo estándar HTML5, que incluye de forma nativa soporte para
video, sonido, multi-threading, primitivas gráficas 2D y 3D, y otras características, lo que da una
infraestructura enorme para el desarrollo de aplicaciones web. En este sentido, Yupp está intentando
11. posicionarse como una opción competitiva entre los frameworks ágiles para el desarrollo de
aplicaciones para esta nueva realidad que está comenzando.
Algunos vínculos interesantes:
● http://www.youtube.com/watch?v=42Gyy2xr5zA
● http://www.youtube.com/watch?v=ErqCqwkwIDE
● http://www.youtube.com/watch?v=jB5KFJULahs
● http://www.youtube.com/watch?v=KMZLM2AhSE0
● http://www.youtube.com/watch?v=ANMrzw7JFzA
12. 3. Instalación y configuración del
framework
Los siguientes pasos suponen que se tiene un servidor web con soporte para PHP instalado. En
particular, se referenciará a la aplicación WAMP que es un paquete que trae Apache, MySQL y PHP
para Windows. Por más información: http://www.wampserver.com
El primer paso es obtener la última versión del framework, para esto hay dos opciones:
● Descargar la liberación
● Descargar del repositorio de desarrollo
3.1 Descargar la liberación
Desde la página de Google Code del proyecto [1], en el área de descargas, residen las últimas
liberaciones en archivos en formato ZIP.
[1] http://code.google.com/p/yupp/
13. 3.2 Descargar del servidor de desarrollo
En Google Code también se encuentra el servidor de versiones (SVN) del proyecto. El código fuente
de desarrollo se puede descargar desde ahí [2] con cualquier cliente SVN.
[2] http://yupp.googlecode.com/svn/YuppPHPFramework
Una vez descargado el framework, se copia su contenido al directorio “www” del WAMP. Para
verificar que se ha copiado correctamente, se inicia el WAMP, en un navegador web se accede a
http://localhost, donde deberá aparecer una página del WAMP. En esa página, hay una zona de
proyectos, ahí debería aparecer el directorio descomprimido donde se ha copiado el framewok. Si se
ingresa al directorio del framework, se debería ver el escritorio de Yupp como se muestra en la
siguiente imagen:
En este escritorio aparecerán todas las aplicaciones instaladas en Yupp Framework. Podemos
comentar varios aspectos de Yupp:
● Soporta múltiples aplicaciones.
● Sirve para que el desarrollador tenga varios proyectos en los que está trabajando.
● Sirve para que el usuario final pueda usar varias aplicaciones.
Yupp es tanto un plataforma de desarrollo, como plataforma de ejecución de aplicaciones web.
Otros aspectos del escritorio son:
● Desde aquí se pueden crear nuevas aplicaciones
● Se pueden filtrar las aplicaciones (útil cuando se tiene una cantidad considerable)
● Se pueden ver novedades relacionadas con Yupp y publicadas en Twitter
● Se puede acceder al listado de aplicaciones (vista por defecto)
● Se puede acceder a la vista de Base de Datos (es necesaria la configuración previa)
● Se pueden ejecutar los tests de las aplicaciones
● Se pueden ejecutar los scripts de bootstrap de las aplicaciones
● Se pueden ejecutar las aplicaciones
Nota: si se intenta acceder a la vista de Bases de Datos se obtendrá un error que indicará que la
base de datos no existe. Esto se debe a que todavía no la hemos creado y configurado.
14. 3.3 Configuración del framework
Yupp Framework fue diseñado para que la configuración sea mínima, representando una tarea muy
simple a realizar una sola vez. La única configuración necesaria es la de la base de datos a utilizar.
Al día de hoy, Yupp Framework soporta MySQL, Postgres y SQLite. Para configurar el gestor de
base de datos, es necesario editar el archivo yupp/core/config/core.config.YuppConfig.class.php. En
este archivo hay un campo $default_datasource, donde se realiza esta configuración en una
estructura como esta:
$default_datasource = array(
self::MODE_DEV => array(
'type' => self::DB_MYSQL,
'url' => 'localhost',
'user' => 'root',
'pass' => '',
'database' => 'yupp_dev'
),
self::MODE_PROD => array(
'type' => self::DB_MYSQL,
'url' => 'localhost',
'user' => 'root',
'pass' => '',
'database' => 'yupp_prod'
),
self::MODE_TEST => array(
'type' => self::DB_MYSQL,
'url' => 'localhost',
'user' => 'root',
'pass' => '',
'database' => 'yupp_test'
)
);
En esta estructura se indica que configuración de base de datos usar para cada modo de ejecución.
Hoy el framework soporta los modos “desarrollo” y “producción”, en un futuro también soportará el
modo “testing”. Para cada modo de ejecución se indica:
● tipo de gestor de bases de datos (MySQL, Postgres o SQLite)
● ubicación del gestor de bases de datos
● usuario y clave
● nombre de la base de datos a utilizar
15. 4. Estructura de Yupp Framework
La estructura (reducida) del framework es la siguiente:
● apps
○ core
○ tests
■ app.xml
● core
○ app
○ basic
○ config
○ db
○ http
○ layout
○ mvc
○ persistent
○ routing
○ support
○ testing
○ utils
○ validation
○ web
○ App
○ FileSystem
○ Yupp
○ YuppLoader
○ YuppSession
● css
● images
● js
● .htaccess
● index.php
Explicación de la estructura
/apps: Este directorio contiene las aplicaciones instaladas en el framework
/apps/core: Esta aplicación es parte del framework. Todas las vistas y acciones como el escritorio
de Yupp, la vista de Bases de Datos, la creación de aplicaciones, etc, son parte de esta aplicación.
/apps/tests: Esta aplicación contiene algunos tests sobre el framework. Por un lado, los tests
automatizados que pueden ser ejecutados desde el escritorio de Yupp. Por otro lado, al ejecutar la
aplicación, se tienen algunas vistas con tests para ejecutar manualmente, que además sirven como
referencia para la programación.
16. /apps/tests/app.xml: Descriptor de aplicaciones, usado por Yupp para presentar correctamente la
aplicación en el escritorio. En el futuro servirá para gestionar aplicaciones.
/core: Es el núcleo del framework. Contiene todas las clases y scripts necesarios para el
funcionamiento del framework.
/core/app: Contiene recursos relacionados con el manejo de las aplicaciones.
/core/basic: Contiene clases útiles para manejar tipos básicos como String y DateTime.
/core/config: Contiene elementos de configuración y la implementación de muchas de las
convenciones de Yupp, como las de nombrado de los archivos.
/core/db: Contiene la implementación de la capa de acceso a datos, los conectores a los distintos
motores de bases de datos, y el paquete de creación de consultas.
/core/http: Contiene la implementación de HTTPRequest y HTTPResponse.
/core/layout: Contiene la clase que da soporte a la definición de layouts en las vistas.
/core/mvc: Contiene la implementación de los elementos básicos para soportar controladores,
vistas, helpers útiles para la generación de vistas, y para el pasaje del modelo desde el controlador a
la vista.
/core/persistent: Contiene la implementación del ORM de Yupp. y clases para la serialización a
JSON y XML.
/core/routing: Este directorio contiene las clases que implementan el ruteo de pedidos al framework,
y la ejecución de acciones de los controladores.
/core/support: Contiene algunas clases útiles para mantener el contexto de ejecución del
framework, para el manejo de medidas de tiempo y para soportar la internacionalización.
/core/testing: Contiene las clases necesarias para implementar tests automáticos.
/core/utils: Contiene clases utilitarias de uso interno del framework.
/core/validation: Contiene las clases que implementan la validación de campos para las clases
persistentes.
/core/web: Contiene las clases que se encargan de manejar los pedidos al framework, procesar urls,
parámetros, y coordinar toda la ejecución de acciones y devolución de vistas.
App: Clase que implementa operaciones útiles sobre las aplicaciones.
FileSystem: Clase que implementa métodos útiles para acceder al sistema de archivos.
Yupp: Clases que implementa operaciones útiles sobre el framework.
YuppLoader: Clase que implementa la carga controlada de clases y scripts.
17. YuppSession: Clase que implementa la gestión de la sesión.
css: En este directorio se colocan las hojas de estilo de uso global (útiles para varias aplicaciones).
images: En este directorio se ponen las imágenes de uso global.
js: En este directorio se ponen los archivos javascript para uso global.
.htaccess: Indica reglas de redirección de pedidos, todos los pedidos van al index.php.
index.php: Punto de entrada de los pedidos del usuario al framework.
18. 5. URLs en Yupp
Como se mencionó antes, Yupp es un framework orientado a convenciones. La primer convención
que debe quedar clara es la del formato de las URLs que el framework puede recibir. Esto es muy
importante, porque toda la ejecución del framework y de sus aplicaciones dependerá de las URLs
que se reciban.
Antes de entrar en los formatos de las URLs, algunos comentarios:
● Yupp soporta varias aplicaciones
● Cada aplicación puede tener varios controladores
● Cada controlador implementa varias acciones (métodos)
● Dependiendo de los parámetros de entrada, una misma acción puede indicar que se
muestren varias vistas, una a la vez.
Ejemplo de URL válida en Yupp:
● http://localhost/yupp/app/controller/action?param1=value1¶m2=value2
Si el framework fue copiado al directorio “yupp” dentro del directorio “www” del WAMP, esta debería
ser una URL válida para Yupp. A continuación se explica cada trozo de la misma:
● http://localhost: acceso al “www” del WAMP, también conocido como “http root”.
● yupp: directorio donde fue copiado el framework dentro del “www” del WAMP.
● app: nombre de la aplicación que se desea ejecutar.
● controller: nombre del controlador, dentro de la aplicación “app”, que se desea ejecutar.
● action: nombre de la acción a ejecutar. Esta acción está implementada en “controller”.
● ?param1=value1¶m2=value2: parámetros que recibirá la acción.
Por ejemplo, si se tiene una aplicación “blog” y se quiere acceder al listado de entradas, la siguiente
podría ser la URL para hacerlo:
● http://localhost/yupp/blog/entradas/list
Yupp Framework soporta otra forma de pasarle parámetros a la acción, sin necesidad de usar el
formato param=value, este es un ejemplo:
● http://localhost/yupp/app/controller/action/value1/value2
Con este formato, el framework transformará los dos últimos trozos de la URL a parámetros de
nombre “_param_1” y “_param_2” respectivamente, y quedarán accesibles con esos nombres para
la acción correspondient.
19. 6. Creando una aplicación simple
Para crear una aplicación simple, se deben seguir estos pasos:
1. Ir al escritorio de Yupp Framework
2. Hacer clic sobre el link “Nueva aplicación”
3. Ingresar los datos ahí pedidos:
a. Nombre de la aplicación: ingresar el nombre, por ejemplo “biblioteca”
b. Descripción: una descripción de la aplicación, por ejemplo “Aplicación para gestión de
libros”
c. Lenguages: para qué idiomas estará disponible la aplicación, por ejemplo “es en” para
español e inglés respectivamente.
d. Nombre del controlador principal: nombre del controlador que se creará por defecto,
por ejemplo “libro”.
4. Hacer clic en “crear”.
Si se cumplieron todos los pasos exitosamente, ser debería volver al escritorio y ver la nueva
aplicación creada. Al hacer clic sobre el icono de la aplicación “biblioteca”, debería mostrarse el texto
“Bienvenido a su nueva aplicación!”. En la siguiente sección se explica en detalle la estructura
interna de la aplicación creada de forma automática por el framework. Ingresando a “/yupp/apps”
desde el sistemas de archivos, deberá haber un nuevo directorio con e nombre “biblioteca”. Dentro
de éste, en el directorio “controllers” debería haber un archivo PHP que implementa el controlador
“libro”, el archivo tendrá el nombre “apps.biblioteca.LibroController.class.php”. Abriendo ese archivo
veremos que la clase tiene una acción implementada, la cual devuelve el mensaje que veíamos
previamente (Bienvenido a su nueva aplicación!).
Creando una clase del modelo
Supongamos que nuestra aplicación debe gestionar libros, entonces necesitamos una clase que
modele un libro. Un libro tiene un título, un género, autor, fecha de edición, idioma y número de
páginas. Para crear esta clase, se deberían seguir los siguientes pasos:
1. Ir al directorio /yupp/apps/biblioteca/model
2. Ahí crear el archivo biblioteca.model.Libro.class.php
3. Programar una clase que herede de PersistentObject
4. Agregar un constructor al que se le pasan 2 parámetros:
a. $args = array ()
b. $isSimpleInstance = false
5. Agregar llamada al constructor de la clase padre en el constructor de esta clase
6. Copiar los métodos estáticos de alguna clase de ejemplo
7. Agregar los atributos mencionados previamente:
a. $this->addAttribute("titulo", Datatypes :: TEXT);
b. $this->addAttribute("genero", Datatypes :: TEXT);
c. $this->addAttribute("autor", Datatypes :: TEXT);
d. $this->addAttribute("fecha", Datatypes :: DATETIME);
e. $this->addAttribute("idioma", Datatypes :: TEXT);
f. $this->addAttribute("numeroPaginas", Datatypes :: INT_NUMBER);
20. Vamos paso por paso
Luego de los primeros 5 pasos, deberíamos tener programada una clase como esta:
class Libro extends PersistentObject {
function __construct($args = array (), $isSimpleInstance = false)
{
parent :: __construct($args, $isSimpleInstance);
}
}
Todas las clases del modelo deben heredar de PersistentObject para contar con las funcionalidades
de persistencia. El constructor debe recibir estos dos parámetros para que internamente la clase
PersistentObject realice las tareas necesarias para crear una clase persistente. Esas tareas son
ejecutadas al hacer una llamada explícita al constructor de la clase padre, que en este caso es la
única línea de código dentro del constructor de Libro.
En el paso 6 se indica que deben copiarse los métodos estáticos, estos métodos permiten realizar
distintas operaciones de persistencia y consulta sobre la clase. Luego del paso 6, la clase deberá
quedar así:
class Libro extends PersistentObject {
function __construct($args = array (), $isSimpleInstance = false)
{
parent :: __construct($args, $isSimpleInstance);
}
public static function listAll(ArrayObject $params)
{
self :: $thisClass = __CLASS__;
return PersistentObject :: listAll($params);
}
public static function count()
{
self :: $thisClass = __CLASS__;
return PersistentObject :: count();
}
public static function get($id)
{
self :: $thisClass = __CLASS__;
return PersistentObject :: get($id);
}
public static function findBy(Condition $condition, ArrayObject $params)
{
self :: $thisClass = __CLASS__;
return PersistentObject :: findBy($condition, $params);
}
public static function countBy(Condition $condition)
{
self :: $thisClass = __CLASS__;
return PersistentObject :: countBy($condition);
}
}
21. Por último, en el paso 7 agregamos los campos de la clase, cada uno con su respectivo tipo. Es
necesario definir el tipo del campo de forma explícita porque PHP no permite definir tipos para los
campos o atributos de una clase, porque PHP es debilmente tipado. Pero para indicarle a la base de
datos de qué tipo serán las columnas de la tabla donde se persistan instancias de la clase Libro, es
necesario la definición de los tipos.
Luego de realizado el paso 7, la clase completa quedaría así:
class Libro extends PersistentObject {
function __construct($args = array (), $isSimpleInstance = false)
{
$this->addAttribute("titulo", Datatypes :: TEXT);
$this->addAttribute("genero", Datatypes :: TEXT);
$this->addAttribute("autor", Datatypes :: TEXT);
$this->addAttribute("fecha", Datatypes :: DATETIME);
$this->addAttribute("idioma", Datatypes :: TEXT);
$this->addAttribute("numeroPaginas", Datatypes :: INT_NUMBER);
parent :: __construct($args, $isSimpleInstance);
}
public static function listAll(ArrayObject $params)
{
self :: $thisClass = __CLASS__;
return PersistentObject :: listAll($params);
}
public static function count()
{
self :: $thisClass = __CLASS__;
return PersistentObject :: count();
}
public static function get($id)
{
self :: $thisClass = __CLASS__;
return PersistentObject :: get($id);
}
public static function findBy(Condition $condition, ArrayObject $params)
{
self :: $thisClass = __CLASS__;
return PersistentObject :: findBy($condition, $params);
}
public static function countBy(Condition $condition)
{
self :: $thisClass = __CLASS__;
return PersistentObject :: countBy($condition);
}
}
22. Para que nuestro controlador sepa de la existencia de la nueva clase del modelo y la pueda utilizar,
debemos incluirla usando YuppLoader, así LibroController tendrá este aspecto:
YuppLoader::load('biblioteca.model', 'Libro');
class LibroController extends YuppController {
public function indexAction()
{
return $this->renderString("Bienvenido a su nueva aplicacion!");
}
}
Suponiendo que ya tenemos la base de datos configurada y corriendo, procedamos a crear la
estructura de la base para poder gestionar los libros.
1. Ingresar al escritorio de Yupp desde un navegador web
2. Ingresar a las pestaña “Base de Datos”
3. Debemos ver una zona para la aplicación “biblioteca”, donde se muestra que la clase Libro se
almacena en cierta tabla, la cual aún no fue creada.
4. Hacer clic en el link “Crear tablas”
5. Si todo salió bien, en la zona de la aplicación “biblioteca” debería decir que la tabla para la
clase Libro fue creada.
6. Volvemos al listado de aplicaciones (escritorio de Yupp) y hacemos clic sobre el icono de la
aplicación “biblioteca”.
7. Si vemos el mensaje “Bienvenido a su nueva aplicacion!”, debemos estar en la siguiente
URL: http://localhost/yupp/biblioteca/libro/index
8. Cambiar la url a: http://localhost/yupp/biblioteca/libro/list
9. El framework nos debería mostrar una vista con el listado de libros, sin ningún libro. Podemos
apreciar una tabla que muestra todos los atributos de nuestra clase Libro.
10. Si hacemos clic en el link “Create”, nos debería mostrar una vista donde podemos ingresar
toda la información de un Libro.
11. Cuidado con la fecha, ingresarla con el siguiente formato: aaaa-mm-dd
12. Al hacer clic en el botón “Create”, debemos estar en una vista que muestra los datos
ingresado.
13. Volvemos al listado cliqueando en el link “list”, y nos debería mostrar el libro recién ingresado.
14. Podemos seguir agregando libros, editando sus datos, o eliminándolos.
Algunos comentarios respecto a los pasos previos:
Generación de estructuras en la base de datos:
Toda la generación de tablas y relaciones en la base de datos la realiza el framework de forma
automática, en base a las clases definidas en el modelo de cada aplicación.
Vistas dinámicas:
Todas las vistas que se ven en el flujo de uso de la aplicación (listado, detalles del libro, creación,
edición) son autogenerdas, no fueron programadas como parte de la aplicación.
Acciones dinámicas:
23. Todas las acciones de listado, altas, bajas y modificaciones están implementadas en el framework, el
programador nunca implementó estas operaciones. Es más, en el controlador LibroController, donde
estas operaciones deben ser implementadas, vemos implementada solo la acción “index”.
Por lo tanto, sin necesidad de programar una aplicación completa, ya se tiene una aplicación
funcional para mostrar y probar. Tampoco es necesario invertir tiempo en diseñar la estructura de la
base de datos, porque también es autogenerada por el framework. Esto es gran parte de la agilidad
que agrega el framework al desarrollo de aplicaciones web.
24. 7. Estructura de las aplicaciones Yupp
Siguiendo con el ejemplo de la aplicación “biblioteca”, en esta sección veremos la estructura interna
de las aplicaciones de Yupp. La estructura generada por el framework para la aplicación “biblioteca”
es la siguiente:
● apps
○ biblioteca
■ bootstrap
■ config
■ controllers
■ i18n
■ model
■ services
■ utils
■ views
■ app.xml
/apps
Directorio donde se ubican las aplicaciones instaladas en el framework, tanto para desarrollo como
para su uso por usuarios finales.
/apps/biblioteca
Directorio de la aplicación “biblioteca”.
/apps/biblioteca/bootstrap
Directorio donde se ubican los scripts de arranque de la aplicación. Estos scripts sirven para agregar
datos en la base de datos para la correcta ejecución de la aplicación, por ejemplo se pueden dar de
alta usuarios administradores.
/apps/biblioteca/config
Directorio donde se colocará todo recurso relativo a la configuración de la aplicación.
/apps/biblioteca/controllers
Directorio que contienen los controladores de la aplicación.
/apps/biblioteca/i18n
Directorio donde se colocarán los scripts de traducción (internacionalización) de la aplicación.
/apps/biblioteca/model
Directorio que contiene las clases del modelo de información persistente de la aplicación.
/apps/biblioteca/services
Directorio que contiene las clases que implementan la lógica de negocio de la aplicación.
25. /apps/biblioteca/utils
Direcorio donde se coloca todo recurso auxiliar para el correcto funcionamiento de la aplicación.
/apps/biblioteca/views
Directorio que contiene las vistas de la aplicación, estas contienen toda la lógica de la interfaz de
usuario de la aplicación.
/apps/biblioteca/app.xml
Descriptor de aplicaciones, que contiene metadatos de la aplicación que son accedidos por el
framework y se utilizarán en el futuro para gestionar aplicaciones.
La estructura mínima para las aplicaciones Yupp podría considerarse similar a la siguiente:
● boostrap
● controllers
● model
● views
● app.xml
Todos los demás directorios que no se utilicen, pueden ser eliminados.
7.1 Configuración de la base de datos por aplicación
En la sección 3.3 se mostró cómo configurar la base de datos en Yupp. Esta configuración es global,
o sea que todas las aplicaciones utilizarán la misma base de datos, con la misma configuración. Esto
no siempre es deseable, por lo que Yupp soporta la configuración de bases de datos por aplicación.
7.1.1 Creando la configuración
Por ejemplo, si quisiéramos que la aplicación “biblioteca” tuviera su propia base de datos,
deberíamos seguir los siguientes pasos:
1. Si el directorio “config” no existe dentro de “apps/biblioteca”, crearlo.
2. Dentro de “apps/biblioteca/config”, crear el archivo “db_config.php”
3. Dentro del archivo, colocar el siguiente código PHP:
$db = array(
'type' => 'mysql',
'url' => 'localhost',
'user' => 'root',
'pass' => '',
'database' => 'yupp_biblioteca'
);
26. El array $db contiene todos los datos de configuración de la base de datos para la aplicación
“biblioteca”. En la clave “type” debe ponerse alguno de estos valores: “mysql”, “sqlite” o “postgres”.
Estos valores están definidos en “core/config/core.config.YuppConfig.class.php”. En el futuro cuando
se soporten otros motores de bases de datos, también podrá elegirse entre ellos. La clave “url” indica
el servidor donde se encuentra instalado el motor de bases de datos. Las claves “user” y “pass” son
usadas para poder acceder a la base de datos. Y por último, la clave “database” define el nombre de
la base de datos que estará utilizando nuestra aplicación.
No es necesaria ninguna otra configuración. Si se cumplen las convenciones de nombrado y
ubicación de los archivos, Yupp framework por sí solo encontrará la configuración y la utilizará.
7.2 Script de bootstrap
Opcionalmente, cada aplicación puede tener un script de boostrap (arranque). Este script se
ejecutará una sola vez luego de instalar la aplicación dentro del framework, y servirá para dar de alta
información necesaria para el correcto funcionamiento de dicha aplicación, como por ejemplo los
usuarios administradores de la aplicación.
El script de bootstrap debe estar situado en el directorio “bootstrap” de la aplicación, y dentro del
directorio un archivo llamado “apps.biblioteca.bootstrap.Bootstrap.script.php” (este sería el nombre
del script de bootstrap para la aplicación “biblioteca”).
Se tienen planes para que en el futuro pueda haber un script de boostrap para cada modo de
ejecución (desarrollo, producción y testing).
7.3 Scripts de testing
Hoy Yupp Framework soporta la definición y ejecución de casos de testing. Este soporte es el
mínimo necesario para estos fines. Se tienen planificadas varias mejoras para el área de testing.
Los casos de testing se definen dentro del directorio “tests” de las aplicaciones Yupp. Un caso de
testing es una clase que hereda de TestCase (clase provista por el framework), donde es necesario
definir el método run(). Por ejemplo, la implementación de un caso de test podría ser el siguiente:
YuppLoader::load('core.testing', 'TestCase');
class TestCase001 extends TestCase {
private $var = 0;
public function run()
{
$this->test1();
$this->test2();
$this->reset();
}
27. public function test1()
{
$this->var++;
$this->assert( $this->var == 1, 'Test igual a 1' );
$this->assert( $this->var != 0, 'Test distinto de 0' );
}
public function test2()
{
$this->assert( is_numeric($this->var, 'Test is_numeric' );
}
public function reset()
{
$this->var = 0;
}
}
El caso de testing debe implementar el método run(), luego dentro de este se invocan todos los
métodos definidos por el programador, que son los que implementan el caso de testing. No hay
restricciones sobre los nombres de los métodos (aparte del método run()).
28. 8. Model: funcionalidades avanzadas
En la sección 1.2 se tuvo una introducción al ORM de Yupp, mostrando cómo se definían las clases
del modelo y los distintos tipos de relaciones entre estas clases. También se mencionó cómo esas
clases y relaciones eran mapeadas a una estructura de base de datos. En esta sección se verán
otros aspectos interesantes que complementan los ya vistos.
8.1 Gestionando relaciones hasOne
Supongamos que la clase Libro que definimos previamente en la sección 6, ahora en lugar de un
atributo “autor” tiene una relación hasOne a una clase Autor que definimos más abajo:
YuppLoader::load("biblioteca.model", "Autor");
class Libro extends PersistentObject {
function __construct($args = array (), $isSimpleInstance = false)
{
$this->addAttribute("titulo", Datatypes :: TEXT);
$this->addAttribute("genero", Datatypes :: TEXT);
$this->addAttribute("fecha", Datatypes :: DATETIME);
$this->addAttribute("idioma", Datatypes :: TEXT);
$this->addAttribute("numeroPaginas", Datatypes :: INT_NUMBER);
$this->addHasOne("autor", "Autor");
parent :: __construct($args, $isSimpleInstance);
}
// Métodos estáticos omitidos
}
class Autor extends PersistentObject
{
function __construct($args = array (), $isSimpleInstance = false)
{
$this->addAttribute("nombre", Datatypes :: TEXT);
$this->addAttribute("fechaNacimiento", Datatypes :: DATE);
parent :: __construct($args, $isSimpleInstance);
}
// Métodos estáticos omitidos
}
29. Una forma sencilla de relacionar una instancia de Libro a una instancia de Autor es en la propia
construcción de las instancias:
$libro = new Libro(
array(
"titulo" => "El ingenioso hidalgo don Quixote de la Mancha",
"genero" => "prosa narrativa",
"fecha" => "1605-01-01",
"idioma" => "es",
"numeroPaginas" => 223,
"autor" => new Autor(
array(
"nombre" => "Miguel de Cervantes Saavedra",
"fechaNacimiento" => "1547-09-29"
)
)
)
);
Otra forma de asociación es mediante el método dinámico “getX”, donde “X” es el nombre de un
atributo, por ejemplo el atributo hasOne. Entonces podríamos tener el siguiente código:
$libro = new Libro(
array(
"titulo" => "El ingenioso hidalgo don Quixote de la Mancha",
"genero" => "prosa narrativa",
"fecha" => "1605-01-01",
"idioma" => "es",
"numeroPaginas" => 223
)
);
$autor = new Autor(
array(
"nombre" => "Miguel de Cervantes Saavedra",
"fechaNacimiento" => "1547-09-29"
)
);
$libro->setAutor( $autor );
Para obtener el autor de una instancia de Libro, se utiliza el método dinámico “getXXX”, donde “XXX”
es el nombre de un atributo, por ejemplo:
$autor = $libro->getAutor();
30. 8.2 Gestionando relaciones hasMany
Para seguir con el ejemplo de los libros, supongamos que ahora Libro tiene una relación hasMany
para los coautores:
YuppLoader::load("biblioteca.model", "Autor");
class Libro extends PersistentObject {
function __construct($args = array (), $isSimpleInstance = false)
{
$this->addAttribute("titulo", Datatypes :: TEXT);
$this->addAttribute("genero", Datatypes :: TEXT);
$this->addAttribute("fecha", Datatypes :: DATETIME);
$this->addAttribute("idioma", Datatypes :: TEXT);
$this->addAttribute("numeroPaginas", Datatypes :: INT_NUMBER);
$this->addHasOne("autor", "Autor");
$this->addHasMany("coautores", "Autor");
parent :: __construct($args, $isSimpleInstance);
}
// Métodos estáticos omitidos
}
Si se tiene una instancia de Libro y dos instancias de Autor, tales que los autores son coautores del
libro, para asociar los coautores al libro se utiliza el método dinámico “addToX”, donde “X” es el
nombre de una relación hasMany, por ejemplo:
$libro = new Libro(...);
$coautor1 = new Autor(...);
$coautor2 = new Autor(...);
$libro->addToCoautores( $coautor1 );
$libro->addToCoautores( $coautor2 );
Para obtener todos los coautores de la instancia de Libro se utiliza el método dinámico “getX” al igual
que en el caso previo para obtener la instancia en la relación hasOne.
$coautores = $libro->getCoautores();
Para quitar un coautor del libro se utiliza el método dinámico “removeFromX”, donde “X” es el
nombre del atributo hasMany. Para invocar a esta operación, la instancia a remover de la relación
debe haber sido persistida en la base de datos, porque es necesario que la instancia tenga
identificador. El identificador solo se establece cuando la instancia es almacenada en la base de
datos.
$libro->removeFromCoautores( $coautor1 );
En este ejemplo es útil recalcar que $coautor1 es removido de la relación hasMany con $libro, pero
que la instancia $coautor1 no es eliminada y sigue persistida en la base de datos.
31. 8.2.1 Tipos de relaciones hasMany
El ORM de Yupp soporta tres tipos distintos de comportamientos para las relaciones hasMany. Estas
relaciones pueden comportarse como colecciones, listas o conjuntos.
Colecciones
Por defecto las relaciones hasMany tienen este comportamiento, que consiste en no tener ningún
tipo de restricción sobre lo que se pone dentro de la relación. Los objetos no tienen orden ni se
verifican duplicados. Ejemplo:
$this->addHasMany("coautores", 'Autor', PersistentObject::HASMANY_COLECTION);
Listas
Una relación hasMany definida con comportamiento de lista, almacena el orden con que los objetos
son agregados a la relación. Esto sirve para obtener instancias persistentes, con el mismo orden en
el que fueron persistidas. Ejemplo:
$this->addHasMany("coautores", 'Autor', PersistentObject::HASMANY_LIST);
Conjuntos
Las relaciones hasMany definidas como conjuntos, implementan la restricción de verificación por
duplicados. Si en una relación hasMany definida como conjunto se intenta agregar dos veces el
mismo objeto, el segundo no será agregado. Esto funciona si los objetos que se agregan fueron
persistidos previamente. Ejemplo:
$this->addHasMany("coautores", 'Autor', PersistentObject::HASMANY_SET);
Nota: como el comportamiento por defecto es de colección, el primer ejemplo se comporta de igual
forma que el siguiente código, sin pasar el tercer parámetro:
$this->addHasMany("coautores", 'Autor');
32. 8.3 Convenciones sobre nombrado de tablas
En la sección 1.2 se vio un ejemplo de clase persistente, donde se declaraba explícitamente el
nombre de la tabla en la base de datos donde las instancias de esa clase iban a ser persistidas. En
esta sección vamos a explicar cuáles son las convenciones en cuanto a los nombres de las tablas en
la base de datos.
8.3.1 Nombrado implícito de tablas
Se toma como nombrado implícito cuando no se define el nombre de la tabla de forma explícita en el
constructor de la clase persistente. En el caso que se vio en la sección 1.2, el nombrado de la tabla
era explícito, haciendo la siguiente invocación:
$this->setWithTable("nombre_de_la_tabla");
En el caso de la aplicación “biblioteca” con la que venimos trabajando, la clase Libro no hace un
nombrado explícito. Por lo tanto se aplica la siguiente convención de Yupp:
Cuando no hay nombrado explícito de la tabla donde se persistirán las instancias de cierta clase del
modelo de información de una aplicación Yupp, el nombre de la tabla será igual al nombre de la
clase en minúsculas.
O sea que para los siguientes nombres de clases, tendremos los siguientes nombres de tablas:
● Libro => libro
● BuenLibro => buenlibro
● BuenLibro2 =>buelibro2
8.3.2 Nombrado explícito de tablas
En la sección anterior se comentó que es el nombrado explícito, que consiste en llamar al método
“setWithTable” con un determinado nombre de tabla. A continuación veremos distintos ejemplos para
los nombres de tablas y como Yupp reacciona ante ellos. Una restricción a considerar es que el
nombre de la tabla no puede contener caracteres que sean usados por los motores de bases de
datos, como por ejemplo el punto (“.”). Yupp no hace ningún tipo de verificación de estos caracteres,
por lo que el programador debe tener especial cuidado.
● Nombre de la tabla: “libro”, nombre aceptado por Yupp: “libro”
● Nombre de la tabla: “Libro”, nombre aceptado por Yupp: “libro”
● Nombre de la tabla: “BuenLibro”, nombre aceptado por Yupp: “buenlibro”
● Nombre de la tabla: “Buen_Libro”, nombre aceptado por Yupp: “buen_libro”
● Nombre de la tabla: “Buen Libro”, nombre aceptado por Yupp: “buen_libro”
33. Las reglas de Yupp sobre los nombres especificados de forma explícita son:
● El nombre especificado se convierte a minúsculas.
● Si el nombre especificado tiene espacios, se convierten a guiones bajos.
8.3.3 Nombrado de tablas de join
Cuando una relación hasMany es definida, como se hizo previamente en la clase Libro, hacia la
clase Autor, con una relación llamada “coautores”, una tabla intermedia es creada para mantener las
relaciones. En el ejemplo de la aplicación “biblioteca”, esta tabla intermedia es una tabla de join que
sirve para persistir y obtener todos los coautores de un determinado libro.
El nombre de la tabla de join para el caso mencionado será “libro_coautores_autor”, debido a que la
clase donde se define la relación se persiste en la tabla “libro”, a que la clase relacionada se persiste
en la tabla “autor”, y a que la relación se llama “coautores”.
8.4 Creando restricciones sobre campos y relaciones
Las clases persistentes permiten la definición de restricciones sobre los campos declarados en dicha
clase.
8.4.1 Definiendo restricciones
Tomando el ejemplo de la clase Autor, de la aplicación “biblioteca”, podemos definir que el nombre
del autor no puede ser vacío. Existen dos formas de hacerlo, la primera es usando la restricción
“blank”. Aquí el ejemplo completo:
class Autor extends PersistentObject
{
function __construct($args = array (), $isSimpleInstance = false)
{
$this->addAttribute("nombre", Datatypes :: TEXT);
$this->addAttribute("fechaNacimiento", Datatypes :: DATE);
$this->addConstraints("nombre", array(
Constraint::blank(false)
));
parent :: __construct($args, $isSimpleInstance);
}
// Métodos estáticos omitidos
}
Entonces, utilizamos blank(false) para indicar que el campo “nombre” no puede ser vacío. Es
necesario diferenciar el caso de un string vacío del caso de un sting null. Si se desea que el nombre
del autor tampoco sea null, debe especificarse una restricción como la siguiente:
34. // Restricciones
$this->addConstraints("nombre", array(
Constraint::nullable(false),
Constraint::blank(false)
));
Mediante la restricción “minLength” que restringe la cantidad de caracteres mínima que debe tener
un campo de tipo TEXT. Por ejemplo podríamos usar la siguiente restricción para indicar que el
nombre debe tener por lo menos 1 carácter y lograr el mismo comportamiento que con
“blank(false)”.:
// Restricciones
$this->addConstraints("nombre", array(
Constraint::minLength(1)
));
Si quisiéramos restringir el largo máximo del nombre, por ejemplo a 100 caracteres, utilizaríamos la
restricción “maxLength”:
// Restricciones
$this->addConstraints("nombre", array(
Constraint::blank(false),
Constraint::maxLength(100)
));
Como vemos, es posible definir varias restricciones para el mismo campo. Ahora, si quisiéramos
definir restricciones para campos numéricos, por ejemplo el campo “numeroPaginas” de la clase
Libro, podríamos decir que un libro debe tener como mínimo 20 páginas y como máximo 3000:
// Restricciones
$this->addConstraints("numeroPaginas", array(
Constraint::min(20),
Constraint::max(3000)
));
Este par de restricciones podría expresarse como una sola restricción “between”, la cual indica que
el valor de un campo debe estar entre dos valores datos, de la siguiente forma:
// Restricciones
$this->addConstraints("numeroPaginas", array(
Constraint::between(20, 3000)
));
Para ver todas las restricciones disponibles acceder a:
http://code.google.com/p/yupp/source/browse/YuppPHPFramework/core/validation/core.validation.Constraints.class.php
35. 8.4.2 Verificando restricciones
Existen dos formas de verificar restricciones sobre los valores que tienen los campos de una clase
persistente. La primera es utilizando el método “validate” de PersistentObject, la segunda es
utilizando el método “save” de PersistentObject.
8.4.2.1 Validando datos mediante restricciones
El siguiente es un ejemplo de validación utilizando el método validate($validateCascade = false)
$autor = new Autor();
$autor->setNombre("");
if (!$autor->validate()) print_r( $autor->getErrors() );
Como al crear la instancia de Autor, se se establece el valor del campo nombre en un string vacío,
violara la restricción de no vacío que fue definida previamente. Por lo tanto, “validate” devolverá
“false” y mediante el método “getErrors” se obtendrán los errores de validación, y lo que se imprime
será parecido a esto:
Array (
[nombre] => Array (
[0] => El valor del atributo 'nombre' no puede ser vacio
)
)
El método “validate” puede recibir opcionalmente un booleano que indica si la validación debe
hacerse en cascada, o sea, si debería ejecutarse sobre la instancia actual y sobre las instancias de
otras clases que tenga relacionadas. Por defecto la validación no se ejecuta en cascada.
8.4.2.2 Validando datos previa a la persistencia
De forma análoga a “validate”, al ejecutar el método “save” también se validan los datos. Esto quiere
decir que cada vez que se desee persistir una instancia de una clase, internamente se invoca al
método “validate”. En el caso que los valores de los campos de la instancia violen alguna de las
restricciones definidas en la clase, la instancia no es persistida y se generan todos los mensajes de
error para cada campo y cada restricción violada. Esto último es accesible mediante el método
“getErrors”. Por lo tanto, el siguiente código producirá el mismo resultado que el del ejemplo anterior
(notar que se invoca a save y no a validate):
$autor = new Autor();
$autor->setNombre("");
if (!$autor->save()) print_r( $autor->getErrors() );
36. 8.5 Definiendo lógica pre-validación
La clase PersistentObject tiene un método protegido “preValidate” (para ser implementado
opcionalmente por sus subclases), el cual es ejecutado previamente a la validación. Este método fue
ideado para modificar los valores de los campos de la clase, previamente a la ejecución de la
validación. Esto tiene varias utilidades.
Validación forzada de restricciones
Si en el método “preValidate” se detecta que un campo no cumple con cierta restricción, o sea que al
verificar la restricción el valor no cumplirá con dicha restricción, se puede modificar el valor del
campo para que cumpla con la restricción y así no se generen errores de validación. Un ejemplo
podría ser si un valor es null, teniendo una restricción “nullable(false)”, y en “preValidate” se cambia
el valor por un string vacío.
Limpieza de valores
Es común que cuando un usuario ingresa algún texto, este venga con espacios o fines de línea
extras al principio o al final. Si ese tipo de valores son asignados en campos de texto de una clase
persistente, dentro del método “preValidate” podría implementarse una limpieza del texto.
Esto podría servir también para aumentar la seguridad sobre lo que se inserta en la base de datos,
ya que un texto podría tener caracteres inválidos, o incluso código javascript o SQL ingresado por un
usuario malicioso, esto también puede detectarse y limpiarse en el método “preValidate”. A
continuación se muestra un ejemplo que quita espacios extra que pueden venir para el campo
“nombre” del autor.
class Autor extends PersistentObject
{
function __construct($args = array (), $isSimpleInstance = false)
{
// Campos
$this->addAttribute("nombre", Datatypes :: TEXT);
$this->addAttribute("fechaNacimiento", Datatypes :: DATE);
// Restricciones
$this->addConstraints("nombre", array(
Constraint::blank(false)
));
parent :: __construct($args, $isSimpleInstance);
}
protected function preValidate()
{
$nombre = $this->getNombre();
if ( !is_null($nombre) ) $this->setNombre( trim($nombre) );
}
// Métodos estáticos omitidos
}
37. 8.6 Usando belongsTo
En el ORM de Yupp, hay varias operaciones que pueden realizarse en cascada, o sea que se
aplican sobre una instancia de cierta clase persistente, y sobre las instancias de otras clases
persistentes que tenga asociada. Para esto se necesita la propiedad “belongsTo”, que indica cuál es
el lado fuerte y cuál el débil en una relación entre dos clases. Por ejemplo, si se tienen dos clases A
y B, “A hasOne B” y “B belongsTo A”, el lado fuerte de la relación es A y el débil es B.
Esta propiedad de las relaciones entre clases puede ser definida de forma explícita, o Yupp puede
inferirla mediante convenciones. A continuación se muestras todos los tipos de relaciones entre 2
clases A y B, y cuál lado es considerado fuerte y cuál débil por Yupp en el caso de no definir de
forma explícita la propiedad belongsTo.
Notación:
● A(*)->(1)B: indica que la clase A tiene una relación unidireccional a B, con A hasOne B.
● A(*)<->(1)B: indica que la clase A tiene una relación bidireccional a B, con A hasOne B y B
hasMany A.
Casos:
1. A(*)->(1)B: Yupp no sabe cual lado es el fuerte, se necesita belongsTo explícito.
2. A(1)<->(1)B: Yupp no sabe cual es el lado fuerte, se necesita belongsTo explícito.
3. A(1)->(*)B: Yupp considera que B belongsTo A
4. A(1)<->(*)B: Yupp considera que B belongsTo A
5. A(*)->(*)B: Yupp considera que B belongsTo A
6. A(*)<->(*)B: Yupp no sabe cual es el lado fuerte, se necesita belongsTo explícito.
Para ejemplificar estos casos, volvamos a la aplicación biblioteca, donde el modelo es el siguiente:
class Autor extends PersistentObject
{
function __construct($args = array (), $isSimpleInstance = false)
{
// Campos
$this->addAttribute("nombre", Datatypes :: TEXT);
$this->addAttribute("fechaNacimiento", Datatypes :: DATE);
// Restricciones
$this->addConstraints("nombre", array(
Constraint::blank(false)
));
parent :: __construct($args, $isSimpleInstance);
}
// Métodos estáticos omitidos
}
38. class Libro extends PersistentObject {
function __construct($args = array (), $isSimpleInstance = false)
{
$this->addAttribute("titulo", Datatypes :: TEXT);
$this->addAttribute("genero", Datatypes :: TEXT);
$this->addAttribute("fecha", Datatypes :: DATETIME);
$this->addAttribute("idioma", Datatypes :: TEXT);
$this->addAttribute("numeroPaginas", Datatypes :: INT_NUMBER);
$this->addHasOne("autor", "Autor");
$this->addHasMany("coautores", "Autor");
parent :: __construct($args, $isSimpleInstance);
}
// Métodos estáticos omitidos
}
O sea que:
● Libro hasOne Autor
● Libro hasMany Autor (coautores)
● No se ha declarado belongsTo explícito en ninguna clase
Con este modelo, podemos ejecutar el siguiente caso de prueba:
$libro = new Libro(
array(
"titulo" => "El ingenioso hidalgo don Quixote de la Mancha",
"genero" => "prosa narrativa",
"fecha" => "1605-01-01",
"idioma" => "es",
"numeroPaginas" => 223,
"autor" => new Autor( array(
"nombre" => "Miguel de Cervantes Saavedra",
"fechaNacimiento" => "1547-09-29"
)),
"coautores" => array(
new Autor( array(
"nombre" => "Sancho Panza",
"fechaNacimiento" => "1547-01-01"
)),
new Autor( array(
"nombre" => "Quijote",
"fechaNacimiento" => "1542-05-21"
))
)
)
);
if( !$libro->save() ) print_r( $libro->getErrors() );
39. Al ejecutar este caso de prueba, veremos que en la base de datos se ha guardado el libro y sus
coautores (en cascada), pero no se ha guardado su Autor. Esto se debe a que la relación “autor”
declarada en la clase Libro, entra en el caso 1 de la inferencia de belongsTo por Yupp framework,
por lo que si queremos que se persista también esa relación a Autor, debemos declarar un
belongsTo explícito en la clase Autor:
YuppLoader::load("biblioteca.model", "Libro");
class Autor extends PersistentObject
{
function __construct($args = array (), $isSimpleInstance = false)
{
$this->belongsTo = array( 'Libro' );
// Campos
$this->addAttribute("nombre", Datatypes :: TEXT);
$this->addAttribute("fechaNacimiento", Datatypes :: DATE);
// Restricciones
$this->addConstraints("nombre", array(
Constraint::blank(false)
));
parent :: __construct($args, $isSimpleInstance);
}
// Métodos estáticos omitidos
}
Realizando este cambio en la clase Autor, el test anterior almacenará en cascada tanto al autor
como a los coautores del libro. Si luego de ejecutar el caso de prueba de la sección 8.5, ejecutamos
el siguiente caso de prueba, el primer autor tendrá entre sus libros al libro que lo tiene como autor:
$libro = Libro::get(1); // Carga el libro con id=1 desde la base de datos
$autor = Autor::get(1); // Carga el autor con id=1 desde la base de datos
$autor->addToLibros($libro); // Agrega el libro a los libros del autor
if( !$autor->save() ) print_r( $autor->getErrors() ); // Intenta persistir
40. 8.7 Definiendo relaciones de muchos a muchos
De la misma forma que se definen las relaciones de uno a muchos usando hasMany, se pueden
definir relaciones bidireccionales de muchos a muchos. Simplemente es necesario colocar una
referencia hasMany en cada una de las clases. Por ejemplo, si desde la clase Autor se quisieran
todos los libros que escribió, ser podría hacer la siguiente modificación al modelo:
class Autor extends PersistentObject
{
function __construct($args = array (), $isSimpleInstance = false)
{
$this->belongsTo = array( 'Libro' );
// Campos
$this->addAttribute("nombre", Datatypes :: TEXT);
$this->addAttribute("fechaNacimiento", Datatypes :: DATE);
$this->addHasMany("libros", "Libro");
// Restricciones
$this->addConstraints("nombre", array(
Constraint::blank(false)
));
parent :: __construct($args, $isSimpleInstance);
}
// Métodos estáticos omitidos
}
Como vimos en las reglas de belongsTo en la sección previa, para definir relaciones de muchos a
muchos bidireccionales, uno de los lados debe tener declarado un belongsTo. En este caso dejamos
declarado el belongsTo en el Autor.
8.8 Eliminación física vs. eliminación lógica
Siempre que se quiera eliminar una instancia de una clase persistente, se debe ser cuidadoso con la
estrategia de eliminado a utilizar. En el caso de la eliminación física, el o los registros
correspondientes a la persistencia de la instancia a eliminar, serán borrados físicamente de la base
de datos y no se podrán recuperar sus datos. En cambio, con la eliminación lógica, los registros
siguen existiendo, pero tienen una marca de “eliminados”, esa marca es utilizada internamente por el
framework para saber que registros se encuentran activos y cuales fueron eliminados de forma
lógica.
41. El siguiente código elimina físicamente una instancia de Libro persistida en la base de datos:
$libro = Libro::get(1); // Carga el libro con id=1 desde la base de datos
$libro->delete(); // Eliminación física
El siguiente código elimina lógicamente una instancia de Libro persistida en la base de datos:
$libro = Libro::get(1); // Carga el libro con id=1 desde la base de datos
$libro->delete(true); // Eliminación física
42. 9. Controladores
Cada aplicación Yupp puede tener un conjunto de controladores, los cuales procesan los pedidos del
usuario, e implementan determinada lógica.
Partiendo del ejemplo del controlador generado para la aplicación “biblioteca”, vamos a completarlo
para mostrar los conceptos detrás de la programación de controladores.
YuppLoader::load('biblioteca.model', 'Libro');
class LibroController extends YuppController {
public function indexAction()
{
return $this->renderString("Bienvenido a su nueva aplicacion!");
}
}
9.1 Convenciones
Ubicación:
Todos los controladores de una aplicación Yupp deben estar dentro el directorio “controllers” de la
aplicación, por ejemplo “biblioteca/controllers”.
Nombres:
En Yupp, los nombres de todas las clases empiezan en mayúsculas. El nombre de las clases que
implementan controladores debe terminar en “Controller”, por ejemplo “LibroController” o
“AutorController”.
Acciones:
Las acciones de un controlador son métodos especiales que pueden ser invocados mediante el
envío de una URL. En la sección 5 vimos cómo están formadas las URLs dentro de Yupp, y que una
parte de estas determina la acción a ejecutar. Un controladores puede además implementar métodos
que no son acciones, por ejemplo métodos auxiliares que son usados por las acciones.
Los nombres de los métodos que implementan acciones son públicos, comienzan en minúsculas y
terminan en “Action”. Además no reciben parámetros de forma explícita. Previamente vimos un
ejemplo con la acción “index” del controlador LibroController.
Retorno de acciones
Las acciones pueden tener varios tipos de retorno. En el ejemplo previo se vio un retorno del tipo
“renderString”, que simplemente muestra en pantalla un determinado texto. En general no vamos a
querer retornar un texto, si no una vista. Más adelante veremos como crear vistas, para esta sección
es suficiente con saber que una vista tiene un determinado nombre y que recibe un conjunto de
parámetros (modelo) que utiliza para generar una página web que se le mostrará al usuario.
43. Si una acción simplemente retorna sin un valor, Yupp intentará mostrar una vista que tenga al mismo
nombre de la acción que se está ejecutando. Por ejemplo, el siguiente código intentará mostrar una
vista llamada “index” (obviamente, si la vista no existe, ocurrirá un error).
public function indexAction()
{
return;
}
Una segunda opción, es especificar explícitamente el nombre de la vista. En este caso, el nombre de
la vista podría ser distinto al de la acción, pero igualmente intentaremos mostrar la vista “index”. Este
código logra el mismo efecto que el anterior.
public function indexAction()
{
return $this->render("index");
}
Digamos ahora que la vista “index” necesita algunos parámetros para mostrarse correctamente. El
controlador puede pasarle un conjunto de parámetros al retornar, por ejemplo el siguiente código
intentará mostrar una vista con el mismo nombre que la acción, o sea “index”, y recibirá un conjunto
de valores.
public function indexAction()
{
return array( 'valor1' => 29, 'valor2' => 'hola' );
}
Redirigiendo pedidos
Al terminar de ejecutar una acción, esta podría no decidir mostrar una vista, si no realizar una
redirección para ejecutar otra acción. Esto genera un nuevo pedido HTTP reentrante. A continuación
se muestra un ejemplo de acción que redirige el pedido a otra acción:
public function indexAction()
{
return $this->redirect(
array(
"controller" => "autor",
"action" => "show",
"params" => array( "id" => 101, "mensaje" => "Hola mundo" )
)
);
}
Si esta fuera la acción “index” de LibroController, lo que haría es redirigir el pedido a la acción “show”
de AutorController, enviándole como parámetros “id=101” y “mensaje=Hola mundo”. Los pedidos
HTTP reentrantes son enviados usando el método GET de HTTP, por lo tanto, el pedido sería para
la siguiente URL de Yupp:
/yupp/biblioteca/autor/show?id=101&mensaje=Hola mundo
44. 9.2 Acciones de infraestructura (scaffolded)
Como vimos en la sección 6, en el controlador LibroController podemos ejecutar acciones que no
están implementadas, como por ejemplo “list”, “show”, “edit” y “create”. Estas acciones se
encuentran implementadas en YuppController, la superclase de todos los controladores. En general
es suficiente la implementación por defecto, pero de ser necesario ejecutar cierta lógica particular,
estas acciones pueden ser implementadas en nuestros controladores. Un caso típico es cuando un
controlador no tiene ninguna clase del modelo de información asociada, por ejemplo LibroController
se encarga de las acciones sobre la clase Libro. Estas acciones se consideran de “infraestructura”
por que son acciones genéricas, capaces de ser aplicadas a cualquier clase del modelo persistente
de cualquier aplicación Yupp.
Existe otro conjunto de acciones que están implementadas en CoreController, el controlador que
implementa las acciones que se realizan desde el escritorio de Yupp. Por ejemplo, si accedemos a la
acción de infraestructura “list” del controlador LibroController mediante la siguiente URL, y luego
vamos al “show” del un libro haciendo clic en su identificador (crear uno si no hay ninguno), vamos a
ver un link hacia su autor relacionado (si tiene uno), haciendo clic ahí veremos los detalles del autor,
aunque no exista el controlador relacionado a la clase Autor.
Comenzamos aquí: http://localhost/yupp/biblioteca/libro/list
Terminamos aquí: http://localhost/yupp/core/core/show?app=biblioteca&class=Autor&id=1
Nota: para tener datos para probar, puedes ejecutar el test que crea un libro y le asocia un autor.
9.3 Recibiendo archivos
Un tipo especial de parámetro que puede ser recibido por el método POST de HTTP son los
archivos. Un usuario puede subir un archivo usando un formulario como este en una vista:
<form method="post" enctype="multipart/form-data">
<input type="file" name="archivo" value="" />
</form>
Del lado del servidor, supongamos que el controlador que recibe el archivo, tiene una acción con el
siguiente código:
public function recibeArchivoAction()
{
print_r($this->params);
}
45. Esta acción mostrará algo similar a esto:
Array (
[file] => Array (
[name] => HCE 004.jpg
[type] => image/jpeg
[tmp_name] => C:wamptmpphp77.tmp
[error] => 0
[size] => 162096
)
)
Si quisiéramos cargar el contenido del archivo subido al servidor en una variable, por ejemplo para
guardarlo en la base de datos:
public function recibeArchivoAction()
{
$filename = $this->params["tmp_name"];
$imagen = FileSystem::read($filename);
}
Y si quisiéramos copiar el archivo a una ubicación determinada:
public function recibeArchivoAction()
{
$filename = $this->params["tmp_name"];
$ubicacion = "dir/subdir/nombreArchivo.jpg";
if (move_uploaded_file($filename, $ubicacion))
{
echo "El archivo ha sido cargado correctamente.";
}
else
{
echo "Ocurrió un error al subir el archivo. No pudo guardarse.";
}
}
46. 9.4 Devolviendo XML o JSON
Previamente vimos como devolver un string desde una acción. El mismo mecanismo puede ser
usado para devolver un string XML o JSON. Estos serán casos de acciones que no devuelven vistas,
si no que implementan servicios para ser consumidos desde la interfaz de usuario (por ejemplo
mediante pedidos AJAX) o desde otros sistemas.
9.4.1 Programando una acción que devuelve JSON
Digamos que dentro de LibroController necesitamos una acción que devuelva los datos de un
determinado libro, pero lo queremos en notación JSON. Para lograrlo, podríamos implementar la
acción de la siguiente manera:
public function jsonShowAction()
{
YuppLoader::load('core.persistent.serialize', 'JSONPO');
$id = $this->params['id']; // Obtiene el parámetro id
$libro = Libro::get( $id ); // Carga el libro con ese id
$json = JSONPO::toJSON( $libro ); // Genera la serialización a json
// Lo que se devolverá en el response HTTP será de tipo json
header('Content-type: application/json');
// Escribe el string json en la respuesta al usuario
return $this->renderString( $json );
}
La URL Yupp para invocar a esta acción, sería: http://localhost/yupp/biblioteca/libro/jsonShow?id=1
Lo que obtendremos será algo así:
{
titulo: "El ingenioso hidalgo don Quixote de la Mancha"
genero: "prosa narrativa"
fecha: "1605-01-01 00:00:00"
idioma: "es"
numeroPaginas: "223"
class: "Libro"
deleted: ""
autor_id: "1"
id: "1"
}
47. 9.4.2 Programando una acción que devuelve XML
Análogamente al ejemplo de JSON, podemos necesitar programar una acción que devuelva XML.
public function xmlShowAction()
{
$id = $this->params['id'];
$libro = Libro::get( $id );
$xml = XMLPO::toXML( $libro );
header('Content-type: text/xml');
return $this->renderString( $xml );
}
La URL Yupp para invocar a esta acción, sería: http://localhost/yupp/biblioteca/libro/xmlShow?id=1
Y el resultado es parecido a este XML:
<Libro>
<titulo>El ingenioso hidalgo don Quixote de la Mancha</titulo>
<genero>prosa narrativa</genero>
<fecha>1605-01-01 00:00:00</fecha>
<idioma>es</idioma>
<numeroPaginas>223</numeroPaginas>
<class>Libro</class>
<deleted/>
<autor_id>1</autor_id>
<id>1</id>
</Libro>
48. 10. Vistas
Las vistas implementan la interfaz de usuario de las aplicaciones Yupp. Es común que las
aplicaciones tengan una vista para listar instancias de cierta clase persistente, otra vista para ver los
detalles de una instancia, otra para crear nuevas instancias y otra para editar instancias. En esta
sección veremos los distintos aspectos involucrados en la creación de este tipo de vistas y de otros
tipos de vistas más complejos.
10.1 Fundamentos para la implementación de vistas
Primero que nada, las vistas no son más que scripts PHP que solo contienen lógica de interfaz de
usuario, o sea que:
● Una vista no debería contener lógica de negocio
● Una vista no debería contener consultas complejas a la base de datos
● Una vista no debería modificar el estado de la aplicación
Las vistas son colocadas en el directorio “views” de las aplicaciones Yupp, con la característica de
que las vistas se organizan según los controladores que las muestran. Por ejemplo, si para el
controlador LibroController, se tuviera una vista llamada “list”, esta vista estaría implementada en la
siguiente ubicación:
apps/biblioteca/views/libro/list.view.php
La convención de nombrado de vistas es simple: el archivo debe terminar en “.view.php”.
A continuación veremos un ejemplo de una vista para el listado de libros:
<?php
// Modelo pasado desde el controlador
$m = Model::getInstance();
?>
<html>
<head>
<style>
table {
border: 1px solid;
}
td {
border: 1px solid;
padding: 5px;
}
</style>
</head>
<body>
<h1>Libros</h1>
49. <table>
<!-- El controlador puso la lista de libros en la clave 'libros' -->
<?php foreach( $m->get('libros') as $libro) : ?>
<tr>
<td><?php echo $libro->getTitulo(); ?></td>
<td><?php echo $libro->getGenero(); ?></td>
<td><?php echo $libro->getIdioma(); ?></td>
</tr>
<?php endforeach; ?>
</table>
</body>
</html>
La acción del controlador que especifica que se debe mostrar la vista anterior, puede ser algo así:
public function listAction()
{
// Carga todos los libros desde la base de datos
$libros = Libro::listAll($this->params);
// Muestra la vista list enviándole los libros como modelo
return array('libros' => $libros);
}
10.2 Uso de helpers
Los helpers son componentes reusables de código que implementan lógica de interfaz de usuario.
Yupp implementa varios helpers que pueden ser utilizados en la programación de vistas, pero el
programador puede definir sus propios helpers cuando los necesite. En las siguientes secciones
veremos algunos helpers interesantes. Por una lista completa de helpers, puedes visitar la
documentación del proyecto: http://www.simplewebportal.net/yupp_framework_php_doc
10.2.1 Helper layout
Un layout sirve para reutilizar una estructura general entre varias vistas para la interfaz de usuario de
una aplicación. La forma de especificar que una vista utiliza un layout es mediante un etiqueta. Por
ejemplo, si la vista “list” del controlador LibroController (que vimos anteriormente) usara un layout
llamado “default”, el código de la vista sería el siguiente:
<?php
// Modelo pasado desde el controlador
$m = Model::getInstance();
?>
<html>
<layout name="default" />
<head>
<style>
table {
50. border: 1px solid;
}
td {
border: 1px solid;
padding: 5px;
}
</style>
</head>
<body>
<h1>Libros</h1>
<table>
<!-- El controlador puso la lista de libros en la clave 'libros' -->
<?php foreach( $m->get('libros') as $libro) : ?>
<tr>
<td><?php echo $libro->getTitulo(); ?></td>
<td><?php echo $libro->getGenero(); ?></td>
<td><?php echo $libro->getIdioma(); ?></td>
</tr>
<?php endforeach; ?>
</table>
</body>
</html>
Importante: la etiqueta que indica el layout a utilizar debe ir inmediatamente después de la etiqueta
“html”.
El código del layout puede ser similar a el siguiente, donde $head es todo lo que está dentro de la
etiqueta “head” de la vista que referencia al layout, y $body es todo lo que está dentro de la etiqueta
“body” de la vista que referencia al layout.
<html>
<head>
<style type="text/css">
...
</style>
<?php echo $head; ?>
</head>
<body>
...
<div style="padding:10px;"><?php echo $body; ?></div>
</body>
</html>
Los layouts son también scripts PHP, pero a diferencia de las vistas, deben cumplir con las
siguientes convenciones:
● Deben mostrar a las variables $head y $ body.
● El archivo debe terminar en “.layout.php”.
● El archivo debe ubicarse en el directorio “views” de la aplicación.
10.2.2 Helper template
51. Los templates son otra forma de reutilizar código entre distintas vistas, pero a diferencia del layout
que es para reutilizar la estructura general de la vista, los templates reutilizan lógica de interfaz de
usuario interna a la vista. Por ejemplo si para mostrar el mismo objeto (podría ser una instancia de
Libro) se utilizara la misma lógica en varias vistas, esa lógica podría ponerse dentro de un template,
y reutilizar el template en dichas vistas. A continuación se muestra un ejemplo de cómo puede ser
una llamada a un template que muestra un libro con cierto formato:
Helpers::template( array("controller" => "libro",
"name" => "details",
"args" => array("libro" => $libro) ) );
Los templates también son scripts PHP cuyo nombre termina en “.template.php”, por ejemplo, el
template referenciado desde el código anterior es “libro.template.php”. Este template podrá tener
código PHP, HTML y de otros tipos. Por ejemplo, el template “libro” podría ser así:
<div>
<b><?php echo $libro->getTitulo(); ?></b>
(<?php echo $libro->getGenero(); ?>, <?php echo $libro->getIdioma(); ?>)
</div>
Entonces podríamos cambiar el código de la vista “list” (vista en la sección 10.2.1) para que utilice el
template:
<?php
$m = Model::getInstance(); // Modelo pasado desde el controlador
?>
<html>
<layout name="default" />
<head>
<style>
table {
border: 1px solid;
}
td {
border: 1px solid;
padding: 5px;
}
</style>
</head>
<body>
<h1>Libros</h1>
<!-- El controlador puso la lista de libros en la clave 'libros' -->
<?php foreach( $m->get('libros') as $libro) : ?>
<!-- Se le pasa el libro al template -->
<?php Helpers::template( array(
"controller" => "libro", "name" => "details",
"args" => array("libro" => $libro)
) ); ?>
<?php endforeach; ?>
</body>
52. </html>
10.2.3 Helper javascript
El helper javascript es útil para controlar la inclusión de scripts javascript. Una posible llamada es la
siguiente:
<?php echo h("js", array("name" => "jquery/jquery-1.5.1.min") ); ?>
El código PHP previo, generará el siguiente código HTML:
<script type="text/javascript" src="/yupp/js/jquery/jquery-1.5.1.min"></script>
10.2.4 Helper ajax link
El helper ajax link, sirve para crear links que al cliquearlos envían un pedido HTTP a una
determinada acción de un controlador, con la característica de que el pedido va por AJAX. Esto
ayuda a crear aplicaciones web más interactivas. Un ajax link podría ser creado de la siguiente
manera:
<?php echo Helpers::ajax_link( array(
"app" => "biblioteca",
"controller" => "libro",
"action" => "jsonShow",
"id" => $libro>getId(),
"body" => "Obtener comentarios por Ajax",
"after" => "after_function",
"before" => "before_function" ) ); ?>
El código PHP previo, generará el siguiente código HMTL/Javascript:
<script type="text/javascript">
function ajax_link_0() {
$.ajax({
url: '/yupp/biblioteca/libro/jsonShow?id=1',
beforeSend: before_function,
success: after_function
});
}
</script>
<a href="javascript:ajax_link_0()" target="_self" ">Obtener datos por Ajax</a>
El código previo indica que al hacer clic sobre el link, se llama a una función Javascript llamada
“before_function”, y al recibir la respuesta del servidor, se llama a la función “after_function”. Ambas
funciones deben ser implementadas por el programador, como se muestra a continuación:
<script type="text/javascript">
53. // Handlers para JQuery
var before_function = function(req, json) {
$('#estado').html( "Cargando..." );
}
var after_function = function(json) {
var libro = json;
alert(libro.titulo +' ('+ libro.genero +')');
$('#estado').html( "" );
}
</script>
Una característica es que el código Javascript generado, dependerá de la librería Javascript que el
programador referencie, esto quiere decir que la implementación de ajax link varía según si el
programador usa Prototype o jQuery. Esto es transparente al programador.
10.3 Vistas de infraestructura (scaffolded)
De la misma forma que las acciones básicas de los controladores (list, show, create, edit, delete)
están disponibles sin necesidad de programarlas, las vistas relacionadas a las acciones list, show,
create y edit, también son generadas por el framework, sin necesidad de programarlas.
Para indicarle a Yupp que se quiere utilizar una vista particular en lugar de las vistas de
infraestructura, simplemente se debe colocar el archivo de la vista, cumpliendo las convenciones de
nombrado y ubicación. No es necesario realizar ningún tipo de configuración extra.
54. 11. Estado actual del proyecto
11.1 Hoja de ruta del proyecto
El proyecto tiene un camino marcado, con el objetivo de lograr una solución robusta, pequeña,
rápida y completa para el desarrollo de aplicaciones web sobre PHP. Ese camino ya está avanzado,
y este tutorial es prueba de ello. Para seguir el avance, y saber en qué puntos se están trabajando,
en nuestra wiki hay una “hoja de ruta”, donde se actualizan los cambios y mejoras realizadas al
framework, para cada versión del mismo.
http://code.google.com/p/yupp/wiki/Hoja_de_ruta
11.2 Información administrativa
Grupo de discusión
Donde realizar consultas, comentarios, compartir experiencias, código, recursos, etc.
http://groups.google.com/group/yuppframeworkphp
Blog
Donde se realizan anuncios y se publican temas interesantes relacionados con el framework y el
desarrollo web en general.
http://yuppframework.blogspot.com/
Twitter
Desde el escritorio del framework pueden ser accedidos los twitts relacionados con este, en general
son para hacer anuncios importantes con respecto al framework.
http://twitter.com/ppazos
Sitio del proyecto
Donde publicamos las liberaciones, las notas de las versiones, preguntas frecuentes. También aquí
están los reportes de bugs y el repositorio de código (SVN).
http://code.google.com/p/yupp/
Documentación del proyecto
Documentación de referencia para el programador.
http://www.simplewebportal.net/yupp_framework_php_doc