Give Me a Tweet, versión 1.0

Introducción.

Preparé el prototipo de esta aplicación web muy simple para experimentar con algunas librerías que tenía por revisar, que a pesar de ser muy sencillas de utilizar es bueno ir conociendo para determinar mas adelante cual de todas las disponibles es la idónea.

Esta es de manera resumida la funcionalidad del prototipo.

  • Obtiene cierta cantidad de tweets de ciertos usuarios predefinidos.
  • Los tweets son alamcenados en caché por una cantidad específica de tiempo.
  • El acceso a la página no requiere de ningún tipo de autenticación por parte del usuario.
  • Cuando el usuario accede al sitio web, el sistema elige un tweet azar y lo muestra.
  • La elección del tweet se realiza sobre los almacenados en el caché.  Si no hay caché o este es demasiado viejo, entonces se renueva automáticamente.
  • Los mensajes que no se encuentran escritos en español son traducidos automáticamente a este idioma.
  • Se prepara un enlace corto a la información del tweet.
  • Se presenta un QRCode con el enlace corto al tweet para ser fácilmente consultado por dispositivos móviles.

Herramientas.

Estas fueron las herramientas utilizadas durante el desarrollo del prototipo.

  1. Netbeans (IDE).
  2. SQLite (persistencia del caché).
  3. Blueprint CSS Framework (framework para la presentación).
  4. PHP (lenguaje de programación).
  5. Yii PHP Framework (framework de desarrollo web).
  6. Extensión de CURL para Yii (acceder al servicio REST fácilmente).
  7. API REST de Twitter (obtener los mensajes).
  8. Google Translate Service (servicio de traducción de textos).
  9. jquery-qrcode para la generación de los códigos QR.
  10. jquery-urlshortener que utiliza el servicio de bit.ly (acortador de URLs).

Prototipo.

 

Prototipo de Give Me a Tweet
Prototipo de Give Me a Tweet

Instalación.

El código fuente del protitpo puede obtenerse desde la siguiente ubicación.

https://github.com/jimezam/Give-Me-a-Tweet/tree/v1.0

Para la ejecución de la aplicación web se requiere que se cuente además de la infraestructura web, con PHP con soporte para SQLite y CURL, y la distribución del Yii PHP Framework (1.1.7 o similar) en una ubicación conocida.

Finalmente se deberán modificar los siguientes archivos para ajustarlos a la infraestructura local.

index.php:

$yii=dirname(__FILE__).’/../../yii-1.1.7.r3135/framework/yii.php’;
$config=dirname(__FILE__).’/protected/config/main.php’;

Ajustar estas rutas a la ubicación real del framework.

protected/views/tweet/show.php:

$.shortenUrl.settings.login  = ‘USUARIO‘;
$.shortenUrl.settings.apiKey = ‘LLAVE DEL API‘;

Modificar estos valores para que correspondan con la información del propietario del servicio.  Esta información se puede obtener de manera gratuita en el sitio web de bit.ly para desarrolladores.

Enlaces.

Los controladores en Yii

Introducción.

Estos se ubican entre el usuario y la aplicación.  Su función es la de controlar la comunicación entre los modelos y la vistas según la solicitud (requerimiento) que ha hecho usuario.

Su clase base es CController y en ellos se implementan Acciones (definen la lógica de la aplicación) y Filtros (establecen validaciones o controles antes y después de la ejecución de las acciones).

El usuario invoca indirectamente a los controladores especificando un ruta a través del controlador frontal o Application.

La ruta del requerimiento.

El URL solicitado determina que controlador y que acción se van a ejecutar para resolver el requerimiento del usuario.

Los URL tienen el siguiente formato.

Sin URL limpias (por defecto).

http://servidor/index.php?r=ControladorId/AcciónId

Con URL limpias.

http://servidor/ControladorId/AcciónId

Si se utilizan módulos (y URL limpias para este ejemplo).

http://servidor/MóduloId/ControladorId/AcciónId

  • El archivo fuente del controlador se ubica en protected/controllers/ControladorIdController.php.
  • El nombre de la clase allí contenida deberá ser ControladorIdController.
  • Se invoca a la acción (ver mas adelante) AcciónId.  En caso de no haberse especificado una se considera la acción por defecto del controlador, comúnmente index.

Las acciones.

Pueden implementarse de dos maneras.

  • Como métodos del mismo controlador.
  • Como clases que heredan de CAction.

Acciones implementadas como métodos del controlador.

  • El nombre del método deberá ser actionAcciónId.

En el siguiente ejemplo se muestra al controlador User que implementa la acción add como un método suyo.

class UserController extends CController
{
    public function actionAdd()
    {
        // Implementación ...
    }
}

Acciones implementadas como clases independientes.

  • Los objetos acción heredan de CAction.
  • El nombre de la clase es AcciónIdAction (por convención, no es obligatorio).
  • Se almacena en un archivo bajo la ruta protected/controllers/controladorId/AcciónIdAction.php.
  • Su ubicación puede referenciarse mediante alias de esta manera: application.controllers.controladorId.AcciónIdAction.
  • Es obligatorio sobreescribir el método run() de la acción para definir allí su implementación.

En el siguiente código se muestra la acción remove del controlador User implementada como una clase independiente.

class RemoveAction extends CAction
{
    public function run()
    {
        // Implementación ...
    }
}

Esta clase se almacena entonces en el archivo protected/controllers/user/RemoveAction.php.

Como paso final de su implementación, es necesario indicarle al controlador de la existencia de la acción.  Para hacer esto es necesario sobreescribir el método actions del controlador de la siguiente manera.

class UserController extends CController
{
   public function actions()
   {
       return array(
           'remove' => 'application.controllers.user.RemoveAction'
       );
   }
}

Los filtros.

  • Permiten realizar verificaciones y validaciones antes y después de la ejecución de las acciones.
  • Una acción puede tener asociados múltiples filtros.
  • Los filtros se ejecutan en el orden en que fueron especificados.
  • Un filtro puede abortar la ejecución de los demás filtros y de la acción misma.
  • De manera análoga a las acciones, los filtros pueden implementarse de dos maneras también.
    • Como métodos del mismo controlador.
    • Como clases que heredan de CFilter.

Filtros implementados como métodos del controlador.

  • El nombre del método debe empezar por la palabra filter.
  • Deberá recibir como parámetro a $filterChain.

En el siguiente ejemplo se muestra al controlador User que implementa al filtro checkUser como un método suyo.

class UserController extends CController
{
    public function filterCheckUser($filterChain)
    {
        // Implementación ... invocar $filterChain -> run()
        // para continuar con el próximo filtro
    }
}

Filtros implementados como clases independientes.

  • Los objetos acción heredan de CFilter.
  • El nombre de la clase es FiltroIdFilter (por convención, no es obligatorio).
  • Se almacena en un archivo bajo la ruta protected/filters/FiltroIdFilter.php.
  • Su ubicación puede referenciarse mediante alias de esta manera: application.filters.FiltroIdFilter.
  • Es obligatorio sobreescribir los métodos preFilter($filterChain) y postFilter($filterChain) del filtro para definir que hacer antes y después de ejecutar la acción.

En el siguiente código se muestra al filtro isValid del controlador User implementado como una clase independiente.

class IsValidFilter extends CFilter
{
    public $admin;

