Tutorial de Zend Framework 2 - Introducción a BD

El primer post (Hola Mundo) fue un simple sitio estático para introducirnos en el Zend Framework. Este es un ejemplo de un sitio simple con una base de datos.Este post continúa de Tutorial de Zend Framework - Hola Mundo

El ejemplo

“Graffitti” (la doble t es adrede) es un sitio donde se cualquier usuario
ingresa el mensaje que quiera. Para hacerlo simple, no va a ser necesario
estar registrado. Cuando se ingresa un mensaje se da un link para poder
borrarlo, para esto se va a usar una clave.

Estructura

Carpetas y archivos

app/
	controllers/
		IndexController.php
		MensajeController.php
		ErrorController.php
	views/
		scripts/
			index/
				index.phtml
			error/
				error.phtml
		helpers/
			BaseUrl.php
	/models
		Mensajes.php
lib/
	Zend/
public/
	.htaccess
	index.php
	css/
		estilo.css

Carpetas nuevas

  • models/ Aca ponemos las clases que pertenecen al modelo, después van a notar que no es nada del ZF que me parecio lógico ponerlas ahi.
  • views/helpers/ En esta carpeta van a estar las clases helper de la Vista.
  • public/css/ Esta carpeta no tiene nada que ver con ZF es para dar una idea de donde poner los estilos, las imágenes, etc.

Base de datos

