Con este artículo comienzo una serie de tutoriales de Kohana.
Kohana es un Framework PHP, es decir: un entorno de trabajo "prefabricado" que ya nos soluciona algunos de los problemas más típicos de la programación.
Como me gusta avanzar programando, programaremos desde la primera línea. Para ello vamos a tener como objetivo la creación de un "ToDoList Manager", es decir: vamos a crear una web que nos permita gestionar tareas.
En el ejemplo se accederá a base de datos. Debo decir que me ha costado mucho encontrar ejemplos válidos que accedan a base de datos.
Prerrequisitos
Voy a utilizar Kohana versión 3.0.8. Seguramente sea compatible con las siguientes.
Un poquito de teoría
Kohana sigue un patrón Modelo-Vista-Controlador (MVC). Hay mucho escrito sobre este patrón, por lo que no me extenderé demasiado:
- Modelo: Los datos tal cual se guardan.
- Vista: La representación en la pantalla.
- Controlador: La lógica de negocio.
Configuración
Vamos a configurar Kohana. Hemos desplegado el framework y vamos al fichero application/bootstrap.php, en el que vamos a habilitar algunas cosas. No podéis copiar a ciegas; aquí sólo os doy un ejemplo de lo que tengo yo puesto. Tened también en cuenta que no lo pego entero, ya que nos valen la mayor parte de los valores por defecto:
<?php Kohana::init(array( 'base_url' => '/~miguel/kohana', 'index_file' => 'index.php', )); Kohana::modules(array( 'database' => MODPATH.'database', // Database access 'orm' => MODPATH.'orm', // Object Relationship Mapping )); ?>
Como es sencillo, voy rápido: Le decimos a Kohana dónde está nuestra aplicación y cuál es el archivo que va a renderizar las cosas. Como véis, yo lo tengo en '/~miguel/kohana'. Después habilitamos algunos módulos, que servirán para acceder a base de datos.
Si sois tan pijos como yo y os gustan las URL's limpias, mejor si indicáis el "index_file" de la manera que se indica arriba y después creáis un .htaccess como el siguiente:
# Turn on URL rewriting RewriteEngine On # Installation directory RewriteBase /~miguel/kohana/ # Protect hidden files from being viewedOrder Deny,Allow Deny From All # Protect application and system files from being viewed RewriteRule ^(?:application|modules|system)\\b.* index.php/$0 [L] # Allow any files or directories that exist to be displayed directly RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d # Rewrite all other URLs to index.php/URL RewriteRule .* index.php/$0 [PT]
Finalmente, configuramos la base de datos en el archivo modules/database/config/database.php. Ya tenéis un patrón y es autoexplicativo. Centraros en 'default' y olvidaros de 'alternate' (o borradlo si queréis).
El modelo
El modelo es lo que existe en la base de datos, básicamente. Así que comenzamos por crear una base de datos:
CREATE TABLE `tasks` (
'id' int(11) NOT NULL AUTO_INCREMENT,
'title' varchar(60) NOT NULL,
'description' longtext NOT NULL,
'created' datetime NOT NULL,
'duedate' datetime DEFAULT NULL,
PRIMARY KEY ('id'),
UNIQUE KEY 'title' ('title')
) ENGINE=MyISAM AUTO_INCREMENT=17 DEFAULT CHARSET=utf8;
Ahora tenemos que decirle a Kohana que use esta tabla. Para ello basta crear el archivo application/classes/model/task.php:
<?php defined("SYSPATH") OR die("No Direct Script Access");
class Model_Task extends ORM
{
}
?>
Bien. Paremos. ¿Para qué queremos un archivo vacío?
Realmente no está vacío. Para empezar, estamos restringiendo el acceso. Todos nuestros archivos PHP salvo las vistas deberían comenzar así, por seguridad. Además, estamos heredando de ORM, y esta clase padre ya nos dará todo lo que necesitamos: accederá a la base de datos y verá qué campos necesitamos aquí.
Personalmente, tanto automatismo no me gusta demasiado y prefiero indicarle qué tabla es la que quiero emplear:
<?php defined("SYSPATH") OR die("No Direct Script Access");
class Model_Task extends ORM
{
public $_table_name = "tasks";
}
?>
Y hemos terminado el modelo. Fácil, ¿no?
El Controlador
Creamos el archivo application/classes/controller/task.php:
<?php defined('SYSPATH') OR die('No Direct Script Access');
class Controller_Task extends Controller
{
public function __construct(Kohana_Request $request)
{
parent::__construct($request);
}
public function action_index ()
{
$vista = View::factory('task_index');
$vista->set ('task_list', $this->get_tasklist());
$this->request->response = $vista;
}
public function action_add ()
{
$vista = View::factory('task_add');
$this->request->response = $vista;
if ( ! isset($_POST) OR ! array_key_exists('title', $_POST))
return;
if ( $this->create_task () )
$vista->set ('message', 'Task added');
else
$vista->set ('message', 'Error al añadir');
}
private function create_task ()
{
$newtask = ORM::factory('task');
$newtask->title = $_POST['title'];
$newtask->description = $_POST['description'];
$newtask->created = strftime('%Y-%m-%d %H.%M.%S');
$newtask->duedate = $this->getAsDate($_POST['duedate']);
$newtask->save();
return $newtask->saved();
}
private function getAsDate ($value)
{
$result = null;
if (isset ($value)) {
$result = $value." 00.00.00";
}
print_r($result);
return $result;
}
private function get_tasklist ()
{
$tasks = ORM::factory('task')->find_all();
$result = array();
foreach ($tasks as $task)
$result[] = $task;
return $result;
}
}
?>
Analicemos un poco este código.
Lo primero de todo es la seguridad, por lo que evitamos el acceso directo.
A continuación se crea una clase "Controller_Task" que hereda de "Controller". El nombre de la clase está sujeto a una convención de nombres, y heredamos de Controller porque somos un controlador :D
El constructor no tiene mucho misterio: tenemos que implementarlo y llamamos al constructor padre. No tenemos nada más que inicializar, por lo que podemos terminar.
La función "action_index" es, como el nombre indica, una acción. Será la acción por defecto que se ejecute cuando alguien realice una solicitud sobre el controlador. Si simplificamos su código hasta un simple "echo 'hola';", podremos comprobar que todo funciona accediendo a la misma dirección que pusimos en la configuración, en 'base_url', seguido del nombre básico del controlador, es decir, 'task'. En mi caso, la dirección es 'http://localhost:/~miguel/kohana/task'. Si no habéis activado las URLs limpias, tendréis que indicar quién debe renderizar esa página, accediendo a algo como 'http://localhost:/~miguel/kohana/index.php/task'.
Como vemos, la acción "action_index" crea una vista a partir de una plantilla y la devuelve como respuesta. Veremos más sobre estas vistas en el apartado siguiente.
La acción "action_add" va a tratar de crear una tarea nueva. Además, voy a reutilizar esta función. Si viene sin datos, sólo mostrará la ventana. Si viene con datos, creará una nueva tarea. Por ello, lo primero que hago es crear la vista que voy a devolver, ya que es común a ambos casos de uso, y después trato de insertarla, guardándome un mensaje en cada caso.
La función "create_task" es quien realmente crea la tarea. Para ello hace uso del ORM, obteniendo los valores de la variable $_POST. Las fechas tengo que modificarlas a mano para que tengan el formato esperado por mysql ("YYYY-MM-DD hh.mm.ss").
La función "get_tasklist" serializa la lista de tareas para pasársela a la vista.
Cosas importantes
Aquí hay dos cosas importantes: el uso de la base de datos y el paso de parámetros con la vista.
Para usar la base de datos, estamos usando el ORM. Éste está bastante bien documentado.
Para obtener datos de la vista, los obtenemos de la variable $_POST, ya que éste ha sido el método que he decido utilizar para el envío de datos (y suele ser el más recomendable). Los nombres de los campos son los que he puesto en la vista y que veremos más adelante.
Para enviar datos a la vista, lo que hago es establecerlos al objeto "View" con la función "set". Esto funcionará como una tabla Hash cuando esté en la vista.
Las vistas
Antes de entrar en materia, como no me gusta repetirme, crearemos un par de archivos. Todas las vistas deben ir en application/views.
Los dos archivos a crear no tienen mayor misterio y no entraré a describirlos:
header.php:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Vista para Kohana PHP
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/base/jquery-ui.css" rel="stylesheet" type="text/css"/>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/jquery-ui.min.js"></script>
<script type="text/javascript">
Date.firstDayOfWeek = 1;
Date.format = 'yyyy/mm/dd';
$(document).ready(function() {
$(".date").datepicker({ dateFormat: 'yy-mm-dd', firstDay: 1 });
});
</script>
</head>
<body>
footer.php:
</body> </html>
Sí, hago algo de uso de jquery, pero no os asustéis que sólo es para la inserción de fechas. El resto de la aplicación será horrible :-P
Vamos con el índice:
task_index.php:
<?php include 'header.php'; ?>
<table>
<thead>
<tr>
<th>Title
<th>Creation
<th>Due date
<th>Planned date
<th>Completed
</tr>
</thead>
<tbody>
<?php
foreach ($task_list as $task)
{
echo "";
echo ''.$task->title." ";
echo "".$task->created." ";
echo "".$task->duedate." ";
echo " ";
}
?>
</tbody>
</table>
<?php echo "".html::anchor("task/add","Add new task"); ?>
<?php include "footer.php"; ?>
Como en la función del controlador "action_index" insertamos la variable "$task_list", ahora podemos recuperarla y recorrerla. De esta manera creamos una vista horrible. Dejo como tarea ponerla bonita, ya que eso no es el tema de este tutorial.
Nos queda poder añadir tareas (task_add.php):
<?php include 'header.php'; ?>
<?php
if (isset($message))
{
print $message;
}
print html::anchor('task','List of tasks');
print form::open();
print form::label('title', 'Título');
print form::input('title');
print form::textarea('description');
print form::label('duedate', 'Due Date:');
print form::input('duedate', null, array ('class'=>'date') );
print form::submit('submit', 'Send');
print form::close();
?>
<?php include 'footer.php'; ?>
En este caso he creado un formulario utilizando las funciones de Kohana. Como véis, los nombres de los campos se corresponden con los que utilicé en la función "create_task".
Fin
Como decían en en 1,2,3... "hasta aquí puedo leer". Como primera aplicación me parece mucho más interesante que el triste "hola mundo", ya que tiene todos los componentes de una aplicación real: modelo, vista y controlador (el "hola mundo" no tiene más que controlador), acceso a bases de datos, etc.
No he entrado con el "update_task" ya que no es más complicado que el "create_task". Lo dejo como ejercicio.
Habrá segunda parte de este tutorial.
Referencias
No hay mucha documentación de Kohana. Sin embargo, el ORM de Kohana sí está bien documentado y no creo que tengáis mayor problema con esa parte. Siempre prefiero utilizar sistemas que me eviten el SQL a mano, ya que éste puede tener "sql-injection".
Acordáos de seguir las Ajaxman o el de Christian Gilè, sin contar los tutoriales de la propia página de Kohana.
Espero que os haya resultado interesante.
Hola MagMax, estoy teniendo un problema con el constructrut me tira:
ResponderSuprimir"ErrorException [ Recoverable Error ]: Argument 2 passed to Kohana_Controller::__construct() must be an instance of Response, none given, called in C:\wamp\www\task\application\classes\controller\task.php on line 7 and defined",
la verdad que no se a que se debe, le falta un parametro response pero en la documentacion oficial no esta, si sabrias que es seria genial, Saludos Fede
Hola.
ResponderSuprimirAntes de nada, disculparme, ya que en el tránsito de Drupal a Blogger parte del código se cambió a lo que no debía.
Si has copi-pasteado algo de código de este artículo, revísalo porque puede tener dos comillas en lugar de comillas dobles. Ya he solucionado el problema para que no vuelva a pasar (en este artículo).
Fede, ¿Te importaría darnos las líneas 6-8 del archivo C:\wamp\www\task\application\classes\controller\task.php?
Ayudarían a investigar :D
Un saludo.
Tengo algo más de ayuda:
ResponderSuprimirAbre el archivo "system/classes/kohana/controller.php", que estará dentro de tu proyecto. Ahí está la función "__construct" y toda la documentación que necesitas :D
En mi caso, la función tiene este aspecto:
public function __construct(Kohana_Request $request)
Lo que quiere decir que no necesita más que un parámetro, de tipo Kohana_Request. Tú le estás pasando dos o bien tu función requiere dos.
Un saludo.
Gracias Mag por las respuestas, te paso las lineas del construcor:
ResponderSuprimir...
public function __construct(Kohana_Request $request)
{
parent::__construct($request);
}
...
Y lo que hay el archivo "kohana/controller.php" es algo asi:
...
public function __construct(Kohana_Request public function __construct(Request $request, Response $response)
{
// Assign the request to the controller
$this->request = $request;
// Assign a response to the controller
$this->response = $response;
}
...
Yo tengo la ultima version de kohana v3.2 la documentacion dice en algun lado lo que pusiste y en otro lado dice que necesita $request y $response como parametros obligatorios, no se que ha que se debe el error, si le saco el constructor me aparece el navegador en blanco.
Saludos
Fede
Corrijo las lineas del "kohana/controller.php" copie mal son asi:
ResponderSuprimir...
public function __construct(Request $request, Response $response)
{
// Assign the request to the controller
$this->request = $request;
// Assign a response to the controller
$this->response = $response;
}
...
Hmmm...
ResponderSuprimirTemo que te voy a utilizar de conejillo de indias, ya que no he probado nada de lo que te voy a contar.
He estado mirando Kohana 3.2.0 y tienes toda la razón del mundo: en esta versión le han metido un parámetro más al constructor. Cuando escribí la receta lo hice con la 3.0.8, y os aseguro que funcionaba (ya sabéis: "a mí me funciona").
Pero que cunda el pánico. Analicemos la nueva situación. Es Kohana quien va a llamar a nuestro constructor, y nuestro constructor está heredando de Kohana, por lo que debería darle lo que pide...
Prueba con este constructor:
public function __construct(Kohana_Request $request, Kohana_Response $response)
{
parent::__construct($request, $response);
}