Monthly Archives: March 2009

Serialización de objetos con PHP

Hoy tuve la necesidad de serializar objetos en PHP para implementar la funcionalidad de cache de las series basadas en los indicadores de un proyecto que estoy realizando y que, de paso, cada día me está quedando mas interesante.

Se me ocurrieron dos opciones, una fueron las funciones de serialización de objetos convencionales que incluye PHP y otra fue utilizar la representación JSON de mis datos para enviarlos y recibirlos de la persistencia, en este caso, archivos de texto.  Finalmente me decidí por utilizar las funciones de serialización sobre las de JSON por dos diferencias funcionales que en este proyecto eran ventajosas.

  • JSON sólo incluye a los atributos públicos, en mi caso eran protegidos.
  • Al recuperar la información en formato JSON y convertirla a objeto PHP nuevamente los datos son recuperados como objetos genéricos, no del tipo exacto como se codificaron.  Las funciones de serialización no tienen este problema.

Las funciones de serialización son muy simples: serialize y unserialize.

  • La primera de ellas recibe el objeto a serializar y retorna su representación en cadena.
    string serialize (mixed)
  • El segundo recibe la representación en cadena del objeto serializado y retorna el objeto PHP correspondiente.
    mixed unserialize (string)

Funcionan perfecto.  Todo fue demasiado sencillo.  Como siempre, en el mundo de Murphy, había un problema.  A los objetos que debía serializar el framework que utilizaba (Kohana) les estaba agregando un atributo protegido llamado db donde se encontraba la información de conexión a la base de datos, incluyendo la contraseña y por ende esta información estaba quedando serializada en mis archivos de caché!  Conclusión: un problema de seguridad terrible y un tamaño sensiblemente mayor para estos archivos.

Los objetos a serializar eran modelos con cierta complejidad de anidamiento de entidades (algunos atributos eran objetos también) por lo cual no era una opción muy interesante el realizar la serialización por mi mismo recorriendo recursivamente las estructuras de datos.  Tampoco pude acceder a los objetos para borrarles (unset) los atributos extra.

Por suerte las funciones de serialización incluyen una facilidad para estos casos.  Internamente se llaman automáticamente los métodos mágicos __sleep justo antes de la serialización y __wakeup justo después de la desserialización.

Mas interesante aún, el método __sleep puede retornar un arreglo con los nombres de los atributos que serán efectivamente incluídos en la serialización de los objetos de dicha clase.  Para remover entonces el atributo db de la serialización sólo tuve que hacer lo siguiente.

    public function __sleep()
    {
        $atributosSerializables = get_object_vars($this);
        unset($atributosSerializables['db']);
        return array_keys($atributosSerializables);
    }

Mi segunda aplicación con Yii: Calculadora Simple

Introducción.

Bien, aquí vamos con mi segundo paso en el camino de aprendizaje con Yii.  El día de hoy voy a hacer una práctica muy sencilla de como manipular los formularios en Yii.  Es en realidad muy fácil, sólo hace falta acostumbrarse.

El objetivo de la aplicación es la de desplegar dos campos (operandos) y una lista de selección (operador) y calcular el resultado de la operación aritmética elegida entre estos valores.  Es en realidad, el típico programita de calculadora.  Utilizará validadores de campos, asignación de campos a partir del request y un modelo basado en formularios.

Creación de la infraestructura básica.

Creación de la estructura de la aplicación.

$ yii/current/framework/yiic webapp /home/www/public/SimpleCalc

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

Your application has been created successfully under /home/www/public/SimpleCalc.

Téngase en cuenta que /home/www/yii/current fue la ruta en donde se instaló el framework en la sesión inicial y /home/www/public es el document root de mi servidor de páginas.

Creación del modelo y el controlador.

$ cd /home/www/public/SimpleCalc

$ protected/yiic shell

Yii Interactive Tool v1.0
Please type 'help' for help. Type 'exit' to quit.

>> model Calculadora
Warning: you do not have a 'db' database connection as required by Active Record.
generate Calculadora.php

The 'Calculadora' class has been successfully created in the following file:
/home/www/public/SimpleCalc/protected/models/Calculadora.php

If you have a 'db' database connection, you can test it now with:
$model=Calculadora::model()->find();
print_r($model);

>> controller Calculadora index
generate CalculadoraController.php
mkdir /home/www/public/SimpleCalc/protected/views/calculadora
generate index.php

Controller 'calculadora' has been created in the following file:
/home/www/public/SimpleCalc/protected/controllers/CalculadoraController.php

