lunes, 1 de junio de 2015

Trabajando con transacciones

Hay dos formas de utilizar las transacciones mientras se trabaja con Active Record.

La primera forma es encerrar explícitamente las llamadas a los métodos de Active Record en un bloque transaccional, como se muestra a continuación:

$cliente Cliente::findOne(123);

Cliente::getDb()->transaction(function($db) use ($cliente) {
    $cliente->id 200;
    $cliente->save();
    // ...otras operaciones BD ...
});

// o alternativamente

$transaction Cliente::getDb()->beginTransaction();
try {
    $cliente->id 200;
    $cliente->save();
    // ...otras operaciones BD ...
    $transaction->commit();
} catch(\Exception $e) {
    $transaction->rollBack();
    throw $e;
}

La segunda manera es hacer una lista de las operaciones de base de datos que requieren soporte transaccional en el método yii\db\ActiveRecord::transactions(). Por ejemplo:

class Cliente extends ActiveRecord
{
    public function transactions()
    {
        return [
            'admin' => self::OP_INSERT,
            'api' => self::OP_INSERT self::OP_UPDATE self::OP_DELETE,
            // lo anterior es equivalente a:
            // 'api' => self::OP_ALL,
        ];
    }
}

El método yii\db\ActiveRecord::transactions() debe devolver una matriz cuyas claves son los nombres de los escenarios y los valores correspondientes a las operaciones que deberían ser encapsuladas dentro de las transacciones. Se debe utilizar las siguientes constantes para referirse a las operaciones de base de datos:

  • OP_INSERT : operación de inserción realizado por: insert() ;
  • OP_UPDATE : operación de actualización realizada por: update() ;
  • OP_DELETE : operación de eliminación realizada por delete() .

Hay que utilizar el símbolo de barra vertical ( | ) para concatenar más de una operación. También se puede utilizar OP_ALL para referirse a las tres operaciones.


También te puede interesar:
Consulta de Datos
Almacenamiento de Datos
Active Record (Registro Activo)

martes, 5 de mayo de 2015

Almacenamiento de datos

Active Record permite guardar fácilmente los datos en la base de datos mediante los siguientes pasos:

  1. Preparar una instancia Active Record
  2. Asignar los nuevos valores a los atributos de Active Record
  3. Llamar a yii\db\ActiveRecord::save() para guardar los datos en la base de datos.

Por ejemplo,

// insertar una nueva filda
$cliente = new Cliente();
$cliente->nombre 'Esteban';
$cliente->correo 'esteban@ejemplo.com';
$cliente->save();

// actualizar una fila existente
$cliente Cliente::findOne(123);
$cliente->correo 'esteban_nuevo@ejemplo.com';
$cliente->save();

El método save() permite insertar una nueva fila o actualizar una en particular, dependiendo del estado de la instancia Active Record. Si la instancia se crea a través del operador new, al llamar a save() se insertará una nueva fila. Si la instancia es el resultado de un método de consulta, al llamar a save() se actualizará la fila asociada con la instancia.

Para conocer cuál es el estado de la instancia  Active Record podemos hacerlo con la propiedad isNewRecord (por ejemplo: $cliente->isNewRecord), la cual devuelve los valores true o false.

Validación de datos

Debido a que yii\db\ActiveRecord se extiende de yii\base\Model, comparte las mismas características de validación de datos. Se puede declarar reglas de validación sobrescribiendo el método rules() y realizar la validación de datos llamando al método validate().

Cuando se llama al método save(), por defecto se llamará al método validate() de forma automática. Únicamente al pasar la validación, se guardarán los datos. Si existe error, el método devuelve false y con la propiedad errors se pueden recuperar los mensajes de error.

Consejo: Si estamos seguros de que los datos no necesitan validación podemos utilizar save(false) para omitir la validación.

Asignación Masiva
Es posible asignar valores a varios atributos a la vez de una instancia Active Record en una sola sentencia PHP, como se muestra a continuación. Debemos tener en cuenta que sólo los atributos del tipo safe pueden tomar valores de ésta manera.

