Registro Activo

Aunque la DAO de Yii puede manejar virtualmente cualquier tarea relacionada con la base de datos, lo más probable es que gastemos el 90% de nuestro tiempo escribiendo algunas sentencias SQL relacionadas con la ejecución de las operaciones CRUD comunes. Es tambien dificil mantener nuestro código cuando éste está mezclado con sentencias SQL. Para solucionar estos problemas, podemos usar los Registros Activos (Active Record).

Registro Activo (AR) es una técnica popular de Mapeo Objeto-Relacional (ORM). Cada clase AR representa una tabla de la base de datos (o vista) cuyos atributos son representados como las propiedades de la clase AR, y una instancia AR representa una fila en esa tabla. La operaciones CRUD comunes son implementadas como metodos de la clase AR. Como resultado, podemos acceder a nuestros datos de una manera más orientada a objetos. Por ejemplo, podemos usar el siguiente código para insertar una nueva fila a la tabla Post:

$post=new Post;
$post->title='post ejemplo';
$post->content='contenido del cuerpor del post';
$post->save();

A continuación describiremos como configurar un AR y usarlo para ejecutar las operaciones CRUD. Mostraremos como usar un AR para tratar con relaciones en la base de datos en la siguiente sección. Por sencillez, usamos la siguiente tabla de la base de datos para nuestros ejemplo en esta sección.

CREATE TABLE Post (
    id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
    title VARCHAR(128) NOT NULL,
    content TEXT NOT NULL,
    createTime INTEGER NOT NULL
);

Nota: AR no pretende resolver todas las tareas relacionadas con la base de datos. Lo mejor es usarlo para modelar tablas de bases de datos en construcciones PHP y ejecutar consultas que no involucren SQLs complejas. Para esos escenarios complejos debe usarse el DAO de Yii.

1. Estableciendo la Conexión con la BD

Los AR dependen de una conexión con una BD para ejecutar operaciones relacionadas con la BD. Por defecto, asumimos que el componente de aplicación db nos da la instancia CDbConnection necesaria que nos sirve como la conexión de la BD. La siguiente configuración de aplicación muestra un ejemplo:

return array(
    'components'=>array(
        'db'=>array(
            'class'=>'system.db.CDbConnection',
            'connectionString'=>'sqlite:path/to/dbfile',
            // activar el cacheo de esquema para mejorar el rendimiento
            // 'schemaCachingDuration'=>3600,
        ),
    ),
);

Consejo: Puesto que AR depende los metadatos de las tablas para determinar la información de la columna, toma tiempo leer los metadatos y analizarlos. Si el esquema de tu base de datos es menos probable que sea cambiado, deberías activar el caché de esquema configurando la propiedad CDbConnection::schemaCachingDuration a un valor mayor que 0.

El soporte para AR está limitado por el DBMS. Actualmente, solo los siguientes DBMS están soportados:

Si querés usar un componente de aplicación diferente de db, o si querés trabajar con múltiples bases de datos usando AR, deberías sobreescribir CActiveRecord::getDbConnection(). La clase CActiveRecord es la clase base para todas las clases AR.

Consejo: Existen dos maneras de trabajar con multiples bases de datos con AR. Si los esquemas de las bases de datos son diferentes, puedes crear diferentes clases base AR con diferentes implementaciones de getDbConnection(). De otra manera, cambiar dinámicamente la variable estática CActiveRecord::db es una mejor idea.

2. Definiendo la Clase AR

Para acceder a una tabla de la base de datos, primero necesitamos definir una clase AR extendiendo CActiveRecord. Cada clase AR representa una única tabla de la base de datos, y una instancia AR representa una fila en esa tabla. El siguiente ejemplo muestra el código mínimo necesario para la clase AR que representa la tabla Post.

class Post extends CActiveRecord
{
    public static function model($className=__CLASS__)
    {
        return parent::model($className);
    }
}

Consejo: Puesto que las clases AR son referencidas frecuentemente en varios lugares, podemos importar todo el directorio que contiene las clases AR, en vez de incluirlas una a una. Por ejemplo, si todos nuestros archivos de clases AR estan bajo protected/models, podemos configurar la aplicación como sigue:

return array(
  'import'=>array(
      'application.models.*',
  ),
);

Por defecto, el nombre de la clase AR es el mismo que el nombre de la tabla de la base de datos. Sobreescribir el método tableName() si son diferentes. El método model() está declarado para cada clase AR (será explicado en breve).

