Estas en: Home > symfony

Entradas etiquetadas con symfony

Symfony: Enrutamiento (II)

0

Por defecto Symfony no muestra el archivo que hace de controlador (index.php, frontend_dev.php, etc) para cambiarlo vamos al archivo settings.yml aquí podemos indicar que no se muestre el nombre del controlador de la siguiente manera:

[codesyntax lang=»text»]

prod:
  .settings:
    no_script_name:  off

[/codesyntax]

 

Hiperenlaces, botones y formularios

[codesyntax lang=»php» title=»Los helpers de enlaces aceptan opciones adicionales»]

/ Opciones adicionales como array asociativo
<?php echo link_to('Mi artículo', 'articulo/ver?titulo=Economia_en_Francia', array(
  'class'  => 'miclasecss',
  'target' => '_blank'
)) ?>

// Opciones adicionales como cadena de texto (producen el mismo resultado)
<?php echo link_to('Mi artículo', 'articulo/ver?titulo=Economia_en_Francia','class=miclasecss target=_blank') ?>
 => <a href="/url/con/enrutamiento/a/Economia_en_Francia" class="miclasecss" target="_blank">Mi artículo</a>

[/codesyntax]

[codesyntax lang=»php» title=»URL que admiten los helpers de enlaces»]

// URI interna
<?php echo link_to('Mi artículo', 'articulo/ver?titulo=Economia_en_Francia') ?>
 => <a href="/url/con/enrutamiento/a/Economia_en_Francia">Mi artículo</a>

// URI interna con parámetros dinámicos
<?php echo link_to('Mi artículo', 'articulo/ver?titulo='.$articulo->getTitulo()) ?>

// URI interna con anclas (enlaces a secciones internas de la página)
<?php echo link_to('Mi artículo', 'articulo/ver?titulo=Economia_en_Francia#seccion1') ?>
 => <a href="/url/con/enrutamiento/a/Economia_en_Francia#seccion1">Mi artículo</a>

// URL absolutas
<?php echo link_to('Mi artículo', 'http://www.ejemplo.com/cualquierpagina.html') ?>
 => <a href="http://www.ejemplo.com/cualquierpagina.html">Mi artículo</a>

[/codesyntax]

[codesyntax lang=»php» title=»Helpers de enlaces para las etiquetas a, input y form»]

// Enlace simple de texto
<?php echo link_to('Mi artículo', 'articulo/ver?titulo=Economia_en_Francia') ?>
 => <a href="/url/con/enrutamiento/a/Economia_en_Francia">Mi artículo</a>

// Enlace en una imagen
<?php echo link_to(image_tag('ver.gif'), 'articulo/ver?titulo=Economia_en_Francia') ?>
 => <a href="/url/con/enrutamiento/a/Economia_en_Francia"><img src="/images/ver.gif?x81947" /></a>

// Boton
<?php echo button_to('Mi artículo', 'articulo/ver?titulo=Economia_en_Francia') ?>
 => <input value="Mi artículo" type="button" onclick="document.location.href='/url/con/enrutamiento/a/Economia_en_Francia';" />

// Formulario
<?php echo form_tag('articulo/ver?titulo=Economia_en_Francia') ?>
 => <form method="post" action="/url/con/enrutamiento/a/Economia_en_Francia" />

[/codesyntax]

[codesyntax lang=»php» title=»Opciones confirm y popup en los helpers de enlaces»]

<?php echo link_to('Borrar elemento', 'item/borrar?id=123', 'confirm=¿Estás seguro?') ?>
 => <a onclick="return confirm('¿Estás seguro?');"
       href="/url/con/enrutamiento/a/borrar/123.html">Borrar elemento</a>

<?php echo link_to('Añadir al carrito', 'carritoCompra/anadir?id=100', 'popup=true') ?>
 => <a onclick="window.open(this.href);return false;"
       href="/url/con/enrutamiento/a/carritoCompra/anadir/id/100.html">Añadir al carrito</a>

<?php echo link_to('Añadir al carrito', 'carritoCompra/anadir?id=100', array(
  'popup' => array('popupWindow', 'width=310,height=400,left=320,top=0')
)) ?>
 => <a onclick="window.open(this.href,'popupWindow','width=310,height=400,left=320,top=0');return false;"
       href="/url/con/enrutamiento/a/carritoCompra/anadir/id/100.html">Añadir al carrito</a>

