Поскольку задача разбиения на страницы и сортировки данных очень распространена, Yii предоставляет набор классов поставщика данных для инкапсуляции.

Поставщик данных - это класс, реализующий yii\data\DataProviderInterface. Он в основном поддерживает получение постранизированных и отсортированных данных. Он обычно используется для работы с виджетами данных, чтобы конечные пользователи могли интерактивно разбивать страницы и сортировать данные.

В выпуски Yii включены следующие классы поставщика данных:

  • yii\data\ActiveDataProvider: использует yii\db\Query или yii\db\ActiveQuery для запроса данных из баз данных и возврата их в виде массивов или экземпляров Active Record.
  • yii\data\SqlDataProvider: выполняет инструкцию SQL и возвращает данные базы данных в виде массивов.
  • yii\data\ArrayDataProvider: берет большой массив и возвращает его фрагмент, основываясь на параметрах разбиения на страницы и сортировки.

Использование всех этих поставщиков данных имеет следующую общую схему:

// create the data provider by configuring its pagination and sort properties
$provider = new XyzDataProvider([
    'pagination' => [...],
    'sort' => [...],
]);

// retrieves paginated and sorted data
$models = $provider->getModels();

// get the number of data items in the current page
$count = $provider->getCount();

// get the total number of data items across all pages
$totalCount = $provider->getTotalCount();

Вы определяете поведение страниц и сортировки поставщика данных, настраивая его свойства разбивки на страницы и сортировки, соответствующие конфигурациям для yii\data\Pagination и yii\data\Sort, соответственно. Вы также можете настроить их на ложь, чтобы отключить разбиение на страницы или функции сортировки.

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

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

Эти поставщики данных в основном различаются по способу указания источника данных.

Active Data Provider

Чтобы использовать yii\data\ActiveDataProvider, вы должны настроить его свойство запроса. Он может принимать либо объект 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, 
        ]
    ],
]);

// returns an array of Post objects
$posts = $provider->getModels();

Если $query в приведенном выше примере создается с использованием следующего кода, поставщик данных будет возвращать необработанные массивы.

use yii\db\Query;

$query = (new Query())->from('post')->where(['status' => 1]); 

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

SQL Data Provider

yii\data\SqlDataProvider работает с исходным оператором SQL, который используется для извлечения необходимых данных. На основе спецификаций сортировки и разбиения на страницы поставщик будет корректировать предложения ORDER BY и LIMIT инструкции SQL соответственно для извлечения только запрошенной страницы данных в нужном порядке.

Чтобы использовать yii\data\SqlDataProvider, вы должны указать свойство sql, а также свойство 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',
        ],
    ],
]);

// returns an array of data rows
$models = $provider->getModels();

Array Data Provider

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'],
    ],
]);

// get the rows in the currently requested page
$rows = $provider->getModels();

Работа с ключами данных

При использовании элементов данных, возвращаемых поставщиком данных, часто требуется идентифицировать каждый элемент данных с помощью уникального ключа. Например, если элементы данных представляют информацию о клиентах, вы можете использовать идентификатор клиента в качестве ключа для данных каждого клиента. Поставщики данных могут возвращать список таких ключей, соответствующих элементам данных, возвращаемым yii\data\DataProviderInterface::getModels(). Например:

use yii\data\ActiveDataProvider;

$query = Post::find()->where(['status' => 1]);

$provider = new ActiveDataProvider([
    'query' => $query,
]);

// returns an array of Post objects
$posts = $provider->getModels();

// returns the primary key values corresponding to $posts
$ids = $provider->getKeys();

В приведенном выше примере, поскольку вы предоставляете объекту yii\data\ActiveDataProvider, а также объект yii\db\ActiveQuery, он достаточно интеллектуальный, чтобы возвращать значения первичного ключа в качестве ключей. Вы также можете явно указать, как рассчитывать значения ключей, настроив ключ yii\data\ActiveDataProvider::$key с именем столбца или вызываемым значением ключевого значения. Например:

// use "slug" column as key values
$provider = new ActiveDataProvider([
    'query' => Post::find(),
    'key' => 'slug',
]);

// use the result of md5(id) as key values
$provider = new ActiveDataProvider([
    'query' => Post::find(),
    'key' => function ($model) {
        return md5($model->id);
    }
]);

Создание собственного Data Provider

Чтобы создать свои собственные классы поставщика данных, вы должны реализовать yii\data\DataProviderInterface. Более простой путь - это расширение от yii\data\BaseDataProvider, которое позволяет вам сосредоточиться на логике поставщика базовых данных. В частности, в основном вам необходимо реализовать следующие методы:

  • prepareModels(): подготавливает модели данных, которые будут доступны на текущей странице и возвращает их как массив.
  • prepareKeys(): принимает массив доступных в настоящее время моделей данных и возвращает связанные с ними ключи.
  • 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;
    }
}