Archivo de marzo, 2011

Symfony: El modelo (I)

0

Las bases de datos son relacionales. PHP 5 y Symfony están orientados a objetos. Para acceder de forma efectiva a la base de datos desde un contexto orientado a objetos, es necesaria una interfaz que traduzca la lógica de los objetos a la lógica relacional. Esta interfaz se llama ORM (object-relational mapping) o «mapeo de objetos a bases de datos», y está formada por objetos que permiten acceder a los datos y que contienen en sí mismos el código necesario para hacerlo.

La utilización de objetos en vez de registros y de clases en vez de tablas, tiene otra ventaja: permite añadir métodos accesores en los objetos que no tienen relación directa con una tabla. Si se dispone por ejemplo de una tabla llamada cliente con dos campos llamados nombre y apellidos, puede que se necesite un dato llamado NombreCompleto que incluya y combine el nombre y los apellidos. En el mundo orientado a objetos, es tan fácil como añadir un método accesor a la clase Cliente. Desde el punto de vista de la aplicación, no existen diferencias entre los atributos Nombre, Apellidos, NombreCompleto de la clase Cliente. Solo la propia clase es capaz de determinar si un atributo determinado se corresponde con una columna de la base de datos.

[codesyntax lang=»php» title=»Los métodos accesores en la clase del modelo permiten ocultar la estructura real de la tabla de la base de datos»]

<?php

public function getNombreCompleto()
{
  return $this->getNombre().' '.$this->getApellidos();
}

[/codesyntax]

Todo el código repetitivo de acceso a los datos y toda la lógica de negocio de los propios datos se puede almacenar en esos objetos. Imagina que se ha definido la clase CarritoCompra en la que se almacenan Productos (que son objetos). Para obtener el precio total del carrito de la compra antes de realizar el pago, se puede crear un método que encapsula el proceso de cálculo:

[codesyntax lang=»php» title=»Los métodos accesores ocultan la lógica de los datos»]

<?php

public function getTotal()
{
  $total = 0;
  foreach ($this->getProductos() as $producto)
  {
    $total += $producto->getPrecio() * $producto->getCantidad();
  }

  return $total;
}

[/codesyntax]

 

ESQUEMA DE BASE DE DATOS DE SYMFONY

Para crear el modelo de objetos de datos que utiliza Symfony, se debe traducir el modelo relacional de la base de datos a un modelo de objetos de datos. Para realizar ese mapeo o traducción, el ORM necesita una descripción del modelo relacional, que se llama «esquema» (schema). En el esquema se definen las tablas, sus relaciones y las características de sus columnas.

La sintaxis que utiliza Symfony para definir los esquemas hace uso del formato YAML. Los archivos schema.yml deben guardarse en el directorio miproyecto/config/.

[codesyntax lang=»text» title=»Ejemplo de schema.yml»]

propel:
  blog_articulo:
    _attributes:   { phpName: Articulo }
    id:
    titulo:        varchar(255)
    contenido:     longvarchar
    created_at:
  blog_comentario:
    _attributes:   { phpName: Comentario }
    id:
    articulo_id:
    autor:         varchar(255)
    contenido:     longvarchar
    created_at:

[/codesyntax]

 

SINTAXIS BÁSICA DE LOS ESQUEMAS

En el archivo schema.yml, la primera clave representa el nombre de la conexión. Puede contener varias tablas, cada una con varias columnas. Siguiendo la sintaxis de YAML, las claves terminan con dos puntos (:) y la estructura se define mediante la indentación con espacios, no con tabuladores.

Cada tabla puede definir varios atributos, incluyendo el atributo phpName (que es el nombre de la clase PHP que será generada para esa tabla). Si no se menciona el atributo phpName para una tabla, Symfony crea una clase con el mismo nombre que la tabla al que se aplica las normas del camelCase.

Las tablas contienen columnas y el valor de las columnas se puede definir de 3 formas diferentes:

  • Si no se indica nada, Symfony intenta adivinar los atributos más adecuados para la columna en función de su nombre y de una serie de convenciones. Por ejemplo, en el listado anterior no es necesario definir la columna id. Symfony por defecto la trata como de tipo entero (integer), cuyo valor se auto-incrementa y además, clave principal de la tabla. En la tabla blog_comentario, la columna articulo_id se trata como una clave externa a la tabla blog_articulo (las columnas que acaban en _id se consideran claves externas, y su tabla relacionada se determina automáticamente en función de la primera parte del nombre de la columna). Las columnas que se llaman created_at automáticamente se consideran de tipo timestamp. Para este tipo de columnas, no es necesario definir su tipo. Esta es una de las razones por las que es tan fácil crear archivos schema.yml.
  • Si sólo se define un atributo, se considera que es el tipo de columna. Symfony entiende los tipos de columna habituales: boolean, integer, float, date, varchar(tamaño), longvarchar (que se convierte, por ejemplo, en tipo text en MySQL), etc. Para contenidos de texto de más de 256 caracteres, se utiliza el tipo longvarchar, que no tiene tamaño definido (pero que no puede ser mayor que 65KB en MySQL). Los tipos date y timestamp tienen las limitaciones habituales de las fechas de Unix y no pueden almacenar valores anteriores al 1 de Enero de 1970. Como puede ser necesario almacenar fechas anteriores (por ejemplo para las fechas de nacimiento), existe un formato de fechas «anteriores a Unix» que son bu_date and bu_timestamp.
  • Si se necesitan definir otros atributos a la columna (por ejemplo su valor por defecto, si es obligatorio o no, etc.), se indican los atributos como pares clave: valor.