Los valores de las columnas de una fila de la tabla pueden ser accedidos como propiedades de la correspondiente instancia de la clase AR. Por ejemplo, el siguiente código establece la columna (atributo) title:

$post=new Post;
$post->title='un post de ejemplo';

Aunque nunca declaramos explicitamente la propiedad title en la clase Post, podemos aún accederla en el código anterior. Esto es debido a que title es una columna en la tabla Post, y CActiveRecord la hace accesible como una propiedad con la ayuda del método mágico de PHP __get(). Será arrojada una excepción si intentamos acceder a una columna no existente de la misma manera.

Información: Para una mejor legibilidad, sugerimos nombrar las tablas de la base de datos y las columnas con las primeras letras de cada palabra distinta en mayúsculas. En particular, los nombres de tablas estan formados poniendo en mayúsculas la primera letra de cada palabra y juntándolas sin espacios; los nombres de las columnas son similares a los de las tablas, excepto que la primer letra de la primer palabra debe permanecer en minúsculas. Por ejemplo, usamos Post como nombre de la tabla que almacena los posts; y usamos createTime para nombrar a la columna de la clave primaria. Esto hace que las tablas luzcan más como tipos de clases y las columnas más como variables. Notar, sin embargo, que usar esta convención puede traer inconvenientes para algunos DBMS como MySQL, que puede comportarse de forma diferente en diferentes sistemas operativos.

3. Creando Registros

Para insertar una nueva fila en una tabla de la base de datos, creamos una nueva instancia de la correspondiente clase AR, establecemos sus propiedades asociadeas con las columnas de la tabla, y llamamos al método save() para finalizar la inserción.

$post=new Post;
$post->title='post ejemplo';
$post->content='contenido del post ejemplo';
$post->createTime=time();
$post->save();

Si la clave primaria de la tabla se autoincrementa, luego de la inserción la instancia AR contendrá la clave primaria actualizada. En el ejemplo anterior, la propiedad id reflejará el valor de la clave primaria del post recien insertado, aún cuando nunca la cambiamos explicitamente.

Si una columna está definida con algún valor estático por defecto (ej.: una string, un número) en el esquema de la tabla, la propiedad correspondiente en la instancia AR tendrá automáticamente un valoar luego de crear la instancia. Una manera de cambiar este valor por defecto es declarando explicitametne la propiedad en la clase AR:

class Post extends CActiveRecord
{
    public $title='por favor ingrese un título';
    ......
}
 
$post=new Post;
echo $post->title;  // esto mostrará: por favor ingrese un título

Desde la versión 1.0.2, a un atributo se le puede asignar un valor de tipo CDbExpression antes de que el registro sea guardado (tante en la inserción como en la actualización) en la base de datos. Por ejemplo, para guardar el timestamp devuelto por la funcion NOW() de MySQL, podemos usar el siguiente código:

$post=new Post;
$post->createTime=new CDbExpression('NOW()');
// $post->createTime='NOW()'; no funcionará porque
// 'NOW()' será tratado como una string
$post->save();

4. Leyendo Registros

Para leer datos en una base de datos, podemos llamar a uno de los métodos find como sigue.

// encontrar el primer registro que cumpla la condición especificada
$post=Post::model()->find($condition,$params);
// encontrar la fila con la clave primaria especificada
$post=Post::model()->findByPk($postID,$condition,$params);
// encontrar la fila con los valores de los atributos especificados
$post=Post::model()->findByAttributes($attributes,$condition,$params);
// encontrar la primer fila usando la sentencia SQL especificada
$post=Post::model()->findBySql($sql,$params);

En lo anterior, llamamos al método find con Post::model(). Recordemos que el método estático model() es requerido por toda clase AR. El método devuelve una instancia que es usada para acceder a los métodos de nivel de clase (algo similar a los métodos de clase estáticos) en un contexto de objetos.

Si el método find encuentra una fila que cumpla con las condiciones de la consulta, devolverá una instancia de Post cuyas propiedades contendran los correspondientes valores de las columnas en la fila de la tabla. Podemos entonces leer los valores cargados como lo hacemos con las propiedades de objetos normales, por ejemplo, echo $post->title;

El método find devolverá null si nada puede ser encontrado en la base de datos con las condiciones de la consulta dada.