$valores = [
    'nombre' => 'Carlos',
    'correo' => 'carlos@ejemplo.com',
];
$cliente = new Cliente();
$cliente->attributes $valores;
$cliente->save();

Actualizando contadores

En algunas ocasiones, tenemos un contador en nuestros registros, por ejemplo, para llevar el número de visitas de un cliente. Haciendo uso de updateCounters() podemos actualizar una o varias columnas de éste tipo.

$cliente Cliente::findOne(100);
// UPDATE `cliente` SET `contador_visitas` = `contador_visitas` + 1 WHERE `id` = 100
$cliente->updateCounters(['contador_visitas' => 1]);

Importante: Si utilizamos yii\db\ActiveRecord::save() para actualizar una columna del tipo contador, podemos terminar con un resultado inexacto, ya que es posible que el mismo contador sea actualizado por múltiples peticiones que están leyendo/grabando el mismo valor del contador.

Atributos sucios

Cuando se llama a save() para guardar una instancia Active Record, sólo los atributos sucios se graban. Un atributo se considera sucio si su valor se ha modificado desde que se cargó desde la base de datos o su guardado más reciente. Se debe tener en cuenta que que la validación de datos se realiza sin tener en cuenta si la instancia Active Record tiene atributos sucios o no.

Active Record mantiene de manera automática la lista de atributos sucios. Lo hace mediante el mantenimiento de una versión anterior de los valores de atributo y su comparación con la más reciente. Se puede llamar yii\db\ActiveRecord::getDirtyAttributes() para obtener los atributos que se encuentran actualmente sucios. También se puede llamar a yii\db\ActiveRecord::markAttributeDirty() para marcar explícitamente un atributo como sucio.

Si estuviéramos interesados en los valores de los atributos antes de su modificación más reciente, podemos llamar a getOldAttributes() o getOldAttribute().

Valores por defecto

Es posible que durante el diseño de nuestra base de datos, hayamos definido valores por defecto para algunas de las columnas de algunas de nuestras tablas. Bajo estas circunstancias, es posible que cuando presentemos un formulario Web, los campos asociados con alguna de las columnas que tienen valores por defecto, muestren dicho valor. Si así lo necesitamos, es posible que rellenar previamente el formulario Web para una instancia Active Record con estos valores predeterminados.

$cliente = new Cliente();
$cliente->loadDefaultValues();
// $cliente->xyz tomará los valores por defecto de la columna "xyz"

Actualización de varias filas

Para actualizar varias filas al mismo tiempo, debemos llamar a updateAll(), el cual es un método estático.

// UPDATE `cliente` SET `estado` = 1 WHERE `correo` LIKE `%@ejemplo.com`
Cliente::updateAll(['estado' => Cliente::ESTADO_ACTIVO], ['like''correo''@ejemplo.com']);

Del mismo modo, se puede llamar a updateAllCounters() para actualizar las columnas tipo contador de varias filas al mismo tiempo.

// UPDATE `cliente` SET `edad` = `edad` + 1
Cliente::updateAllCounters(['edad' => 1]);


Eliminación de datos

Para borrar una sola fila, primero debemos recuperar esa fila y llamar al método yii\db\ActiveRecord::delete().

$cliente Cliente::findOne(123);
$cliente->delete();

También podemos eliminar varias filas llamando a yii\db\ActiveRecord::deleteAll().

Cliente::deleteAll(['estado' => Customer::ESTADO_INACTIVO]);

Hay que ser cuidado al utilizar deleteAll, ya que podemos borrar toda la tabla si no especificamos alguna condición.



También te puede interesar:
Consulta de Datos
Acceso a Datos
Active Record (Registro Activo)

viernes, 24 de abril de 2015

Acceso a datos

Como se había mencionado anteriormente, los datos traídos de la base de datos se alamcenan en instancias Active Record, y cada fila del resultado de la consulta corresponde a una sola instancia Active Record. Se puede acceder a los valores de columna mediante el acceso a los atributos de las instancias Active Record, por ejemplo,

// "id" y "correo" son los nombres de columna de la tabla "cliente"
$cliente Cliente::findOne(123);$id $cliente->id;$correo $cliente->correo;