You may access it in the browser using the following URL:

http://hostname/path/to/index.php?r=calculadora

Complementar el modelo.

El archivo es SimpleCalc/protected/models/Calculadora.php.  La clase base para los modelos que recubren formularios de usuario es CFormModel.

class Calculadora extends CFormModel
{
    // ...
}

Los valores manejados por el modelo serán tres: dos operandos y un operador.

    public $operando1;
    public $operando2;
    public $operador;

Los modelos en Yii permiten que se especifique las etiquetas que se asociarán a los campos del modelo cuando se presenten en una vista.  Para este caso, los operadores se llamarán A y B respectivamente.

    public function attributeLabels()
    {
        return array(
            'operando1' => 'A',
            'operando2' => 'B'
        );
    }

Se especifican las reglas que deben contener los campos del modelo para considerarlo válido según los siguientes criterios.

  • Los operandos 1 y 2 son requeridos y deberán ser de tipo real.
  • El operador deberá ser una de las siguientes opciones: +, -, *, /.
  • El operador2 no podrá ser cero si la operación es división.

Para las dos primeras verificaciones se utilizan reglas incluídas en Yii, para la última se utiliza una personalizada definida por un método del modelo (validarDivCero).

    public function rules()
    {
        return array(
            array('operando1', 'required'),
            array('operando1', 'type', 'type' => 'float'),
            array('operando2', 'required'),
            array('operando2', 'type', 'type' => 'float'),
            array('operador',  'in', 'range' => array('+', '-', '*', '/')),
            array('operando2', 'validarDivCero')
        );
    }
    public function validarDivCero($attribute, $params)
    {
        if($this -> operador == '/' && $this -> operando2 == 0)
            $this -> addError('operando2', 'El denominador de una división no puede ser cero.');
    }

Se especifican cuales de los campos son seguros para ser asignados automáticamente a partir de los valores recibidos en el $_POST.  Para este caso, como son todos los disponibles, podría obviarse la especificación.

    public function safeAttributes()
    {
        return array(
            'operando1, operando2, operador'
        );
    }

Se implementa un método operar() que contiene la lógica de la Calculadora, es decir, suma, resta, multiplica o divide según se le solicite.

    public function operar()
    {
        $r = 0;
        switch ($this -> operador)
        {
            case '+': $r = $this -> operando1 + $this -> operando2;
                      break;
            case '-': $r = $this -> operando1 - $this -> operando2;
                      break;
            case '/': $r = $this -> operando1 / $this -> operando2;
                      break;
            case '*': $r = $this -> operando1 * $this -> operando2;
                      break;
        }
        return $r;
    }

Agregué un método adicional, toString(), para preparar la versión en cadena de la operación solicitada para realizar algunas pruebas que mas adelante comentaré.

    public function toString()
    {
        if($this -> operando1 === null || $this -> operando2 === null)
            return "Operación indefinida!";
        return "{$this -> operando1} {$this -> operador} {$this -> operando2}";
    }

Complementar el controlador.

El archivo se encuentra en SimpleCalc/protected/controllers/CalculadoraController.php.

La acción index deberá mostrar el formulario vacío y dejarlo listo para que el usuario ingrese los datos a operar.  Se le indica entonces que utilice la vista index y se le envía con ella un objeto Calculadora recién construído para que muestre sus campos vacíos.

    public function actionIndex()
    {
        $this -> render('index', array('calc' => new Calculadora()));
    }

La acción operar será invocada por el formulario (ubicado en la vista) cuando el usuario haya ingresado los operandos, seleccionado el operador y esté solicitando su operación.  Su misión es la de crear un objeto Calculadora a partir de la información recibida, verificar su información y en caso de ser posible, ejecutar la operación y mostrar su resultado al usuario.

    public function actionOperar()
    {
        $calculadora = new Calculadora();
        if(isset($_POST['Calculadora']))
        {
            $calculadora -> attributes = $_POST['Calculadora'];
            if($calculadora -> validate())
            {
                // Exito
                $this -> render('index', array('calc' => $calculadora,
                                               'resultado' => $calculadora -> operar()));
                exit;
            }
        }
        // Fracaso
        $this -> render('index', array('calc' => $calculadora));
        exit;
    }

Crear la vista.

Este mini proyecto sólo utiliza una vista, index, a través de la cual se solicitan los datos al usuario y se muestra el resultado de la operación.  El archivo se encuentra en SimpleCalc/protected/views/calculadora/index.php.