Las columnas también pueden definir el atributo phpName, que es la versión modificada de su nombre según las convenciones habituales (Id, Titulo, Contenido, etc) y que normalmente no es necesario redefinir.

 

LAS CLASES DEL MODELO

Como ya has podido leer, Symfony a través de Propel genera una clase para cada tabla. Para generar esas clases simplemente, una vez hayas terminado de crear el esquema del modelo, en la consola escribe:

> php symfony propel:build-model

Al ejecutar ese comando, se analiza el esquema y se generan las clases base del modelo, que se almacenan en el directorio lib/model/om/ del proyecto:

  • BaseArticulo.php
  • BaseArticuloPeer.php
  • BaseComentario.php
  • BaseComentarioPeer.php

Además, se crean las verdaderas clases del modelo de datos en el directorio lib/model/:

  • Articulo.php
  • ArticuloPeer.php
  • Comentario.php
  • ComentarioPeer.php

Sólo se han definido dos tablas y se han generado ocho archivos. Aunque este hecho no es nada extraño, merece una explicación.

Puede ser necesario añadir métodos y propiedades personalizadas en los objetos del modelo (piensa por ejemplo en el método getNombreCompleto()). También es posible que a medida que el proyecto se esté desarrollando, se añadan tablas o columnas. Además, cada vez que se modifica el archivo schema.yml se deben regenerar las clases del modelo de objetos mediante el comando propel-build-model. Si se añaden los métodos personalizados en las clases que se generan, se borrarían cada vez que se vuelven a generar esas clases.

Las clases con nombre Base del directorio lib/model/om/ son las que se generan directamente a partir del esquema. Nunca se deberían modificar esas clases, porque cada vez que se genera el modelo, se borran todas las clases.

Por otra parte, las clases de objetos propias que están en el directorio lib/model heredan de las clases con nombre Base. Estas clases no se modifican cuando se ejecuta la tarea propel:build-model, por lo que son las clases en las que se añaden los métodos propios.
[codesyntax lang=»php» title=»Archivo de ejemplo de una clase del modelo, en lib/model/Articulo.php»]

<?php

class Articulo extends BaseArticulo
{
}

[/codesyntax]

Articulo y Comentario son clases objeto que representan un registro de la base de datos. Permiten acceder a las columnas de un registro y a los registros relacionados. Por tanto, es posible obtener el título de un artículo invocando un método del objeto Articulo:

[codesyntax lang=»php» title=»Las clases objeto disponen de getters para los registros de las columnas»]

<?php

$articulo = new Articulo();

// ...
$titulo = $articulo->getTitulo();

[/codesyntax]

ArticuloPeer y ComentarioPeer son clases de tipo «peer«; es decir, clases que tienen métodos estáticos para trabajar con las tablas de la base de datos. Proporcionan los medios necesarios para obtener los registros de las tablas. Sus métodos devuelven normalmente un objeto o una colección de objetos de la clase objeto relacionada:

[codesyntax lang=»php» title=»Las clases «peer» contienen métodos estáticos para obtener registros de la base de datos»]

<?php

// $articulos es un array de objetos de la clase Articulo
$articulos = ArticuloPeer::retrieveByPks(array(123, 124, 125));

[/codesyntax]

#symfony, I18N, UTF-8 y Dreamweaver

0

Supongo que ya sabrás de lo que voy a hablar, sí, codificación de caracteres y el jodío de Dreamweaver. Te cuento:

Estoy haciendo algunas pruebas con Symfony y su sistema de internacionalización ( I18N ), y para ello he hecho que el charset que muestre la plantilla sea utf-8 (además de las tablas de la BD y las conexiones desde y hacía la BD), lo curioso es que cuando he ido a verlo en el navegador el resultado me aparecía con chinos (unos cuadrados (aunque pueden ser otros símbolos) que sustituyen a las letras con tilde), total que me he tirado todo el día dándole vueltas al tema, y el problema lo tenía en Dreamweaver que, por defecto, guarda los archivos en «Europeo occidental» y no en utf-8.

Te explico todo lo que he modificado en el proyecto de Symfony, por si te pasa algo parecido puedas comparar. Con esta configuración que te voy a mostrar el sistema de internacionalización de Symfony funciona al 100%:

Antes de nada te informo que esto está pensado para Symfony 1.4, versiones superiores o inferiores pueden necesitar una configuración distinta. Cuando hablo de Dreamweaver me refiero a la versión CS3, aunque muy probablemente algo parecido haya en versiones superiores.

settings.yml

Doy por hecho que ya has creado el proyecto, al menos una aplicación y un módulo con el que hacer pruebas. Nos vamos al archivo settings.yml de la app y añadimos:

 

[codesyntax lang=»text» title=»Configuración del archivo settings.yml para internacionalización»]

all:
  .settings:

    # Indicamos la cultura por defecto
    # Aquí poned la que os interese
    default_culture: es_ES   

    # Indicamos la codificación de caracteres
    charset: utf-8

    # Esto lo dejo a tu elección
    # Puedes escribir esta línea para que el helper I18N esté
    # en todas las plantillas de forma global
    # Lo bueno de usar esta opción es que puedes añadir
    # más helpers:
    # standard_helpers: [I18N,text, etc]
    standard_helpers: [I18N]  

    # O puedes escribir esta otra línea, pero
    # en cada una de las plantillas tendrás que incluir
    # al prinicipio <?php use_helper('I18N') ?>
    # la decisión es tuya
    i18n: true

[/codesyntax]

 

routing.yml

Modificamos este archivo para indicar el módulo que se mostrará como página de inicio (homepage):

 

[codesyntax lang=»text»]

# Esto es lo que está por defecto
homepage:
  url:   /
  param: { module: default, action: index }