[/codesyntax]

 

Symfony: El enrutamiento (I)

0

Continuo con el libro de Symfony 1.4 en esta ocasión toca el enrutamiento.

Normalmente las urls que escribimos son así:

http://www.ejemplo.com/web/galeria/album.php?nombre=mis%20vacaciones
http://www.ejemplo.com/web/weblog/publico/post/listado.php
http://www.ejemplo.com/web/general/contenido/pagina.php?nombre=sobre%20nosotros

Y deberían ser así:

http://www.ejemplo.com/articulos/economia/2006/sectores-actividad.html

Principalmente porque son mucho más legibles para los usuarios y buscadores, y además evitamos mostrar información a un posible atacante.

El sistema de enrutamiento utiliza un archivo de configuración especial, llamado routing.yml, en el que se pueden definir las reglas de enrutamiento.

[codesyntax lang=»text»]

articulo_segun_titulo:
  url:    articulos/:tema/:ano/:titulo.html
  param:  { module: articulo, action: permalink }

[/codesyntax]

Todas las peticiones realizadas a una aplicación Symfony son analizadas en primer lugar por el sistema de enrutamiento (que es muy sencillo porque todas las peticiones se gestionan mediante un único controlador frontal). El sistema de enrutamiento busca coincidencias entre la URL de la petición y los patrones definidos en las reglas de enrutamiento. Si se produce una coincidencia, las partes del patrón que tienen nombre se transforman en parámetros de la petición y se juntan a los parámetros definidos en la clave param:.

Después, la petición se pasa a la acción permalink del módulo articulo, que dispone de toda la información necesaria en los parámetros de la petición para obtener el artículo solicitado.

El mecanismo de enrutamiento también funciona en la otra dirección. Para mostrar las URL en los enlaces de una aplicación, se debe proporcionar al sistema de enrutamiento la información necesaria para determinar la regla que se debe aplicar a cada enlace. Además, no se deben escribir los enlaces directamente con etiquetas <a> (ya que de esta forma no se estaría utilizando el sistema de enrutamiento) sino con un helper especial:

[codesyntax lang=»text»]

// El helper url_for() transforma una URI interna en una URL externa
<a href="<?php echo url_for('articulo/permalink?tema=economia&ano=2006&titulo=sectores-actividad') ?>">pincha aquí</a>

// El helper reconoce que la URI cumple con la regla articulo_segun_titulo
// El sistema de enrutamiento crea una URL externa a partir de el
 => <a href="http://www.ejemplo.com/articulos/economia/2006/sectores-actividad.html">pincha aquí</a>

// El helper link_to() muestra directamente un enlace
// y evita tener que mezclar PHP y HTML
<?php echo link_to(
  'pincha aqui',
  'articulo/permalink?tema=economia&ano=2006&titulo=sectores-actividad'
) ?>

// Internamente link_to() llama a url_for(), por lo que el resultado es el mismo
 => <a href="http://www.ejemplo.com/articulos/economia/2006/sectores-actividad.html">pincha aquí</a>

[/codesyntax]

Symfony: El modelo (III)

0

CONEXIONES A LA BASE DE DATOS

[codesyntax lang=»text» title=»Conexión básica»]

> php symfony configure:database "mysql://login:password@localhost/blog"

[/codesyntax]

[codesyntax lang=»text» title=»Definiendo una conexión para un entorno concreto de la aplicación»]

> php symfony --env=prod configure:database "mysql://login:password@localhost/blog"

[/codesyntax]

[codesyntax lang=»text» title=»Definiendo una conexión para una aplicación concreta»]

> php symfony --app=frontend configure:database "mysql://login:password@localhost/blog"

[/codesyntax]

 

[codesyntax lang=»text» title=»Definiendo una conexión diferente de la principal para el proyecto»]

> php symfony --name=otraconexion configure:database "mysql://login:password@localhost/blog"

[/codesyntax]

 

Las opciones de conexión con la base de datos también se pueden establecer manualmente en el archivo databases.yml que se encuentra en el directorio config/.

 

[codesyntax lang=»text» title=»Opciones abreviadas de la conexión a la base de datos»]

all:
  propel:
    class:          sfPropelDatabase
    param:
      dsn:          mysql://login:password@localhost/blog

[/codesyntax]

[codesyntax lang=»text» title=»Ejemplo de conexiónes con la base de datos»]

