Провайдеры данных ============== В разделах [Постраничное разделение данных](output-pagination.md) и [Сортировка](output-sorting.md) было описано, как сделать возможность для конечных пользователей, чтобы они могли выбирать определённую страницу для вывода данных и сортировку их по некоторым колонкам. Провайдер данных это класс, который реализует [[yii\data\DataProviderInterface]]. Такая реализация поддерживает в основном разбивку на страницы и сортировку. Они обычно используются для работы [виджетов данных](output-data-widgets.md), что позволяет конечным пользователям интерактивно использовать сортировку данных и их разбивку на страницы. В Yii реализованы следующие классы провайдеров данных: * [[yii\data\ActiveDataProvider]]: использует [[yii\db\Query]] или [[yii\db\ActiveQuery]] для запроса данных из базы данных, возвращая их в виде массива или экземпляров [Active Record](db-active-record.md). * [[yii\data\SqlDataProvider]]: выполняет запрос SQL к базе данных и возвращает результат в виде массива. * [[yii\data\ArrayDataProvider]]: принимает большой массив и возвращает выборку из него с возможностью сортировки и разбивки на страницы. Использование всех этих провайдеров данных имеет общую закономерность: ```php // создание провайдера данных с конфигурацией для сортировки и постраничной разбивки $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`. [Виджеты данных](output-data-widgets.md), такие как [[yii\grid\GridView]], имеют свойство `dataProvider`, которое может принимать экземпляр провайдера данных для отображения его данных. Например: ```php echo yii\grid\GridView::widget([ 'dataProvider' => $dataProvider, ]); ``` Эти провайдеры данных в некоторой степени различаются по использовании, в зависимости от источника данных. Далее опишем более подробно использование каждого провайдера данных. ## ActiveDataProvider Для использования [[yii\data\ActiveDataProvider]], необходимо настроить его свойство [[yii\data\ActiveDataProvider::query|query]]. Оно принимает любой [[yii\db\Query]] или [[yii\db\ActiveQuery]] объект. Если использовать первый, то данные будут возвращены в виде массивов, если второй - данные также могут быть возвращены в виде массивов, а также в виде экземпляров [Active Record](db-active-record.md). Например: ```php 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` в этом примере на следующий код, то будут возвращены сырые массивы. ```php use yii\db\Query; $query = (new Query())->from('post')->where(['status' => 1]); ``` > Совет: Если query содержит условия сортировки в `orderBy`, то новые условия, полученные от конечных пользователей (через настройки `sort`) будут добавлены к существующим условиям в `orderBy`. Любые условия в `limit` и `offset` and `offset` будут переписаны запросом конечного пользователя к различным страницам ( через конфигурацию `pagination`) По умолчанию, [[yii\data\ActiveDataProvider]] использует компонент приложения `db` для подключения к базе данных. Можно использовать разные базы данных, настроив подключение через конфигурацию свойства [[yii\data\ActiveDataProvider::db]]. ## SqlDataProvider [[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]]. Например: ```php 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]] будет изменяться провайдером данных для возврата только текущей запрошенной страницы. Провайдеру необходимо знать общее количество данных в запросе для корректного вычисления разбивки на доступные страницы. ## ArrayDataProvider [[yii\data\ArrayDataProvider]] лучше использовать для работы с большим массивом. Этот провайдер помогает вернуть выборку из большого массива с сортировкой по одному или нескольким колонкам. Для использования [[yii\data\ArrayDataProvider]] необходимо определить свойство [[yii\data\ArrayDataProvider::allModels|allModels]], как большой массив. Элементы в большом массиве могут быть ассоциативными массивами (например результаты выборки из [DAO](db-dao.md)) или объекты ( [Active Record](db-active-record.md) экземпляры). Например: ```php 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](#active-data-provider) и [SQL Data Provider](#sql-data-provider), ArrayDataProvider менее эффективный потому, что требует загрузки *всех* данных в память. ## Принципы работы с ключами данных При возврате данных с помощью провайдера, часто требуется идентификация каждого элемента по уникальному ключу. Например, если данные - это какая-то информация по клиенту, то возможно понадобится использовать ID клиента, как ключ для данных по каждому клиенту. Провайдер данных через [[yii\data\DataProviderInterface::getModels()]] может вернуть список из ключей и соответствующего набора данных. Например, ```php 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]], как имя колонки или функция вычисления значений ключа. Например: ```php // используется "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 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; } } ```