# Lo único que cambio es el nombre del módulo, que en mi caso es 'login'
homepage:
  url:   /
  param: { module: login, action: index }

[/codesyntax]

 

 

view.yml

Este archivo creo que no hacia falta modificarlo para la internacionalización, pero por si acaso:

[codesyntax lang=»text»]

  metas:
    language:     es

[/codesyntax]

 

indexSuccess.php del módulo

Añadimos el texto que vamos a probar:

[codesyntax lang=»php»]

// No voy a poner todo el código que tengo en mi plantilla
// así que pongo solo un ejemplo

<?php echo __('Hola Mundo!') ?>

// Esto te mostrará el texto en español

[/codesyntax]

Bien, como le hemos dado el valor «es_ES» a «default_culture«, Symfony no mostrará ninguna traducción sino el valor que le hemos indicado en la plantilla. Para hacer una prueba en condiciones vamos a modificar la cultura de un usuario a «en_EN«, esto mantendrá la cultura de todo el proyecto como «es_ES«.

 

actions.class.php del módulo

En la acción Index escribimos lo siguiente:

[codesyntax lang=»php»]

class loginActions extends sfActions
{
 /**
  * Executes index action
  *
  * @param sfRequest $request A request object
  */
  public function executeIndex(sfWebRequest $request)
  {

        // Esta línea cambiará la cultura SÓLO para el usuario
	$this -> getUser() -> setCulture('en_EN');

	return sfView::SUCCESS;
  }
}

[/codesyntax]

Obviamente nos falta crear el archivo que le indicará a Symfony el texto que debe utilizar para su sustitución. A ello voy:

 

english.en.xml

Este archivo se guarda en la carpeta «i18n» de la app, aunque también puedes crear la carpeta dentro del módulo y guardar el archivo allí. Este archivo tiene un formato especial que se debe mantener:

[codesyntax lang=»text»]

<?xml version="1.0" encoding="utf-8"?>
<xliff version="1.0">
  <file orginal="global" source-language="en_EN" datatype="plaintext">
    <body>
      <trans-unit id="1">
        <source>Hola Mundo!</source>
        <target>Hello World!</target>
      </trans-unit>
      <trans-unit id="2">
        <source>soy un texto</source>
        <target>I'm a text</target>
      </trans-unit>
      <trans-unit id="3">
        <source>Adiós</source>
        <target>Bye</target>
      </trans-unit>
    </body>
  </file>
</xliff>

[/codesyntax]

Cosas importantes respecto de este archivo:

  • Le puedes poner cualquier nombre pero debe acabar en *.culture.xml, por ejemplo: login.en.xml, registro.en.xml, administracion.fr.xml, comentario.it.xml.
  • También se puede poner la cultura completa, es decir, en vez de solo *.en.xml puedes nombrarlo como *.en_EN.xml.
  • El parámetro «source-language» siempre debe indicar la cultura que se va a traducir, en este caso es el ingles (en_EN).
  • Cada texto a traducir está dentro de la etiqueta <trans-unit>, que tiene el atributo id, pues bien, cada frase a traducir debe aumentar el id ( 1,2,3,4,5….500 etc)
  • No es necesario que todas las frases estén en un solo archivo, puede haber varios archivos con textos diferentes, por ejemplo, para el menú, la cabecera, el pie de página, etc.,. Importante: aunque sean archivos para idiomas diferentes el nombre del archivo siempre debe ser el mismo, variando, eso sí, la cultura.

Una vez guardado el archivo volvemos al indexSuccess.php y lo modificamos para que quede tal que así;

[codesyntax lang=»php»]

// No voy a poner todo el código que tengo en mi plantilla
// así que pongo solo un ejemplo

<?php echo __('Hola Mundo!', null, 'login') ?>

// Ahora mostrará el texto que corresponda con la cultura del usuario
// Además lo buscará en un archivo concreto en este caso login.en.xml

[/codesyntax]

Y así se internacionaliza un proyecto Symfony. Ahora el problemita de Dreamweaver:

El bicho, (por llamarlo de alguna manera) tiene una opción para abrir archivos que no indiquen su codificación. Normalmente está en «Europeo occidental» y debería estar en «Unicode (utf-8)«. Para cambiarlo accedemos al menú «Edición -> Preferencias…» y en «Nuevo documento» busca un desplegable que ponga «Codificación pred.«; ahí elige «Unicode (utf-8)«, activa la casilla que dice «Utilizar al abarir archivos existentes que no indiquen su codificación» y en el desplegable de abajo (Formulario de normas Unicode) selecciona «C (descomposición de compatibilidad seguida de composición canónica)«. Pulsa aceptar y prueba el en navegador.

Tal vez tengas que limpiar la cache de Symfony para ello solo tienes que escribir en la consola (que debe apuntar a la carpeta donde tienes el proyecto) symfony cc o php symfony cc.

Si sigue fallando, abre el archivo indexSuccess.php del módulo con el bloc de notas, y sin modificar nada, vete a Archivo -> Guardar como… , ahí podrás elegir la codificación, en nuestro caso, utf-8, y para evitar que se guarde como un archivo .txt elige «Todos los archivos» y así se guardará en php. Vuelve a limpiar la caché y vuelve a probar, ahora sí te debería funcionar bien.

Symfony: El controlador (III)

0

SEGURIDAD DE LA ACCIÓN

Con Symfony podemos controlar quién puede acceder a una acción concreta, utilizando para ello las herramientas de identificación de usuarios que el framework nos provee.

Una acción segura sólo podrá ser ejecutada por un usuario registrado y que tenga la acreditación para ello.

  • Las acciones seguras requieren que los usuarios estén autenticados.
  • Las credenciales son privilegios de seguridad agrupados bajo un nombre y que permiten organizar la seguridad en grupos.