prod:
  propel:
    param:
      hostspec:           mi_servidor_datos
      usuarioname:           mi_nombre_usuario
      password:           xxxxxxxxxx

all:
  propel:
    class:                sfPropelDatabase
    param:
      phptype:            mysql     # fabricante de la base de datos
      hostspec:           localhost
      database:           blog
      usuarioname:           login
      password:           passwd
      port:               80
      encoding:           utf8      # Codificación utilizada para crear la tabla
      persistent:         true      # Utilizar conexiones persistentes

[/codesyntax]

Los valores permitidos para el parámetro phptype corresponden a los tipos de bases de datos soportados por PDO:

  • mysql
  • mssql
  • pgsql
  • sqlite
  • oracle

Si se utiliza una base de datos de tipo SQLite, el parámetro hostspec debe indicar la ruta al archivo de base de datos.

[codesyntax lang=»text» title=»Opciones de conexión a una base de datos SQLite»]

all:
  propel:
    class:          sfPropelDatabase
    param:
      phptype:      sqlite
      database:     %SF_DATA_DIR%/blog.db

[/codesyntax]

 

EXTENDER EL MODELO

Los métodos del modelo que se generan automáticamente están muy bien, pero no siempre son suficientes. Si se incluye lógica de negocio propia, es necesario extender el modelo añadiendo nuevos métodos o redefiniendo algunos de los existentes.

 

AÑADIR NUEVOS MÉTODOS

Los nuevos métodos se pueden añadir en las clases vacías del modelo que se generan en el directorio lib/model/. Se emplea $this para invocar a los métodos del objeto actual y self:: para invocar a los métodos estáticos de la clase actual. No se debe olvidar que las clases personalizadas heredan los métodos de las clases Base del directorio lib/model/om/.

[codesyntax lang=»php» title=»Personalizar el modelo, en lib/model/Articulo.php»]

<?

class Articulo extends BaseArticulo
{
  public function __toString()
  {
    return $this->getTitulo();  // getTitulo() se hereda de BaseArticulo
  }
}

[/codesyntax]

También se pueden extender las clases peer, como por ejemplo para obtener todos los artículos ordenados por fecha de creación:

[codesyntax lang=»php» title=»Personalizando el modelo, en lib/model/ArticuloPeer.php»]

<?

class ArticuloPeer extends BaseArticuloPeer
{
  public static function getTodosOrdenadosPorFecha()
  {
    $c = new Criteria();
    $c->addAscendingOrderByColumn(self::CREATED_AT);
    return self::doSelect($c);
  }
}

[/codesyntax]

 

[codesyntax lang=»php» title=»El uso de métodos personalizados del modelo es idéntico al de los métodos generados automáticamente»]

<?

foreach (ArticuloPeer::getTodosOrdenadosPorFecha() as $articulo)
{
  echo $articulo;   // Se llama al método mágico __toString()
}

[/codesyntax]

 

REDEFINIR MÉTODOS EXISTENTES

Si alguno de los métodos generados automáticamente en las clases Base no satisfacen las necesidades de la aplicación, se pueden redefinir en las clases personalizadas. Solamente es necesario mantener el mismo número de argumentos para cada método.

Por ejemplo, el método $articulo->getComentarios() devuelve un array de objetos Comentario, sin ningún tipo de ordenamiento. Si se necesitan los resultados ordenados por fecha de creación siendo el primero el comentario más reciente, se puede redefinir el método getComentarios(). Como el método getComentarios() original (guardado en lib/model/om/BaseArticulo.php) requiere como argumentos un objeto de tipo Criteria y una conexión, la nueva función debe contener esos mismos parámetros.

[codesyntax lang=»php» title=»Redefiniendo los métodos existentes en el modelo, en lib/model/Articulo.php»]

<?

public function getComentarios($criteria = null, $con = null)
{
  if (is_null($criteria))
  {
    $criteria = new Criteria();
  }
  else
  {
    // Los objetos se pasan por referencia en PHP5, por lo que se debe clonar
    // el objeto original para no modificarlo
    $criteria = clone $criteria;
  }
  $criteria->addDescendingOrderByColumn(ComentarioPeer::CREATED_AT);

  return parent::getComentarios($criteria, $con);
}

[/codesyntax]

El método personalizado acaba llamando a su método padre en la clase Base, lo que se considera una buena práctica. No obstante, es posible saltarse completamente la clase Base y devolver el resultado directamente.

 

