Estas en: Home > sintaxis

Entradas etiquetadas con sintaxis

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 (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]

Ir arriba