14 KiB
Proveedores de datos
En las secciones sobre paginación y ordenación se describe como permitir a los usuarios finales elegir que se muestre una página de datos en particular, y ordenar los datos por algunas columnas. Como la tarea de paginar y ordenar datos es muy común, Yii proporciona un conjunto de clases proveedoras de datos para encapsularla.
Un proveedor de datos es una clase que implementa la interfaz yii\data\DataProviderInterface. Básicamente se encarga de obtener datos paginados y ordenados. Normalmente se usa junto con widgets de datos para que los usuarios finales puedan paginar y ordenar datos de forma interactiva.
Yii incluye las siguientes clases proveedoras de datos:
- yii\data\ActiveDataProvider: usa yii\db\Query o yii\db\ActiveQuery para consultar datos de bases de datos y devolverlos como arrays o instancias Active Record.
- yii\data\SqlDataProvider: ejecuta una sentencia SQL y devuelve los datos de la base de datos como arrays.
- yii\data\ArrayDataProvider: toma un array grande y devuelve una rodaja de él basándose en las especificaciones de paginación y ordenación.
El uso de todos estos proveedores de datos comparte el siguiente patrón común:
// Crear el proveedor de datos configurando sus propiedades de paginación y ordenación
$provider = new XyzDataProvider([
'pagination' => [...],
'sort' => [...],
]);
// Obtener los datos paginados y ordenados
$models = $provider->getModels();
// Obtener el número de elementos de la página actual
$count = $provider->getCount();
// Obtener el número total de elementos entre todas las páginas
$totalCount = $provider->getTotalCount();
Se puede especificar los comportamientos de paginación y ordenación de un proveedor de datos
configurando sus propiedades yii\data\BaseDataProvider::pagination y
yii\data\BaseDataProvider::sort, que corresponden a las configuraciones para
yii\data\Pagination y yii\data\Sort respectivamente. También se pueden configurar a
false
para inhabilitar las funciones de paginación y/u ordenación.
Los widgets de datos, como yii\grid\GridView, tienen una
propiedad llamada dataProvider
que puede tomar una instancia de un proveedor de datos y
mostrar los datos que proporciona. Por ejemplo,
echo yii\grid\GridView::widget([
'dataProvider' => $dataProvider,
]);
Estos proveedores de datos varían principalmente en la manera en que se especifica la fuente de datos. En las siguientes secciones se explica el uso detallado de cada uno de estos proveedores de datos.
Proveedor de datos activo
Para usar yii\data\ActiveDataProvider, hay que configurar su propiedad yii\data\ActiveDataProvider::query. Puede tomar un objeto yii\db\Query] o [[yii\db\ActiveQuery. En el primer caso, los datos devueltos serán arrays. En el segundo, los datos devueltos pueden ser arrays o instancias de Active Record. Por ejemplo:
use yii\data\ActiveDataProvider;
$query = Post::find()->where(['state_id' => 1]);
$provider = new ActiveDataProvider([
'query' => $query,
'pagination' => [
'pageSize' => 10,
],
'sort' => [
'defaultOrder' => [
'created_at' => SORT_DESC,
'title' => SORT_ASC,
]
],
]);
// Devuelve un array de objetos Post
$posts = $provider->getModels();
En el ejemplo anterior, si $query
se crea el siguiente código, el proveedor de datos
devolverá arrays en bruto.
use yii\db\Query;
$query = (new Query())->from('post')->where(['state' => 1]);
Note: Si una consulta ya tiene la cláusula
orderBy
, las nuevas instrucciones de ordenación dadas por los usuarios finales (mediante la configuración desort
) se añadirán a la cláusulaorderBy
previa. Las cláusulaslimit
yoffset
que pueda haber se sobrescribirán por la petición de paginación de los usuarios finales (mediante la configuración depagination
).
Por omisión, yii\data\ActiveDataProvider usa el componente db
de la aplicación como
conexión con la base de datos. Se puede indicar una conexión con base de datos diferente
configurando la propiedad yii\data\ActiveDataProvider::db.
Proveedor de datos SQL
yii\data\SqlDataProvider funciona con una sentencia SQL en bruto, que se usa para obtener
los datos requeridos.
Basándose en las especificaciones de yii\data\SqlDataProvider::sort y
yii\data\SqlDataProvider::pagination, el proveedor ajustará las cláusulas
ORDER BY
y LIMIT
de la sentencia SQL acordemente para obtener sólo la página de datos
solicitados en el orden deseado.
Para usar yii\data\SqlDataProvider, hay que especificar las propiedades yii\data\SqlDataProvider::sql y yii\data\SqlDataProvider::totalCount. Por ejemplo:
use yii\data\SqlDataProvider;
$count = Yii::$app->db->createCommand('
SELECT COUNT(*) FROM post WHERE status=:status
', [':status' => 1])->queryScalar();
$provider = new SqlDataProvider([
'sql' => 'SELECT * FROM post WHERE status=:status',
'params' => [':status' => 1],
'totalCount' => $count,
'pagination' => [
'pageSize' => 10,
],
'sort' => [
'attributes' => [
'title',
'view_count',
'created_at',
],
],
]);
// Devuelve un array de filas de datos
$models = $provider->getModels();
Info: La propiedad yii\data\SqlDataProvider::totalCount se requiere sólo si se necesita paginar los datos. Esto es porque el proveedor modificará la sentencia SQL especificada vía yii\data\SqlDataProvider::sql para que devuelva sólo la pagina de datos solicitada. El proveedor sigue necesitando saber el número total de elementos de datos para calcular correctamente el número de páginas.
Proveedor de datos de arrays
Se recomienda usar yii\data\ArrayDataProvider cuando se trabaja con un array grande. El proveedor permite devolver una página de los datos del array ordenados por una o varias columnas. Para usar yii\data\ArrayDataProvider, hay que especificar la propiedad yii\data\ArrayDataProvider::allModels como el array grande. Los elementos del array grande pueden ser arrays asociativos (por ejemplo resultados de consultas de DAO u objetos (por ejemplo instancias de Active Record. Por ejemplo:
use yii\data\ArrayDataProvider;
$data = [
['id' => 1, 'name' => 'name 1', ...],
['id' => 2, 'name' => 'name 2', ...],
...
['id' => 100, 'name' => 'name 100', ...],
];
$provider = new ArrayDataProvider([
'allModels' => $data,
'pagination' => [
'pageSize' => 10,
],
'sort' => [
'attributes' => ['id', 'name'],
],
]);
// Obtener las filas de la página solicitada
$rows = $provider->getModels();
Note: En comparación con Active Data Provider y SQL Data Provider, Array Data Provider es menos eficiente porque requiere cargar todos los datos en memoria.
Trabajar con las claves de los datos
Al utilizar los elementos de datos devueltos por un proveedor de datos, con frecuencia necesita identificar cada elemento de datos con una clave única. Por ejemplo, si los elementos de datos representan información de los clientes, puede querer usar el ID de cliente como la clave de cada conjunto de datos de un cliente. Los proveedores de datos pueden devolver una lista de estas claves correspondientes a los elementos de datos devueltos por yii\data\DataProviderInterface::getModels(). Por ejemplo:
use yii\data\ActiveDataProvider;
$query = Post::find()->where(['status' => 1]);
$provider = new ActiveDataProvider([
'query' => $query,
]);
// Devuelve un array de objetos Post
$posts = $provider->getModels();
// Devuelve los valores de las claves primarias correspondientes a $posts
$ids = $provider->getKeys();
En el ejemplo superior, como se le proporciona a yii\data\ActiveDataProvider un objeto yii\db\ActiveQuery, es lo suficientemente inteligente como para devolver los valores de las claves primarias como las claves. También puede indicar explícitamente cómo se deben calcular los valores de la clave configurando yii\data\ActiveDataProvider::key con un nombre de columna o un invocable que calcule los valores de la clave. Por ejemplo:
// Utiliza la columna «slug» como valores de la clave
$provider = new ActiveDataProvider([
'query' => Post::find(),
'key' => 'slug',
]);
// Utiliza el resultado de md5(id) como valores de la clave
$provider = new ActiveDataProvider([
'query' => Post::find(),
'key' => function ($model) {
return md5($model->id);
}
]);
Creación de un proveedor de datos personalizado
Para crear su propio proveedor de datos personalizado, debe implementar yii\data\DataProviderInterface. Una manera más fácil es extender yii\data\BaseDataProvider, que le permite centrarse en la lógica central del proveedor de datos. En particular, esencialmente necesita implementar los siguientes métodos:
- yii\data\BaseDataProvider::prepareModels(): prepara los modelos de datos que estarán disponibles en la página actual y los devuelve como un array.
- yii\data\BaseDataProvider::prepareKeys(): acepta un array de modelos de datos disponibles actualmente y devuelve las claves asociadas a ellos.
- yii\data\BaseDataProvider::prepareTotalCount(): devuelve un valor que indica el número total de modelos de datos en el proveedor de datos.
Debajo se muestra un ejemplo de un proveedor de datos que lee datos CSV eficientemente:
<?php
use yii\data\BaseDataProvider;
class CsvDataProvider extends BaseDataProvider
{
/**
* @var string nombre del fichero CSV a leer
*/
public $filename;
/**
* @var string|callable nombre de la columna clave o un invocable que la devuelva
*/
public $key;
/**
* @var SplFileObject
*/
protected $fileObject; // SplFileObject es muy práctico para buscar una línea concreta en un fichero
/**
* {@inheritdoc}
*/
public function init()
{
parent::init();
// Abrir el fichero
$this->fileObject = new SplFileObject($this->filename);
}
/**
* {@inheritdoc}
*/
protected function prepareModels()
{
$models = [];
$pagination = $this->getPagination();
if ($pagination === false) {
// En caso de que no haya paginación, leer todas las líneas
while (!$this->fileObject->eof()) {
$models[] = $this->fileObject->fgetcsv();
$this->fileObject->next();
}
} else {
// En caso de que haya paginación, leer sólo una única página
$pagination->totalCount = $this->getTotalCount();
$this->fileObject->seek($pagination->getOffset());
$limit = $pagination->getLimit();
for ($count = 0; $count < $limit; ++$count) {
$models[] = $this->fileObject->fgetcsv();
$this->fileObject->next();
}
}
return $models;
}
/**
* {@inheritdoc}
*/
protected function prepareKeys($models)
{
if ($this->key !== null) {
$keys = [];
foreach ($models as $model) {
if (is_string($this->key)) {
$keys[] = $model[$this->key];
} else {
$keys[] = call_user_func($this->key, $model);
}
}
return $keys;
}
return array_keys($models);
}
/**
* {@inheritdoc}
*/
protected function prepareTotalCount()
{
$count = 0;
while (!$this->fileObject->eof()) {
$this->fileObject->next();
++$count;
}
return $count;
}
}
Filtrar proveedores de datos usando filtros de datos
Si bien puede construir condiciones para un proveedor de datos activo manualmente tal y como se describe en las secciones Filtering Data y Separate Filter Form de la guía de widgets de datos, Yii tiene filtros de datos que son muy útiles si necesita condiciones de filtro flexibles. Los filtros de datos se pueden usar así:
$filter = new ActiveDataFilter([
'searchModel' => 'app\models\PostSearch'
]);
$filterCondition = null;
// Puede cargar los filtros de datos de cualquier fuente.
// Por ejemplo, si prefiere JSON en el cuerpo de la petición,
// use Yii::$app->request->getBodyParams() aquí abajo:
if ($filter->load(\Yii::$app->request->get())) {
$filterCondition = $filter->build();
if ($filterCondition === false) {
// Serializer recibiría errores
return $filter;
}
}
$query = Post::find();
if ($filterCondition !== null) {
$query->andWhere($filterCondition);
}
return new ActiveDataProvider([
'query' => $query,
]);
El propósito del modelo PostSearch
es definir por qué propiedades y valores se permite filtrar:
use yii\base\Model;
class PostSearch extends Model
{
public $id;
public $title;
public function rules()
{
return [
['id', 'integer'],
['title', 'string', 'min' => 2, 'max' => 200],
];
}
}
Los filtros de datos son bastante flexibles. Puede personalizar cómo se construyen las condiciones y qué operadores se permiten. Para más detalles consulte la documentación de la API en \yii\data\DataFilter.