15 diciembre, 2009

Empezando con Kohana, ¿mejor que CodeIgniter?

He traducido este tutorial de NetTuts+ creado por Cristian Gilè sobre el framework PHP Kohana, al igual que hice en su día con el de CodeIgniter.

Kohana es un framework de PHP5 que sigue la arquitectura MVC (Modelo-Vista-Controlador). Hay varias razones por las que deberías elegir Kohana, pero las principales son seguridad, ligereza y simplicidad. En este tutorial, introduciré sus características principales y, con una simple demostración, mostraré cuánto tiempo Kohana puede ahorrarte potencialmente.


Paso 1: ¿Qué es Kohana?



Kohana es un framework de PHP5 que sigue la arquitectura MVC. MVC mantiene separadas la lógica de la presentación. Esto permite  crear un código más limpio y ahorrar tiempo en la búsqueda de errores. Para los que no estén familiarizados con esta arquitectura:
  • Un modelo representa datos sobre los que la aplicación opera. Normalmente es una base de datos.
  • Una vista contiene el código de presentación como HTML, CSS y JavaScript.
  • Un controlador interpreta la entrada del usuario y redirige al modelo y/o vista.
Kohana era originalmente una escisión de CodeIgniter (CI), que es un producto open source de la empresa EllisLab. Hay muchas similitudes entre CI y Kohana, pero todo el código es nuevo o ha sido completamente reescrito. Tal y como puedes leer en la página oficial de Kohana, las características principales son:
  • Altamente seguro
  • Extremadamente ligero
  • Breve curva de aprendizaje
  • Utiliza el patrón MVC
  • 100% compatible con la codificación UTF-8
  • Arquitectura débilmente acoplada
  • Extremadamente fácil de extender

Paso 2: Descargando Kohana



Empecemos. Visita la página oficial de Kohana y haz click sobre la caja verde de la esquina derecha para descargar la última versión. Todas las bibliotecas, asistentes y vistas de Kohana están incluidos en el paquete de descarga por defecto, pero puedes seleccionar módulos extra, herramientas de terceros, y los lenguajes que desees. Para el propósito de este tutorial, el paquete por defecto será suficiente. Haz click en "Download Kohana!" para iniciar la descarga.




Paso 3: Instalando Kohana

Una vez que se ha terminado la descarga:
  1. Descomprimir el zip
  2. Renombrar la carpeta "Kohana_vx.x.x" (x.x.x=número de versión) a "kohana" y súbela a la carpeta raíz de tu servidor web.
  3. Edita el fichero de configuración global application/config/config.php de esta forma:
       $config['site_domain'] = 'localhost/kohana';
  4. Si estás usando un sistema unix (linux, MacOSX), las subcarpetas de la instalación podrían haber perdido sus permisos durante la descompresión del zip. Haz un chmod a 755 de todas ellas ejecutando find . -type d -exec chmod 755 {} \; desde la carpeta raíz de la instalación de Kohana.
  5. Asegúrate de que application/logs y application/cache no sean de sólo lectura. Haz chmod a 666 sobre ellas.
  6. Ahora, apunta el navegador a http://localhost/kohana/ . Automáticamente, el framework llamará a install.php y comprobará los requerimientos con el servidor.
Kohana se ejecutará en prácticamente cualquier entorno que cumpla la configuración mínima:
  • Servidor con soporte de Unicode
  • PHP con una versión igual o posterior a la 5.2.3
  • Un servidor HTTP. Sugiero XAMPP, que es una herramienta sencilla todo-en-uno para instalar MySQL, PHP y Perl.
  • Una base de datos (MsSQL, MySQL, MySQLi, PostgreSQL, PDOSqlite).
También hay extensiones necesarias:
  • PCRE
  • iconv
  • mcrypt
  • SPL
Si la instalación se completa con éxito, serás dirigido a esta página de prueba:


Si alguna de las pruebas falla, deberás corregirla antes de continuar.

Si todas las comprobaciones tienen éxito, dirígete a la carpeta de Kohana y elimina o renombra el archivo install.php. Recarga la página en el navegador y verás una página de bienvenida como esta:




Paso 4: Configurando Kohana

