lunes, 26 de enero de 2015

DAO - Database Access Objects (Objetos de acceso a base de datos)

Si hemos trabajando con la primera versión de Yii, recordaremos que Yii provee una API orientada al acceso de bases de datos relacionales. En esta versión el concepto se mantiene, pero se lo hace de una manera diferente.

El DAO de Yii está construido sobre la base de PDO (PHP Data Objects - Objetos de datos de PHP) , y el cual provee métodos más avanzados para el acceso a la base de datos, incluyendo Query Builder (Constructor de Consultas) y Active Record (Registro Activo).

Al utilizar el DAO de Yii, solo se requiere de la consulta SQL y arreglos PHP. El resultado, es que accedemos a la base de datos de una manera eficiente.

El DAO de Yii soporta las siguientes bases de datos:

  • MySQL
  • MariaDB
  • SQLite
  • PostgreSQL
  • CUBRID: versión 9.3 o superior.
  • Oracle
  • MSSQL: versión 2008 o superior.


Creando conexiones a bases de datos

Para acceder a una base de datos, se debe conectar a ella mediante la creación de una instancia de yii\db\Connection:

$db = new yii\db\Connection([
    'dsn' => 'mysql:host=localhost;dbname=example',
    'username' => 'root',
    'password' => '',
    'charset' => 'utf8',
]);

Puesto que es muy seguro que necesitemos conectarnos a nuestra base de datos desde algunas partes de nuestro desarrollo, lo común es configurarlo como un componente de la aplicación:

return [
    // ...
    'components' => [
        // ...
        'db' => [
            'class' => 'yii\db\Connection',
            'dsn' => 'mysql:host=localhost;dbname=example',
            'username' => 'root',
            'password' => '',
            'charset' => 'utf8',
        ],
    ],
    // ...];

Para acceder a la base de datos, se utiliza Yii::$app->db.

Dependiendo de la base de datos que utilicemos, depende el nombre de origen de datos (DSN). Por ejemplo:


  • MySQL, MariaDB: mysql:host=localhost;dbname=mibasededatos
  • SQLite: sqlite:/path/al/archivo/base_de_datos
  • PostgreSQL: pgsql:host=localhost;port=5432;dbname=mibasededatos
  • CUBRID: cubrid:dbname=demodb;host=localhost;port=33000
  • MS SQL Server (vía sqlsrv driver): sqlsrv:Server=localhost;Database=mibasededatos
  • MS SQL Server (vía dblib driver): dblib:host=localhost;dbname=mibasededatos
  • MS SQL Server (vía mssql driver): mssql:host=localhost;dbname=mibasededatos
  • Oracle: oci:dbname=//localhost:1521/mibasededatos


Si nos conectamos a través de ODBC hay que configurar la propiedad yii\db\Connection::$driverName de manera similar a:

 'db' => [
  'class' => 'yii\db\Connection' ,
  'driverName' => 'mysql' ,
  'dsn' => 'odbc:Driver={MySQL};Server=localhost;Database=test' ,
  'username' => 'root' ,
  'password' => '' , 
 ],

Obviamente, también es necesario especificar el usuario y clave.

Es importante conocer que, al configurar nuestra instancia de conexión a base de datos, la conexión no se establece hasta que ejecutemos nuestra primera consulta SQL o llamemos explícitamente al método open().

Ejecutando consultas SQL

Para ejecutar consultas SQL debemos seguir los siguientes pasos:

  1. Crear la consulta SQL con yii\db\Command;
  2. Especificar los valores de nuestros parámetros (opcional);
  3. Ejecutar la consulta llamando a uno de los métodos de yii\db\Command.


A continuación unos cuantos ejemplos:

$db = new yii\db\Connection(...datos_de_conexion...);

// retorna un conjunto de filas. cada fila es una matriz asociativa de nombres y valores de las columnas.
// un arreglo vacío es devuelto si no hay resultados
$posts $db->createCommand('SELECT * FROM post')
            ->queryAll();

// retorna una única fila (la primera)
// se retorna false si no hay resultados
$post $db->createCommand('SELECT * FROM post WHERE id=1')
           ->queryOne();

// retorna a única columna (la primera)
// un arreglo vacío es devuelto si no hay resultados
$titles $db->createCommand('SELECT title FROM post')
             ->queryColumn();

// retorna un escalar
// se retorna false si no hay resultados
$count $db->createCommand('SELECT COUNT(*) FROM post')
             ->queryScalar();

Nota: Para conservar la precisión, los datos obtenidos de bases de datos están representados como cadenas, incluso si los tipos de columna de base de datos correspondientes son numéricos.

Es posible que, exactamente después de establecer la conexión a nuestra base de datos, necesitemos realizar alguna consulta, como por ejemplo para ajustar la zona horaria o el juego de carácteres. Para ello, podemos utilizar el controlador de eventos yii\db\Connection::EVENT_AFTER_OPEN de manera similar a:

return [
    // ...
    'components' => [
        // ...
        'db' => [
            'class' => 'yii\db\Connection',
            // ...
            'on afterOpen' => function($event) {
                // $event->sender se refiere a la conexión a BD
                $event->sender->createCommand("SET time_zone = 'UTC'")->execute();
            }
        ],
    ],
    // ...
];

Parámetros

Cuando creamos un comando de base de datos desde un SQL con parámetros, debemos hacerlo utilizando el método de parámetros vinculantes para prevenir ataques de inyección SQL. Por ejemplo,

$post $db->createCommand('SELECT * FROM post WHERE id=:id AND status=:status')
           ->bindValue(':id'$_GET['id'])
           ->bindValue(':status'1)
           ->queryOne();

En la sentencia SQL, se puede incrustar uno o varios marcadores de posición (por ejemplo :id en el ejemplo anterior). Un marcador de posición de parámetro debe ser una cadena que comienza con dos puntos. A continuación, se puede llamar a uno de los métodos correspondientes para enlazar los valores de los parámetros:

  • bindValue(): enlaza con un valor de parámetro único
  • bindValues(): enlaza valores de parámetro múltiples en una sola llamada
  • bindParam(): similar a bindValue() pero soporta referencias a parámetros

Los siguientes ejemplos muestran las diferentes alternativas:

$params = [':id' => $_GET['id'], ':status' => 1];
$post $db->createCommand('SELECT * FROM post WHERE id=:id AND status=:status')
           ->bindValues($params)
           ->queryOne();
           $post $db->createCommand('SELECT * FROM post WHERE id=:id AND status=:status'$params)
           ->queryOne();

El enlace de parámetros es implementado a través de sentencias preparadas, lo que a más de ayudar a evitar ataques de inyección SQL también mejora el rendimiento mediante la preparación de una sentencia SQL una sola vez y ejecutarla varias veces con diferentes parámetros. Por ejemplo,

$command $db->createCommand('SELECT * FROM post WHERE id=:id');
$post1 $command->bindValue(':id'1)->queryOne();$post2 $command->bindValue(':id'2)->queryOne();

Debido a que bindParam() admite parámetros vinculantes por referencia, el código anterior también puede escribirse como el siguiente:

$command $db->createCommand('SELECT * FROM post WHERE id=:id')
              ->bindParam(':id'$id);
$id 1;$post1 $command->queryOne();
$id 2;$post2 $command->queryOne();

Ejecución de consultas No-Select

Hasta el momento hemos visto los métodos queryAlgoMas() que permiten realizar consultas a la base de datos a través de una sentencia del tipo select. Es posible que también requiramos ejecutar sentencias como UPDATE, DELETE, INSERT para lo cual podemos utilizar el método yii\db\Command::execute() de manera similar a la siguiente:

$db->createCommand('UPDATE post SET status=1 WHERE id=1')
   ->execute();

El método  yii\db\Command::execute() retorna el número de filas afectadas por la sentencia.

También podemos llamar a  insert(), update(), delete() para construir el correspondiente SQL indicando el nombre de la tabla, columnas y valores.

// INSERT (tabla, columnas y valores)
$db->createCommand()->insert('usuario', [
    'nombre' => 'Santiago',
    'edad' => 30,
])->execute();

// UPDATE (tabla, columnas y valores, condición)
$db->createCommand()->update('usuario', ['estado' => 1], 'edad > 30')->execute();
// DELETE (tabla, condición)
$db->createCommand()->delete('usuario''estado = 0')->execute();

Y si lo que requerimos es insertar múltiples filas se puede utilizar batchInsert(), lo cual es más eficiente al momento de insertar varias filas que de una en una.

// tabla, columnas, valores
$db->createCommand()->batchInsert('usuario', ['nombre''edad'], [
    ['Tomás'30],
    ['Lorena'20],
    ['Linda'25],
])->execute();



También te puede interesar:
Trabajando con bases de datos
Generador de Consultas (Query Builder)
Active Record (Registro Activo)

miércoles, 21 de enero de 2015

Configuraciones

Las configuraciones se utilizan ampliamente en Yii al crear nuevos objetos o inicializar los objetos existentes. Las configuraciones por lo general incluyen el nombre de la clase objeto que se está creando, y una lista de valores iniciales que se deben asignar a las propiedades del objeto. Las configuraciones también pueden incluir una lista de los controladores que deben concederse a las eventos y/o una lista de comportamientos que también serán añadidas al objeto.

A continuación, una configuración para crear e inicializar una conexión de base de datos:

$config = [
    'class' => 'yii\db\Connection',
    'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
    'username' => 'root',
    'password' => '',
    'charset' => 'utf8',
];
$db Yii::createObject($config);

El método Yii::createObject() toma un arreglo configuración como su argumento y crea un objeto creando una instancia de la clase llamada en la configuración. Cuando se crea una instancia del objeto, el resto de la configuración se utiliza para inicializar las propiedades del objeto, controladores de eventos y comportamientos.

Si ya se tiene un objeto, se puede utilizar Yii::configure() para inicializar las propiedades del objeto con un arreglo de configuración:

Yii::configure($object$config);

Nótese que en este caso, el arreglo de configuración no debe tener un elemento class.

Formato de Configuración

El formato de una configuración se puede describir formalmente como:

[
    'class' => 'ClassName',
    'propertyName' => 'propertyValue',
    'on eventName' => $eventHandler,
    'as behaviorName' => $behaviorConfig,
]

donde:

  • class especifica un nombre de clase completo para el objeto que se está creando.
  • propertyName especifica los valores iniciales de la propiedad con nombre. Las índices son los nombres de las propiedades y los valores son los valores iniciales correspondientes. Sólo las variables miembro públicas y propiedades definidas por getters/setters pueden configurarse.
  • on eventName indica los controladores que deberán adjuntarse a los eventos del objeto. Nótese que los índices del arreglo se forman con el prefijo on.
  • as behaviorName indica qué comportamientos deben estar adjuntos al objeto. Nótese que los índices del arreglo se forman con el prefijo as; el valor, $behaviorConfig, representa la configuración para la creación de un comportamiento, como una configuración normal descrita aquí.


A continuación se muestra un ejemplo que muestra una configuración con los valores iniciales de las propiedades, controladores de eventos y comportamientos:

[
    'class' => 'app\components\SearchEngine',
    'apiKey' => 'xxxxxxxx',
    'on search' => function ($event) {
        Yii::info("Término buscado: " $event->keyword);
    },
    'as indexer' => [
        'class' => 'app\components\IndexerBehavior',
        // ... valores iniciales de las propiedades ...
    ],
]

Utilizando Configuraciones

Las configuraciones se utilizan en muchos lugares en Yii. Vamos a describir configuraciones de aplicaciones y configuraciones para widget, dos principales usos de configuraciones.

Configuraciones de aplicación

La configuración de una aplicación es probablemente uno de los más complejos arreglos en Yii. Esto se debe a la clase application tiene un montón de propiedades y eventos configurables. Más importante aún, las propiedades de sus componentes puede recibir una gran variedad de configuraciones para crear componentes que se registran a través de la aplicación. El siguiente es un resumen del archivo de configuración de la aplicación para la plantilla de aplicación básica.

$config = [
    'id' => 'basic',
    'basePath' => dirname(__DIR__),
    'extensions' => require(__DIR__ '/../vendor/yiisoft/extensions.php'),
    'components' => [
        'cache' => [
            'class' => 'yii\caching\FileCache',
        ],
        'mailer' => [
            'class' => 'yii\swiftmailer\Mailer',
        ],
        'log' => [
            'class' => 'yii\log\Dispatcher',
            'traceLevel' => YII_DEBUG 0,
            'targets' => [
                [
                    'class' => 'yii\log\FileTarget',
                ],
            ],
        ],
        'db' => [
            'class' => 'yii\db\Connection',
            'dsn' => 'mysql:host=localhost;dbname=stay2',
            'username' => 'root',
            'password' => '',
            'charset' => 'utf8',
        ],
    ],
];

Como se puede notar, la configuración no tiene un índice class. Esto se debe a que se utiliza en el script de entrada, donde ya se le da el nombre de la clase,

(new yii\web\Application($config))->run();

Configuraciones para Widgets

Al utilizar los widgets , a menudo es necesario utilizar las configuraciones para personalizar las propiedades de los mismos. Tanto los métodos yii\base\Widget::widget() y yii\base\Widget::begin() pueden ser utilizados para crear un widget. En ambos casos toman un arreglo de configuración como el siguiente:

use yii\widgets\Menu;

echo Menu::widget([
    'activateItems' => false,
    'items' => [
        ['label' => 'Home''url' => ['site/index']],
        ['label' => 'Products''url' => ['product/index']],
        ['label' => 'Login''url' => ['site/login'], 'visible' => Yii::$app->user->isGuest],
    ],
]);

El código anterior crea un widget Menu e inicializa su propiedad activateItems a false. La propiedad items también se configura con los elementos del menú que se mostrarán.

Archivos de Configuración

Cuando una configuración es muy compleja, una práctica común es almacenarlo en uno o varios archivos PHP, conocidos como archivos de configuración. Un archivo de configuración devuelve un arreglo PHP que representa la configuración. Por ejemplo, es posible mantener una configuración de la aplicación en un archivo llamado web.php como la siguiente:

return [
    'id' => 'basic',
    'basePath' => dirname(__DIR__),
    'extensions' => require(__DIR__ '/../vendor/yiisoft/extensions.php'),
    'components' => require(__DIR__ '/components.php'),
];

Puesto que la configuración de los componentes también es compleja, se la puede almacenar en el archivo components.php e incluirla (require) desde éste archivo web.php tal como lo muestra el ejemplo. A su vez, el archivo components.php tendría:

return [
    'cache' => [
        'class' => 'yii\caching\FileCache',
    ],
    'mailer' => [
        'class' => 'yii\swiftmailer\Mailer',
    ],
    'log' => [
        'class' => 'yii\log\Dispatcher',
        'traceLevel' => YII_DEBUG 0,
        'targets' => [
            [
                'class' => 'yii\log\FileTarget',
            ],
        ],
    ],
    'db' => [
        'class' => 'yii\db\Connection',
        'dsn' => 'mysql:host=localhost;dbname=stay2',
        'username' => 'root',
        'password' => '',
        'charset' => 'utf8',
    ],
];

Para obtener una configuración guardada en un archivo de configuración solamente se la debe incluir utilizando require:

$config = require('path/to/web.php');
(new yii\web\Application($config))->run();

Configuraciones predeterminadas

El método Yii::createObject() se implementa en base a un contenedor de inyección de dependencias. Esto permite especificar un conjunto de las llamadas configuraciones predeterminadas que se aplicarán a todas las instancias de las clases especificadas cuando se creen usando Yii::createObject(). Las configuraciones predeterminadas se pueden especificar llamando a Yii::$container->set() en el código bootstrapping.

Por ejemplo, si se desea personalizar yii\widgets\LinkPager para que al paginar se muestren hasta 5 botones de página (el valor predeterminado es 10) se puede utilizar el siguiente código para tal objetivo:

\Yii::$container->set('yii\widgets\LinkPager', [
    'maxButtonCount' => 5,
]);

En este caso, si no se utilizara la configuración por defecto, habría que configurar maxButtonCount en cada lugar que se pagine.

Variables de Ambiente

Las configuraciones a menudo varían de acuerdo con el entorno en el que una aplicación se ejecuta. Por ejemplo, en el ambiente de desarrollo, es posible que se desee utilizar una base de datos llamada mydb_dev, mientras que en el servidor de producción es posible que se desee utilizar la base de datos mydb_prod base de datos. Para facilitar entornos de conmutación, Yii proporciona una constante llamada YII_ENV que se puede definir en el script de entrada. Por ejemplo,

defined('YII_ENV') or define('YII_ENV''dev');

Se puede definir YII_ENV con uno de los siguientes valores:

  • prod: ambiente de producción. La constante YII_ENV_PROD se evaluará como verdadero. Este es el valor por defecto de YII_ENV si no se lo define.
  • dev: ambiente de desarrollo. La constante YII_ENV_DEV se evaluará como verdadero.
  • test: ambiente de pruebas. La constante YII_ENV_TEST se evaluará como verdadero.

Con estas constantes de ambiente se puede especificar las condiciones iniciales basados en el ambiente actual. Por ejemplo, nuestra configuración de aplicación puede contener el siguiente código para habilitar la barra de depuración y depurador en nuestro ambiente de desarrollo:

$config = [...];

if (YII_ENV_DEV) {
    // configuración para ambiente de desarrollo 'dev'
    $config['bootstrap'][] = 'debug';
    $config['modules']['debug'] = 'yii\debug\Module';
}

return $config;


También te puede interesar:
Comportamientos (Behaviors)
Eventos
Propiedades
Componentes


Comportamientos (Behaviors)

Los comportamientos son instancias de  yii\base\Behavior, o de una clase hija. Los comportamientos, también conocidos como mixins, permiten mejorar la funcionalidad de un componente existente sin necesidad de cambiar la herencia de la clase. Adjuntando un comportamiento a un componente se le "inyectan" los métodos y propiedades del comportamiento, se logra que esos métodos y propiedades accesibles como si estuvieran definidos en la propia clase componente. Por otra parte, un comportamiento puede responder a los acontecimientos desencadenados por el componente, que permite a los comportamientos para personalizar también la ejecución normal de código del componente.

Definiendo Comportamientos

Para definir un comportamiento, se debe crear una clase de yii\base\Behavior, o en su defecto, de una clase hija de la misma. Por ejemplo:

namespace app\components;

use yii\base\Behavior;

class MyBehavior extends Behavior{
    public $prop1;

    private $_prop2;

    public function getProp2()
    {
        return $this->_prop2;
    }

    public function setProp2($value)
    {
        $this->_prop2 $value;
    }

    public function foo()
    {
        // ...
    }
}

El código presentado define una clase Comportamiento app\components\MyBehavior con dos propiedades, con dos propiedades y un método (prop1, prop2, foo), Nótese que la propiedad prop2 se define a través del captador getProp2 y el definidor setProp2(). Esto se debe a que  yii\base\Behavior se extiende de yii\base\Object y por lo tanto soporta la definición de propiedades a través de captadores (getter) y definidores (setter).

Debido a que ésta clase es un comportamiento (behavior), cuando sea añadido a un componente, el componente también tendrá las propiedades prop1 y prop2 y el método foo().

Consejo: Dentro del comportamiento, se puede acceder al componente al cual está adjunto, a través de la propiedad yii\base\Behavior::$owner

Gestionando Eventos de Componentes

Si un comportamiento necesita responder a los eventos disparados por el componente al cual está unido, se debe sobrescribir el método yii\base\Behavior::events(). Por ejemplo:

namespace app\components;

use yii\db\ActiveRecord;
use yii\base\Behavior;

class MyBehavior extends Behavior{
    // ...

    public function events()
    {
        return [
            ActiveRecord::EVENT_BEFORE_VALIDATE => 'beforeValidate',
        ];
    }

    public function beforeValidate($event)
    {
        // ...
    }
}

El método events() debe retornar una lista de eventos y sus correspondientes controladores. El ejemplo anterior declara que el evento EVENT_BEFORE_VALIDATE existe y define su controlador  beforeValidate(). Al especificar un controlador de eventos, se puede utilizar uno de los siguientes formatos:

  • una cadena que se refiere al nombre de un método de la clase comportamiento, como en el ejemplo anterior
  • un arreglo de un objeto o nombre de clase, y un nombre de método como una cadena (sin paréntesis), por ejemplo, [$object, 'methodName'];
  • una función anónima

La firma de un controlador de eventos es la siguiente,

function ($event) {
}

donde $event se refiere al parámetro del evento.

Adjuntando Comportamientos

Se puede adjuntar un comportamiento de forma estática o dinámica. La primera opción es la más común.

Para adjuntar un comportamiento de manera estática, se debe sobrescribir el método behaviors() de la clase componente al que se adjunta el comportamiento. El método behaviors()debe devolver una lista de configuraciones del comportamiento. Cada configuración del comportamiento puede ser un nombre de clase comportamiento o un arreglo de configuración:

namespace app\models;

use yii\db\ActiveRecord;
use app\components\MyBehavior;

class User extends ActiveRecord{
    public function behaviors()
    {
        return [
            // comportamiento anónimo, sólo nombre de la clase
            MyBehavior::className(),

            // comportamiento con nombre, sólo nombre de la clase
            'myBehavior2' => MyBehavior::className(),

            // comportamiento anónimo, arreglo de configuración
            [
                'class' => MyBehavior::className(),
                'prop1' => 'value1',
                'prop2' => 'value2',
            ],

            // comportamiento con nombre, arreglo de configuración
            'myBehavior4' => [
                'class' => MyBehavior::className(),
                'prop1' => 'value1',
                'prop2' => 'value2',
            ]
        ];
    }
}

Se puede asociar un nombre con un comportamiento especificando la clave del arreglo correspondiente a la configuración del comportamiento. En este caso, el comportamiento se denomina un comportamiento con nombre. En el ejemplo anterior, hay dos comportamientos con nombre: myBehavior2 y myBehavior4 . Si un comportamiento no está asociado con un nombre, se llama un comportamiento anónimo.

Para adjuntar un comportamiento dinámicamente, hay que llamar al método yii\base\Component::attachBehavior() de el componente al que se adjunta el comportamiento:

use app\components\MyBehavior;
// adjunta un objeto comportamiento
$component->attachBehavior('myBehavior1', new MyBehavior);
// adjunta una clase comportamiento
$component->attachBehavior('myBehavior2'MyBehavior::className());
// adjunta una arreglo de configuración
$component->attachBehavior('myBehavior3', [
    'class' => MyBehavior::className(),
    'prop1' => 'value1',
    'prop2' => 'value2',
]);

Se puede adjuntar múltiples comportamientos de una sola vez usando el método yii\base\Component::attachBehaviors():

$component->attachBehaviors([
    'myBehavior1' => new MyBehavior,  // un comportamiento con nombre
    MyBehavior::className(),          // un comportamiento anónimo
]);

También se puede adjuntar comportamientos a través de configuración:

[
    'as myBehavior2' => MyBehavior::className(),

    'as myBehavior3' => [
        'class' => MyBehavior::className(),
        'prop1' => 'value1',
        'prop2' => 'value2',
    ],
]

Utilizando Comportamientos

Para utilizar un comportamiento, primero hay que adjuntarlo a un componente de acuerdo a lo descrito anteriormente. Una vez que el comportamiento se ha adjuntado al componente, la utilización es simple.

Se puede acceder a una variable miembro pública o una propiedad definida por un captador (getter) y/o definidor (setter):

// "prop1" is una propiedad definida en la clase comportamiento
echo $component->prop1;$component->prop1 $value;

También se puede llamar a un método público de manera similar:

// foo() is a método público definido en la clase comportamiento
$component->foo();

Como se puede ver, a pesar que $component no define ni prop1 ni foo(), pueden ser usados como parte de la definición del componente debido al comportamiento (behavior) que se ha adjuntado.

Si dos comportamientos definen la misma propiedad o método y ambos están unidos a un mismo componente, el comportamiento que se une al componente primero tendrá prioridad cuando se accede a la propiedad o método.

Un comportamiento puede estar asociado con un nombre cuando está unido a un componente. Si este es el caso, se puede acceder al objeto comportamiento utilizando el nombre:

$behavior $component->getBehavior('myBehavior');

También se puede obtener todos los comportamientos adjunto a un componente:

$behaviors $component->getBehaviors();

Separando Comportamientos

Para separar un comportamiento, se debe llamar a yii\base\Component::detachBehavior() con el nombre asociado al comportamiento:

$component->detachBehavior('myBehavior1');

También se puede separar todos los comportamientos:

$component->detachBehaviors();



También te puede interesar:
Definición de un mixin
Configuraciones
Eventos
Propiedades
Componentes