USO DE COMPORTAMIENTOS EN EL MODELO

Algunas de las modificaciones que se realizan en el modelo son genéricas y por tanto se pueden reutilizar. Por ejemplo, los métodos que hacen que un objeto del modelo sea reordenable o un bloqueo de tipo optimistic en la base de datos para evitar conflictos cuando se guardan de forma concurrente los objetos se pueden considerar extensiones genéricas que se pueden añadir a muchas clases.

Symfony encapsula estas extensiones en «comportamientos» (del inglés behaviors). Los comportamientos son clases externas que proporcionan métodos extras a las clases del modelo. Las clases del modelo están definidas de forma que se puedan enganchar estas clases externas y Symfony extiende las clases del modelo mediante sfMixer.

Para habilitar los comportamientos en las clases del modelo, se debe modificar una opción del archivo config/propel.ini:

propel.builder.AddBehaviors = true // El valor por defecto es false

Symfony no incluye por defecto ningún comportamiento, pero se pueden instalar mediante plugins. Una vez que el plugin se ha instalado, se puede asignar un comportamiento a una clase mediante una sola línea de código. Si por ejemplo se ha instalado el plugin sfPropelParanoidBehaviorPlugin en la aplicación, se puede extender la clase Articulo con este comportamiento añadiendo la siguiente línea de código al final del archivo Articulo.class.php:

[codesyntax lang=»php»]

<?

sfPropelBehavior::add('Articulo', array(
  'paranoid' => array('column' => 'deleted_at')
));

[/codesyntax]

 

Después de volver a generar el modelo, los objetos de tipo Articulo que se borren permanecerán en la base de datos, aunque será invisibles a las consultas que hacen uso de los métodos del ORM, a no ser que se deshabilite temporalmente el comportamiento mediante sfPropelParanoidBehavior::disable().

Desde la versión 1.1 de Symfony también es posible declarar los comportamientos directamente en el archivo schema.yml, incluyéndolos bajo la clave _behaviors.

La lista de plugins de Symfony disponible en el wiki incluye numerosos comportamientos http://trac.symfony-project.org/wiki/SymfonyPlugins#Behaviors. Cada comportamiento tiene su propia documentación y su propia guía de instalación.

 

SINTAXIS EXTENDIDA DEL ESQUEMA

Atributos

Se pueden definir atributos específicos para las conexiones y las tablas. Estas opciones se establecen bajo la clave _attributes.

[codesyntax lang=»text»]

propel:
  _attributes:   { noXsd: false, defaultIdMethod: none, package: lib.model }
  blog_articulo:
    _attributes: { phpName: Articulo }

[/codesyntax]

Si se quiere validar el esquema antes de que se genere el código asociado, se debe desactivar en la conexión el atributo noXSD. La conexión también permite que se le indique el atributo defaultIdMethod. Si no se indica, se utilizará el método nativo de generación de IDs –por ejemplo, autoincrement en MySQL o sequences en PostgreSQL. El otro valor permitido es none.

El atributo package es como un namespace; indica la ruta donde se guardan las clases generadas automáticamente. Su valor por defecto es lib/model/, pero se puede modificar para organizar el modelo en una estructura de subpaquetes. Si por ejemplo no se quieren mezclar en el mismo directorio las clases del núcleo de la aplicación con las clases de un sistema de estadísticas, se pueden definir dos esquemas diferentes con los paquetes lib.model.business y lib.model.stats.

Ya se ha visto el atributo de tabla phpName, que se utiliza para establecer el nombre de la clase generada automáticamente para manejar cada tabla de la base de datos.

Las tablas que guardan contenidos adaptados para diferentes idiomas (es decir, varias versiones del mismo contenido en una tabla relacionada, para conseguir la internacionalización) también pueden definir dos atributos adicionales.

[codesyntax lang=»text»]

propel:
  blog_articulo:
    _attributes: { isI18N: true, i18nTable: db_group_i18n }

[/codesyntax]

 

DETALLE DE LAS COLUMNAS

La sintaxis básica ofrece dos posibilidades: dejar que Symfony deduzca las características de una columna a partir de su nombre (indicando un valor vacío para esa columna) o definir el tipo de columna con uno de los tipos predefinidos.
[codesyntax lang=»text»]