[codesyntax lang=»text» title=»Estableciendo restricciones de acceso, en apps/frontend/modules/mimodulo/config/security.yml»]

ver:
  is_secure:   off       # Todos los usuarios pueden ejecutar la acción "ver"

modificar:
  is_secure:   on        # La acción "modificar" es sólo para usuarios autenticados

borrar:
  is_secure:   on        # Sólo para usuarios autenticados
  credentials: admin     # Con credencial "admin"

all:
  is_secure:  off        # off es el valor por defecto

[/codesyntax]

 

Lo que sucede cuando un usuario trata de acceder una acción restringida depende de sus credenciales:

  • Si el usuario está autenticado y tiene las credenciales apropiadas, entonces la acción se ejecuta.
  • Si el usuario no está autenticado, es redireccionado a la acción de login.
  • Si el usuario está autenticado, pero no posee las credenciales apropiadas, será redirigido a la acción segura por defecto.

Las páginas login y secure son bastante simples, por lo que seguramente será necesario personalizarlas. Se puede configurar que acciones se ejecutan en caso de no disponer de suficientes privilegios en el archivo settings.yml de la aplicación cambiando el valor de las propiedades:

[codesyntax lang=»text» title=»Las acciones de seguridad por defecto se definen en apps/frontend/config/settings.yml»]

all:
  .actions:
    login_module:  default
    login_action:  login

    secure_module: default
    secure_action: secure

[/codesyntax]

 

OTORGANDO ACCESO

Los métodos utilizados para permitir acceso a una acción segura se encuentran en el objeto sfUser. El estado identificado se establece con setAuthenticated() y se puede comprobar con isAuthenticated():

[codesyntax lang=»php» title=»Estableciendo el estado de autenticación del usuario»]

<?php

class miCuentaActions extends sfActions
{
  public function executeLogin($peticion)
  {
    if ($peticion->getParameter('login') == 'valor')
    {
      $this->getUser()->setAuthenticated(true);
    }
  }

  public function executeLogout()
  {
    $this->getUser()->setAuthenticated(false);
  }
}

[/codesyntax]

Las credenciales son un poco más complejas de tratar, ya que se pueden verificar, agregar, quitar y borrar:

[codesyntax lang=»php»]

<?php

class miCuentaActions extends sfActions
{
  public function executeEjemploDeCredenciales()
  {
    $usuario = $this->getUser();

    // Agrega una o más credenciales
    $usuario->addCredential('parametro');
    $usuario->addCredentials('parametro', 'valor');

    // Verifica si el usuario tiene una credencial
    echo $usuario->hasCredential('parametro');                         => true

    // Verifica si un usuario tiene una de las credenciales
    echo $usuario->hasCredential(array('parametro', 'valor'));         => true

    // Verifica si el usuario tiene ambas credenciales
    echo $usuario->hasCredential(array('parametro', 'valor'), true);   => true

    // Quitar una credencial
    $usuario->removeCredential('parametro');
    echo $usuario->hasCredential('parametro');                         => false

    // Elimina todas las credenciales (útil en el proceso de logout)
    $usuario->clearCredentials();
    echo $usuario->hasCredential('valor');                             => false
  }
}

[/codesyntax]

Las credenciales se pueden utilizar también para mostrar contenido autenticado en una plantilla:

[codesyntax lang=»php»]

<?php

<ul>
  <li><?php echo link_to('seccion1', 'content/seccion1') ?></li>
  <li><?php echo link_to('seccion2', 'content/seccion2') ?></li>
  <?php if ($sf_user->hasCredential('seccion3')): ?>
  <li><?php echo link_to('seccion3', 'content/seccion3') ?></li>
  <?php endif; ?>
</ul>

[/codesyntax]

 

CREDENCIALES COMPLEJAS

a sintaxis YAML utilizada en el archivo security.yml permite restringir el acceso a usuarios que tienen una combinación de credenciales, usando asociaciones de tipo AND y OR. Con estas combinaciones, se pueden definir flujos de trabajo y sistemas de manejo de privilegios muy complejos — como por ejemplo, un sistema de gestión de contenidos (CMS) cuya parte de gestión sea accesible solo a usuarios con credencial admin, donde los artículos pueden ser editados solo por usuarios con credenciales de editor y publicados solo por aquellos que tienen credencial de publisher:

[codesyntax lang=»text»]

editarArticulo:
  credentials: [ admin, editor ]              # admin AND editor

publicarArticulo:
  credentials: [ admin, publisher ]           # admin AND publisher

gestionUsuarios:
  credentials: [[ admin, superuser ]]         # admin OR superuser

[/codesyntax]

Cada vez que se añade un nuevo nivel de corchetes, la lógica cambia entre AND y OR. Así que se pueden crear combinaciones muy complejas de credenciales, como la siguiente:

[codesyntax lang=»text»]

credentials: [[root, [supplier, [owner, quasiowner]], accounts]]
             # root OR (supplier AND (owner OR quasiowner)) OR accounts

[/codesyntax]

 

CONFIGURACIÓN DEL MÓDULO

Algunas características de los módulos dependen de la configuración. Para modificarlas, se debe crear un archivo module.yml en el directorio config/ y se deben definir parámetros para cada entorno (o en la sección all: para todos los entornos).

[codesyntax lang=»text» title=»Configuración del módulo, en apps/frontend/modules/mimodulo/config/module.yml»]

all:                  # Para todos los entornos
  enabled:            true
  is_internal:        false
  view_class:         sfPHP
  partial_view_class: sf

[/codesyntax]