CREATE TABLE `mensajes` (
  `id_mensaje` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `mensaje` text NOT NULL,
  `autor` varchar(100) NULL,
  `clave` char(32) NOT NULL,
  `fecha` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id_mensaje`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Códigos

Descargar archivos

public/index.php
<?php

// Definimos la zona para la fecha
date_default_timezone_set('America/Argentina/Buenos_Aires'); 

// Agregamos el directorio donde se encuentra la carpeta Zend/ con todo el ZF
set_include_path(
	get_include_path() . PATH_SEPARATOR
	. '../lib/' . PATH_SEPARATOR
	. '../app/models/' . PATH_SEPARATOR
	. '../app/controllers/'
);

// Dejamos que Zend maneje el include de las clases
// por que registra la funcion __autoload
require_once 'Zend/Loader.php';
Zend_Loader::registerAutoload();

// Creamos un adaptador de la base de datos
$db = Zend_Db::factory('Pdo_Mysql', array(
    'host'     => '127.0.0.1',
    'username' => 'root',
    'password' => '',
    'dbname'   => 'decilo'
));
// y lo registramos para como default para nuestras tablas
Zend_Db_Table_Abstract::setDefaultAdapter($db);

// Ejecutamos la app
Zend_Controller_Front::run('../app/controllers');

Con la función date_default_timezone_set
establecemos nuestra fecha y hora. También vamos a usarla para formatear la fecha.
En el include path ponemos la ruta de los modelos, además de las otras.

Conexión a la base de datos con Zend_Db

Las clases que participan en el manejo de base de datos son muchas y de varios tipos distintos.
La primeros tipos que podemos mencionar son los adaptadores. Estos son los encargados de conectarse
con algúna base de datos de distintos tipos (MySQL, Postgre, SQLite, Oracle, etc).
La idea es proveer una capa de común interfaz para hacer las distintas peticiones de base de datos.

Algo asi como la extensión PDO de PHP. Para cada acceso de base de
datos existe un adaptador. Incluso existe un adaptador para cada tipo de acceso PDO.

$db = new Zend_Db_Adapter_Pdo_Mysql(array(
    'host'     => 'localhost',
    'username' => 'root',
    'password' => '',
    'dbname'   => 'decilo'
));

En el código index.php lo que usamos fue el factory
de Zend_Db que nos crea la clase según el adaptador que le pasemos como primer parámetro.

Luego lo que hacemos es registrarlo como nuestro adaptador por defecto, para que cada clase Zend_Db_Table
que creemos use este adaptador (a menos que le definamos otro).

Zend_Db_Table_Abstract::setDefaultAdapter($db);

app/controllers/IndexController.php
<?php

class IndexController extends Zend_Controller_Action
{
	public function indexAction()
	{
		$form = MensajeController::getForm();
		if(strtolower($_SERVER['REQUEST_METHOD']) == 'post')
		{
			if($form->isValid($_POST))
			{
				$datosMensaje = $form->getValues();
				$mensajeNuevo = Mensajes::insertar($datosMensaje);
				$form->populate(array('mensaje' => '')); // limpia el campo mensaje
				$this->view->mensaje = $mensajeNuevo;
			}
		}
		$this->view->mensajeForm = $form;
		$this->view->mensajesHoy = Mensajes::hoy();
		$this->view->eliminado = $this->getRequest()->getParam('eliminado');
	}
}

app/controllers/ErrorController.php
<?php
/**
 * Gestor de errores
 */
class ErrorController extends Zend_Controller_Action
{
	public function errorAction()
	{
        $errors = $this->_getParam('error_handler');
        switch ($errors->type) {
            case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER:
            case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION:
                $this->view->mensaje = 'No existe el controlador o la accion';
                break;
            default:
                $this->view->mensaje = $errors->exception->getMessage();
                break;
        }
	}
}

Por defecto cuando hay un error al ejecutar el FrontController ZF intenta
ejecutar el controlador Error y la acción error (ErrorController->errorAction()).

Desde ahi podemos obtener el error a travez del parámetro error_handler.

app/controllers/MensajeController.php
<?php

class MensajeController extends Zend_Controller_Action
{
	/**
	 * Elimina el mensaje
	 */
	public function eliminarAction()
	{
		$request = $this->getRequest();
		// obtiene los parametros pasados por GET
		$clave = $request->getParam('clave', false);
		$idMensaje = $request->getParam('id', false);
		$eliminado = Mensajes::eliminar($idMensaje, $clave);
		// ejecuta IndexController->indexAction() pasandole eliminado como parametro GET
		$this->_forward('index', 'index', null, compact('eliminado'));
	}
	/**
	 * Formulario de mensaje
	 *
	 * @return Zend_Form
	 */
	public static function getForm()
	{
		// creamos el formulario
		$form = new Zend_Form( array(
			'method' => 'post',
			'elements' => array(
				// campo mensaje
				'mensaje' => array('textarea', array(
					'required' => 'true',
					'label' => 'Mensaje *',
					'filters' => array('StringTrim'),
				)),
				// campo autor
				'autor' => array('text', array(
					'label' => 'Autor',
					'value' => 'Anónimo',
					'filters' => array('StringTrim'),
				)),
				// botón enviar
				'submit' => array('submit', array(
					'label' => 'Enviar graffitti »',
				))
			)
		));
		return $form;
	}

}

Zend_Form

Zend_Form
es un paquete que intenta cubrir la validación,
el filtrado y el renderizado
de los formularios.
Estas clases son muy útiles para crear formularios de manera rápida, desde

PHP y sin preocuparnos mucho por el HTML.

app/views/scripts/index/index.phtml
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Graffitti</title>

<link href="<?=$this->baseUrl()?>/css/estilo.css" rel="stylesheet" type="text/css" media="screen" />
</head>
<body>
<div id="wrapper">

<div id="header">
<h1><a href="<?=$this->baseUrl()?>/">Graffitti</a></h1>

</div>

<div id="content">

<div id="nuevo_mensaje">
<?php if($this->eliminado): ?>
<h2>Graffitti borrado</h2>
<p>Tú graffitti ha sido borrado correctamente.</p>

<?php endif; ?>
<?php
if($this->mensaje):
$linkEliminar = $this->url(array(
'controller' => 'mensaje',
'action' => 'eliminar',
'id' => $this->mensaje->id_mensaje,
'clave' => $this->mensaje->clave
));
?>

<h2>Graffitti publicado</h2>
<p>
Puedes eliminarlo mas tarde con este link
<input class="url_eliminar" value="http://<?=$_SERVER['HTTP_HOST']?><?=$linkEliminar?>"
onclick="this.select()" readonly="readonly" />
o ahora desde <a href="<?=$linkEliminar?>">borrar mensaje</a>.

</p>
<?php endif; ?>
<h2>Decí lo que quieras</h2>
<?=$this->mensajeForm?>
</div>

<?php if(count($this->mensajesHoy)): ?>

<div id="mensajes_hoy">
<h2>Graffittis de hoy</h2>
<div id="mensajes">
<ul>
<?php foreach($this->mensajesHoy as $mensaje): ?>
<li>
<p><?=$mensaje->mensaje?></p>

»
<span class="autor">
escrito por <span title="autor"><?=$mensaje->autor?></span>
</span>
</li>
<?php endforeach; ?>
</ul>

</div>
</div>
<?php endif; ?>

</div>

</div>
</body>
</html>

app/views/scripts/error/error.phtml
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Graffitti</title>
</head>
<body>

<h1>Error</h1>
<p>
<?=$this->mensaje?>
</p>
</body>
</html>

app/views/helpers/BaseUrl.php
<?php

class Zend_View_Helper_BaseUrl
{
	function baseUrl()
	{
		$fc = Zend_Controller_Front::getInstance();
		return $fc->getBaseUrl();
	}
}

Los helpers de la vista
son clases simples que le agregan funcionalidad a la vista sin necesidad de modificar
la clase que gestiona la vista (Zend_View en nuestro caso).
Los helpers tienen un prefijo (por defecto es Zend_View_Helper_) y luego el nombre
por ejemplo MiHelper. Deben tener un método miHelper() que es el que va a llamarse
desde la vista.

<?php

class Zend_View_Helper_MiHelper
{
	public function miHelper()
	{
		// ejecutar o retornar algo para la vista
	}
}
<p>Hola, esta es la llamada a mi helper</p>
<p><strong><?=$this->miHelper()?></strong></p>
app/models/Mensajes.php
<?php

class Mensajes extends Zend_Db_Table
{
	/**
	 * Obtiene los mensajes de hoy
	 * @return Zend_Db_Table_Rowset
	 */
	static public function hoy()
	{
		$tablaMensajes = new self();
		$where = 'fecha >= CURRENT_DATE';
		$order = 'fecha DESC';
		return $tablaMensajes->fetchAll($where, $order);
	}
	/**
	 * Guarda en la tabla y retorna el row guardado.
	 *
	 * @param array $datos
	 * @return Zend_Db_Table_Row
	 */
	static public function insertar(array $datos)
	{
		// creamos la clave
		$datos['clave'] = md5('prefijo_seguridad' . time());
		$tablaMensajes = new self();
		// crea un objeto Zend_Db_Table_Row asignandole los datos.
		$mensajeNuevo = $tablaMensajes->createRow($datos);
		if($mensajeNuevo->save())
		{
			return $mensajeNuevo;
		}
	}
	/**
	 * Elimina el mensaje.
	 *
	 * @param mixed $idMensaje
	 * @param string $clave
	 * @return boolean
	 */
	static public function eliminar($idMensaje, $clave)
	{
		$tablaMensajes = new self();
		$db = self::getDefaultAdapter();
		return $tablaMensajes->delete(array(
			'id_mensaje = ' . $db->quote($idMensaje),
			'clave = ' . $db->quote($clave)
		));
	}

}

Las tablas y los campos, Zend_Db_Table y Zend_Db_Table_Row

A diferencia de otros frameworks que usan el patrón Active Record,
ZF usa el Table Data Gateway.

Este patrón mantiene todas las operaciones de base de datos (select, update, etc.)
en una misma clase. Para cada tabla de la base de datos se define una clase que
extiende de Zend_Db_Table_Abstract.
Nota: en esta clase no hace falta declarar nada, por los siguientes motivos:

  1. Ya declaramos un adaptador default, va a usar ese.
  2. El nombre de la tabla se corresponde con el nombre de la clase, asi que no hace
    falta definirlo.
  3. ZF va a leer el resto de los datos del schema, ya va a saber cual es la PK,

    los campos, de que tipo es cada uno, etc.

Cuando hacemos un fetchAll lo que obtenemos es un Zend_Db_Table_Rowset, que
es una colección de objetos Zend_Db_Table_Row. Este ultimo tipo de objeto es
una implementación del patrón Row Data Gateway.
Esto quiere decir que se puede acceder a cada columna de la tabla en Zend_Db_Table_Row
al estilo $row->columna.
También obtenemos ese objeto cuando hacemos un createRow().

Tags: , , ,

6 Responses to “Tutorial de Zend Framework 2 - Introducción a BD”

  1. AlejoLp Says:

    Hola!

    Buena explicación!

    Casualmente hace unos dias estuve mirando Zend Framework y una de las cosas que noté fue justamente lo de BaseUrl. Es MUY RARO que no lo hayan incluído dentro de Zend_View de forma nativa.

    Saludos

  2. daniel Says:

    Hola Rodrigo;
    estuve buscando material sobre zend framework, y di con tu sitio, los estuve viendo y la verdad que saque bastante informacion, espero que sigas publicando articulos porque estan claramente explicados.

    Saludos Daniel (montevideo - uruguay)

  3. Paola Says:

    Es una pena que no hayas escrito mas articulos de Zend Framework, ya que los explicas muy bien.

  4. Albo Says:

    Gracias Paola, estoy bastante mal con los tiempos pero en cuanto pueda voy a volver a rehacer estos tutoriales ya que la versión 1.8 de Zend Framework cambió un poco las cosas.

    Saludos

  5. Diego Says:

    Que cambió en la version 1.8 ?

  6. Albo Says:

    Basicamente en la 1.8 cambio la forma de hacer bootstrapping [1], se agregó el concepto de Zend_Application y los recursos y se agregó Zend_Tool (antes estaba a prueba ahora esta en la versión oficial) que es una herramienta similar a la de RoR y Symfony para crear las aplicaciones y los modulos desde ahi y que cree todo la estructura del directorio, entre otras cosas.

    [1] http://groups.google.com/group/webandbeer/browse_thread/thread/5141ce69642a3ba3