propel:
  blog_articulo:
    id:                 # Symfony se encarga de esta columna
    titulo: varchar(50)  # Definir el tipo explícitamente

[/codesyntax]

Se pueden definir muchos más aspectos de cada columna. Si se definen, se utiliza un array asociativo para indicar las opciones de la columna.

[codesyntax lang=»text»]

propel:
  blog_articulo:
    id:       { type: integer, required: true, primaryKey: true, autoIncrement: true }
    name:     { type: varchar(50), default: foobar, index: true }
    group_id: { type: integer, foreignTable: db_group, foreignReference: id, onDelete: cascade }

[/codesyntax]

Los parámetros de las columnas son los siguientes:

  • type: Tipo de columna. Se puede elegir entre boolean, tinyint, smallint, integer, bigint, double, float, real, decimal, char, varchar(tamano), longvarchar, date, time, timestamp, bu_date, bu_timestamp, blob y clob
  • required: valor booleano. Si vale true la columna debe tener obligatoriamente un valor.
  • default: el valor por defecto.
  • primaryKey: valor booleano. Si vale true indica que es una clave primaria.
  • autoIncrement: valor booleano. Si se indica true para las columnas de tipo integer, su valor se auto-incrementará.
  • sequence: el nombre de la secuencia para las bases de datos que utilizan secuencias para las columnas autoIncrement (por ejemplo PostgreSQL y Oracle).
  • index: valor booleano. Si vale true, se construye un índice simple; si vale unique se construye un índice único para la columna.
  • foreignTable: el nombre de una tabla, se utiliza para crear una clave externa a otra tabla.
  • foreignReference: el nombre de la columna relacionada si las claves externas se definen mediante foreignTable.
  • onDelete: determina la acción que se ejecuta cuando se borra un registro en una tabla relacionada. Si su valor es setnull, la columna de la clave externa se establece a null. Si su valor es cascade, se borra el registro relacionado. Si el sistema gestor de bases de datos no soporta este comportamiento, el ORM lo emula. Esta opción solo tiene sentido para las columnas que definen una foreignTable y una foreignReference.
  • isCulture: valor booleano. Su valor es true para las columnas de tipo culture en las tablas de contenidos adaptados a otros idiomas.

 

CLAVES EXTERNAS

Además de los atributos de columna foreignTable y foreignReference, es posible añadir claves externas bajo la clave _foreignKeys: de cada tabla.

[codesyntax lang=»text»]

propel:
  blog_articulo:
    id:
    titulo:     varchar(50)
    usuario_id: { type: integer }
    _foreignKeys:
      -
        foreignTable: blog_usuario
        onDelete:     cascade
        references:
          - { local: usuario_id, foreign: id }

[/codesyntax]

La sintaxis alternativa es muy útil para las claves externas múltiples y para indicar un nombre a cada clave externa.

[codesyntax lang=»text»]

_foreignKeys:
  mi_clave_externa:
    foreignTable:  db_usuario
    onDelete:      cascade
    references:
      - { local: usuario_id, foreign: id }
      - { local: post_id, foreign: id }

[/codesyntax]

 

INDICES

Además del atributo de columna index, es posible añadir claves índices bajo la clave _indexes: de cada tabla. Si se quieren crean índices únicos, se debe utilizar la clave _uniques:. En las columnas que requieren un tamaño, por ejemplo por ser columnas de texto, el tamaño del índice se indica entre paréntesis, de la misma forma que se indica el tamaño de cualquier columna.

[codesyntax lang=»text»]

propel:
  blog_articulo:
    id:
    titulo:            varchar(50)
    created_at:
    _indexes:
      mi_indice:      [titulo(10), usuario_id]
    _uniques:
      mi_otro_indice: [created_at]

[/codesyntax]

 

COLUMNAS VACÍAS

Cuando Symfony se encuentra con una columna sin ningún valor, utiliza algo de magia para determinar su valor.

[codesyntax lang=»text»]

// Las columnas vacías llamadas "id" se consideran claves primarias
id:         { type: integer, required: true, primaryKey: true, autoIncrement: true }

// Las columnas vacías llamadas "XXX_id" se consideran claves externas
loquesea_id:  { type: integer, foreignTable: db_loquesea, foreignReference: id }

// Las columnas vacías llamadas created_at, updated at, created_on y updated_on
// se consideran fechas y automáticamente se les asigna el tipo "timestamp"
created_at: { type: timestamp }
updated_at: { type: timestamp }