    public function preFilter($filterChain)
    {
        // Se aplica antes de ejecutarse la acción.
        // Si retorna true continúa el proceso, false lo
        // aborta y no se ejecuta la acción solicitada.

        return $exito;
    }

    public function postFilter($filterChain)
    {
        // Se aplica después de ejecutarse la acción.
    }
}

Esta clase se almacena entonces en el archivo protected/filters/IsValidFilter.php.

Como paso final de su implementación, es necesario indicarle al controlador de la existencia del filtro y determinar su alcance sobre las acciones del mismo.  Para hacer esto es necesario sobreescribir el método filters del controlador de la siguiente manera.

class UserController extends CController
{
   public function filters()
   {
       return array(
           'checkUser + add, remove',

           array(
               'application.filters.IsValidFilter - add, remove',
               'admin' => false
           )
       );
   }
}
  • checkUser es un filtro basado en un método del controlador.
  • isValid es un filtro basado en una clase externa.
  • Es posible especificar los filtros con una notación de arreglo para determinar valores específicos para los atributos del filtro.

Los operadores + y actúan como determinadores del alcance de los filtros sobre las acciones especificadas de la siguiente manera.

  • + determina exactamente a cuales acciones se les debe aplicar el filtro.  De esta manera, el filtro checkUser se aplicará a las acciones add y remove únicamente.
  • determina a cuales acciones NO se les debe aplicar el filtro.  Así, el filtro isValid se aplicará a todas las acciones EXCEPTO a add y remove.
  • Si no se especifica ninguno de los dos modificadores, el filtro aplicará a todas las acciones del controlador.

Enlaces.

Experimentando con Yii y DAO: manipulando formularios y registros simples

Introducción.

El día de hoy amanecí con ganas de seguir experimentando con Yii.  Al ya tener documentado el acceso a la base de datos a través de DAO y todo el manejo de los formularios es hora de probar la teoría con algo un poco mas elaborado.

El experimento de hoy consiste en crear una tabla de Usuarios (nombre de usuario, contraseña y edad) y un formulario que permita su administración (agregar, editar, listar y remover), utilizando por supuesto formularios basados en CFormModel y el acceso a la base de datos con CDbCommand.

Screenshot_0.1
Vista del formulario de la versión 0.1

Instalación preliminar.

Mi equipo utiliza Linux Ubuntu 9.04 por lo que algunos procedimientos son particulares al estilo *nix, en especial los relacionados con la instalación de paquetes y el manejo de la línea de comando, y será necesario realizar ajustes específicos si se desea utilizarlos en Windows.

  • Realicé la instalación de Apache y PHP (con soporte para SQLite 3.x) de acuerdo con los pasos establecidos anteriormente.
  • Realicé la instalación del framework de Yii versión 1.0.7 sin modificaciones significativas a lo establecido anteriormente.  La estructura inicial de directorios es la siguiente.

/home/web              Directorio base de los archivos relacionados con web.

/yii               Directorio de distribuciones de Yii.

/1.0.7         Distribución 1.0.7 de Yii.

/current       Enlace dinámico a la distribución mas reciente de Yii.

/public Directorio público (Document Root).

/YiiDaoDemo    Directorio base de la aplicación de prueba.

Para facilidad en el acceso de la aplicación yiic se realizó también el siguiente ajuste al final ~/.profile.

PATH=$PATH:/home/web/yii/current/framework
export PATH

Creación de los archivos base de la aplicación.

$ cd /home/web/public

$ yiic webapp YiiDaoDemo

Create a Web application under ‘/home/web/public/YiiDaoDemo’? [Yes|No] Yes
mkdir /home/web/public/YiiDaoDemo
mkdir /home/web/public/YiiDaoDemo/protected
mkdir /home/web/public/YiiDaoDemo/protected/views
mkdir /home/web/public/YiiDaoDemo/protected/views/system
generate protected/views/system/.yii
mkdir /home/web/public/YiiDaoDemo/protected/views/layouts
generate protected/views/layouts/main.php
mkdir /home/web/public/YiiDaoDemo/protected/views/site
generate protected/views/site/login.php
generate protected/views/site/contact.php
generate protected/views/site/index.php
generate protected/yiic.bat
mkdir /home/web/public/YiiDaoDemo/protected/controllers
generate protected/controllers/SiteController.php
generate protected/yiic.php
mkdir /home/web/public/YiiDaoDemo/protected/components
mkdir /home/web/public/YiiDaoDemo/protected/components/views
generate protected/components/views/mainMenu.php
generate protected/components/UserIdentity.php
generate protected/components/MainMenu.php
mkdir /home/web/public/YiiDaoDemo/protected/runtime
generate protected/runtime/.yii
mkdir /home/web/public/YiiDaoDemo/protected/messages
generate protected/messages/.yii
mkdir /home/web/public/YiiDaoDemo/protected/config
generate protected/config/main.php
generate protected/config/console.php
mkdir /home/web/public/YiiDaoDemo/protected/models
generate protected/models/LoginForm.php
generate protected/models/ContactForm.php
mkdir /home/web/public/YiiDaoDemo/protected/extensions
generate protected/extensions/.yii
generate protected/.htaccess
mkdir /home/web/public/YiiDaoDemo/protected/commands
mkdir /home/web/public/YiiDaoDemo/protected/commands/shell
generate protected/commands/shell/.yii
generate protected/yiic
mkdir /home/web/public/YiiDaoDemo/themes
mkdir /home/web/public/YiiDaoDemo/themes/classic
mkdir /home/web/public/YiiDaoDemo/themes/classic/views
mkdir /home/web/public/YiiDaoDemo/themes/classic/views/system
generate themes/classic/views/system/.yii
mkdir /home/web/public/YiiDaoDemo/themes/classic/views/layouts
generate themes/classic/views/layouts/.yii
generate themes/classic/views/.htaccess
mkdir /home/web/public/YiiDaoDemo/themes/classic/views/site
generate themes/classic/views/site/.yii
mkdir /home/web/public/YiiDaoDemo/css
generate css/bg.gif
generate css/form.css
generate css/main.css
mkdir /home/web/public/YiiDaoDemo/images
generate images/.yii
generate index.php
mkdir /home/web/public/YiiDaoDemo/assets
generate assets/.yii

Your application has been created successfully under /home/web/public/YiiDaoDemo.

Creación y configuración de la base de datos.

$ mkdir -p YiiDaoDemo/protected/data

$ cd YiiDaoDemo/protected

$ vi data/script.sql

CREATE TABLE usuario
(
 username        CHAR(16)        NOT NULL,
 contrasena      CHAR(32)        NOT NULL,
 edad            INT             ,

 PRIMARY KEY(username)
);

$ sqlite3 data/basededatos.db < data/script.sql

$ chown -R www-data data

$ ls -l data

-rw-r–r– 1 www-data jimezam 3072 2009-07-20 15:16 basededatos.db
-rw-r–r– 1 www-data jimezam  126 2009-07-20 15:16 script.sql

$ vi config/main.php