Kohana está preparado para funcionar. No se necesita realizar ninguna configuración. Este framework es genial, ¿no te parece? Vamos a ver un poco de código, sígueme.


Paso 5: El primer proyecto con Kohana

Los tutoriales de programación tradicionales empiezan con el ejemplo "hello world". Pero yo creo que una aplicación simple puede dar una idea clara de cómo funciona el framework. Vamos a construir un gestor de colecciones de CDs -- sólo como divertida demostración. Antes de empezar a escribir código es necesaria una introducción breve a la distribución de carpetas de Kohana.



Nuestra aplicación se ubicará en la carpeta application. En esta carpeta hay varias subcarpetas pero sólo necesitamos lo siguiente para nuestro proyecto:
  • config guarda los archivos de configuración codificados como simples arrays estáticos.
  • controllers guarda nuestra clase de controladores personalizada
  • models guarda nuestra clase de modelos personalizada
  • views guarda nuestro archivos personalizados escritos en HTML (o cualquier lenguaje de markup o scripts necesarios para mostrar los datos y los controles de interfície para el usuario)
El resto de subcarpetas no se necesitan para este tutorial, por lo que te invito a aprender más posteriormente sobre ellos en la página de Kohana.

La carpeta system guarda el núcleo de Kohana y herramientas como bibliotecas (library), asistentes (helper) y archivos de configuración predefinidos. En este proyecto vamos a usar algunas bibliotecas y algunos asistentes - buenas herramientas para acelerar tu trabajo.

La carpeta assets  no pertenece a Kohana por defecto. Yo la he creado para guardar archivos estáticos como CSS, JS o imágenes. Después mostraré como incluirlos en el proyecto.

La carpeta modules es el lugar donde guardar las colecciones reusables de archivos relacionados que unidos aportan una funcionalidad particular a una aplicación. El módulo de autenticación de usuarios, ofrecido por el equipo Kohana, es un ejemplo de módulo.

Ésta es una muy breve introducción al sistema de archivos de Kohana, pero es suficiente para los propósitos de este tutorial. No quiero aburrirte con más teoría.


Paso 6: La base de datos del proyecto

He seleccionado MySQL para mi proyecto, pero recuerda que Kohana soporta igualmente MsSQL, MySQLi, PostgreSQL, o PDOSqlite. Crea una base de datos llamada "cd_collection" o elige el nombre que prefieras, y ejecuta la siguiente SQL mediante phpMyAdmin o cualquier herramienta para administrar la base de datos.