Lo primero que hago es crear las etiquetas de <form> que enviarán el requerimiento a la acción operar del controlador Calculadora tan pronto como el usuario utilice el botón de submit.

<h2>Calculadora Simple</h2>
<br />
<div>
    <?= CHtml::form(CHtml::normalizeUrl(array('calculadora/operar'))); ?>
    <!-- El contenido del formulario va aquí. -->
    </form>
</div>

Se determina en donde se mostrarán los mensajes de error al usuario.

    <?= CHtml::errorSummary($calc); ?>

Se agregan los campos para los operandos, cada uno consta de una etiqueta (recordar el método attributeLabels del modelo) y del campo de texto.  En medio de ellos se incluye una lista de selección única con el signo de operación.

    <?= CHtml::activeLabel($calc, 'operando1'); ?>
    <?= CHtml::activeTextField($calc, 'operando1'); ?>
    <?= CHtml::activeDropDownList($calc, 'operador', array('+' => '+',
                                                           '-' => '-',
                                                           '*' => '*',
                                                           '/' => '/')); ?>
    <?= CHtml::activeLabel($calc, 'operando2'); ?>
    <?= CHtml::activeTextField($calc, 'operando2'); ?>

Se incluye un botón de submit para el envío del formulario.

    <?= CHtml::submitButton('='); ?>

Se incluye además un espacio para desplegar la respuesta en caso de tenerse disponible.

    <span style='font-size: 20px'>
        <?= (isset($resultado)) ? $resultado : ""; ?>
    </span>

Finalmente se muestra la representación en cadena (recordar el método toString del modelo) de la tarea realizada.

    <span style='font-weight: bolder;'>Operación: </span>
    <?= $calc -> toString(); ?> =
    <?= (isset($resultado)) ? $resultado : ""; ?>

Nótese como a lo largo de la vista se hace referencia al objeto $calc para obtener la información de la calculadora.  Este objeto es enviado desde el controlador cuando se le hace el render a la vista.  Igual sucede con la variable $resultado la cual, en este caso, sólo es enviada cuando el procesamiento de la operación es exitoso.

Acceder a la aplicación.

Utilizando un navegador web se debe acceder a la siguiente ubicación.

http://hostname/SimpleCalc/index.php?r=calculadora/index

Presentación final de la aplicación.

Presentación final de la aplicación.

Principales flujos en Yii

Secuencia del procesamiento de un requerimiento.

El típico flujo de datos

El típico flujo de datos

  1. El usuario realiza un requerimiento al sistema mediante la construcción de un URL de la forma:
    http://hostname/index.php?r=post/show&id=1.
  2. El script de inicio (index.php) es interpretado, se crea la instancia de la aplicación y esta es ejecutada.
  3. La aplicación construye el objeto request a partir de la información suministrada por el usuario.
  4. La aplicación, gracias al componente urlManager, determina cual es el controlador relacionado en el request.
    En este caso, el controlador es post cuya clase es PostController y la acción es show junto con el atributo id cuyo valor es de 1.
  5. La aplicación crea una instancia del controlador específico, determina el tipo y modo de ejecutar la acción y determina los filtros asociados.  Ejecuta los filtros y si es permitido por ellos, invoca la acción.
  6. La implementación de la acción se ejecuta.  El modelo Post lee de la base de datos el registro cuyo id es 1.
  7. La acción prepara la vista show con la información del modelo Post.
  8. La vista ejecuta los wigets que se incluyan en su interior.
  9. La vista resultante se incluye en el layout general.
  10. La acción envía la vista para que sea mostrada al usuario final.

Secuencia de desarrollo de una aplicación en Yii.

  1. Crear el esqueleto de la aplicación: yiic webapp.
  2. Configurar la aplicación.
  3. Crear un modelo para cada tipo de datos manipulado.  yiic model para las tablas de la base de datos.
  4. Crear un controlador para cada tipo de requerimientos de usuario.  "Si un modelo necesita ser accedido por el usuario, se debe crear su correspondiente controlador".  yiic crud para los modelos de base de datos.
  5. Implementar las acciones y sus correspondientes vistas.
  6. Configurar los filtros necesarios sobre las acciones de los controladores.
  7. Crear el tema de la aplicación si es necesario.
  8. Crear los mensajes traducidos para la internacionalización si es necesaria.
  9. Aplicar las técnicas de caché según los requerimientos.
  10. Realizar ajustes menores y desplegar la aplicación final.

Enlaces.

Convenciones en Yii