Es importante tomar en cuenta que no deberíamos re-declarar ninguno de los atributos de Active Record ya que los define Yii de manera automática.

Transformación de datos

Sucede a menudo que los datos que se ingresan y/o se muestran están en un formato diferente del utilizado en el almacenamiento de los datos en una base de datos. Por ejemplo, supongamos que la fecha de nacimiento de los clientes está almacenados como marcas de tiempo UNIX (aunque no es un buen diseño), mientras que en la mayoría de los casos lo ideal sería manipular las fechas en un formato más legible como 'yyyy/mm/dd' . Para lograr este objetivo, se pueden definir métodos de transformación de datos en la clase Active Record del cliente como se muestra a continuación:

class Cliente extends ActiveRecord{
    // ...

    public function getBirthdayText()
    {
        return date('Y/m/d'$this->birthday);
    }
    
    public function setBirthdayText($value)
    {
        $this->birthday strtotime($value);
    }
}

Ahora, en lugar de acceder a $customer->birthday (que nos mostraría una marca de tiempo), accedemos a $customer->birthdayText (que nos presentará la fecha en formato yyyy/mm/dd)

Recuperación de datos en matrices

Mientras que la recuperación de datos en términos de objetos Active Record es conveniente y flexible, no siempre es deseable cuando se tiene que traer una gran cantidad de datos debido al uso grande de memoria. En este caso, podemos recuperar datos utilizando arreglos PHP llamando asArray() antes de ejecutar un método de consulta:

// retorna todos los clientes
// cada cliente es retornado como un arreglo asociativo
$clientes Cliente::find()
    ->asArray()
    ->all();

Nota: Si bien este método ahorra memoria y mejora el rendimiento, está más cerca de la capa de abstracción de la base de datos y por ende se perderá la mayor parte de las características de Active Record. Una distinción muy importante radica en el tipo de datos de los valores de columna. Cuando retornan los datos en los casos de Active Record, los valores de columna serán asignados automáticamente en función de los tipos de columna reales; por otra parte, cuando retornan datos en arreglos, los valores de columna serán cadenas (ya que son el resultado de PDO sin ningún procesamiento), independientemente de sus tipos de columna reales.

Recuperación de datos en lotes

En la entrada Generador de Consultas (Query Builder), hemos visto que podemos utilizar la consulta por lotes para minimizar el uso de memoria al consultar una gran cantidad de datos de la base de datos. Podemos utilizar la misma técnica en Active Record. Por ejemplo,

// extrae 10 cliente de una sola vez
foreach (Cliente::find()->batch(10) as $clientes) {
    // $clientes es un arreglo de 10 o menos objetos Cliente
}

// extrae 10 clientes a la vez e itera uno por uno
foreach (Cliente::find()->each(10) as $cliente) {
    // $cliente es un objeto Cliente
}

// consulta en lotes con carga lenta
foreach (Cliente::find()->with('ordenes')->each() as $cliente) {
    // $cliente es un objeto Cliente
}



También te puede interesar:
Consulta de Datos
Almacenamiento de Datos
Active Record (Registro Activo)

miércoles, 22 de abril de 2015

Consulta de Datos

Después de haber declarado una clase Active Record,

namespace app\models;

use yii\db\ActiveRecord;

class Cliente extends ActiveRecord{
    const ESTADO_INACTIVO 0;
    const ESTADO_ACTIVO 1;
    
    /**
     * @Retorna una cadena con el nombre de la tabla asociada a ésta clase ActiveRecord.
     */
    public static function tableName()
    {
        return 'cliente';
    }
}

podemos utilizarla para consultar datos de la tabla de base de datos correspondiente. El proceso se lo realiza normalmente en los tres pasos siguientes:

  1. Crear un nuevo objeto de consulta llamando al método yii\db\ActiveRecord::find();
  2. Construir el objeto de consulta llamando a cualquiera de los métodos de construcción de consultas;
  3. Llamar a un método de consulta para recuperar los datos en términos de instancias de Active Record.

A continuación algunos ejemplos:

// retorna un único cliente cuyo ID es 123
// SELECT * FROM `cliente` WHERE `id` = 123
$cliente Cliente::find()
    ->where(['id' => 123])
    ->one();