[/codesyntax]

Para las claves externas, Symfony busca una tabla cuyo phpName sea igual al principio del nombre de la columna; si se encuentra, se utiliza ese nombre de tabla como foreignTable.

 

TABLAS I18N

Symfony permite internacionalizar los contenidos mediante tablas relacionadas. De esta forma, cuando se dispone de contenido que debe ser internacionalizado, se guarda en 2 tablas distintas: una contiene las columnas invariantes y otra las columnas que permiten la internacionalización.

Todo lo anterior se considera de forma implícita cuando en el archivo schema.yml se dispone de una tabla con el nombre cualquiernombre_i18n.

[codesyntax lang=»text»]

propel:
  db_group:
    id:
    created_at:

  db_group_i18n:
    name:        varchar(50)

[/codesyntax]

[codesyntax lang=»text»]

propel:
  db_group:
    _attributes: { isI18N: true, i18nTable: db_group_i18n }
    id:
    created_at:

  db_group_i18n:
    id:       { type: integer, required: true, primaryKey: true, foreignTable: db_group, foreignReference: id, onDelete: cascade }
    culture:  { isCulture: true, type: varchar(7), required: true, primaryKey: true }
    name:     varchar(50)

[/codesyntax]

 

COMPORTAMIENTOS

Los comportamientos son plugins que modifican el modelo de datos para añadir nuevas funcionalidades a las clases de Propel.

[codesyntax lang=»text»]

propel:
  blog_articulo:
    titulo:         varchar(50)
    _behaviors:
      paranoid:     { column: deleted_at }

[/codesyntax]

 

Symfony: El modelo (II)

0

Cuando Symfony construye el modelo, crea una clase de objeto base para cada una de las tablas definidas en schema.yml. Cada una de estas clases contiene una serie de constructores y accesores por defecto en función de la definición de cada columna: los métodos new, getXXX() y setXXX() (donde XXX es el nombre del campo) permiten crear y obtener las propiedades de los objetos:

[codesyntax lang=»php» title=»Métodos generados en una clase objeto»]

<?php

$articulo = new Articulo();
$articulo->setTitulo('Mi primer artículo');
$articulo->setContenido('Este es mi primer artículo. \n Espero que te guste.');

$titulo    = $articulo->getTitulo();
$contenido = $articulo->getContenido();

[/codesyntax]

 

Para establecer el valor de varios campos a la vez, se puede utilizar el método fromArray(), que también se genera para cada clase objeto:

[codesyntax lang=»php» title=»El método fromArray() es un setter múltiple»]

<?php

$articulo->fromArray(array(
  'titulo'   => 'Mi primer artículo',
  'contenido' => 'Este es mi primer artículo. \n Espero que te guste.'
));

[/codesyntax]

[codesyntax lang=»php» title=»Las claves externas se traducen en getters especiales»]

<?php

// Relación de "muchos a uno"
echo $comentario->getArticulo()->getTitulo();
 => Mi primer artículo
echo $comentario->getArticulo()->getContenido();
 => Este es mi primer artículo.
    Espero que te guste.

// Relación "uno a muchos"
$comentarios = $articulo->getComentarios();

[/codesyntax]

La columna articulo_id de la tabla blog_comentario define implícitamente una clave externa a la tabla blog_articulo. Cada comentario está relacionado con un artículo y un artículo puede tener muchos comentarios. Las clases generadas contienen 5 métodos que traducen esta relación a la forma orientada a objetos, de la siguiente manera:

  • $comentario->getArticulo(): para obtener el objeto Articulo relacionado
  • $comentario->getArticuloId(): para obtener el ID del objeto Articulo relacionado
  • $comentario->setArticulo($articulo): para definir el objeto Articulo relacionado
  • $comentario->setArticuloId($id): para definir el objeto Articulo relacionado a partir de un ID
  • $articulo->getComentarios(): para obtener los objetos Comentario relacionados

[codesyntax lang=»php» title=»Las claves externas se traducen en un setter especial»]

<?php

$comentario = new Comentario();
$comentario->setAutor('Steve');
$comentario->setContenido('¡Es el mejor artículo que he leído nunca!');

// Añadir este comentario al anterior objeto $articulo
$comentario->setArticulo($articulo);