CREATE TABLE `albums` (
  `id` int(11) NOT NULL auto_increment,
  `name` varchar(50) collate utf8_bin NOT NULL,
  `author` varchar(50) collate utf8_bin NOT NULL,
  `genre_id` int(11) NOT NULL,
  PRIMARY KEY  (`id`),
  KEY `genre_id` (`genre_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=19 ;

INSERT INTO `albums` (`id`, `name`, `author`, `genre_id`) VALUES
(2, 'Lines, Vines And Trying Times', 'Jonas Brothers', 16),
(3, 'The E.N.D.', 'The Black Eyed Peas', 16),
(4, 'Relapse', 'Eminem', 18),
(5, 'Monuments And Melodies', 'Incubus', 1),
(6, 'Thriller', 'Michael Jackson', 16),
(7, 'Back in Black', 'AC/DC', 4),
(8, 'The Dark Side of the Moon', 'Pink Floyd', 4),
(9, 'Bat out of Hell', 'Meat Loaf', 4),
(10, 'Backstreet Boys', 'Millennium', 16),
(11, 'Rumours', 'Fleetwood Mac', 4),
(12, 'Come on Over', 'Shania Twain', 16),
(13, 'Led Zeppelin IV', 'Led Zeppelin', 4),
(14, 'Jagged Little Pill', 'Alanis Morissette', 4),
(15, 'Sgt. Pepper''s Lonely Hearts Club Band', 'The Beatles', 16),
(16, 'Falling into You', 'Cv©line Dion', 16),
(17, 'Music Box', 'Mariah Carey', 16),
(18, 'Born in the U.S.A.', 'Bruce Springsteen', 4);

CREATE TABLE `genres` (
  `id` int(11) NOT NULL auto_increment,
  `name` varchar(50) collate utf8_bin NOT NULL,
  PRIMARY KEY  (`id`),
  UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=22 ;

INSERT INTO `genres` (`id`, `name`) VALUES
(1, 'Alternative Rock'),
(2, 'Blues'),
(3, 'Classical'),
(4, 'Rock'),
(5, 'Country'),
(6, 'Dance'),
(7, 'Folk'),
(8, 'Metal'),
(9, 'Hawaiian'),
(10, 'Imports'),
(11, 'Indie Music'),
(12, 'Jazz'),
(13, 'Latin'),
(14, 'New Age'),
(15, 'Opera'),
(16, 'Pop'),
(17, 'Soul'),
(18, 'Rap'),
(20, 'Soundtracks'),
(21, 'World Music');

ALTER TABLE `albums`
  ADD CONSTRAINT `genre_inter_relational_constraint` FOREIGN KEY (`genre_id`) REFERENCES `genres` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;

Como puedes ver, el SQL crea dos tablas, álbumes y géneros, y los puebla con algunos datos. La última sentencia SQL agrega una restricción para la clave ajena "genre_id".

La estructura de la base de datos es muy simple y no necesita mayor explicación:



Ahora, debes decirle a Kohana dónde está tu base de datos y cómo acceder a ella. Edita el archivo de configuración global system/config/database.php de esta forma:

$config['default'] = array
(
    'benchmark'     => TRUE,
    'persistent'    => FALSE,
    'connection'    => array
    (
        'type'     => 'mysql',    
        'user'     => 'root',    
        'pass'     => 'root',    
        'host'     => 'localhost',    
        'port'     => FALSE,        
        'socket'   => FALSE,        
        'database' => 'cd_collection'
    ),
    'character_set' => 'utf8',
    'table_prefix'  => '',
    'object'        => TRUE,
    'cache'         => FALSE,
    'escape'        => TRUE
);

Este código le dice a Kohana que se conecte a una base de datos MySQL llamada "cd_collection" en localhost con el nombre de usuario "root" y la clave "root". Debes cambiarlas de acuerdo con tu configuración de servidor de base de datos.


Paso 7: Creando el controlador

Creémos nuestro primer controlador. Recuerda estas convenciones:
  • el nombre de archivo del controlador debe estar en minúsculas, por ejemplo album.php
  • la clase controlador debe coincidir con su nombre de archivo pero iniciada con mayúscula y debe añadir _Controller, por ejemplo Album_Controller
  • Debe tener la clase Controller como padre
Igualmente, recuerda cómo Kohana estructura sus URLs y cómo puedes llamar a un método de controlador; por ejemplo http://hostname/carpeta_de_kohana/index.php/controller/function

Echa un vistazo a este simple controlador.

<?php defined('SYSPATH') OR die('No direct access allowed.');

class Album_Controller extends Controller
{
    public function __construct()    
    {
        parent::__construct();
    }
    
    public function index()
    {
          echo "My first controller";    
    }
}

PHP5 OOP (programación orientada a objetos) es un prerrequisito. Para aprender más sobre ello, puedes leer aquí.

La función constructor, llamada __construct, inicializa la clase y llama al constructor padre. La función index es la función por defecto, por lo que será usada si llamamos al controlador sin especificar una función (por ejemplo, http://localhost/index.php/kohana/album. Tras el nombre del controlador no hay ninguna función, la función de índice será llamada por defecto).

Dadas estas reglas básicas, enfoquémonos en la aplicación. El controlador de album implementa todas las acciones para la gestión de colección de álbumes. Este controlador nos permite crear un nuevo álbum, mostrar los álbumes guardados en la base de datos, actualizar un álbum o borrar un álbum.

Crea un archivo llamado album.php en application/controllers/ y pega los siguiente:

<?php defined('SYSPATH') OR die('No direct access allowed.');
    
class Album_Controller extends Controller
{
     private $album_model;
     private $genre_model;
    
     private $list_view;
     private $create_view;
     private $update_view;

    public function __construct()    
    {
        parent::__construct();
        $this->album_model   = new Album_Model;
        $this->genre_model      = new Genre_Model;
          $this->list_view       = new View('list');
          $this->update_view      = new View('update');
          $this->create_view      = new View('create');
    }

    public function index()
    {
         $this->show_albums_list();
    }
        
    private function show_albums_list()
    {
        $albums_list = $this->album_model->get_list();
        $this->list_view->set('albums_list',$albums_list);
        $this->list_view->render(TRUE);
    }
    
    public function show_create_editor()
    {
        $this->create_view->set('genres_list',$this->get_genres_list());
         $this->create_view->render(TRUE);
    }

    public function show_update_editor($id)
    {
        $album_data = $this->album_model->read($id);
        $this->update_view->set('album_id',$album_data[0]->id);
        $this->update_view->set('name',$album_data[0]->name);
        $this->update_view->set('author',$album_data[0]->author);
        $this->update_view->set('genre_id',$album_data[0]->genre_id);
        $this->update_view->set('genres_list',$this->get_genres_list());
        $this->update_view->render(TRUE);
    }

    public function create()
    {
        $album_data=array(
        'name'        => $this->input->post('name'),
        'author'      => $this->input->post('author'),
        'genre_id'  => $this->input->post('genre_id')
        );
        $this->album_model->create($album_data);
        url::redirect('album');
    }

    public function update()
    {
        $album_data = array(
            'name'        => $this->input->post('name'),
            'author'      => $this->input->post('author'),
            'genre_id'  => $this->input->post('genre_id')
        );
        $this->album_model->update($this->input->post('album_id'),$album_data);
         url::redirect('album');
    }
 
     public function delete($id)
    {
        $this->album_model->delete($id);
        url::redirect('album');
    }

    private function get_genres_list()
    {
        $db_genres_list  = $this->genre_model->get_list();
        $genres_list  = array();
        
        if(sizeof($db_genres_list) >= 1)
        {
            foreach($db_genres_list as $item)
            {
                $genres_list[$item->id] = $item->name;
            }
        }
        return $genres_list;
    }
}

Déjame explicar lo que hace este código.

Se declaran cinco variables miembro al inicio:

  private $album_model;
  private $genre_model;

  private $list_view;
  private $create_view;
  private $update_view;

Estos miembros son privados porque quiero limitar su visibilidad sólo a esta clase.

En el método construct los objetos de modelo y vista son creados usando los cinco miembros:

  $this->album_model   = new Album_Model;
  $this->genre_model      = new Genre_Model;
  $this->list_view       = new View('list');
  $this->update_view      = new View('update');
  $this->create_view      = new View('create');

Para crear un objeto modelo usa esta sintaxis:
$obj_name = new Name_Model;

Para crear un objeto vista, usa esta sintaxis:
$obj_name = new View('view_filename_without_extension');

Ahora hay dos objetos para acceder al modelo de álbum y género, y tres objetos para acceder a las vistas necesarias para generar la presentación.

El método index llama al método show_albums_list que lista todos los álbumes guardados en la base de datos.
$albums_list = $this->album_model->get_list();   
$this->list_view->set('albums_list',$albums_list);  
$this->list_view->render(TRUE);

En este método puedes ver cómo el modelo y la vista objeto son usada para acceder a métodos relativos. "get_list" es un método del modelo (lo veremos luego) que devuelve todos los álbumes guardados en la base de datos. El resultado se guarda en el array "$album_list". Para pasar el array de resultados del controlador a la vista, el método "set" del objeto vista es llamado. Este método requiere dos parámetros: una nueva variable vacía (album_list) contiene datos de una variable existente ($album_list). Ahora la nueva variable vacía "album_list" contine el array $album_list (veremos después como mostrar su contenido en la vista). El método "render", con el parámetro TRUE, es necesario para generar datos para el navegador.

El método show_create_editor muestra la interfície de usuario para insertar un nuevo álbum.
   1. $this->create_view->set('genres_list',$this->get_genres_list());
$this->create_view->render(TRUE);

La lista de géneros es pasada a la vista.

El método show_update_editor muestra la interfície de usuario para actualizar un álbum existente.

$album_data = $this->album_model->read($id);  
$this->update_view->set('album_id',$album_data[0]->id);  
$this->update_view->set('name',$album_data[0]->name);  
$this->update_view->set('author',$album_data[0]->author);  
$this->update_view->set('genre_id',$album_data[0]->genre_id);  
$this->update_view->set('genres_list',$this->get_genres_list());
$this->update_view->render(TRUE);

"read" es un método del modelo (lo veremos después) que devuelve datos ($album_data) del álbum con un identificador igual a $id. Entonces, cada elemento del álbum de datos devuelto es pasado a la vista.

El método create recibe datos para un nuevo álbum a partir de la vista y guarda los datos recibidos en la base de datos.
$album_data=array(  
'name'      => $this->input->post('name'),  
'author'    => $this->input->post('author'),  
'genre_id'  => $this->input->post('genre_id')  
);  
$this->album_model->create($album_data);  
url::redirect('album');

$album_data es un array que contiene los datos POST de la vista. Para guardar el álbum, el array se pasa al método del modelo create. La última línea es una llamada a un método asistente. Los asistentes (helpers) son funciones que te asisten en el desarrollo. Las clases asistente son automáticamente cargadas por Kohana. Los asistentes se declaran como métodos estáticos de una clase, por lo que no hay necesidad de instanciar la clase. En este caso, el método "redirect" del asistente "url" es llamado y le dice a Kohana que redirija el navegador al controlador de álbum. Esto evita una nueva inserción (por ejemplo, al pulsar F5).

Los métodos update (actualizar) y delete (borrar) funcionan de la misma forma que create.

El último método get_genres_list obtiene la lista de géneros del modelo ($db_genres_list) y construye un nuevo array ($genres_list) para el control de selección (select) en las vistas.
$db_genres_list  = $this->genre_model->get_list();   
$genres_list  = array();  
 
if(sizeof($db_genres_list) >= 1)  
{  
    foreach($db_genres_list as $item)  
    {  
        $genres_list[$item->id] = $item->name;  
    }  
}  
return $genres_list;


Paso 8: Creando el modelo para el proyecto

Creemos los modelos para nuestra aplicación web. Conviene recordar las convenciones indicadas anteriormente a la hora de crear las clases. A continuación está el código para el modelo del álbum. Crea un archivo llamado album.php en application/models/ y pega en él el siguiente código:

<?php defined('SYSPATH') OR die('No direct access allowed.');

class Album_Model extends Model
{
    private $album_table;
    private $genre_table;

     public function __construct()
        {
              parent::__construct();
         $this->album_table = 'albums';
        $this->genre_table = 'genres';
     }
 
     public function read($id)
     {
        $this->db->where('id', $id);
        $query = $this->db->get($this->album_table);
         return $query->result_array();
    }
     
    public function delete($id)
     {
        $this->db->delete($this->album_table, array('id' => $id));
     }
 
     public function update($id,$data)
     {
        $this->db->update($this->album_table, $data, array('id' => $id));
     }
     
    public function create($data)
     {
           $this->db->insert($this->album_table, $data);
     }
    
    public function get_list()
    {
         $this->db->select('albums.id as id,albums.name as name,albums.author as author, genres.name as genre');  
         $this->db->from($this->album_table);  
         $this->db->join($this->genre_table,'genres.id','albums.genre_id');
         $query = $this->db->get();
         return $query->result_array();
    }
}
Todos los métodos de los modelos usan la sintaxis del constructor de consultas (Query builder). Esta herramienta de Kohana acelera el tiempo de desarrollo con bases de datos y simplifica la creación de consultas.

Dos variables miembro son declaradas al inicio de la clase:
private $album_table;  
private $genre_table;

Estos miembros son privados porque quiero limitar la visibilidad sólo a esta clase. Son los contenedores de los nombres de las tablas de la base de datos.

La primera línea en el método del constructor carga la biblioteca de base de datos de Kohana en $this->db. En las dos siguientes líneas, se inicializan las dos variables miembro.
parent::__construct();  
$this->album_table = 'albums';  
$this->genre_table = 'genres';

La consulta en el método read recupera los registros de álbum que tengan un cierto identificador ("$id").
$this->db->where('id', $id);  
    $query = $this->db->get($this->album_table);   
        return $query->result_array();

La consulta en el método delete elimina la fila de la tabla de álbumes que tenga un cierto identificador ("$id").
$this->db->delete($this->album_table, array('id' => $id));

La consulta en el método update actualiza la fila de la tabla de álbumes que tenga un cierto identificador ("$id") con los nuevos valores del array "$data".
$this->db->update($this->album_table, $data, array('id' => $id));

El array "$data" debe contener nombres de registros como claves del array, y sus valores como valores del array. El array debe tener esta forma:
$data = array(  
    'name'          =>   'album_name',  
    'author'        =>   'author_name',  
    'genre_id'      =>   'genre_id'    
    );

La consulta en el método get_list recupera todas las filas de álbumes.
$this->db->select('albums.id as id,albums.name as name,albums.author as author, genres.name as genre');    
$this->db->from($this->album_table);    
$this->db->join($this->genre_table,'genres.id','albums.genre_id');  
$query = $this->db->get();  
return $query->result_array();

Ahora el modelo para los géneros. Crea un archivo llamado genre.php en application/models/ y pega el siguiente código en él:
<?php defined('SYSPATH') OR die('No direct access allowed.');

class Genre_Model extends Model
{
  private $genre_table;

  function __construct()
  {
    parent::__construct();
    $this->genre_table = 'genres';
  }

  function get_list()
  {
    $query = $this->db->get($this->genre_table);
    return  $query->result_array();        
  }
}

Este modelo es muy simple, por lo que no malgasteré tiempo comentándolo. Los modelos y el controlador están preparados para funcionar. Ahora trabajemos sobre las vistas.


Paso 9: Creando la vista del proyecto

Las vistas son archivos que contienen la capa de presentación de la aplicación. Su propósito es mantener esta información separada de la lógica de la aplicación para facilitar la reusabilidad y mantener el código limpio. Para este proyecto se necesitan tres vistas: una para listar la colección de álbumes, otra para crear un nuevo álbum, y otra para editar un álbum existente.

Crea un archivo llamado list.php en application/views/ y pega el siguiente código en él:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<?php
    echo html::stylesheet(array
        (
         'assets/css/style'
        ),
        array
        (
         'screen'
        ), FALSE);
?>
        <title>CD COLLECTION</title>
        </head>
        <body>
        <?php
                echo html::image('assets/images/add.png');
                echo html::anchor('album/show_create_editor', 'Add new album');
        ?>
        <table class="list" cellspacing="0">
        <tr>
            <td colspan="5" class="list_title">CD Collection</td>
        </tr>    
        <tr>
            <td class="headers">Album name</td>
            <td class="headers">Author</td>
            <td colspan='3' class="headers">Genre</td>
        
        </tr>    
        <?php
            foreach($albums_list as $item)
            {
                echo "<tr>";
                echo "<td class='item'>".$item->name."</td>";
                echo "<td class='item'>".$item->author."</td>";
                echo "<td class='item'>".$item->genre."</td>";
                echo "<td class='item'>".html::anchor('album/delete/'.$item->id,html::image('assets/images/delete.png'))."</td>";        
                echo "<td class='item'>".html::anchor('album/show_update_editor/'.$item->id,html::image('assets/images/edit.png'))."</td>";        
                echo "</tr>";
            }
        ?>
        </table>
        </body>
        </html>

Esta vista muestra una página html que contiene una lista de todos los álbumes. Esta lista ha sido creada usando un bucle foreach que imprime la información en una tabla html. Para cada fila de álbum, hay dos imágenes: una "cruz roja" y una "libro de bolsillo". Enlazan respectivamente al método delete y al método update del controlador. Ambos pasan el identificador del álbum al controlador usando una petición GET. Sobre la lista hay un botón para crear un nuevo álbum. En este código también se usa un asistente html ofrecido por Kohana que acelera las operaciones para escribir páginas html.

Creémos ahora un archivo llamado create.php en application/views/.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<?php
    echo html::stylesheet(array
    (
        'assets/css/style'
    ),
    array
    (
        'screen'
    ), FALSE);
?>
<title>CD COLLECTION</title>
</head>
<body>
<?php echo form::open('album/create'); ?>
<table class='editor'>
<tr>
    <td colspan='2' class='editor_title'>Create new album</td>
</tr>
<?php
    echo "<tr>";
    echo "<td>".form::label('name', 'Name: ')."</td>";
    echo "<td>".form::input('name', '')."</td>";
    echo "</tr>";
    
    echo "<tr>";
    echo "<td>".form::label('author', 'Author: ')."</td>";    
    echo "<td>".form::input('author', '')."</td>";    
    echo "<tr/>";
    
    echo "<tr>";
    echo "<td>".form::label('genre', 'Genre: ')."</td>";    
    echo "<td>".form::dropdown('genre_id',$genres_list)."</td>";
    echo "<tr/>";
    
    echo "<tr>";
    echo "<td colspan='2' align='left'>".form::submit('submit', 'Create album')."</td>";
    echo "</tr>";
?>
</table>
<?php echo form::close(); ?>
</body>
</html>

La última es la vista de actualización. Crea un archivo update.php en application/views/.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<?php
    echo html::stylesheet(array
    (
        'assets/css/style'
    ),
    array
    (
        'screen'
    ), FALSE);
?>
<title>CD COLLECTION</title>
</head>
<body>
<?php echo form::open('album/update'); ?>
<table class='editor'>
<tr>
    <td colspan='2' class='editor_title'>Update album</td>
</tr>
<?php
    echo "<tr>";
    echo "<td>".form::label('name', 'Name: ')."</td>";
    echo "<td>".form::input('name', $name)."</td>";
    echo "</tr>";
    
    echo "<tr>";
    echo "<td>".form::label('author', 'Author: ')."</td>";    
    echo "<td>".form::input('author', $author)."</td>";    
    echo "<tr/>";
    
    echo "<tr>";
    echo "<td>".form::label('genre', 'Genre: ')."</td>";    
    echo "<td>".form::dropdown('genre_id',$genres_list, $genre_id)."</td>";
    echo "<tr/>";
    
    echo "<tr>";
    echo "<td colspan='2' align='left'>".form::submit('submit', 'Update album')."</td>";
    echo "</tr>";
    
?>
</table>
<?php
    echo form::hidden('album_id',$album_id);
    echo form::close();
?>
</body>
</html>

El primero es un editor simple que permite al usuario insertar información sobre un nuevo álbum. Los campos como autor y nombre serán insertados usando una entrada html y el género usando un combo. Una vez que el usuario hace click en el botón create, toda la información se pasa como una petición POST al método create/update del controlador de álbum. Cuando el controlador recibe estas variables posteadas llama al modelo que inserta un nuevo álbum en la base de datos. Los formularios de ambas vistas hacen uso del asistente de formularios.

Para dar un poco de estilo a nuestra aplicación, crea la carpeta assets en la carpeta raíz de Kohana al mismo nivel de la carpeta de aplicaciones. Ábrelo y crea dos nuevas carpetas: css e images.

En la carpeta css crea un nuevo archivo llamado style.css y pega lo siguiente en él:
a {
    font-family: Verdana, Geneva, Arial, Helvetica, sans-serif ;
    font-weight: normal;
    font-size: 12px;
    color: #00F;
    vertical-align:text-top;
}

img {
    border: 0;
}

label {
    font-family: Verdana, Geneva, Arial, Helvetica, sans-serif ;
    font-weight: normal;
    font-size: 12px;
}

input {
    border: 1px solid #000;
}

select {
    width:185px;
}

table.editor
{
    text-align: center;
    font-family: Verdana, Geneva, Arial, Helvetica, sans-serif ;
    font-weight: normal;
    font-size: 11px;
    color: #fff;
    width: 280px;
    background-color: #666;
    border: 0px;
    border-collapse: collapse;
    border-spacing: 0px;
}

table.editor td.editor_title
{
    background-color: #666;
    color: #fff;
    padding: 4px;
    text-align: left;
    font-weight: bold;
    font-size: 16px;
}

table.editor td
{
    padding: 4px;
}

table.list
{
    text-align: center;
    font-family: Verdana, Geneva, Arial, Helvetica, sans-serif ;
    font-weight: normal;
    font-size: 11px;
    color: #fff;
    width: 280px;
    background-color: #666;
    border: 0px;
    border-collapse: collapse;
    border-spacing: 0px;
}

table.list td.item
{
    background-color: #CCC;
    color: #000;
    padding: 4px;
    text-align: left;
    border: 1px #fff solid;
}

table.list td.list_title,table.list td.headers
{
    background-color: #666;
    color: #fff;
    padding: 4px;
    text-align: left;
    border-bottom: 2px #fff solid;
    font-weight: bold;
}

table.list td.list_title
{
    font-size: 16px;
}

table.list td.headers
{
    font-size: 12px;
}

Ahora copia las siguientes imágenes en la carpeta images:

Eso es todo. Apunta el navegador a http://localhost/kohana/index.php/album y deberías ver algo parecido a esto:



Si intentas crear un nuevo álbum o editar uno existente, deberías ver algo similar a esto:



Paso 10: Pensamientos finales

Desde luego, algunas mejoras son necesarias para esta aplicación, pero con muy poco código has creado una pequeña aplicación web. Ahora, sabes cómo usar el patrón MVC con Kohana, y cómo usar las bibliotecas y asistentes de la base de datos. Para aprender más, lee la documentación oficial.

Gracias a Kohana, el mantenimiento de código se convierte en una tarea fácil, y añadir nuevas características es pan comido. Espero que hayas disfrutado del tutorial.

05 diciembre, 2009

No toquéis el "with"

En los últimos tiempos se ha satanizado la orden with, por parte de personas de renombre como el mismo Douglas Crockford. En su artículo With statement considered harmful explica que, a pesar de ser similar a otros lenguajes, la falta en JavaScript de que un bloque tenga un ámbito propio puede fomentar errores difíciles de detectar. Si dentro del bloque del with se asigna un valor a una variable, no hay forma de saber si se está haciendo sobre un miembro del objeto pasado con el with o si se está modificando una variable global:

var bing=1;
with(ooo.eee.ooo.eee.ooo) {
  bing=2;
}
// en este punto, que toma el valor 2? 
// bing==2 o ooo.eee.ooo.eee.ooo.bing==2
De hecho propone en sus recomendaciones para la próxima versión de ECMAScript que está orden sea declarada obsoleta. Sin embargo las cosas no son tan sencillas como parecen. De nuevo, Andrea Giammarchi le saca punta al asunto como ya hizo con la polémica del desconocido segundo parámetro de eval, acusando a los que lo quitaban del lenguaje de perder una oportunidad perfecta de generar un entorno controlado para ejecutar secuencias en las que no se confía. Y también pasó con la propuesta de eliminar callee ante la que Andrea defendió que no se hiciese. Con esta sencilla estructura se consigue mucha más potencia que con el uso tradicional pasando al with una variable con un objeto:
with({o:myreference}){
    o.doStuff();
    o.var1 = "loquesea";
    // etc etc ...
};
También se facilita construcciones commo la siguiente que resulta extremadamente compacta y, por tanto, poco susceptible de ganar espacio con una compresión de código tradicional:
with(document)
  with(documentElement)
    insertBefore(
      createElement("script"),
      firstChild
    )
    .text = "alert(1)"
;
Sólo que además permite aprovechar esa definición en línea del parámetro-objeto para realizar llamadas a funciones, usar el operador terciario ( condicion ? cierto : falso ), o especificar parámetros por defecto:
// ejemplo de argumentos con nombre y sin usar closures
with({
  obj:myObject,
  // valores por defecto en línea por si acaso
  collection:collection || [],
  callback:myCaseAnalyzer,
  // variables locales
  i:0,
  length:collection ? collection.length : 0,
  // variable local no inicializada
  tmp:null
}){
  for(;i < length; ++i){
    tmp = callback.call(obj, collection[i]);
    if(tmp){
      tmp.doStuff();
      collection[i] = tmp;
    };
  };
};
Que resulta ser equivalente a los típicos closures tan utilizados. Otro uso interesante sería la tradicional llamada Ajax:
with(this.XMLHttpRequest ?
  new XMLHttpRequest :
  new ActiveXObject("Microsoft.MSXML")
){
  open("get", "?ajax=true", true);
  onreadystatechange = function(){
    if(readyState === 4)
      // realiza acciones con responseText/XML
      alert(responseText)
    ;
  };
  send(null);
}

Últimos links en indiza.com