Alias de rutas.

  • Permite establecer nombres cortos para ciertas rutas de uso frecuente.
  • Utiliza el punto como separador de ruta: AliasRuta.directorio1.directorio2.objetivo.
  • Es posible definir nuevos alias invocando YiiBase::setPathOfAlias().
  • El proceso contrario, convertir un alias en su correspondiente ruta, puede realizarse invocando YiiBase::getPathOfAlias().
  • Yii define los siguientes alias de manera predefinida.
    • system: directorio del framework.
    • application: directorio base de aplicaciones.
    • webroot: directorio donde se encuentra el script de entrada.
  • Los alias se pueden utilizar en múltiples situaciones.
    Yii::import('system.web.CController');

    Inclusive referenciando grupos de clases.
    Yii::import('system.web.*');

Código.

  • PHP soporta el uso de namespaces a partir de su versión 5.3.0.
  • Las clases nativas de Yii inician siempre con la letra C.
  • Para evitar colisiones de nombres se recomienda que las clases definidas por el usuario inicien por una determinada letra (diferente de la anterior).
  • Los nombres de las variables, funciones y clases deben estar en camel case, es decir, en mayúsculas la primera letra de cada palabra.
    • Los nombres de las variables/atributos y métodos empiezan por minúsculas.
    • Los nombres de las clases empiezan por mayúsculas.
    • Los atributos privados empiezan por underscore ('_').

Rutas.

  • Por defecto se utiliza el siguiente formato.

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

  • Es posible reescribir esta ruta a una mas amigable con SEO.

    http://hostname/ControladorId/AcciónId.html

  • Si se obvia la acción se utilizará la acción por defecto: index.
  • Si se obvian el controlador y la acción se utilizarán sus versiones por defecto: main/index.

Archivos.

  • Los nombres de los archivos de clases dependen de la clase pública que contienen y la nomenclatura para esa entidad.
  • Cada archivo debe contener una única clase pública y cero o mas clases privadas.
  • Los archivos de configuración pueden tener un nombre arbitrario.

Directorios.

  • Esta es la estructura general de directorios de Yii.
    • WebRoot/protected: es el directorio base de las aplicaciones.
      • A él le hace referencia el alias application.
      • Debe ser protegido en contra del acceso directo por los usuarios con un .htaccess.
      • Se puede personalizar a través de CWebApplication::basePath.
    • WebRoot/protected/runtime: archivos temporales generados durante la ejecución.
      • Debe tener privilegios de lectura/escritura por parte del servidor web.
      • Se puede personalizar a través de CApplication::runtimePath.
    • WebRoot/protected/extensions: almacena extensiones desarrolladas por terceros.
      • Se puede personalizar a través de CApplication::extensionPath.
    • WebRoot/protected/modules: almacena los módulos de la aplicación.
    • WebRoot/protected/controllers: almacena las clases de los controladores.
      • Se puede personalizar a través de CWebApplication::controllerPath.
    • WebRoot/protected/views: almacena los archivos de las vistas.
      • Se puede personalizar a través de CWebApplication::viewPath.
    • WebRoot/protected/views/ControladorId: almacena las vistas de un controlador específico.
      • Se puede personalizar a través de CController::getViewPath.
    • WebRoot/protected/views/layouts: almacena los archivos del layout de las vistas.
      • Se puede personalizar a través de CWebApplication::layoutPath.
    • WebRoot/protected/views/system: almacena las vistas del sistema.
      • Se puede personalizar a través de CWebApplication::systemViewPath.
    • WebRoot/assets: almacena los assets publicados.
      • Debe tener privilegios de lectura/escritura por parte del servidor web.
      • Se puede personalizar a través de CAssetManager::basePath.
    • WebRoot/themes: almacena los temas de la aplicación.
      • Cada subdirectorio representa un tema diferente.
      • Se puede personalizar a través de CThemeManager::basePath.

Enlaces.

Introducción al MVC de Yii – Parte II

Vista.

  • Es un archivo de PHP con elementos de interfaz de usuario.
  • Su implementación no debe alterar el estado de los modelos.
  • Sólo debe dedicarse a generar la presentación de la respuesta al usuario, así los modelos deberán ser gordos y los controladores delgados.
  • El nombre del archivo que la contiene es "{VistaId}.php" y se ubica bajo la ruta protected/controllers/{ControladorId}.
  • La generación de la vista se realiza con la instrucción CController::render('VistaId', array('var' => $valor)).  De esta manera $var se convierte en una variable local para la vista.
  • Desde la Vista es posible acceder al controlador a través del objeto $this y consecuentemente a sus atributos: $this -> atributo.