// Sintaxis alternativa
// Solo es correcta cuando el objeto artículo ya
// ha sido guardado anteriormente en la base de datos
$comentario->setArticuloId($articulo->getId());

[/codesyntax]

El método getArticulo() devuelve un objeto de tipo Articulo, que permite utilizar el método accesor getTitulo(). Se trata de una alternativa mucho mejor que realizar la unión de las tablas manualmente, ya que esto último necesitaría varias líneas de código (empezando con la llamada al método $comment->getArticuloId()).

La variable $comentarios contiene un array de objetos de tipo Comentario. Se puede mostrar el primer comentario mediante $comentarios[0] o se puede recorrer la colección entera mediante foreach ($comentarios as $comentario).

Al utilizar el constructor new se crea un nuevo objeto, pero no un registro en la tabla blog_articulo. Si se modifica el objeto, tampoco se reflejan esos cambios en la base de datos. Para guardar los datos en la base de datos, se debe invocar el método save() del objeto.

[codesyntax lang=»php»]

<?php

$articulo->save();

[/codesyntax]

[codesyntax lang=»php» title=»Borrar registros de la base de datos mediante el método delete() del objeto relacionado»]

<?php

foreach ($articulo->getComentarios() as $comentario)
{
  $comentario->delete();
}

[/codesyntax]

Si se conoce la clave primaria de un registro concreto, se puede utilizar el método retrieveByPk() de la clase peer para obtener el objeto relacionado.

[codesyntax lang=»php»]

<?php

$articulo = ArticuloPeer::retrieveByPk(7);

[/codesyntax]

En algunos casos, la clave primaria está formada por más de una columna. Es esos casos, el método retrieveByPK() permite indicar varios parámetros, uno para cada columna de la clave primaria.

También se pueden obtener varios objetos a la vez mediante sus claves primarias, invocando el método retrieveByPKs(), que espera como argumento un array de claves primarias.

 

Cuando se quiere obtener más de un registro, se debe utilizar el método doSelect() de la clase peer correspondiente a los objetos que se quieren obtener. Por ejemplo, para obtener objetos de la clase Articulo, se llama al método ArticuloPeer::doSelect().

El primer parámetro del método doSelect() es un objeto de la clase Criteria, que es una clase para definir consultas simples sin utilizar SQL, para conseguir la abstracción de base de datos.

Un objeto Criteria vacío devuelve todos los objetos de la clase.

[codesyntax lang=»php» title=»Obtener registros mediante Criteria con el método doSelect() (Criteria vacío)»]

<?php

$c = new Criteria();
$articulos = ArticuloPeer::doSelect($c);

// Genera la siguiente consulta SQL
SELECT blog_articulo.ID, blog_articulo.TITLE, blog_articulo.CONTENIDO,
       blog_articulo.CREATED_AT
FROM   blog_articulo;

[/codesyntax]

Para las selecciones más complejas de objetos, se necesitan equivalentes a las sentencias WHERE, ORDER BY, GROUP BY y demás de SQL. El objeto Criteria dispone de métodos y parámetros para indicar todas estas condiciones. Por ejemplo, para obtener todos los comentarios escritos por el usuario Steve y ordenados por fecha:

[codesyntax lang=»php» title=»Obtener registros mediante Criteria con el método doSelect() (Criteria con condiciones)»]

<?php

$c = new Criteria();
$c->add(ComentarioPeer::AUTOR, 'Steve');
$c->addAscendingOrderByColumn(ComentarioPeer::CREATED_AT);
$comentarios = ComentarioPeer::doSelect($c);

// Genera la siguiente consulta SQL
SELECT blog_comentario.ARTICULO_ID, blog_comentario.AUTOR, blog_comentario.CONTENIDO,
       blog_comentario.CREATED_AT
FROM   blog_comentario
WHERE  blog_comentario.autor = 'Steve'
ORDER BY blog_comentario.CREATED_AT ASC;

[/codesyntax]

as constantes de clase que se pasan como parámetros a los métodos add() hacen referencia a los nombres de las propiedades. Su nombre se genera a partir del nombre de las columnas en mayúsculas. Por ejemplo, para indicar la columna contenido de la tabla blog_articulo, se utiliza la constante de clase llamada ArticuloPeer::CONTENIDO.

Otro ejemplo del uso de Criteria con condiciones múltiples. En el ejemplo se obtienen todos los comentarios del usuario Steve en los artículos que contienen la palabra enjoy y además, ordenados por fecha.

 

