Programación y sistemas

Symfony: El modelo (III)

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]

 

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

Descubre más desde Interadictos

Suscríbete ahora para seguir leyendo y obtener acceso al archivo completo.

Seguir leyendo