return array(
    // ...
    // application components
    'components'=>array(
        //...
        'db'=>array(
            'connectionString'=>'sqlite:protected/data/basededatos.db',
        ),
        //...

Creación del modelo para el formulario (UsuarioForm).

  • Al ser un modelo para manejar la información proveniente de informulario, hereda de CFormModel.
    class UsuarioForm extends CFormModel
    {
        // ...
    }
  • Se almacena en el archivo YiiDaoDemo/protected/models/UsuarioForm.php.
  • Incluye los siguientes atributos de acuerdo con su contenido.
    public $username;     /* Nombre de usuario */
    public $contrasena;   /* Contraseña del usuario */
    public $contrasena2;  /* Confirmación de la contraseña */
    public $edad;         /* Edad del usuario */
  • Se definen las siguientes etiquetas para cada uno de los atributos descritos anteriormente.
    public function attributeLabels()
    {
        return array(
            'username'    => 'Nombre de usuario',
            'contrasena'  => 'Contraseña',
            'contrasena2' => 'Contraseña (repetir)',
            'edad'        => 'Edad'
        );
    }
  • Se establecen las restricciones de validación para cada uno de los atributos.
    public function rules()
    {
        return array (
            array('username, contrasena, contrasena2, edad', 'required'),
            array('edad', 'numerical', 'integerOnly' => true),
            array('contrasena', 'compare', 'compareAttribute' => 'contrasena2'),
            array('username', 'length', 'max' => 16),
            array('contrasena', 'length', 'max' => 16),
            array('contrasena2', 'length', 'max' => 16)
        );
    }

Creación de la vista del formulario.

  • Será manejada por el modelo UsuarioForm.
  • Contiene dos partes generales.
    1. Un formulario que permite ingresar la información del Usuario para después agregarlo o editar su contenido de la base de datos.
    2. Un listado de los usuarios registrados en la base de datos desde el cual es posible remover los usuarios marcados o editar la información del usuario elegido.
  • Es la única vista de la aplicación así que será la vista de la acción index del controlador (AdminUsuario) a definirse próximamente.
  • Se almacena en el archivo YiiDaoDemo/protected/views/adminUsuario/index.php.
  • En la parte superior se muestran los mensajes flash provenientes de otras acciones.  Esto es útil para mostrar mensajes de error o éxito como “registro insertado”.
    <h1>Administración de usuarios</h1>
    
    <?php if(Yii::app() -> user -> hasFlash('mensajeEstado')):?>
    <div style="color: #0000FF">
     <?php echo Yii::app() -> user -> getFlash('mensajeEstado'); ?>
    </div>
    <?php endif; ?>
  • La primera parte del formulario permite manipular la información del Usuario.
    <h2>Datos del usuario</h2>
    
    <!-- Abre el formulario especificando su ACTION y METHOD -->
    <?php echo CHtml::beginForm(CHtml::normalizeUrl(array('adminUsuario/index')), 'post'); ?>  
    
    <div class='formulario'>  
    
     <!-- Muestra los mensajes de error asociados al modelo UsuarioForm -->
     <!-- El objeto $usuarioForm es enviado desde el controlador -->
     <?php echo CHtml::errorSummary($usuarioForm); ?>  
    
     <!-- Se genera el formulario con las etiquetas y valores asociados al modelo UsuarioForm -->
     <table>
     <tr>
         <td><?php echo CHtml::activeLabel($usuarioForm, 'username'); ?></td>
         <td><?php echo CHtml::activeTextField($usuarioForm, 'username'); ?> </td>
     </tr>
     <tr>
         <td><?php echo CHtml::activeLabel($usuarioForm, 'contrasena'); ?></td>
         <td><?php echo CHtml::activePasswordField($usuarioForm, 'contrasena'); ?></td>
     </tr>
     <tr>
         <td><?php echo CHtml::activeLabel($usuarioForm, 'contrasena2'); ?></td>
         <td><?php echo CHtml::activePasswordField($usuarioForm, 'contrasena2'); ?></td>
     </tr>
     <tr>
         <td><?php echo CHtml::activeLabel($usuarioForm, 'edad'); ?></td>
         <td><?php echo CHtml::activeTextField($usuarioForm, 'edad'); ?></td>
     </tr>
     </table>
    
     <br />
    
     <!-- Genera el botón de enviar el cual cumplirá funciones de Agregar y Editar -->
     <!-- según exista o no el registro en la base de datos -->
     <div class='acciones'>
         <?php echo CHtml::submitButton('Enviar', array('id' => 'enviar')); ?>
     </div>  
    
    </div>  
    
    <!-- Cierra el formulario -->
    <?php echo CHtml::endForm(); ?>
  • La segunda parte muestra un listado de los usuarios registrados en la base de datos con botones para las siguientes acciones.
    • Remover: eliminar de la base de datos a los usuarios cuyos registros se encuentren señalados con la marca.
    • Editar: editar la información del usuario correspondiente a la fila elegida.
      <h2>Listado de usuarios registrados</h2>
      
      <!-- Abre el formulario especificando su ACTION y METHOD -->
      <?php echo CHtml::beginForm(CHtml::normalizeUrl(array('adminUsuario/editar')), 'post'); ?>
      
      <!-- Crea un campo oculto para guardar el ID del registro a editarse -->
      <?php echo CHtml::hiddenField('UsuarioForm[elemento]', '', array()); ?>
      
      <!-- Genera el listado de los usuarios registrados -->
      <table style='border: 1px solid #FF0000;'>
      <tr style='background-color: pink; text-align: center; font-weight: bold;'>
          <td>Marcas</td>
          <td>Username</td>
          <td>Edad</td>
          <td>Opciones</td>
      </tr>
      
      <!-- El listado de $usuarios es enviado por el controlador a la vista -->
      <?php $i = 0; ?>
      <?php foreach($usuarios as $usuario): ?>
      <tr>
          <!-- Crea el checkbox marca[$i], no seleccionado y cuyo valor es el nombre del usuario asociado -->
          <td style='text-align: center;'>
              <?= CHtml::checkBox("UsuarioForm[marca][{$i}]", false, array('value' => $usuario['username'])); ?>
          </td>
      
          <!-- Nombre del usuario -->
          <td><?= $usuario['username']; ?></td>
      
          <!-- Edad del usuario -->
          <td style='text-align: center;'><?= $usuario['edad']; ?></td>
      
          <!-- Botón de editar del usuario asociado -->
          <!-- Al presionarse actualiza el valor del campo oculto 'elemento' con el -->
          <!-- el nombre del usuario asociado para la edición -->
          <td><?= CHtml::submitButton('Editar', array(
              'onClick' => "document.getElementById('UsuarioForm_elemento').value = '{$usuario['username']}'"
          )); ?></td>
      </tr>
      <?php $i++; ?>
      <?php endforeach; ?>
      </table>
      
      <br />
      
      <!-- Botón para remover los usuarios seleccionados -->
      <!-- Al presionarse actualiza el ACTION del formulario para ser manejado por la -->
      <!-- acción correcta del controlador -->
      <?= CHtml::submitButton('Remover', array(
          'onClick' => "document.getElementById('formListado').action = '" . CHtml::normalizeUrl(array('adminUsuario/remover')) . "'"
      )); ?>
      
      <!-- Cierra el formulario -->
      <?php echo CHtml::endForm(); ?>

Creación del modelo para el usuario.

  • Al ser un modelo para manejar la persistencia de los datos, hereda de CActiveRecord.  Debe tenerse en cuenta que en este experimento en particular se utilizarán los métodos de  DAO en lugar de las facilidades que el ActiveRecord provee directamente.
    class Usuario extends CActiveRecord
    {
        // ...
    }
  • Se almacena en el archivo YiiDaoDemo/protected/models/Usuario.php.
  • Se definen cuales atributos pueden ser asignados de manera automática.
    public function safeAttributes()  
    {  
        return 'username, contrasena, edad';  
    }
  • Se implementa el agregar el usuario a la base de datos mediante un INSERT de SQL.
       	public function agregar()
    	{
    		$sql = "INSERT INTO Usuario(username, contrasena, edad) " .
    		       "VALUES (:username, :contrasena, :edad)";
    
    		$comando = Yii::app() -> db -> createCommand($sql);
    
    		$comando -> bindParam(":username", $this -> username, PDO::PARAM_STR);
    		$comando -> bindParam(":contrasena", $this -> contrasena, PDO::PARAM_STR);
    		$comando -> bindParam(":edad", $this -> edad, PDO::PARAM_INT);
    
    		$control = $comando -> execute();
    
    		return ($control > 0);
    	}
  • Se implementa el editar el usuario de la base de datos mediante un UPDATE de SQL.
    	public function actualizar()
    	{
    		$sql = "UPDATE Usuario SET contrasena=:contrasena,edad=:edad " .
    		       "WHERE username=:username";
    
    		$comando = Yii::app() -> db -> createCommand($sql);
    
    		$comando -> bindParam(":username", $this -> username, PDO::PARAM_STR);
    		$comando -> bindParam(":contrasena", md5($this -> contrasena), PDO::PARAM_STR);
    		$comando -> bindParam(":edad", $this -> edad, PDO::PARAM_INT);
    
    		$control = $comando -> execute();
    
    		return ($control > 0);
    	}
  • Se implementa el remover el usuario de la base de datos mediante un DELETE de SQL.
    	public function remover()
    	{
    		$sql = "DELETE FROM Usuario WHERE username=:username";
    
    		$comando = Yii::app() -> db -> createCommand($sql);
    
    		$comando -> bindParam(":username", $this -> username, PDO::PARAM_STR);
    
    		$control = $comando -> execute();
    
    		return ($control > 0);
    	}
  • Se implementa el recuperar el usuario de la base de datos mediante un SELECT de SQL.
            public function recuperar()
    	{
    		$sql = "SELECT * FROM usuario WHERE username = :username";
    
    		$comando = Yii::app() -> db -> createCommand($sql);
    
    		$comando -> bindParam(":username", $this -> username, PDO::PARAM_STR);
    
    		$fila = $comando -> queryRow();
    
    		if($fila === false)
    			return false;
    
    		$this -> username   = $fila['username'];
    		$this -> contrasena = $fila['contrasena'];
    		$this -> edad       = $fila['edad'];
    
    		return true;
           }
  • De manera similar se implementa el existe que verifica si el usuario, identificado por su nombre de usuario, ya se encuentra registrado en la base de datos o no.
    	public function existe()
    	{
    		$sql = "SELECT username FROM usuario WHERE username = :username";
    
    		$comando = Yii::app() -> db -> createCommand($sql);
    
    		$comando -> bindParam(":username", $this -> username, PDO::PARAM_STR);
    
    		return ($comando -> queryScalar() == $this -> username);
    	}
  • Se implementa el método estático listarTodos que retorna el listado completo de los usuarios registrados en la base de datos.
    	public static function listarTodos()
    	{
    		$sql = "SELECT * FROM usuario";
    
    		return Yii::app() -> db -> createCommand($sql) ->  query();
    	}

Creación del controlador.

  • Se almacena en el archivo YiiDaoDemo/protected/controllers/AdminUsuarioController.php.
  • Se definen tres acciones diferentes.
    • index: maneja el agregar y editar registros (primer FORM).
    • editar: maneja el presionar el botón de editar para mostrar la información del usuario en el formulario (segundo FORM).
    • remover: maneja el presionar el botón de remover para eliminar de la base de datos a los usuarios seleccionados (segundo FORM).
      class AdminUsuarioController extends CController
      {
          // ...
      }
  • Acción editar: sucede cuando el usuario presiona el botón Editar frente a uno de los registros de los usuarios.  El sistema debe responder mostrando los datos del usuario seleccionado en el primer formulario.
    	public function actionEditar()
    	{
                    /* Verificar que se cuente con información del usuario (request) */
    
    		if(!isset($_POST['UsuarioForm']))
    		{
    			Yii::app() -> request -> redirect(CHtml::normalizeUrl(array('adminUsuario/index')));
    			exit;
    		}
    
                    /* Se obtiene el valor del 'elemento' que deberá tener el nombre del usuario a editar */
    
    		$elemento = $_POST['UsuarioForm']['elemento'];
    
                    /* Se intenta recuperar el usuario solicitado */
    
    		$usuario = new Usuario();
    		$usuario -> username = $elemento;
    		$control = $usuario -> recuperar();
    
    		$usuarioForm = new UsuarioForm();
    
                    /* Si se tiene éxito, se pasa la información del Usuario al UsuarioForm
                       para ser mostrado en la vista */
    
    		if($control)
    		{
    			$usuarioForm -> attributes = array(
    				'username' => $usuario -> username,
    				'edad' => $usuario -> edad
    			);
    		}
    		else
    		{
                            /* Si se fracasa, se muestra en la vista el nombre de usuario y un mensaje flash */
    
    			$usuarioForm -> username = $usuario -> username;
    
    			$usuarioForm -> addError('username', 'El usuario elegido no existe en la base de datos');
    		}
    
                    /* Se muestra la vista del formulario con la información recuperada y el listado
                       de usuarios registrados */
    
    		$this -> render('index',
    			array(
    				'usuarioForm' => $usuarioForm,
    				'usuarios' => Usuario::listarTodos()
    			));
    	}
  • Acción remover: sucede cuando el usuario presiona el botón de remover usuarios en la parte inferior.  El sistema debe responder eliminando de la base de datos a los usuarios seleccionados.
    	public function actionRemover()
    	{
                    /* Verifica que se cuente con información del usuario (request),
                       de lo contrario, redirecciona a la página inicial */
    
    		if(!isset($_POST['UsuarioForm']))
    		{
    			Yii::app() -> request -> redirect(CHtml::normalizeUrl(array('adminUsuario/index')));
    			exit;
    		}
    
                    /* Obtiene las marcas del formulario, los valores de las marcas corresponden a los
                       los nombres de los usuarios a remover */
    
    		$usuarios = array_values($_POST['UsuarioForm']['marca']);
    
    		$total = 0;
    		$borrados = 0;
    
    		foreach($usuarios as $username)
    		{
                            /* Crea un nuevo usuario con el nombre de usuario a remover */
    
    			$usuario = new Usuario();
    			$usuario -> username = $username;
    
                            /* Ejecuta su remoción */
    
    			if($usuario -> remover())
    				$borrados ++;
    
    			$total ++;
    		}
    
                    /* Prepara un mensaje flash con el estado final de la remoción */
    
    		Yii::app() -> user -> setFlash('mensajeEstado', "Se removieron {$borrados} de {$total} registros.");
    
                    /* Redirecciona a la página inicial de la aplicación */
    
    		Yii::app() -> request -> redirect(CHtml::normalizeUrl(array('adminUsuario/index')));
    		exit;
    	}
  • Acción index: sucede cuando la aplicación es invocada por primera vez, el sistema debe responder mostrando el formulario vacío.  También es invocada cuando el usuario presiona el botón Enviar para editar o agregar un nuevo usuario a la base de datos, el sistema debe responder realizando la acción solicitada.
    	public function actionIndex()
    	{
    		$usuarioForm = new UsuarioForm();
    
                    /* Si se cuenta con información del usuario (request) entonces
                       se agregará un nuevo usuario o se realizará una edición del mismo */
    
    		if(isset($_POST['UsuarioForm']))
    		{
                            /* Carga el modelo con la información del formulario */
    
    			$usuarioForm -> attributes = $_POST['UsuarioForm'];
    
                            /* Verifica que su información sea válida */
    
    			if($usuarioForm -> validate())
    			{
                                    /* Crea un Usuario con su información */
    
    				$usuario = new Usuario();
    
    				$usuario -> attributes = $_POST['UsuarioForm'];
    
    				try
    				{
    					$control = false;
    
                                            /* Si el usuario existe ... */
    
    					if(!$usuario -> existe())
    					{
                                                    /* Se intenta agregarlo */
    
    						$control = $usuario -> agregar();
    					}
    					else
    					{
                                                    /* De lo contrario, se intenta actualizar */
    
    						$control = $usuario -> actualizar();
    					}
    
                                            /* Si la acción tuvo éxito ... */
    
    					if($control)
    					{
                                                    /* Muestra un mensaje flash de éxito */
    
    						Yii::app() -> user -> setFlash('mensajeEstado', 'El registro fue incorporado exitosamente ' .
    						                                                'en la base de datos');
    
                                                    /* Limpia los valores del formulario */
    
    						$usuarioForm = new UsuarioForm();
    					}
    					else
    					{
                                                    /* Si falló ... muestra un mensaje flash de fracaso */
    
    						$usuarioForm -> addError('username', 'El registro no pudo ser incorporado en ' .
    						                                     'la base de datos, inténtelo nuevamente');
    					}
    				}
    				catch (Exception $e)
    				{
                                            /* Si se lanzó una excepción durante el proceso, esta se muestra y
                                               considera como un error */
    
    					$usuarioForm -> addError('username', $e -> getMessage());
    				}
    			}
    		}
    
                    /* Se muestra la vista del formulario con la información recuperada y el listado
                       de usuarios registrados */
    
    		$this -> render('index',
    		                array(
    					'usuarioForm' => $usuarioForm,
    					'usuarios' => Usuario::listarTodos()
    				));
    	}

Conclusiones.

  • A pesar de que Yii parece ser un framework muy interesante para el desarrollo con PHP aún no me siento cómodo con él.
  • Aún quedan varios temas para revisar y experimentar de Yii con los que espero se aclare un poco mas el panorama.
  • El acceso a la base de datos mediante los objetos DAO es muy claro y fácil de implementar.
  • Para la implementación del CRUD es mas conveniente utilizar el ActiveRecord que se estará analizando próximamente y utilizar el DAO para las consultas demasiado complejas.
  • En este, mi tercer programita en Yii, se aclararon varias dudas que tenía, sin embargo persisten otras, en particular esta: cómo limpio/filtro la información proveniente del usuario ?

Enlaces.

public function agregar()
{
$sql = “INSERT INTO Usuario(username, contrasena, edad) ” .
“VALUES (:username, :contrasena, :edad)”;

$comando = Yii::app() -> db -> createCommand($sql);

$comando -> bindParam(“:username”, $this -> username, PDO::PARAM_STR);
$comando -> bindParam(“:contrasena”, $this -> contrasena, PDO::PARAM_STR);
$comando -> bindParam(“:edad”, $this -> edad, PDO::PARAM_INT);

$control = $comando -> execute();

return ($control > 0);
}

Data Access Objects (DAO) con Yii

Introducción.

Establecer la conexión.

De manera explícita.

  • Se basa en la creación de un objeto CDbConnection, la especificación de un Data Source Name (DSN) y la información de autenticación del usuario de conexión si es necesaria.
    $conexion = CDbConnection($DSN, $username, $password);
    $conexion -> active = true;        // Abrir la conexión, se lanzan excepciones en error.
    // ...
    $conexion -> active = false;       // Cerrar la conexión.
  • El formato del DSN depende del módulo de PDO utilizado.
    1. SQLite: sqlite:/ruta/al/archivo/sqlite
    2. MySQL: mysql:host=<HOST>;dbname=<NAME>
    3. PostgreSQL: pgsql:host=<HOST>;port=<PORT [5432]>;dbname=<NAME>
    4. MSSQL: mssql:host=<HOST>;dbname=<NAME>
    5. Oracle: oci:dbname=//<HOST>:<PORT [1521]>/<NAME>

De manera automática.

  • La clase CDbConnection es un CApplicationComponent por lo que puede ser configurado como un componente de aplicación.
  • Para hacer esto es necesario modificar la configuración de la aplicación (protected/config/main.php) de la siguiente manera para agregar al componente db.
    array (
        // ...
        'components' => array (
            'db' => array (
                'class' => 'CDbConnection',
                'connectionString' => 'mysql:host=database.server.com;dbname=demo_db',
                'username' => 'demo_user',
                'password' => 'demo_pass'
            )
        )
    );
  • De esta manera la conexión se realiza de manera automática y puede accederse mediante Yii::app() -> db.

Ejecutar sentencias.

  • Es necesario crear un CDbCommand a través de CDbConnection::createCommand().
    $comando = $conexion -> createCommand($sql);
    // $comando -> text = $nuevoSQL;              // Actualiza el SQL de un comando
  • La sentencia puede ejecutarse de dos maneras.
    1. execute() para sentencias de modificación como INSERT, UPDATE y DELETE.  En éxito retorna el número de registros afectados.
    2. query() para sentencias de consulta (SELECT).  En éxito retorna un CDbDataReader.
  • Existen otros métodos mas específicos del estilo queryXXX() que facilitan la obtención de los resultados.
    1. $filas = $comando -> queryAll().   Consulta y retorna inmediatamente todas las filas del resultado.
    2. $fila = $comando -> queryRow().   Consulta y retorna inmediatamente la primera fila del resultado.
    3. $columna = $comando -> queryColumn().   Consulta y retorna inmediatamente la primera columna del resultado.
    4. $valor = $comando -> queryScalar().   Consulta y retorna inmediatamente el valor de la primera fila y primera columna del resultado.

Obtener los resultados.

  • Si se utilizó un método del estilo queryXXX() el valor obtenido como resultado es retornado inmediatamente.
  • Si se utilizó el método query(), los resultados pueden obtenerse de tres maneras.
    1. Mediante múltiples llamados a CDataReader::read().
      $dataReader = $comando -> query();
      while(($fila = $dataReader -> read()) !== false)
      {
          // ...
      }
    2. Mediante la construcción foreach la cual extrae una fila por cada iteración.
      foreach($dataReader as $fila)
      {
          // ...
      }
    3. Obtiene inmediatamente un arreglo con todas las filas del resultado.
      $filas = $dataReader -> readAll();

Utilizar transacciones.

  • Se basan en la manipulación de un objeto de clase CDbTransaction.
$transaccion = $conexion -> beginTransaction();           // Inicio de la transacción.
try
{
    $conexion -> createCommand($sql) -> execute();        // Ejecuta las sentencias SQL.
    // ...
    $transaccion -> commit();                             // Almacena los cambios realizados en los datos.
}
catch(Exception $e)                                       // Captura cualquier excepción sucedida.
{
    $transaccion -> rollBack();                           // En error se cancela la transacción y deshacen sus cambios.
}

Parametrizar consultas.

  • Son útiles para evitar la inyección de SQL y para aumentar el rendimiento en la ejecución de consultas recurrentes.
  • Se basa en la ubicación de marcas en el código SQL que después son reemplazadas por valores dinámicamente.
  • Las marcas pueden tener un nombre (etiquetas únicas como :nombre y :contrasena) o ser anónimas (representadas por ?).
  • Para reemplazar las marcas por los valores finales pueden utilizarse los siguientes métodos.
    1. CDbCommand::bindParam().  Relaciona la marca con una referencia a la variable PHP.  Para valores grandes esta opción es preferible en términos de desempeño.
    2. CDbCommand::bindValue().  Relaciona la marca con el valor de una varible PHP.
  • El reemplazo de las marcas debe hacerse antes de que la consulta sea ejecutada.
$sql = "INSERT INTO usuario(nombre, contrasena) VALUES(:username, :password)";
$comando = $conexion -> createCommand($sql);

// Agregar el primer usuario.
$comando -> bindParam(":username", $nombre1, PDO::PARAM_STR);
$comando -> bindParam(":password", $contrasena1, PDO::PARAM_STR);
$comando -> execute();

// Agregar el segundo usuario.
$comando -> bindParam(":username", $nombre2, PDO::PARAM_STR);
$comando -> bindParam(":password", $contrasena2, PDO::PARAM_STR);
$comando -> execute();

// ...

Las constantes que determinan el tipo del valor a reemplazarse por la marca pueden consultarse en la sección de Constantes Predefinidas de PDO en el manual de PHP.

Asociar columnas.

  • Yii provee una facilidad adicional para la manipulación de resultados provenientes de la base de datos.
  • Consiste en relacionar variables con las columnas del resultado así se actualizarán de manera automática en cada iteración que obtenga una nueva fila de datos.
$sql = "SELECT nombre, contrasena FROM usuario";
$dataReader = $conexion -> createCommand($sql) -> query();

// Asociar las variables $username y $password a las columnas uno y dos del resultado.
$dataReader -> bindColumn(1, $username);
$dataReader -> bindColumn(2, $password);

while($dataReader -> read() !== false)
{
    // $username y $password tendrán el valor de cada fila
    // del resultado en cada una de las iteraciones.
}

Enlaces.

Modelos de formularios con Yii (formulario y acción)

Introducción.

Le etapa final de la implementación de la aplicación basada en formularios web corresponde con la implementación del formulario (vista) y de la acción del controlador que la va a procesar.

Definición de las etiquetas del formulario.

  • Permite definir desde el modelo las etiquetas que acompañarán a cada uno de los atributos del mismo.
  • Es preferible este método en lugar de escribirlos manualmente en la vista ya que permite modificarlos según sucedan ciertas situaciones.  Por ejemplo, resaltarlos en rojo (mediante CSS) cuando la validación arroja un valor en un campo específico.
  • Para definirlas es necesario sobreescribir el método attributeLabels() del modelo.  Por defecto se retornan los mismos nombres de los atributos.
public function attributeLabels()
{
    return array(
        'value1'   => 'Operando #1',
        'value2'   => 'Operando #2',
        'operator' => 'Operación'
    );
}

Creación del formulario.

  • Yii provee algunos ayudantes para la creación y estandarización de código HTML mediante la clase CHtml como CHtml::textField, CHtml::dropDownList, CHtml::beginForm y CHtml::endForm.
<div class='formulario'>
    <?php echo CHtml::beginForm(); ?>
    <?php echo CHtml::errorSummary($calc); /* $calc es una referencia al modelo */ ?>

    <div class='operacion'>
        <div class='operando'>
            <?php echo CHtml::activeLabel($calc, 'value1'); ?>
            <?php echo CHtml::activeTextField($calc, 'value1'); ?>
        </div>
        <div class='operador'>
            <?php echo CHtml::activeLabel($calc, 'operator'); ?>
            <?php echo CHtml::activeTextField($calc, 'operator'); ?>
        </div>
        <div class='operando'>
            <?php echo CHtml::activeLabel($calc, 'value2'); ?>
            <?php echo CHtml::activeTextField($calc, 'value2'); ?>
        </div>
</div>
<div class='acciones'>
    <?php echo CHtml::submitButton('Ejecutar'); ?>
</div>

<?php echo CHtml::endForm(); ?>
</div>

Creación de la acción.

  • Contiene la lógica del controlador que manipula los modelos de acuerdo al requerimiento recibido por parte del usuario.
public function actionCalculate()
{
    $calc = new CalculatorForm();

    if(isset($_POST['CalculatorForm']))                         /* If the form was submitted */
    {
        $calc -> attributes = $_POST['CalculatorForm'];         /* Populate the form with POST data */

        if($calc -> validate())                                 /* Validate the input data */
            // ...                                              /* On success do something */

        $this -> render('result', array('calc' => $calc));      /* Render a view */
    }
}

Enlaces.

Modelos de formularios con Yii (validación)

Introducción.

Después de poblado el modelo con la información proporcionada por el usuario a través del formulario, la validación verifica que la información del modelo sea correcta mediante la aplicación de un conjunto de reglas definidas específicamente para cada modelo.

Especificación de reglas.

  • Para especificar las reglas de validación de un modelo es necesario sobreescribir el método rules().
    public function rules()
    {
        return array(
            array('value1, value2, operator', 'required'),
            array('value1, value2', 'numerical')
        );
    }
  • El método rules() retorna un arreglo cuyos elementos corresponden con la siguiente especificación.
    array('<lista de atributos>', '<validador>', 'on' => '<lista de escenarios>', ... <opciones adicionales>)
    • El listado de atributos corresponde con los nombres separados por comas de los atributos a los cuales se les aplica la regla de valildación.
    • El nombre del validador determina el tipo de verificación que se debe realizar sobre los atributos.
    • El parámetro on (opcional) permite especificar en cuales escenarios (lista separada por comas) aplica la regla.
    • Las opciones adicionales son parejas nombre/valor que permiten asignar valores iniciales a las propiedades del validador.

Tipos de validadores.

Existen tres tipos de formas como es posible especificar las reglas de validación.

  1. Como un método del modelo.
  • La firma del método deberá corresponder con la siguiente.
    /**
     * @param string El nombre del atributo que será validado.
     * @param array Opciones adicionales especificadas en la regla de validación.
     */
    public function NombreValidador($atributo, $parametros)
  1. Como una clase que hereda de CValidator.
  2. Como un alias de un validador predefinido.

Validadores predefinidos.

Corresponden a alias mas cortos y sencillos de los validadores mas utilizados sobre los atributos.

Alias Clase Descripción
captcha CCaptchaValidator Debe ser igual al código de verificación mostrado en el CAPTCHA.
compare CCompareValidator Es igual a otro o a una constante.
email CEmailValidator Es una dirección de correo electrónico válida.
default CDefaultValueValidator Asigna un valor por defecto.
exist CExistsValidator Existe en una columna específica de la tabla.
file CFileValidator Contiene el nombre de un archivo subido al servidor.
filter CFilterValidator Transforma al atributo con el filtro.
in CRangeValidator Pertenece a una lista predefinida de valores.
length CStringValidator Su longitud se encuentra dentro de cierto rango específico.
match CRegularExpressionValidator Coincide con la expresión regular especificada.
numerical CNumberValidator Es un valor numérico.
required CRequiredValidator No permite valores vacíos.
type CTypeValidator Verifica que sea del tipo especificado.
unique CUniqueValidator Es único en la columna de la tabla en la base de datos.
url CUrlValidator Es una URL válida.

A continuación se muestras algunos ejemplos de estos validadores.

/* Se requiere un valor Username, no puede dejarse vacío */
array('username', 'required'),

/* Username deberá tener entre 3 y 12 carácteres */
array('username', 'length', 'min' => 3, 'max' => 12),

/* En el escenario 'registro', password deberá coincidir con password2 */
array('password', 'compare', 'compareAttribute' => 'password2', 'on' => 'registro'),

/* En el escenario 'login', password deberá ser autenticado (validador de método [1] o de clase [2]) */
array('password', 'authenticate', 'on' => 'login')

Ejecución de la validación.

  • Es posible forzar a la validación manualmente mediante el llamado al método CModel::validate().  El llamado a métodos como CActiveRecord::save() lanzan automáticamente la validación.
  • El retorno del método CModel::validate() se utiliza para verificar si la validación fue exitosa o si falló alguna de las reglas.
  • Es posible (opcional) definir un escenario para la ejecución de la validación.  En caso de especificarse se tomarán en cuenta las reglas que NO tienen definido un escenario (parámetro on) y aquellas cuyo escenario coincida con el definido para la ejecución de la validación.  En caso de no especificarse un escenario, se tomarán en cuenta únicamente las primeras reglas, las que NO tienen definido un escenario.
$modelo -> scenario = 'registro';

if($modelo -> validate())
{
    // Éxito en la validación
}
else
{
    // Fracaso en la validación
}
  • Es posible obtener mayor información del validador mediante los siguientes métodos que pueden ser aplicados globalmente para todos los atributos o a uno en específico.
    • CModel::hasErrors().  Verifica si hubo o no errores en la validación.
    • CModel::getErrors().  Obtiene los mensajes de error generados durante la validación.

Enlaces.

Modelos de formularios con Yii (definición y asignación de atributos)

Introducción.

En Yii existen dos tipos de modelos: los basados en información que proviene de un formulario (form model) la cual se va obtener, manipular y desechar, y la proveniente de una base de datos (active record model) la cual provee las operaciones de CRUD sobre los datos.  En este artículo estaré haciendo referencia al primero de los modelos, el relacionado con la manipulación de información proveniente del usuario a través de un formulario.

El procedimiento típico para el desarrollo de las aplicaciones basadas en formularios es el siguiente.

  1. Crear un modelo que represente en sus campos a los datos que van a ser manipulados.
  2. Crear un controlador con sus respectivas acciones que respondan a los requerimientos del usuario.
  3. Crear una vista con el/los formulario/s asociados a las acciones.

Las acciones generales que es posible definir en un modelo basado en formularios son siguientes.

  • Definición.
  • Asignación de atributos (datos).
  • Validación.
  • Implementación de la acción.
  • Creación del formulario.

Definición.

  • Los modelos basados en formularios heredan de CFormModel.
  • Sus atributos definen los datos que van a ser almacenados del formulario.
  • Si se especifican valores por defecto para los atributos su estado se reflejará en las vistas correspondientes a los formularios.
class CalculatorForm extends CFormModel
{
    public $value1;
    public $value2;
    public $operator = '+';    // Default value.
}

Asignación de datos.

Es posible especificar valores para los atributos del modelo de dos maneras.

  1. Asignación individual.
  2. Asignación general.

Asignación individual.

  • Hace referencia al acceso directo de los atributos.
  • Se debe utilizar cuando es necesario realizar algún tipo de verificación o cálculo de la información proveniente del formulario.
$model = new CalculatorForm();
$model -> operator = '*';

Asignación general.

  • Asigna todos los atributos del modelo cuyos nombres coincidan con la información proporcionada, habitualmente en $_POST.
  • Se incluye un escenario que permite diferenciar cuales atributos si se deberán modificar durante la asignación general.
$model = new CalculatorForm();
$model -> scenario = 'calculate';
if(isset($_POST['CalcForm']))
    $model -> attributes = $_POST['CalcForm'];
  • Es posible filtrar/asegurar cuales son los valores seguros para asignar masivamente por cada uno de los escenarios posibles.
  • Para hacer esto es necesario sobreescribir el método safeAttributes() del modelo.
public function safeAttributes()
{
    return array(
        parent::safeAttributes(),

        /* Atributos que pueden ser asignados en todos los scenarios */
        'atrib1, atrib2, ...',

        /* Atributos que sólo pueden ser asignados en el escenario X */
        'escenarioX' => 'atribA, atribB, ...'
    );
}
  • Si ninguno de los atributos del modelo será dependiente de un escenario es posible simplificar el retorno de safeAttributes() en una cadena conteniendo el listado de los nombres de los atributos seguros para asignación general.
public function safeAttributes()
{
    /* Asignación general sin dependencia de escenarios */
    return 'atrib1, atrib2, ...';
}

Enlaces.

Los componentes en Yii

Introducción.

  • Son objetos escritos basados en una especificación.
  • Heredan de la clase CComponent.
  • Es posible acceder y modificar sus propiedades así como lanzar y manejar sus eventos.

Propiedades.

  • Son atributos públicos de lectura y escritura.
  • Pueden definirse de dos maneras.
    • Declarando un atributo público en el componente.
      • En este caso el nombre de la propiedad es sensible a mayúsculas.
    • Definiendo los métodos setter y getter correspondientes.
      • En este caso el nombre de la propiedad no es sensible a mayúsculas.
      • Para implementar la propiedad imageType es necesario implementar los siguientes métodos.
        public function getImageType()
        {
            return $this -> _imageType;
        }
        
        public function setImageType($value)
        {
            $this -> _imageType = $value;
        }
      • Este método es mas flexible ya que permite definir una lógica del negocio que condicione el acceso a las propiedades.
      • De igual manera es posible implementar propiedades de sólo lectura (sin setter) o de sólo escritura (sin getter).

Eventos.

  • Los eventos del componente son propiedades que toman referencias a métodos (event handlers) como valores.
  • Estos métodos son invocados automáticamente cuando sucede el evento asociado.
  • Un evento del componente es definido por la implementación de un método cuyo nombre empieza por on y termina con el nombre del evento asociado (no es sensible a mayúsculas).
    • Para el evento click -> el método deberá ser entonces: onClicked.
      public function onClicked($event)
      {
          // ...
      }
  • Los eventos ($event) son instancias de la clase CEvent.
  • Los manejadores de evento se asginan de la siguiente manera.
    $component -> onClicked = $callback;
    • $callback deberá ser una referencia válida en PHP según las siguientes opciones.
      • El nombre de una función global.
      • Un método de una clase, para este caso deberá ser de la forma array($objeto, ‘nombreMétodo’).
  • Varios manejadores de evento pueden asociarse a un mismo evento, estos se ejecutarán en el orden en que fueron creados.
  • Un manejador de eventos puede evitar que se invoquen los demás manejadores de eventos al realizar la siguiente modificación.
    $event -> handled = true;

Comportamientos.

  • Un comportamiento es un objeto cuyos métodos pueden ser transferidos a los componentes que se le agreguen.
  • Es decir, reune funcionalidades en lugar de realizar una especialización como lo hace la herencia convencional.
  • Es posible agregar múltiples comportamientos lo que se asemeja a la herencia múltiple.
  • Son análogos a los mixins que incluyen otros lenguajes orientados a objetos (como Ruby) o que implementan otros frameworks (como Symfony).
  • Existen varias maneras de implementar un comportamiento.
    • Como una clase que implementa a la interfaz IBehavior.
    • Como una clase que hereda de CBehavior.
    • Si va a ser agregado a un modelo puede ser una clase que herede de CModelBehavior o CActiveRecordBehavior según corresponda.
  • Para utilizar un comportamiento primero debe ser agregado a un componente.
    $comportamiento -> attach($nombre, $componente);
    $componente -> métodoDelComportamiento();
  • Los comportamientos pueden ser activados y desactivados según se desee en cada componente.
    $componente -> enableBehavior($nombre);
    $componente -> disableBehavior($nombre);
  • Si dos o mas comportamientos agregados a un componente entran en conflicto porque tienen métodos con igual nombre, la precedencia la tendrá el comportamiento que haya sido agregado primero.

Enlaces.

Los módulos en Yii

Introducción.

  • Son unidades autocontenidas de software.
  • Tienen sus propios modelos, vistas, controladores y demás componentes de soporte.
  • Se asemejan a una aplicación pero no pueden ser desplegados individualmente, deben residir como parte de  una aplicación.
  • Los usuarios acceeden a los controladores de los módulos de la misma forma como se acceden los controladores convencionales.
  • Son útiles para dividir el desarrollo y mantenimiento de aplicaciones muy grandes en módulos independientes.
  • Facilitan la reutilización de código a alto nivel.
  • Se utilizan para implementar funcionalidades como autenticación, manejo de usuarios, manejo de comentarios, etc.

Estructura.

  • Cada módulo tiene una clase principal que hereda de CWebModule.
  • El nombre de la clase se determina de la siguiente manera: ucfirst(IdentificadorMódulo) . ‘Module’.
    • Ejemplo, la clase del módulo gallery debe llamarse GalleryModule.
  • Esta clase se utiliza para almacenar información y características que son comúnes a todo el módulo.
    • CWebModule::params almacena los parámetros del módulo.
    • CWebModule:: components comparte componentes de aplicación a nivel del módulo.

Creación.

Utilizando yiic.

yiic permite crear fácilmente un esqueleto básico del módulo para empezar a trabajar sobre él.

$ cd WebRoot/MiAplicacion
$ protected/yiic shell

Yii Interactive Tool v1.0
Please type ‘help’ for help.  Type ‘exito’ to quit.

>> module gallery

Uso.

  • Copie el directorio del módulo en el directorio modules de la aplicación.
  • Declare el módulo (con su identificador) en la propiedad modules de la configuración de la aplicación de la siguiente manera.
    return array (
        // ...
        'modules' => array('gallery', /* ... */),
        // ...
    );
  • Es posible especificar valores iniciales para las propiedades del módulo de la siguiente manera.
    return array (
        // ...
        'modules' => array(
            'gallery' => array(
                'fileType' => 'png',
                'rows' => 10,
                'cols' => 3
            ),
            /* ... */
         ),
        // ...
    );
  • La instancia del módulo puede ser accedida a través de la propiedad module del controlador acrivo.
    $rows = Yii::app() -> controller -> module -> rows;
  • Los módulos se acceden con la ruta módulo/controlador/acción.
    • Para el ejemplo, gallery/image/add corresponderá entonces con el URL http://www.servidor.com/index.php?r=gallery/image/add.
  • Un módulo puede contener a su vez a otros módulos los cuales pueden ser accedidos de manera similar: móduloPadre/móduloHijo/controlador/acción.

Enlaces.

Las vistas en Yii

Introducción.

  • Son archivos PHP convencionales.
  • Pueden incluír elementos de interfaz de usuario basados en Yii.
  • Se recomienda que las vistas accedan a la información de los modelos pero que no los modifiquen.
  • Deben permanecer lo mas simples posibles, lógicas complejas deberían llevarse hasta el controlador.
  • El nombre del archivo donde se almacena la vista corresponde con el identificador junto con la extensión .php.
    • La vista edit se almacenará en el archivo edit.php.
  • Los archivos de las vistas se almacenan bajo la ruta protected/views/<Identificador del controlador>.
  • Desde la vista es posible acceder a los atributos del controlador a través del objeto $this.
  • Para procesar una vista se debe invocar el método CController::render(<Identificador de vista>).
  • Es posible pasar explícitamente información desde el controlador a la vista en el momento de su generación.
    $this -> render('edit', array (
        'variable1' => $valor1,
        'variable2' => $valor2
    ));
    • En la vista edit se podrá acceder a $variable1 y $variable2 como variables convencionales.

Layouts (diseños).

  • El layout permite especificar el diseño común y constante de la interfaz de usuario.
  • Evita la necesidad de agregar elementos comúnes (archivos javascript y css, título, subtítulo, menú izquierdo, pies, etc) a todas las páginas en cada una de ellas.
  • El layout aplica a todo el sitio web y se aplica automáticamente al invocar el método render.
  • Para generar una vista sin el layout es necesario utilizar el método renderPartial.
  • El layout utilizado por defecto se encuentra en views/layouts/main.php.
    • Es posible modificar esto alterando CWebApplication::layout para toda la aplicación o CController::layout para todas las acciones del controlador.
  • En el layout se debe incluír la variable $content para especificar la ubicación en la que se agregará el contenido de la vista específica al controlador/acción ejecutado (parte no constante de la interfaz de usuario).

Widgets.

  • Los widgets son componentes predefinidos para la presentación de la interfaz de usuario.
  • Generalmente agregan una funcionalidad gráfica compleja, específica y autocontenida.
  • Heredan de CWidget.
  • Su forma de invocarlos depende de si tienen o no contenido en su cuerpo.
    • Sin contenido en su cuerpo.
      <? php $this -> widget('ruta.a.la.ClaseWidget'); ?>
    • Con contenido en su cuerpo.
      <? php $this -> beginWidget('ruta.a.la.ClaseWidget'); ?>
      ... contenido del cuerpo del Widget ...
      <? php $this -> endWidget(); ?>
  • Es posible definir valores iniciales para los atributos del Widget durante su invocación con widget o beginWidget.
    $this -> widget('CMaskedTextField', array (
        'mask' => '99/99/9999'
    ));

Creación de nuevos Widgets.

  • El nuevo widget debe heredar de CWidget e implementar los métodos init y run.
    class NuevoWidget extends CWidget
    {
        public function init()
        {
            // Invocado por CController::beginWidget().
        }
    
        public function run()
        {
            // Invocado por CController::endWidget().
        }
    }
  • Tienen sus propias vistas y se ubican por defecto en el directorio views bajo el directorio que contiene la clase del widget.
  • Estas vistas se generan invocando CWidget::render().

Vistas del sistema.

  • Estas vistas se utilizan para mostrar errores del sistema o información de registro.
  • Las excepciones de HTTP (CHTTPException) se despliegan con las vistas errorXXX donde XXX es el código del error HTTP generado.
  • Las vistas por defecto provistas por Yii se ubican en framework/views y pueden personalizarse definiéndolas en protected/views/system.

Enlaces.