El parámetro enabled permite desactivar todas las acciones en un módulo. En ese caso, todas las acciones se redireccionan a la acción module_disabled_module/module_disabled_action (tal y como se define en el archivo settings.yml).

El parámetro is_internal permite restringir la ejecución de todas las acciones de un módulo a llamadas internas. Esto es útil por ejemplo para acciones de envío de correos electrónicos que se deben llamar desde otras acciones para enviar mensajes de e-mail, pero que no se deben llamar desde el exterior.

El parámetro view_class define la clase de la vista. Debe heredar de sfView. Sobreescribir este valor permite utilizar otros sistemas de generación de vistas con otros motores de plantillas, como por ejemplo Smarty.

El parámetro partial_view_class define la clase de la vista que se emplea para los elementos parciales de este módulo. La clase indicada debe heredar de sfPartialView.

Symfony, creando el archivo schema.yml para una base de datos ya existente

0

Sí, sigo dándole caña a Symfony… y cometiendo errores de novato xD.

Para ir probando Symfony con un proyecto real, he decidido utilizar una base de datos ya existente para generar una nueva versión del proyecto anterior, y como ya sabrás para que Symfony (bueno, en este caso Propel) genere las clases necesarias para trabajar con la BD es necesario que la estructura de ésta esté en el archivo schema.yml dentro de la carpeta /config del proyecto.

Para ello, primero hay que configurar la conexión a la base de datos en symfony de la siguiente manera:

symfony configure:database «mysql://login:password@localhost/basededatos»
symfony configure:database «mysql:host=localhost;dbname=nombreBD» root pass

Ahora le decimos a Propel que genere el archivo schema.yml a partir de la base de datos:

symfony propel:build-schema

Propel coge los datos de configuración de la BD que le hemos dado anteriormente.

Y con esto se genera el archivo schema.yml y symfony está listo para generar las clases.

 

PD1: Propel utiliza PDO para conectar con la BD, si por alguna razón os muestra un error diciendo que no encuentra el driver o algo por el estilo, simplemente id al archivo php.ini y habilitad la extensión pdo_mysql y reiniciar apache, esto lo debería arreglar.

PD2: Cada vez que tengas que generar el modelo recuerda de limpiar la caché de symfony para evitarte problemas:

symfony cc

PD3: Si al generar el modelo, Propel te devuelve un error de «tabla duplicada» o parecido, solo tienes que ir a la carpeta /config del proyecto y eliminar un archivo llamado generated-schema.yml (si no recuerdo mal).

Symfony: El controlador (II)

0

El primer parámetro de todas las acciones (que habitualmente hemos llamado $peticion) representa un objeto llamado sfWebRequest. Ya conocemos uno de sus métodos que nos devuelve el valor de un parámetro, getParameter(‘miparametro’). En el capítulo del libro de Symfony podéis ver una tabla con los métodos más utilizados de el objeto sfWebRequest.

 

SESIONES

Para acceder a la sesión del usuario utilizamos getUser() que es una instancia de la clase sfUser. Se pueden guardar cualquier tipo de dato excepto objetos, el motivo es que entre una petición y otra el objeto de la sesión se serializa.

 

[codesyntax lang=»php» title=»El objeto sfUser puede contener atributos personalizados del usuario disponibles en todas las peticiones»]

<?php

class mimoduloActions extends sfActions
{
  public function executePrimeraPagina($peticion)
  {
    $nombre = $peticion->getParameter('nombre');

    // Guardar información en la sesión del usuario
    $this->getUser()->setAttribute('nombre', $nombre);
  }

  public function executeSegundaPagina()
  {
    // Obtener información de la sesión del usuario con un valor por defecto
    $nombre = $this->getUser()->getAttribute('nombre', 'Anónimo');
  }
}

[/codesyntax]

Para verificar si un atributo ha sido definido para un usuario, se utiliza el método hasAttribute(). Los atributos se guardan en un contenedor de parámetros que puede ser accedido por el método getAttributeHolder(). También permite un borrado rápido de los atributos del usuario con los métodos usuales del contenedor de parámetros:

[codesyntax lang=»php» title=»Eliminando información de la sesión del usuario»]

<?php

class mimoduloActions extends sfActions
{
  public function executeBorraNombre()
  {
    $this->getUser()->getAttributeHolder()->remove('nombre');
  }

  public function executeLimpia()
  {
    $this->getUser()->getAttributeHolder()->clear();
  }
}

[/codesyntax]

[codesyntax lang=»php» title=»Las plantillas también tienen acceso a los atributos de la sesión del usuario»]

<p>
  Hola, <?php echo $sf_user->getAttribute('nombre') ?>
</p>

[/codesyntax]

 

ATRIBUTOS FLASH

El atributo flash permite crear variables que serán destruidas después de la siguiente petición:

[codesyntax lang=»php»]

<?php

//Creamos el atributo en la primera acción
$this->getUser()->setFlash('atributo', $valor);

//Recuperamos el atributo en la segunda acción
$valor = $this->getUser()->getFlash('atributo');

//Después en la siguiente petición el atributo se eliminará

//En la plantilla podemos llamarlo de la siguiente manera:
<?php if ($sf_user->hasFlash('atributo')): ?>
  <?php echo $sf_user->getFlash('atributo') ?>
<?php endif; ?>

//O de una forma más simple:
<?php echo $sf_user->getFlash('atributo') ?>

[/codesyntax]

 

MANEJO DE SESIONES

El manejo de sesiones de Symfony se encarga de gestionar automáticamente el almacenamiento de los IDs de sesión tanto en el cliente como en el servidor. Sin embargo, si se necesita modificar este comportamiento por defecto, es posible hacerlo.