Cuando llamammos a find, usamos $condition y $params para especificar las condiciones de la consulta. Aquí, $condition puede ser una string representando la cláusula WHERE en una sentencia SQL, y ``$paramses un arreglo de parámetros cuyos valores deben ser enlazados a los marcadores de posición en$condition`. Por ejemplo,

// find the row with postID=10
$post=Post::model()->find('postID=:postID', array(':postID'=>10));

Podemos tambien usar $condition para especificar condiciones de consultas más complejas. En vez de una strign, dejamos a $condition ser una instancia de CDbCriteria, que nos permite especificar otras condiciones ademas de la cláusula WHERE. Por ejemplo,

$criteria=new CDbCriteria;
$criteria->select='title';  // seleccionar solo la columna 'title'
$criteria->condition='postID=:postID';
$criteria->params=array(':postID'=>10);
$post=Post::model()->find($criteria); // $params no es necesario

Notar que, cuando usamos CDbCriteria como condición de la consulta, el parámetro $params ya no es necesario, puesto que puede ser especificado en CDbCriteria, como se muestra arriba.

Una forma alternativa a CDbCriteria es pasar un arreglo al método find. Las claves y los valores del arreglo corresponden a las propiedades del criterio y sus valores respectivamente. El ejemplo anterior puede ser reescrito como sigue,

$post=Post::model()->find(array(
    'select'=>'title',
    'condition'=>'postID=:postID',
    'params'=>array(':postID'=>10),
));

Información: Cuando una condición de consulta es sobre que algunas columnas tengan valores específicos, podemos usar findByAttributes(). Dejaremos al parámetro $attributes ser un arreglo de los valores indexados por los nombres de las columnas. En algunos frameworks, esta tarea puede ser lograda llamando métodos como findByNameAndTitle. Aunque este enfoque parece atractivo, frecuentemente causa confusión, conflictos y cuestiones como sensibilidad a mayúsculas/minúsculas de los nombres de columna.

Cuando múltiples filas de datos coinciden con una condidición de consulta especificada, podemos traerlas todas juntas usando los siguientes métodos findAll, cada uno de los cuales tiene su método contraparte find, que ya mencionamos anteriormente.

// encontrar todas las filas que cumplan la condición especificada
$posts=Post::model()->findAll($condition,$params);
// encontrar todas las filas con la clave primaria especificada
$posts=Post::model()->findAllByPk($postIDs,$condition,$params);
// encontrar todas las filas con los valores de atributos especificados
$posts=Post::model()->findAllByAttributes($attributes,$condition,$params);
// encontrar todas las filas usando la sentencia SQL especificada
$posts=Post::model()->findAllBySql($sql,$params);

Si nada coincide con la condición de la consulta, findAll devolverá un arreglo vacío. Esto es diferente a find, quien devuelve null si no se encuentra cosa alguna.

Además de los métodos find y findAll descriptos anteriormente, por conveniencia también se proveen los siguientes métodos:

// obtener el número de filas que cumplan la condición especificada
$n=Post::model()->count($condition,$params);
// obtener el número de filas usando la sentencia SQL especificada
$n=Post::model()->countBySql($sql,$params);
// comprobar si existe al menos una fila que cumpla la condición especificada
$exists=Post::model()->exists($condition,$params);

5. Actualizando Registros

Luego de que una isntancia AR sea rellenada con valores, podemos cambiarlos y volver a guardarlos en la tabla de la base de datos.

$post=Post::model()->findByPk(10);
$post->title='nuevo titulo del post';
$post->save(); // guardar cambios en la base de datos

Como podemos ver, usamos el mismo método save() para ejecutar las operaciones de inserción y actualización. Si una instancia AR es creada usando el operador new, llamar a save() insertará una nueva fila en la tabla de la base de datos; si la instancia AR es el resultado de la llamada a algún método find o findAll, llamar a save() actualizará la fila existente en la tabla. De hecho, podemos usar CActiveRecord::isNewRecord para decir si una instancia AR es nueva o no.

También es posible actualizar una o varias filas en una tabla de la base de datos sin cargarlas primero. AR provee los siguientes convenientes métodos de nivel de clase para este propósito:

// actualizar las filas que coincidan con la condición especificada
Post::model()->updateAll($attributes,$condition,$params);
// actualizar las filas que coincidan con la condición especificada y con la(s) clave(s) primaria(s)
Post::model()->updateByPk($pk,$attributes,$condition,$params);
// update counter columns in the rows satisfying the specified conditions
Post::model()->updateCounters($counters,$condition,$params);

En lo anterior, $attributes es un arreglo de valores de columna indexado por nombres de columna; $counters es un arreglo de valores incrementales indexados por nombres de columna; y $condition y $params son como se describió en las subsecciones previas.

6. Borrando Registros

Podemos también borrar una fila de datos si una instancia AR ha sido rellenada con esa fila.

$post=Post::model()->findByPk(10); // asumiendo que existe un post cuyo ID es 10
$post->delete(); // borra la fila de la tabla de la base de datos

Nota, luego del borrado, la instancia AR permanece intacta, pero la correspondiente fila en la tabla de la base de datos ya no está.

Los siguientes métodos de nivel de clase se proveen para borrar filas sin la necesidad de cargarlas primero:

// borra todas las filas que coincidan con la condición especificada
Post::model()->deleteAll($condition,$params);
// borra todas las filas que coincidan con la condición especificada y con la(s) clave(s) primaria(s)
Post::model()->deleteByPk($pk,$condition,$params);

7. Validación de Datos

Cuando insertamos o actualizamos una fila, frecuentemente necesitamos comprobar que los valores de las columnas cumplen ciertas reglas. Esto es especialmente importante si los valores de la columna son provistos por usuarios finales. En general, nunca debemos confiar en nada que provenga del lado del cliente.

AR ejecuta la validación de datos automáticamente cuando se invoca a save(). La validación está basada en las reglas especificadas en el método rules() de la clase AR. Para más detalles acerca de como especificar reglas de validación, ver la sección Declarando Relgas de Validación. A continuación está el flujo de trabajo típico necesario para guardar un registro:

if($post->save())
{
    // los datos son válidos y están insertados/actualizados exitosamente
}
else
{
    // los datos no son válidos. Llamar a  getErrors() para obtener los mensajes de error
}

Cuando los datos a insertar o actualizar son enviados por usarios finales en un formulario HTML, necesitamos asignarlos a las correspondientes propiedades AR. Podemos hacerlo como sigue:

$post->title=$_POST['title'];
$post->content=$_POST['content'];
$post->save();

Si existen muchas columnas, veremos una larga lista de dichas asignaciones. Esto se puede aliviar haciendo uso de la propiedad attributes como se muestra a continuación. Más detalles pueden ser encontrados en la sección Asegurando las Asignaciones de Atributos y en la sección Creating Action.

// asumimos que $_POST['Post'] es un arreglo de valores de columna indexados por nombres de columna
$post->attributes=$_POST['Post'];
$post->save();

8. Comparando Registros

Como las filas de las tablas, las instancias AR están identificadas de manera única por los valores de su clave primaria. Por lo tanto, para comparar dos instancias AR, solo es necesario comparar los valores de sus claves primarias, asumiendo que pertenezcan a la misma clase AR. Sin embargo, una manera más simple es llamar a CActiveRecord::equals().

Info: Información: A diferencia de la implementación de AR en otros frameworks, Yii soporta claves primaris compuestas en su AR. Una clave primaria consiste de dos o más columnas. Correspondientemente, en Yii el valor de la clave primaria está representado como un arreglo. La propiedad primaryKey nos da el valor de la clave primaria de una instancia AR.

9. Personalización

CActiveRecord provee algunos métodos que pueden ser sobreescritos en las clases que la heredan para personalizar su flujo de trabajo.

  • beforeValidate y afterValidate: estos métodos son invocados antes y después de que la validación sea ejecutada.

  • beforeSave y afterSave: estos métodos son invocados antes y después de que la instancia AR sea guardada.

  • beforeDelete y afterDelete: estos métodos son invocados antes y después de que la instancia AR sea borrada.

  • afterConstruct: este método es invocado por cada instancia AR creada usando el operador new.

  • afterFind: este método es invocado por cada instancia AR creada como resultado de una búsqueda.

10. Usando Transacciones con AR

Cada instancia AR contiene una propiedad llamada dbConnection que es una instancia de CDbConnection. Por lo tanto podemos utilizar la característica transaction provista por el DAO de Yii si se desea cuando trabajamos con AR:

$model=Post::model();
$transaction=$model->dbConnection->beginTransaction();
try
{
    // encontar y guardad son dos pasos que pueden ser intervenidos por otra solicitud
    // por lo tanto usaremos una transacción para asegurar su consistencia e integridad
    $post=$model->findByPk(10);
    $post->title='nuevo título del post';
    $post->save();
    $transaction->commit();
}
catch(Exception $e)
{
    $transaction->rollBack();
}
$Id: database.ar.txt 688 2009-02-17 02:57:56Z freakpol $

Be the first person to leave a comment

Please to leave your comment.