Провайдеры данных в Yii 2
Провайдеры данных в Yii 2

Провайдеры данных

В разделах Постраничное разделение данных и Сортировка было описано, как сделать возможность для конечных пользователей, чтобы они могли выбирать определённую страницу для вывода данных и сортировку их по некоторым колонкам.



Провайдер данных это класс, который реализует [[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|pagination]] и [[yii\data\BaseDataProvider::sort|sort]], которые соответствуют настройкам [[yii\data\Pagination]] and [[yii\data\Sort]]. Вы можете отключить сортировку и разбивку на страницы путём выставления их настроек в false.

Виджеты данных, такие как [[yii\grid\GridView]], имеют свойство dataProvider, которое может принимать экземпляр провайдера данных для отображения его данных. Например:

echo yii\grid\GridView::widget([
    'dataProvider' => $dataProvider,
]);

Эти провайдеры данных в некоторой степени различаются по использовании, в зависимости от источника данных. Далее опишем более подробно использование каждого провайдера данных.

Active Data Provider

Для использования [[yii\data\ActiveDataProvider]], необходимо настроить его свойство [[yii\data\ActiveDataProvider::query|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]);

Note: Если query содержит условия сортировки в orderBy, то новые условия, полученные от конечных пользователей (через настройки sort) будут добавлены к существующим условиям в orderBy. Любые условия в limit и offset будут переписаны запросом конечного пользователя к различным страницам (через конфигурацию pagination).

По умолчанию [[yii\data\ActiveDataProvider]] использует компонент приложения db для подключения к базе данных. Можно использовать разные базы данных, настроив подключение через конфигурацию свойства [[yii\data\ActiveDataProvider::db]].

Sql Data Provider

[[yii\data\SqlDataProvider]] работает с сырыми запросами SQL, которые используются для извлечение необходимых данных. Основываясь на спецификации из [[yii\data\SqlDataProvider::sort|sort]] и [[yii\data\SqlDataProvider::pagination|pagination]], провайдер данных будет добавлять конструкции ORDER BY и LIMIT к SQL-запросу, для возврата только запрошенной страницы данных с учётом определённой сортировки.

Для использования [[yii\data\SqlDataProvider]], необходимо настроить свойства [[yii\data\SqlDataProvider::sql|sql]] и [[yii\data\SqlDataProvider::totalCount|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|totalCount]] обязательно только тогда, когда вам нужна разбивка на страницы. Всё потому, что запрос SQL [[yii\data\SqlDataProvider::sql|sql]] будет изменяться провайдером данных для возврата только текущей запрошенной страницы. Провайдеру необходимо знать общее количество данных в запросе для корректного вычисления разбивки на доступные страницы.

Array Data Provider

[[yii\data\ArrayDataProvider]] лучше использовать для работы с большим массивом. Этот провайдер помогает вернуть выборку из большого массива с сортировкой по одному или нескольким колонкам. Для использования [[yii\data\ArrayDataProvider]] необходимо определить свойство [[yii\data\ArrayDataProvider::allModels|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();

Note: Сравнивая с 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' => $query,
]);

// возвращает массив объектов 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()|prepareModels()]]: подготавливает модели данных, которые будут доступны в текущей странице и возвращает их в виде массива.
  • [[yii\data\BaseDataProvider::prepareKeys()|prepareKeys()]]: принимает массив имеющихся в настоящее время моделей данных и возвращает ключи, связанные с ними.
  • [[yii\data\BaseDataProvider::prepareTotalCount()|prepareTotalCount]]: возвращает значение, указывающее общее количество моделей данных в провайдере данных.

Ниже приведён пример провайдера данных, который эффективно считывает данные из CSV:

<?php
use yii\data\BaseDataProvider;

class CsvDataProvider extends BaseDataProvider
{
    /**
     * @var string имя CSV-файла для чтения
     */
    public $filename;
    
    /**
     * @var string|callable имя столбца с ключом или callback-функция, возвращающие его
     */
    public $key;
    
    /**
     * @var SplFileObject
     */
    protected $fileObject; // с помощью SplFileObject очень удобно искать конкретную строку в файле
    
 
    /**
     * {@inheritdoc}
     */
    public function init()
    {
        parent::init();
        
        // открыть файл
        $this->fileObject = new SplFileObject($this->filename);
    }
 
    /**
     * {@inheritdoc}
     */
    protected function prepareModels()
    {
        $models = [];
        $pagination = $this->getPagination();
 
        if ($pagination === false) {
            // в случае отсутствия разбивки на страницы - прочитать все строки
            while (!$this->fileObject->eof()) {
                $models[] = $this->fileObject->fgetcsv();
                $this->fileObject->next();
            }
        } else {
            // в случае, если разбивка на страницы есть - прочитать только одну страницу
            $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;
    }
}

Заберите ссылку на статью к себе, чтобы потом легко её найти!
Раз уж досюда дочитали, то может может есть желание рассказать об этом месте своим друзьям, знакомым и просто мимо проходящим?
Не надо себя сдерживать! ;)

Старт! Горячий старт на просторы интернета
Старт! Горячий старт на просторы интернета
Старт! Меню