En el lado del cliente, las sesiones son manejadas por cookies. La cookie de Symfony se llama Symfony, pero se puede cambiar su nombre editando el archivo de configuración factories.yml:

[codesyntax lang=»text»]

all:
  storage:
    class: sfSessionStorage
    param:
      session_name: mi_nombre_cookie

[/codesyntax]

En el lado del servidor, Symfony guarda por defecto las sesiones de usuario en archivos. Se pueden almacenar en la base de datos cambiando el valor del parámetro class en factories.yml:

[codesyntax lang=»text»]

all:
  storage:
    class: sfMySQLSessionStorage
    param:
      db_table:    session    # Nombre de la tabla que guarda las sesiones
      database:    propel     # Nombre de la conexión a base de datos que se utiliza
      # Parámetros opcionales
      db_id_col:   sess_id    # Nombre de la columna que guarda el identificador de la sesión
      db_data_col: sess_data  # Nombre de la columna que guarda los datos de la sesión
      db_time_col: sess_time  # Nombre de la columna que guarda el timestamp de la sesión

[/codesyntax]

La opción database define el nombre de la conexión a base de datos que se utiliza. Posteriormente, Symfony utiliza el archivo databases.yml para determinar los parámetros con los que realiza la conexión (host, nombre de la base de datos, usuario y password).

Las clases disponibles para el almacenamiento de sesiones son sfMySQLSessionStorage, sfMySQLiSessionStorage, sfPostgreSQLSessionStorage y sfPDOSessionStorage. La clase recomendada es sfPDOSessionStorage. Para deshabilitar completamente el almacenamiento de las sesiones, se puede utilizar la clase sfNoStorage.

La expiración de la sesión se produce automáticamente después de 30 minutos. El valor de esta opción se puede modificar para cada entorno en el mismo archivo de configuración factories.yml, concretamente en la factoría correspondiente al usuario (user):

[codesyntax lang=»text»]

all:
  user:
    class:       myUser
    param:
      timeout:   1800           # Tiempo de vida de la sesión en segundos

[/codesyntax]

Symfony: Generando un proyecto utilizando el ORM Propel

1

La versión 1.4 de Symfony utiliza por defecto como ORM a Doctrine, ya que tiene mejor rendimiento, pero las prácticas de Symfony como por ejemplo Jobeet utiliza Propel. Asi que, para poder utilizar este ORM hay que generar la aplicación con el parámetro –orm, tal como sigue:

symfony generate:project mi_proyecto –orm=Propel

De esta forma, Propel, será el ORM por defecto. Aunque siempre podrás cambiarlo a Doctrine.

Symfony: El controlador (I)

0

Continuo dándole caña a Symfony. Ahora toca el controlador.

¿Qué hace el controlador? Pues lo siguiente:

  • El controlador frontal es el único punto de entrada a la aplicación. Carga la configuración y determina la acción a ejecutarse.
  • Las acciones contienen la lógica de la aplicación. Verifican la integridad de las peticiones y preparan los datos requeridos por la capa de presentación.
  • Los objetos request, response y session dan acceso a los parámetros de la petición, las cabeceras de las respuestas y a los datos persistentes del usuario. Se utilizan muy a menudo en la capa del controlador.
  • Los filtros son trozos de código ejecutados para cada petición, antes o después de una acción. Por ejemplo, los filtros de seguridad y validación son comúnmente utilizados en aplicaciones web. Puedes extender el framework creando tus propios filtros.

EL CONTROLADOR FRONTAL

http://localhost/index.php/mimodulo/miAccion

La URL de arriba es un ejemplo de que tarea realiza el controlador frontal. En este caso nuestro controlador frontal es el archivo index.php que se encargará de ejecutar miAccion de mimodulo y generar la página que mostrará al usuario.

EL TRABAJO DEL CONTROLADOR EN DETALLE

Estas son las tareas que ejecuta el controlador antes de que se muestre la página al usuario:

  1. Carga la clase de configuración del proyecto y las librerías de Symfony.
  2. Crea la configuración de la aplicación y el contexto de Symfony.
  3. Carga e inicializa las clases del núcleo del framework.
  4. Carga la configuración.
  5. Decodifica la URL de la petición para determinar la acción a ejecutar y los parámetros de la petición.
  6. Si la acción no existe, redireccionará a la acción del error 404.
  7. Activa los filtros (por ejemplo, si la petición necesita autenticación).
  8. Ejecuta los filtros, primera pasada.
  9. Ejecuta la acción y produce la vista.
  10. Ejecuta los filtros, segunda pasada.
  11. Muestra la respuesta.

index.php, EL CONTROLADOR FRONTAL POR DEFECTO

Este archivo se encuentra en la carpeta /web del proyecto y contiene el siguiente código:

[codesyntax lang=»php»]

<?php    

  require_once(dirname(__FILE__).'/../config/ProjectConfiguration.class.php');

  $configuration = ProjectConfiguration::getApplicationConfiguration('frontend', 'prod', false);
  sfContext::createInstance($configuration)-&gt;dispatch();

[/codesyntax]

Lo que hace es simple, carga el archivo de la clase de configuración, llama a la clase y despacha la petición usando el método dispatch() de la clase sfContext.

Podemos crear otros controladores frontales simplemente copiando el archivo y modificando el segundo parámetro del método getApplicationConfiguration().

Para ello copiaremos el código del archivo index.php a frontend_staging.php y sustituiremos el segundo parámetro del método getApplicationConfiguration() que en vez de ser prod (de producción), será staging. Utilizaremos este controlador para que el cliente pueda probar la aplicación antes de ponerla en producción.

