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.
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.
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 usamoscreateTime
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.
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();
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 comofindByNameAndTitle
. 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);
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.
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);
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();
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.
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.
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();
}
Signup or Login in order to comment.