Layout.

  • Es un tipo especial de Vista cuya funcionalidad permite componer la presentación a partir de varias vistas para generar un contenido estandarizado.  Por ejemplo, mantener secciones de cabecera, pies, menú izquierdo y contenido.
  • El contenido de la Vista se incluye en el Layout a través de la variable $content.
  • Se incluye automáticamente durante la ejecución de CController::render().  Si se desea evitar este comportamiento se debe utilizar CController::renderPartial().
  • Por defecto su contenido se toma desde protected/views/layouts/main.php.  Esto se puede personalizar modificando CWebApplication::layout (a nivel de toda la aplicación) o CController::layout (a nivel del controlador actual unicamente).

Widget.

  • Su clase base es CWidget.
  • Son componentes autocontenidos de interfaz de usuario y su utilidad es la de permitir crear con ellos interfaces mas completas.  Por ejemplo un calendario y un campo de autocompletar.
  • Su utilización se puede realizar de dos maneras según su contenido.
    • Sin cuerpo.
      <?php this -> widget('ruta.al.Widget', array('atributo' => $valor)); ?>
    • Con cuerpo.
      <?php this -> beginWidget('ruta.al.Widget', array('atributo' => $valor)); ?>
      ... Cuerpo del Widget ...
      <?php this -> endWidget(); ?>
  • Su implementación se basa en dos métodos: init y run que son invocados por beginWidget y endWidget respectivamente.
  • Los Widgets cuentan con sus propias Vistas almacenadas en /views bajo el directorio donde se encuentra su clase.

Vistas del sistema.

  • Se utilizan para presentar errores del sistema o información de registro.
  • Su nombre corresponde con el formato errorXXX donde XXX es el código HTTP del error al que corresponden.
  • Las vistas predefinidas se localizan en framework/views y pueden personalizarse desplegando versiones nuevas bajo protected/views/system.

Módulo.

  • Su clase base es CWebModule.
  • Es una unidad autocontenida de software con sus propios modelos, vistas, controladores y demás componentes.
  • Permite desarrollar funcionalidades independientes (manejo de usuarios, manejo de contenido, etc.) que pueden reutilizarse fácilmente entre varias aplicaciones.
  • A diferencia de las Aplicaciones, un Módulo no puede ser desplegado por si mismo.
  • El nombre de la clase es "ucfirst({MóduloId})Module" y se ubica bajo la ruta protected/modules/{MóduloId}.
  • El esquelo del Módulo se puede crear a partir del comando "module MóduloId"de yiic.
  • Para utilizarse en una Aplicación debe ser declarado en la propiedad modules de protected/config/main.php de la siguiente manera.
    • Sin valores iniciales para sus propiedades.
      return array(
          ...
          'modules' => array('miModulo', ... ),
          ...
      );
    • Especificando valores iniciales para las propiedades de los módulos.
      return array(
          ...
          'modules' => array('miModulo' => array
                                           (
                                              'titulo' => 'Hola Mundo',
                                              'lontigud' => 50
                                           ),
                             ... ),
          ...
      );
  • Puede accederse a través del controlador de la siguiente manera: Yii::app() -> controlador -> modulo -> propiedades.
  • La ruta del requerimiento se complementa con el módulo: MóduloId/ControladorId/AcciónId.
  • Los Módulos puede contener a otros Módulos en su interior: MóduloExternoId/MóduloInternoId/ControladorId/AcciónId.

Enlaces.

Introducción al MVC de Yii – Parte I

Introducción.

MVC es un patrón de diseño que separa de manera clara y precisa los tres componentes de una aplicación: el modelo, la vista y el controlador.  Su objetivo principal es el de separar la lógica del negocio de la lógica de la presentación para darle estructura a la implementación y facilitar con esto su posterior mantenimiento.

Como se mencionó anteriormente, el modelo consta de tres partes de acuerdo con la siguiente descripción.

  • El modelo representa la lógica de la aplicación, que se encuentra inmersa en los datos y en las reglas del negocio.
  • La vista hace referencia a la interfaz de usuario, a la presentación de la información.
  • El controlador actúa como mediador entre la solicitud del usuario y los modelos y vistas involucrados en la ejecución.
Estructura de una aplicación Yii.

Estructura de una aplicación Yii.

Aplicación.

  • Yii utiliza adicionalmente el patrón de diseño del controlador frontal.
  • Representado por el index.php y es el único punto de acceso del usuario a la aplicación web.
  • En el controlador frontal se crea la instancia de la Aplicación cuya función es la de recibir los requerimientos del usuario y remitirlos a los controladores apropiados para su posterior procesamiento.
  • Representa el contexto de ejecución del procesamiento de los requerimientos del usuario.
  • Puede accederse en cualquier momento a través del singleton Yii::app().
  • Su configuración se realiza en la ruta protected/config, especialmente en el archivo main.php.