Una vez hecho esto modificaremos el archivo app.yml en la carpeta /config de la aplicación (en este caso sería /frontend/config) como sigue:

[codesyntax lang=»php»]

staging:
  mail:
    webmaster: falso@misitio.com
    contacto: falso@misitio.com
all:
  mail:
    webmaster: webmaster@misitio.com
    contacto: contacto@mysite.com

[/codesyntax]

Ahora solo tienes que escribir la siguiente URL para ver como se comporta el nuevo controlador frontal:

http://localhost/frontend_staging.php/mimodulo/index

LAS ACCIONES

[codesyntax lang=»php»]

<?php

class mimoduloActions extends sfActions
 {
   public function executeIndex()
   {
     // ...
   }
 }

[/codesyntax]

Las acciones son métodos de una clase que hereda de sfActions y se encuentran agrupadas por módulos. Esta clase se encuentra en el archivo actions.class.php en la carpeta /actions del módulo.

Para añadir más acciones sólo es necesario añadir más métodos execute al objeto sfActions:

[codesyntax lang=»php»]

<?php

class mimoduloActions extends sfActions
{
  public function executeIndex()
  {
    // ...
  }

  public function executeListar()
  {
    // ...
  }
}

[/codesyntax]

Y para ver en el navegador estas acciones sólo habría que escribir:

Para executeIndex() -> http://localhost/frontend_dev.php/mimodulo/index
Para executeListar() -> http://localhost/frontend_dev.php/mimodulo/listar

 

SEPARANDO LAS ACCIONES EN ARCHIVOS DIFERENTES

Para crear acciones de un mismo módulo en archivos diferentes se debe crear una clase que extienda sfAction (en vez de sfActions que utilizabamos anteriormente), en un archivo llamado nombreAccionAction.class.php y el nombre del método en cada archivo será, simplemente, execute.

[codesyntax lang=»php» title=»Archivo de una sola acción, en frontend/modules/mimodulo/actions/indexAction.class.php»]

<?php

class indexAction extends sfAction
{
  public function execute($peticion)
  {
    // ...
  }
}

[/codesyntax]

[codesyntax lang=»php» title=»Archivo de una sola acción, en frontend/modules/mimodulo/actions/listAction.class.php»]

<?php

class listarAction extends sfAction
{
  public function execute($peticion)
  {
    // ...
  }
}

[/codesyntax]

 

OBTENIENDO INFORMACIÓN DE LAS ACCIONES

sfActions proporciona acceso a sfContext::createInstance() a través de getContext() que devuelve un objeto que guarda una referencia de todos los objetos del núcleo de symfony relacionados con la petición dada:

[codesyntax lang=»php» title=»Métodos comunes de sfActions»]

<?php

class mimoduloActions extends sfActions
{
  public function executeIndex($peticion)
  {
    // Obteniendo parametros de la petición
    $password      = $peticion->getParameter('password');

    // Obteniendo información del controlador
    $nombreModulo  = $this->getModuleName();
    $nombreAccion  = $this->getActionName();

    // Obteniendo objetos del núcleo del framework
    $sesionUsuario = $this->getUser();
    $respuesta     = $this->getResponse();
    $controlador   = $this->getController();
    $contexto      = $this->getContext();

    // Creando variables de la acción para pasar información a la plantilla
    $this->setVar('parametro', 'valor');
    $this->parametro = 'valor';           // Versión corta.
  }
}

[/codesyntax]

  • sfController: El objeto controlador (->getController())
  • sfRequest: El objeto de la petición (->getRequest())
  • sfResponse: El objeto de la respuesta (->getResponse())
  • sfUser: El objeto de la sesión del usuario (->getUser())
  • sfDatabaseConnection: La conexión a la base de datos (->getDatabaseConnection())
  • sfLogger: El objeto para los logs (->getLogger())
  • sfI18N: El objeto de internacionalización (->getI18N())

Se puede llamar al método sfContext::getInstance() desde cualquier parte del código.

 

TERMINACIÓN DE LAS ACCIONES

Normalmente cuando finalizamos un método o función y queremos devolver algún dato utilizamos la palabra return seguida de alguna variable u objeto. En Symfony es más o menos igual, me explico:

Symfony, una vez a procesado la acción, enviará los datos a la plantilla para que el usuario pueda visualizarlo. Por defecto, Symfony siempre utiliza return sfView::SUCCESS para buscar la plantilla que debe mostrar los datos:

[codesyntax lang=»php» title=»Acciones que llaman a las plantillas indexSuccess.php y listarSuccess.php»]

<?php

public function executeIndex()
{
  return sfView::SUCCESS;
}

public function executeListar()
{
}

[/codesyntax]

Al ser sfView::SUCCESS la vista por defecto, si no se indica en la acción, Symfony buscará una plantilla llamada nombreacciónSuccess.php.

En el caso de que quisieramos mostrar al usuario otra plantilla en caso de error haríamos lo siguiente:

[codesyntax lang=»php»]

<?php

return sfView::ERROR;

[/codesyntax]

Esto buscará una plantilla llamada nombreacciónError.php. Si queremos mostrar una vista personalizada:

[codesyntax lang=»php»]

<?php

return 'MiResultado';

[/codesyntax]

En ete caso, Symfony buscará una plantilla llamada nombreacciónMiResultado.php (ojo a las mayusculas ya que Symfony es case sensitive)

Si no se quiere utilizar ninguna vista:

[codesyntax lang=»php»]

<?php

return sfView::NONE;

[/codesyntax]

En el caso de que la acción vaya a ser utilizada por Ajax, podemos devolver los datos sin necesidad de pasar por la vista con el método renderText():

[codesyntax lang=»php»]

<?php