// retorna todos los cliente activos ordenados por su ID
// SELECT * FROM `cliente` WHERE `estado` = 1 ORDER BY `id`
$cliente Cliente::find()
    ->where(['estado' => Cliente::ESTADO_ACTIVO])
    ->orderBy('id')
    ->all();

// retorna el número de clientes activos
// SELECT COUNT(*) FROM `cliente` WHERE `estado` = 1
$conteo Cliente::find()
    ->where(['estado' => Cliente::ESTADO_ACTIVO])
    ->count();

// retorna todos los clientes en un arreglo de clientes
// SELECT * FROM `customer`
$clientes Cliente::find()
    ->indexBy('id')
    ->all();

En la parte anterior, $cliente es un objeto Cliente, mientras que $clientes (en plural) es un conjunto de objetos Clientes. Todos ellos se rellenan con los datos recuperados de la tabla cliente.

Puesto que yii\db\ActiveQuery se extiende de yii\db\Query, podemos utilizar todos los métodos de construcción de consultas y métodos de consulta descritos en la sección Generador de Consultas (Query Builder).

Es una tarea común consultar por los valores de la clave primaria o un conjunto de valores de columna, por lo que Yii ofrece dos métodos de acceso directo para este propósito:

  • yii\db\ActiveRecord::findOne(): devuelve una sola instancia Active Record con la primera fila del resultado de la consulta.
  • yii\db\ActiveRecord::findAll(): devuelve un conjunto de instancias de Active Record con todos los resultados de la consulta.


Ambos métodos pueden recibir en sus parámetros los siguientes formatos:

  • un valor escalar: el valor es tratado como el valor deseado de la clave primaria a ser buscado. Yii determinará automáticamente qué columna es la columna de clave primaria mediante la lectura de la información del esquema de base de datos.
  • un arreglo de valores escalares: el arreglo se trata como los valores de la clave primaria deseados que se buscarán.
  • un arreglo asociativo: las claves son los nombres de columna y los valores son los correspondientes valores de las columnas deseadas a ser buscados.

Por ejemplo:

// retorna un único cliente cuyo ID es 123
// SELECT * FROM `cliente` WHERE `id` = 123
$clienteCliente::findOne(123);

// retorna cliente cuyos ID son 100, 101, 123 o 124
// SELECT * FROM `cliente` WHERE `id` IN (100, 101, 123, 124)
$clientes Cliente::findAll([100101123124]);

// retorna un cliente activo cuyo ID is 123
// SELECT * FROM `cliente` WHERE `id` = 123 AND `estado` = 1
$cliente Cliente::findOne([
    'id' => 123,
    'estado' => Cliente::ESTADO_ACTIVO,
]);

// retorna todos los clientes inactivos
// SELECT * FROM `cliente` WHERE `estado` = 0
$customer Cliente::findAll([
    'estado' => Cliente::ESTADO_INACTIVO,
]);

Se debe tomar en cuenta que ni yii\db\ActiveRecord::findOne(), ni yii\db\ActiveQuery::one() añadirán LIMIT 1 a la sentencia generada. Si sabemos que la consulta va a retornar más de un registro, debemos añadirlo de manera explícita para mejorar el rendimiento, por ejemplo

Cliente::find()->limit(1)->one()

En lugar de utilizar los métodos de consulta, podemos sobrescribir la sentencia sql y obtener los datos en un Active Record. Para ello debemos llamar al método yii\db\ActiveRecord::findBySql() de manera similar a:

// retorna todos los clientes inactivos
$sql 'SELECT * FROM cliente WHERE estado=:estado';
$clientes Cliente::findBySql($sql, [':estado' => Cliente::ESTADO_INACTIVO])->all();

No debemos llamar a métodos adicionales luego de llamar a findBySql() pues serán ignorados.


También te puede interesar:
Acceso a Datos
Almacenamiento de Datos
Active Record (Registro Activo)


Active Record (Registro Activo)