[codesyntax lang=»php»]

<?php

$c = new Criteria();
$c->add(ComentarioPeer::AUTOR, 'Steve');
$c->addJoin(ComentarioPeer::ARTICULO_ID, ArticuloPeer::ID);
$c->add(ArticuloPeer::CONTENIDO, '%enjoy%', Criteria::LIKE);
$c->addAscendingOrderByColumn(ComentarioPeer::CREATED_AT);
$comentarios = ComentarioPeer::doSelect($c);

// Genera la siguiente consulta SQL
SELECT blog_comentario.ID, blog_comentario.ARTICULO_ID, blog_comentario.AUTOR,
       blog_comentario.CONTENIDO, blog_comentario.CREATED_AT
FROM   blog_comentario, blog_articulo
WHERE  blog_comentario.AUTOR = 'Steve'
       AND blog_articulo.CONTENIDO LIKE '%enjoy%'
       AND blog_comentario.ARTICULO_ID = blog_articulo.ID
ORDER BY blog_comentario.CREATED_AT ASC

[/codesyntax]

Además del método doSelect(), todas las clases peer tienen un método llamado doCount(), que simplemente cuenta el número de registros que satisfacen las condiciones pasadas como parámetro y devuelve ese número como un entero.

Las clases peer también incluyen métodos doDelete(), doInsert() y doUpdate() (todos ellos requieren como parámetro un objeto de tipo Criteria).

Por último, si solo se quiere obtener el primer objeto, se puede reemplazar el método doSelect() por doSelectOne(). Es muy útil cuando se sabe que las condiciones de Criteria solo van a devolver un resultado, y su ventaja es que el método devuelve directamente un objeto en vez de un array de objetos.

A veces, no es necesario obtener los objetos, sino que solo son necesarios algunos datos calculados por la base de datos. Por ejemplo, para obtener la fecha de creación de todos los artículos, no tiene sentido obtener todos los artículos y después recorrer el array de los resultados.

Para realizar consultas a la base de datos con PDO, es necesario realizar los siguientes pasos:

  • Obtener la conexión con la base de datos.
  • Construir la consulta.
  • Crear una sentencia con esa consulta.
  • Iterar el result set que devuelve la ejecución de la sentencia.

[codesyntax lang=»php» title=»Consultas SQL personalizadas con PDO»]

<?php

conexion = Propel::getConnection();
$consulta = 'SELECT MAX(%s) AS max FROM %s';
$consulta = sprintf($consulta, ArticuloPeer::CREATED_AT, ArticuloPeer::TABLE_NAME);
$sentencia = $conexion->prepare($consulta);
$sentencia->execute();
$resultset = $sentencia->fetch(PDO::FETCH_OBJ);
$max = $resultset->max;

[/codesyntax]

Normalmente, cuando una tabla tiene una columna llamada created_at, se utiliza para almacenar un timestamp de la fecha de creación del registro. La misma idea se aplica a las columnas updated_at, cuyo valor se debe actualizar cada vez que se actualiza el propio registro.

La buena noticia es que Symfony reconoce estos nombres de columna y se ocupa de actualizar su valor de forma automática. No es necesario establecer manualmente el valor de las columnas created_at y updated_at; se actualizan automáticamente. Lo mismo se aplica a las columnas llamadas created_on y updated_on.
[codesyntax lang=»php» title=»Las columnas created_at y updated_at se gestionan automáticamente»]

<?php

$comentario = new Comentario();
$comentario->setAutor('Steve');
$comentario->save();

// Muestra la fecha de creación
echo $comentario->getCreatedAt();
 => [fecha de la operación INSERT de la base de datos]

[/codesyntax]

Además, los getters de las columnas de fechas permiten indicar el formato de la fecha como argumento:

[codesyntax lang=»php»]

<?php

echo $comentario->getCreatedAt('Y-m-d');

[/codesyntax]

 

«Chuletas» para Symfony

0

Más de uno conocerá esas imágenes o PDFs que, de forma resumida, muestran todos los métodos y funciones de los lenguajes de programación o de frameworks. Con Symfony eso no es diferente, además de ser muy útil en las primeras etapas de aprendizaje. Os dejo el link:

http://trac.symfony-project.org/wiki/CheatSheets

 

¡Qué lo disfrutéis!

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: 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.

Ir arriba