public function executeIndex()
{
  $this->getResponse()->setContent("<html><body>¡Hola Mundo!</body></html>");

  return sfView::NONE;
}

// Es equivalente a
public function executeIndex()
{
  return $this->renderText("<html><body>¡Hola Mundo!</body></html>");
}

[/codesyntax]

En algunos casos se necesita enviar sólo las cabeceras de la petición, como en el caso de J-SON:

[codesyntax lang=»php»]

<?php

public function executeActualizar()
{
  $salida = '<"titulo","Mi carta sencilla"],["nombre","Sr. Pérez">';
  $this->getResponse()->setHttpHeader("X-JSON", '('.$salida.')');

  return sfView::HEADER_ONLY;
}

[/codesyntax]

Si se quiere utilizar una plantilla específica, se debe prescindir de la sentencia return y utilizar el método setTemplate():

[codesyntax lang=»php»]

<?php

$this->setTemplate('miPlantillaPersonalizada');

[/codesyntax]

 

SALTANDO A OTRA ACCIÓN

Hay dos formas de saltar a otra acción:

[codesyntax lang=»php»]

<?php

//Si la acción debe continuar en otro módulo
$this->forward('otroModulo', 'index');

//Si la acción debe redireccionar a otro módulo o una web externa
$this->redirect('otroModulo/index');
$this->redirect('http://www.google.com/');

[/codesyntax]

En el caso de que quisieramos redireccionar para mostrar un error 404:

[codesyntax lang=»php»]

<?php

public function executeVer($peticion)
{
  $articulo = ArticuloPeer::retrieveByPK($peticion->getParameter('id'));
  if (!$articulo)
  {
    $this->forward404();
  }
}

[/codesyntax]

Si estás buscando la acción y la plantilla del error 404, las puedes encontrar en el directorio $sf_symfony_lib_dir/controller/default/. Se puede personalizar esta página agregado un módulo default a la aplicación, sobrescribiendo el del framework, y definiendo una acción error404 y una plantilla error404Success dentro del nuevo módulo. Otro método alternativo es el de establecer las constantes error_404_module y error_404_action en el archivo settings.yml para utilizar una acción existente.

La clase sfActions tiene algunos métodos más, llamados forwardIf(), forwardUnless(), forward404If(), forward404Unless(), redirectIf() y redirectUnless(). Estos métodos simplemente requieren un parámetro que representa la condición cuyo resultado se emplea para ejecutar el método. El método se ejecuta si el resultado de la condición es true y el método es de tipo xxxIf() o si el resultado de la condición es false y el método es de tipo xxxUnless():

[codesyntax lang=»php»]

<?php

// Esta acción es equivalente a la mostrada en el Listado 6-11
public function executeVer($peticion)
{
  $articulo = ArticuloPeer::retrieveByPK($peticion->getParameter('id'));
  $this->forward404If(!$articulo);
}

// Esta acción también es equivalente
public function executeVer()
{
  $articulo = ArticuloPeer::retrieveByPK($peticion->getParameter('id'));
  $this->forward404Unless($articulo);
}

[/codesyntax]

 

REPITIENDO CÓDIGO PARA VARIAS ACCIONES DE UN MÓDULO

Si por alguna razón nuestras acciones siempre van a ejecutar el mismo código al inicio o al final del método, podemos evitar duplicar código creando los métodos postExecute() y preExecute(), pero además podemos añadir al archivo de la acción (con sfAction) o acciones (con sfActions) nuestros propios métodos siempre que no empiecen con execute y no sean métodos públicos (utilizando para ello las sentencias protected o private).

[codesyntax lang=»php»]

<?php

class mimoduloActions extends sfActions
{
  public function preExecute()
  {
    // El código insertado aquí se ejecuta al principio de cada llamada a una acción
    // ...
  }

  public function executeIndex($peticion)
  {
    // ...
  }

  public function executeListar($peticion)
  {
    // ...
    $this->miPropioMetodo();  // Se puede acceder a cualquier método de la clase acción
  }

  public function postExecute()
  {
    // El código insertado aquí se ejecuta al final de cada llamada a la acción
    ...
  }

  protected function miPropioMetodo()
  {
    // Se pueden crear métodos propios, siempre que su nombre no comience por "execute"
    // En ese case, es mejor declarar los métodos como protected o private
    // ...
  }
}

[/codesyntax]

Optimización de WordPress

WP-SynHighlight, plugin para insertar código en tus post

4

Llevaba unos días buscando un plugin que me permitiese añadir trozos de código fuente en los post sobre programación, ya que el que tenía debía trabajar en la vista HTML y, la verdad, es un engorro. Así que buscando y buscando encontré este plugin que permite escribir código fuente en el editor visual.

Este plugin, al igual que la mayoría que permiten añadir código fuente a los post, está basado en Geshi, que es el responsable de procesar el código y darle el formato deseado.

Su instalación es igual que la de cualquier otro plugin de WordPress, pero lo interesante de este plugin es que dispone de un editor visual para añadir el código fuente, donde podrás cambiar muchos aspectos de como se visualizará el código.

Además permite añadir código fuente en los comentarios utilizando para ello los QuickTags de la siguiente manera:

Si quisiéramos añadir código PHP a un comentario escribiríamos lo siguiente:

[*codesyntax lang=»php»]

<?php echo «Hello World!»; ?>

[*/codesyntax]

Eso sí, sin los asteriscos. Así quedaría el fragmento de código:

[codesyntax lang=»php»]

<?php echo «Hello World!»; ?>

[/codesyntax]

 

Geshi permite añadir código de una gran cantidad de lenguajes. Podeis ver un listado en su página principal: http://qbnz.com/highlighter/index.php

Ir arriba