Componente.

  • Su clase base es CComponent.
  • Implementan funcionalidades específicas con la suficiente flexibilidad para poderse compartir con otras aplicaciones.
  • Se pueden incluír y configurar facilmente en el archivo de configuración de la aplicación (arreglo components).
  • Se acceden a través de Yii::app() -> ComponenteId.
  • Su creación es perezosa, es decir, sus instancias sólo son creadas cuando son invocados por primera vez.  Es posible evitar este comportamiento listando los componentes bajo la sección preload de la configuración de la aplicación.
  • Sus propiedades pueden definirse de dos maneras.
    • Como atributos públicos de la instancia.
      • Su nombre es sensible a mayúsculas.
      • Su acceso es directo.
      • No permite definir restricciones sobre su modificación.
    • A través de los métodos de consulta y modificadores.
      • Su nombre no es sensible a mayúsculas.
      • Se deben definir los métodos getAtributo y setAtributo según se requiera el acceso.
      • La implementación de estos métodos permite filtrar el acceso a la propiedad.
  • Yii incluye en su distribución múltiples componentes listos para ser utilizados.

Controlador.

  • Su clase base es CController.
  • Su misión es la de dirigir la interacción entre los modelos (lógica del negocio) y las vistas (presentación) para producir el resultado que el usuario desea a partir de su requerimiento.
  • Contiene las acciones.
  • Determina la acción por defecto: index.  Es posible modificar su valor mediante CController::defaultAction.
  • El nombre de la clase es "{ControladorId}Controller".
  • El nombre del archivo que lo contiene es "{ControladorId}Controller.php" y se ubica bajo la ruta protected/controllers.
  • La búsqueda para localizar una clase de un controlador se rige por las siguientes reglas.
    1. Si la aplicación define a CWebApplication::catchAllRequest se rige por esta propiedad y el controlador especificado en el requerimiento es obviado.
    2. Si se encuentra un alias para este controlador en CWebApplication::controllerMap es utilizado.
    3. Si para el requerimiento ruta/ControladorId se encuentra el archivo protected/controllers/ruta/ControladorIdController.php es utilizada la clase contenida en él.
    4. Si hay módulos definidos en la aplicación la búsqueda incluye sus rutas de manera similar a la regla anterior.
    5. El controlador no fue encontrado y se lanza una excepción CHttpException.

Acciones.

  • Corresponden a las actividades que el controlador puede realizar.
  • Pueden definirse de dos maneras.
    • Como un método de la clase controlador.
      • El nombre del método debe corresponder con el formato "action{AcciónId}".
      • De esta manera, la acción agregar se encuentra implementada en el método actionAgregar.
    • Como una subclase de la clase CAction.
      • Se define una clase por cada acción.
      • El nombre de la clase corresponde con el formato "{AcciónId}Action".
      • El nombre del archivo donde se almacena la acción tiene el formato "{AcciónId}Action.php" y se almacena bajo protected/controllers/{ControladorId}.
      • De esta manera, la acción agregar se encuentra implementada en la clase AgregarAction.
      • La clase debe implementar el método run donde se implementa la lógica de la acción.
        class AgregarAction extends CAction
        {
            public function run()
            {
                // Lógica de la acción.
            }
        }
      • Es necesario sobreescribir el método actions() del controlador para hacerle saber de la existencia de la acción.
        class UsuarioController extends CController
        {
            public function actions()
            {
                return array(
                    'adicionar' => 'application.controllers.usuario.AgregarAction'
                );
            }
        }
      • En el ejemplo anterior se le especificó al controlador de Usuario que la acción adicionar se relaciona con la clase AgregarAction definida anteriormente y que se encuentra ubicada bajo protected/controllers/usuario.