Active Record proporciona una interfaz orientada a objetos para acceder y manipular los datos almacenados en bases de datos. Una clase Active Record está asociada con una tabla de base de datos, una instancia Active Record corresponde a una fila de esa tabla, y un atributo de una instancia de Active Record representa el valor de una columna en particular en la fila. En lugar de escribir sentencias SQL, podemos acceder a los atributos de Active Record y llamar a métodos Active Record para acceder y manipular los datos almacenados en las tablas de base de datos.

Por ejemplo, supongamos que Cliente es una clase Active Record que se asocia con la tabla de clientes y nombre es una columna de la tabla de clientes. Para insertar una nueva fila en la tabla Cliente escrbimos algo similar a:

$cliente = new Cliente();
$cliente->nombre 'Juan';
$cliente->save();


El código anterior es equivalente a usar la siguiente declaración SQL para MySQL, que es menos intuitiva, más propensa a errores, y podemos tener incluso problemas de compatibilidad si utilizamos otro tipo de base de datos:

$db->createCommand('INSERT INTO `cliente` (`nombre`) VALUES (:nombre)', [
    ':nombre' => 'Juan',
])->execute();

Yii proporciona el soporte Active Record para las siguientes bases de datos relacionales:

  • MySQL 4.1 o superior: via yii\db\ActiveRecord
  • PostgreSQL 7.3 o superior: via yii\db\ActiveRecord
  • SQLite 2 y 3: via yii\db\ActiveRecord
  • Microsoft SQL Server 2008 o superior: via yii\db\ActiveRecord
  • Oracle: via yii\db\ActiveRecord
  • CUBRID 9.3 o superior: via yii\db\ActiveRecord (Se debe tener en cuenta que debido a un error en la extensión PDO, valores ente comillas no funcionan adecuadamente, por lo que se requiere tanto el cliente como el servidor de CUBRID 9.3)
  • Sphinx: via yii\sphinx\ActiveRecord, requiere la extensión the yii2-sphinx
  • ElasticSearch: via yii\elasticsearch\ActiveRecord, requiere la extensión the yii2-elasticsearch


Además, Yii también admite el uso de Active Record con las siguientes bases de datos NoSQL:

  • Redis 2.6.12 o superior: vía yii\redis\ActiveRecord, requiere la extensión yii2-redis
  • MongoDB 1.3.0 o superior: vía yii\mongodb\ActiveRecord, requiere la extensión yii2-mongodb


Declarando Clases Active Record

Una clase Active Record se declara extendiendo yii\db\ActiveRecord. Debido a que cada clase Active Record se asocia con una tabla de base de datos, en esta clase se debe sobrescribir el método tableName() para especificar a qué tabla la clase está asociada.

En el siguiente ejemplo, declaramos una clase Cliente asociada a la tabla de base de datos cliente:

namespace app\models;

use yii\db\ActiveRecord;

class Cliente extends ActiveRecord{
    const ESTADO_INACTIVO 0;
    const ESTADO_ACTIVO 1;
    
    /**
     * @Retorna una cadena con el nombre de la tabla asociada a ésta clase ActiveRecord.
     */
    public static function tableName()
    {
        return 'cliente';
    }
}

Las instancias Active Record son consideradas como modelos. Por esta razón las colocamos bajo el nombre de espacio app\models.

Y puesto que, yii\db\ActiveRecord se extiende de yii\base\Model, hereda todas las características, tales como atributos, reglas de validación, serialización de datos, entre otros.

Conexión a Bases de Datos

De forma predeterminada, Active Record utiliza el componente de aplicación db como la conexión de base de datos para acceder y manipular los datos de la base de datos. El componente db se lo configura de manera similar a:

return [
    'components' => [
        'db' => [
            'class' => 'yii\db\Connection',
            'dsn' => 'mysql:host=localhost;dbname=testdb',
            'username' => 'demo',
            'password' => 'demo',
        ],
    ],
];

Si requerimos de una conexión de base de datos diferente, debemos sobrescribir el método getDb():

class Cliente extends ActiveRecord{
    // ...

    public static function getDb()
    {
        // utiliza el componente "db2"
        return \Yii::$app->db2;  
    }
}



También te puede interesar:
Trabajando con bases de datos
DAO - Database Access Objects (Objetos de acceso a base de datos)
Consulta de Datos