16 KiB
Провайдеры данных
В разделах Постраничное разделение данных и Сортировка было описано, как сделать возможность для конечных пользователей, чтобы они могли выбирать определённую страницу для вывода данных и сортировку их по некоторым колонкам.
Провайдер данных это класс, который реализует yii\data\DataProviderInterface. Такая реализация поддерживает в основном разбивку на страницы и сортировку. Они обычно используются для работы виджетов данных, что позволяет конечным пользователям интерактивно использовать сортировку данных и их разбивку на страницы.
В Yii реализованы следующие классы провайдеров данных:
- yii\data\ActiveDataProvider: использует yii\db\Query или yii\db\ActiveQuery для запроса данных из базы данных, возвращая их в виде массива или экземпляров Active Record.
- yii\data\SqlDataProvider: выполняет запрос SQL к базе данных и возвращает результат в виде массива.
- yii\data\ArrayDataProvider: принимает большой массив и возвращает выборку из него с возможностью сортировки и разбивки на страницы.
Использование всех этих провайдеров данных имеет общую закономерность:
// создание провайдера данных с конфигурацией для сортировки и постраничной разбивки
$provider = new XyzDataProvider([
'pagination' => [...],
'sort' => [...],
]);
// Получение данных с разбивкой на страницы и сортировкой.
$models = $provider->getModels();
// получение количества данных на текущей странице
$count = $provider->getCount();
// получение общего количества данных на всех страницах
$totalCount = $provider->getTotalCount();
Определение поведений сортировки и разбивки для провайдера данных устанавливается через его свойства
yii\data\BaseDataProvider::pagination и yii\data\BaseDataProvider::sort, которые соответствуют
настройкам yii\data\Pagination and yii\data\Sort. Вы можете отключить сортировку и разбивку на страницы путём
выставления их настроек в false
.
Виджеты данных, такие как yii\grid\GridView, имеют свойство dataProvider
, которое может
принимать экземпляр провайдера данных для отображения его данных. Например:
echo yii\grid\GridView::widget([
'dataProvider' => $dataProvider,
]);
Эти провайдеры данных в некоторой степени различаются по использовании, в зависимости от источника данных. Далее опишем более подробно использование каждого провайдера данных.
ActiveDataProvider
Для использования yii\data\ActiveDataProvider, необходимо настроить его свойство yii\data\ActiveDataProvider::query. Оно принимает любой yii\db\Query или yii\db\ActiveQuery объект. Если использовать первый, то данные будут возвращены в виде массивов, если второй - данные также могут быть возвращены в виде массивов, а также в виде экземпляров Active Record. Например:
use yii\data\ActiveDataProvider;
$query = Post::find()->where(['status' => 1]);
$provider = new ActiveDataProvider([
'query' => $query,
'pagination' => [
'pageSize' => 10,
],
'sort' => [
'defaultOrder' => [
'created_at' => SORT_DESC,
'title' => SORT_ASC,
]
],
]);
// возвращает массив Post объектов
$posts = $provider->getModels();
Если изменить $query
в этом примере на следующий код, то будут возвращены сырые массивы.
use yii\db\Query;
$query = (new Query())->from('post')->where(['status' => 1]);
Совет: Если query содержит условия сортировки в
orderBy
, то новые условия, полученные от конечных пользователей (через настройкиsort
) будут добавлены к существующим условиям вorderBy
. Любые условия вlimit
иoffset
andoffset
будут переписаны запросом конечного пользователя к различным страницам ( через конфигурациюpagination
)
По умолчанию, yii\data\ActiveDataProvider использует компонент приложения db
для подключения к базе данных. Можно
использовать разные базы данных, настроив подключение через конфигурацию свойства yii\data\ActiveDataProvider::db.
SqlDataProvider
yii\data\SqlDataProvider работает с сырыми запросами SQL, которые используются для извлечение необходимых данных.
Основываясь на спецификации из yii\data\SqlDataProvider::sort и yii\data\SqlDataProvider::pagination,
провайдер данных будет добавлять ORDER BY
и LIMIT
конструкции к SQL запросу, для возврата только запрошенной
страницы данных с учётом определённой сортировки.
Для использования yii\data\SqlDataProvider, необходимо настроить свойства yii\data\SqlDataProvider::sql и yii\data\SqlDataProvider::totalCount. Например:
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',
],
],
]);
// возвращает массив данных
$models = $provider->getModels();
Совет: Свойство yii\data\SqlDataProvider::totalCount обязательно только тогда, когда вам нужна разбивка на страницы. Всё потому, что запрос SQL yii\data\SqlDataProvider::sql будет изменяться провайдером данных для возврата только текущей запрошенной страницы. Провайдеру необходимо знать общее количество данных в запросе для корректного вычисления разбивки на доступные страницы.
ArrayDataProvider
yii\data\ArrayDataProvider лучше использовать для работы с большим массивом. Этот провайдер помогает вернуть выборку из большого массива с сортировкой по одному или нескольким колонкам. Для использования yii\data\ArrayDataProvider необходимо определить свойство yii\data\ArrayDataProvider::allModels, как большой массив. Элементы в большом массиве могут быть ассоциативными массивами (например результаты выборки из DAO) или объекты ( Active Record экземпляры). Например:
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'],
],
]);
// получает строки для текущей запрошенной странице
$rows = $provider->getModels();
Совет: Сравнивая с Active Data Provider и SQL Data Provider, ArrayDataProvider менее эффективный потому, что требует загрузки всех данных в память.
Принципы работы с ключами данных
При возврате данных с помощью провайдера, часто требуется идентификация каждого элемента по уникальному ключу. Например, если данные - это какая-то информация по клиенту, то возможно понадобится использовать ID клиента, как ключ для данных по каждому клиенту. Провайдер данных через yii\data\DataProviderInterface::getModels() может вернуть список из ключей и соответствующего набора данных. Например,
use yii\data\ActiveDataProvider;
$query = Post::find()->where(['status' => 1]);
$provider = new ActiveDataProvider([
'query' => Post::find(),
]);
// возвращает массив объектов Post
$posts = $provider->getModels();
// возвращает значения первичного ключа в соответствии с $posts
$ids = $provider->getKeys();
В выше описанном примере, так как yii\data\ActiveDataProvider предоставляется один yii\db\ActiveQuery объект, то в этом случае провайдер достаточно умён, чтобы вернуть значения первичных ключей в качестве идентификатора. Также есть возможность настроить способ вычисления значение идентификатора, через настройку yii\data\ActiveDataProvider::key, как имя колонки или функция вычисления значений ключа. Например:
// используется "slug" колонка как ключ
$provider = new ActiveDataProvider([
'query' => Post::find(),
'key' => 'slug',
]);
// используется результат md5(id) как ключ
$provider = new ActiveDataProvider([
'query' => Post::find(),
'key' => function ($model) {
return md5($model->id);
}
]);
Создание своего провайдера данных
Для создания своих классов провайдера данных, необходимо реализовать yii\data\DataProviderInterface. Простой способ сделать это - наследовать yii\data\BaseDataProvider, который помогает сфокусироваться на логике ядра провайдера данных. В основном необходимо реализовать следующие методы:
- yii\data\BaseDataProvider::prepareModels():подготавливает модели данных, которые будут доступны в текущей странице и возвращает их в виде массива.
- yii\data\BaseDataProvider::prepareKeys(): принимает массив имеющихся в настоящее время моделей данных и возвращает ключи, связанные с ними.
- yii\data\BaseDataProvider::prepareTotalCount():возвращает значение, указывающее общее количество моделей данных в провайдере данных.
Ниже приведён пример провайдера данных, который эффективно считывает данные из CSV:
<?php
use yii\data\BaseDataProvider;
class CsvDataProvider extends BaseDataProvider
{
/**
* @var string name of the CSV file to read
*/
public $filename;
/**
* @var string|callable name of the key column or a callable returning it
*/
public $key;
/**
* @var SplFileObject
*/
protected $fileObject; // SplFileObject is very convenient for seeking to particular line in a file
/**
* @inheritdoc
*/
public function init()
{
parent::init();
// open file
$this->fileObject = new SplFileObject($this->filename);
}
/**
* @inheritdoc
*/
protected function prepareModels()
{
$models = [];
$pagination = $this->getPagination();
if ($pagination === false) {
// in case there's no pagination, read all lines
while (!$this->fileObject->eof()) {
$models[] = $this->fileObject->fgetcsv();
$this->fileObject->next();
}
} else {
// in case there's pagination, read only a single page
$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;
} else {
return array_keys($models);
}
}
/**
* @inheritdoc
*/
protected function prepareTotalCount()
{
$count = 0;
while (!$this->fileObject->eof()) {
$this->fileObject->next();
++$count;
}
return $count;
}
}