Filtros.

  • Se ejecutan antes y/o después de una acción.
  • Su finalidad es la de realizar verificaciones o adecuaciones antes y/o después de procesar la solicitud del usuario.  Como es el caso de verificar la autenticación del usuario antes de realizar cualquier acción administrativa.
  • Una acción puede tener múltiples filtros.
  • El orden de ejecución de los filtros corresponde con el orden de aparición en la lista.
  • Un filtro puede evitar que los siguientes filtros se ejecuten.
  • De manera similar a las Acciones, los Filtros pueden definirse de dos maneras.
    • Como un método de la clase Controlador.
      • El nombre del método debe corresponder con el formato "filter{FiltroId}".
      • De esta manera, el filtro verificarAutenticacion se encuentra implementado en el método filterVerificarAutenticacion.
        public function filterVerificarAutenticacion($filterChain)
        {
            // El llamado a $filterChain -> run() continúa con la invocación del siguiente filtro.
        }
    • Como una subclase de la clase CFilter.
      • Se define una clase por cada filtro.
      • El nombre de la clase corresponde con el formato "{FiltroId}Filter".
      • El nombre del archivo donde se almacena el filtro tiene el formato "{FiltroId}Filter.php" y se almacena bajo protected/filters.
      • De esta manera, el filtro estadisticas se encuentra implementado en la clase EstadisticasFilter.
      • La clase debe implementar los métodos preFilter y postFilter donde se implementa la lógica del filtro antes y después de la ejecución de la acción respectivamente.
        class EstadisticasFilter extends CFilter
        {
            public function preFilter($filterChain)
            {
                // Lógica del filtro previo a la ejecución de la acción.
                // Si $respuesta es 'true' se continúa con la ejecución de los filtros
                // y eventualmente con la ejecución de la acción.  Si es 'false' la
                // la ejecución de la acción es abortada.
                return $respuesta;
            }
            public function postFilter($filterChain)
            {
                // Lógica del filtro posterior a la ejecución de la acción.
            }
        }
  • En ambos casos es necesario indicarle al controlador la existencia del filtro.  Para esto se debe sobreescribir el método CController::filters() de manera que retorne la lista de filtros a ejecutarse por cada acción.
    class UsuarioController extends CController
    {
        // ...
        public function filters()
        {
            return array(
                'verificarAutenticacion + crear, editar',
                array(
                    'application.filters.EstadisticasFilter - crear, editar',
                    'activado' => true,
                    'frecuencia' => 'diaria'
                )
            );
        }
    }
  • El ejemplo anterior especifica dos filtros para el controlador Usuario con las siguientes características.
    • El filtro verificarAutenticacion esta basado en un método mientras que el filtro estadisticas está basado en una clase.
    • Se establecieron valores iniciales para la configuración del filtro de estadisticas.  Estos valores (activado = true y frecuencia = 'diaria') se asignan a los atributos del filtro en el momento de su construcción.
    • Los modificadores + y - determinan sobre cuales acciones se ejecutan efectivamente los filtros.
      • El filtro verificarAutenticacion se le aplicará UNICAMENTE a las acciones crear y editar.
      • El filtro estadisticas se le aplicará a TODAS las acciones EXCEPTUANDO a las acciones de crear y editar.
      • Si no se especifica ningún modificador se sobreentiende que aplica a TODAS las acciones.

Modelo.

  • Su clase base es CModel.
  • Representa una unica fuente de datos como una fila de una tabla de una base de datos o un formulario con información provista por el usuario.
  • Cada campo de la fuente de datos se convierte en un atributo del modelo.
  • Cada atributo tiene una etiqueta y puede ser validado a partir de un conjunto de reglas.
  • Hay dos tipos de modelo según la fuente de datos que representan.
    • El modelo de formularios se representa con la clase CFormModel.
      • Mantiene información recibida a partir de un formulario del usuario para ser procesada y posteriormente descartada.
    • El modelo de registro activo se representa con la clase CActiveRecord.
      • Se basa en el patrón de diseño de Active Record.
      • Representa un registro de una tabla de una base de datos.
      • Permite su manipulación de una manera orientada a objetos.
      • Los campos del registro se convierten en atributos del modelo.

Enlaces.

Instalación y primera prueba de Yii

Instalación del framework.

Después de instalados los paquetes de LAMP (Linux, Apache, MySQL y PHP) procedemos con la primera instalación de Yii.  En el equipo de desarrollo, el DocumentRoot se encuentra referenciando a /home/www/public, de esta manera en /home/www se almacenarán los recursos que están relacionados con las aplicaciones web pero que no deben ser publicados, es decir, serán privados.

Se descarga la última versión disponible del Yii Framework desde la siguiente ubicación.

http://www.yiiframework.com/download/

Para este caso fue el archivo yii-1.0.3.tar.gz el cual fue descargado en /home/www.

$ tar zxvf yii-1.0.3.r780.tar.gz

$ mkdir yii

$ mv yii-1.0.3.r780 yii/1.0.3

$ ln -s /home/www/yii/1.0.3/ /home/www/yii/current

La actualización del PATH como se muestra a continuación sólo modifica el ambiente del shell donde se ejecute; para realizar esta actualización de manera perdurable en el tiempo debe agregarse al final del archivo ~/.profile (para el usuario en sesión) o /etc/profile (para todos los usuarios del equipo).

$ PATH=/home/www/yii/current/framework:$PATH

Creación de la primera aplicación utilizando la herramienta yiic.

$ mkdir /home/www/public/demo1

$ yiic webapp /home/www/public/demo1

Acceder con un navegador web a la siguiente URL para verificar el esqueleto de aplicación generado.

http://localhost/demo1/

Deberá aparecer algo similar a lo mostrado en la siguiente imagen.

Aplicación base generada con 'yiic'

Aplicación base generada con 'yiic'

La aplicación incluye tres secciones.

  1. Home: una página con texto estático (ver imagen).
  2. Contact: un formulario de contacto con validación de tipos y captcha.
  3. Login: un formulario de registro de usuario para el ingreso a la aplicación con su respectiva autenticación.

Creación y conexión a la base de datos y la tabla User.

$ mkdir -p /home/www/public/demo1/protected/data

$ cd /home/www/public/demo1/protected/data

$ vi script.sql

CREATE TABLE User (

id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
username VARCHAR(128) NOT NULL,
password VARCHAR(128) NOT NULL,
email VARCHAR(128) NOT NULL

);

$ sqlite3 source.db < script.sql

$ ls -l

-rw-r–r– 1 jimezam jimezam  168 2009-03-07 09:56 script.sql
-rw-r–r– 1 jimezam jimezam
3072 2009-03-07 09:57 source.db

$ vi /home/www/public/demo1/protected/config/main.php

return array(
'basePath'=>dirname(__FILE__).DIRECTORY_SEPARATOR.'..',
'name'=>'Mi primera aplicación – Demo1',
// …
'db'=>array(
'connectionString'=>'sqlite:protected/data/source.db',
),
// …
);

Implementando las operaciones CRUD para User utilizando yiic.

$ cd /home/www/public/demo1

$ yiic shell

Yii Interactive Tool v1.0
Please type 'help' for help. Type 'exit' to quit.
>> model User
generate User.php

The 'User' class has been successfully created in the following file:
/home/www/public/demo1/protected/models/User.php

If you have a 'db' database connection, you can test it now with:
$model=User::model()->find();
print_r($model);

>> crud User
generate UserController.php
mkdir /home/www/public/demo1/protected/views/user
generate create.php
generate update.php
generate list.php
generate show.php
generate admin.php
generate _form.php

Crud 'user' has been successfully created. You may access it via:
http://hostname/path/to/index.php?r=user

>> exit

Inicie sesión a través del próximo URL y utilizando admin/admin como nombre de usuario y contraseña respectivamente.

http://localhost/demo1/index.php?r=site/login

Acceda al siguiente URL para utilizar el scaffolding de la tabla User recién creado.

http://localhost/demo1/index.php?r=user/admin

Después de agregar algunos datos deberá aparecer algo similar a lo mostrado en la siguiente imagen.

Scaffolding para la tabla User

Scaffolding para la tabla User

Problema con los assets.

Los assets son recursos del proyecto web que son publicados para ser utilizados directamente por los clientes. Para este proyecto se ubican en /home/www/public/demo1/assets y en esta ubicación se comparten las imágenes, las hojas de estilo y las librerías de Javascript.

Extrañamente encontré un problema.  Todas las opciones de la aplicación demo1 funcionaban correctamente exceptuando la eliminación (delete) de usuarios.  En el log de Apache aparecía el siguiente mensaje.

[Sat Mar 07 10:55:56 2009] [error] [client 127.0.0.1] (13)Permission denied: file permissions deny server access: /home/www/public/demo1/assets/d0586e3b/jquery.yii.js, referer: http://localhost/demo1/index.php?r=user/admin

Revisando la carga de los recursos de la página descubrí que tanto jquery.js como jquery-yii.js estaban fallando con un error 403.  Descubrí entonces que era un problema de permisos sobre estos archivos, muy probablemente motivado porque estoy utilizando suPHP (paquetes libapache2-mod-suphp y suphp-common en Ubuntu) que permite ejecutar los scripts bajo los permisos de sus propietarios.  Para resolver el problema fue necesario agregar el permiso de lectura para otros sobre los archivos relacionados con el siguiente comando.

$ chmod -R o+r /home/www/public/demo1/assets

Enlaces.