在 Pagination 和 Sorting 部分,我們已經(jīng)介紹了如何允許終端用戶選擇一個(gè)特定的數(shù)據(jù)頁(yè)面,根據(jù)一些字段對(duì)它們進(jìn)行展現(xiàn)與排序。因?yàn)榉猪?yè)和排序數(shù)據(jù)的任務(wù)是很常見的,所以 Yii 提供了一組封裝好的 data provider 類。
數(shù)據(jù)提供者是一個(gè)實(shí)現(xiàn)了 [[yii\data\DataProviderInterface]] 接口的類。它主要用于獲取分頁(yè)和數(shù)據(jù)排序。它經(jīng)常用在 data widgets 小物件里,方便終端用戶進(jìn)行分頁(yè)與數(shù)據(jù)排序。
下面的數(shù)據(jù)提供者類都包含在 Yii 的發(fā)布版本里面:
所有的這些數(shù)據(jù)提供者遵守以下模式:
// 根據(jù)配置的分頁(yè)以及排序?qū)傩詠?lái)創(chuàng)建一個(gè)數(shù)據(jù)提供者
$provider = new XyzDataProvider([
'pagination' => [...],
'sort' => [...],
]);
// 獲取分頁(yè)和排序數(shù)據(jù)
$models = $provider->getModels();
// 在當(dāng)前頁(yè)獲取數(shù)據(jù)項(xiàng)的數(shù)目
$count = $provider->getCount();
// 獲取所有頁(yè)面的數(shù)據(jù)項(xiàng)的總數(shù)
$totalCount = $provider->getTotalCount();
你可以通過(guò)配置 [[yii\data\BaseDataProvider::pagination|pagination]] 和 [[yii\data\BaseDataProvider::sort|sort]]的屬性來(lái)設(shè)定數(shù)據(jù)提供者的分頁(yè)和排序行為。屬性分別對(duì)應(yīng)于 [[yii\data\Pagination]] 和 [[yii\data\Sort]]。你也可以對(duì)它們配置 false 來(lái)禁用分頁(yè)和排序特性。
Data widgets,諸如 [[yii\grid\GridView]],有一個(gè)屬性名叫 dataProvider
,這個(gè)屬性能夠提供一個(gè)數(shù)據(jù)提供者的示例并且可以顯示所提供的數(shù)據(jù),例如,
echo yii\grid\GridView::widget([
'dataProvider' => $dataProvider,
]);
這些數(shù)據(jù)提供者的主要區(qū)別在于數(shù)據(jù)源的指定方式上。在下面的部分,我們將詳細(xì)介紹這些數(shù)據(jù)提供者的使用方法。
為了使用 [[yii\data\ActiveDataProvider]],你應(yīng)該配置其 [[yii\data\ActiveDataProvider::query|query]] 的屬性。它既可以是一個(gè) [[yii\db\Query]] 對(duì)象,又可以是一個(gè) [[yii\db\ActiveQuery]] 對(duì)象。假如是前者,返回的數(shù)據(jù)將是數(shù)組;如果是后者,返回的數(shù)據(jù)可以是數(shù)組也可以是 Active Record 對(duì)象。例如,
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,
]
],
]);
// 返回一個(gè) Post 實(shí)例的數(shù)組
$posts = $provider->getModels();
假如在上面的例子中,$query
用下面的代碼來(lái)創(chuàng)建,則數(shù)據(jù)提供者將返回原始數(shù)組。
use yii\db\Query;
$query = (new Query())->from('post')->where(['status' => 1]);
注意:假如查詢已經(jīng)指定了
orderBy
從句,則終端用戶給定的新的排序說(shuō)明(通過(guò)sort
來(lái)配置的)將被添加到已經(jīng)存在的從句中。任何已經(jīng)存在的limit
和offset
從句都將被終端用戶所請(qǐng)求的分頁(yè)(通過(guò)pagination
所配置的)所重寫。
默認(rèn)情況下,[[yii\data\ActiveDataProvider]]使用 db
應(yīng)用組件來(lái)作為數(shù)據(jù)庫(kù)連接。你可以通過(guò)配置 [[yii\data\ActiveDataProvider::db]]的屬性來(lái)使用不同數(shù)據(jù)庫(kù)連接。
[[yii\data\SqlDataProvider]] 應(yīng)用的時(shí)候需要結(jié)合需要獲取數(shù)據(jù)的 SQL 語(yǔ)句?;?[[yii\data\SqlDataProvider::sort|sort]] 和 [[yii\data\SqlDataProvider::pagination|pagination]] 規(guī)格,數(shù)據(jù)提供者會(huì)根據(jù)所請(qǐng)求的數(shù)據(jù)頁(yè)面(期望的順序)來(lái)調(diào)整 ORDER BY
和 LIMIT
的 SQL 從句。
為了使用 [[yii\data\SqlDataProvider]],你應(yīng)該指定 [[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',
],
],
]);
// 返回包含每一行的數(shù)組
$models = $provider->getModels();
說(shuō)明:[[yii\data\SqlDataProvider::totalCount|totalCount]] 的屬性只有你需要分頁(yè)數(shù)據(jù)的時(shí)候才會(huì)用到。這是因?yàn)橥ㄟ^(guò) [[yii\data\SqlDataProvider::sql|sql]] 指定的 SQL 語(yǔ)句將被數(shù)據(jù)提供者所修改并且只返回當(dāng)前頁(yè)面數(shù)據(jù)。數(shù)據(jù)提供者為了正確計(jì)算可用頁(yè)面的數(shù)量仍舊需要知道數(shù)據(jù)項(xiàng)的總數(shù)。
[[yii\data\ArrayDataProvider]] 非常適用于大的數(shù)組。數(shù)據(jù)提供者允許你返回一個(gè)經(jīng)過(guò)一個(gè)或者多個(gè)字段排序的數(shù)組數(shù)據(jù)頁(yè)面。為了使用 [[yii\data\ArrayDataProvider]],你應(yīng)該指定 [[yii\data\ArrayDataProvider::allModels|allModels]] 屬性作為一個(gè)大的數(shù)組。這個(gè)大數(shù)組的元素既可以是一些關(guān)聯(lián)數(shù)組(例如:DAO查詢出來(lái)的結(jié)果)也可以是一些對(duì)象(例如:Active Record實(shí)例)例如,
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'],
],
]);
// 獲取當(dāng)前請(qǐng)求頁(yè)的每一行數(shù)據(jù)
$rows = $provider->getModels();
注意:數(shù)組數(shù)據(jù)提供者與 Active Data Provider 和 SQL Data Provider 這兩者進(jìn)行比較的話,會(huì)發(fā)現(xiàn)數(shù)組數(shù)據(jù)提供者沒(méi)有后面那兩個(gè)高效,這是因?yàn)閿?shù)組數(shù)據(jù)提供者需要加載所有的數(shù)據(jù)到內(nèi)存中。
當(dāng)使用通過(guò)數(shù)據(jù)提供者返回的數(shù)據(jù)項(xiàng)的時(shí)候,你經(jīng)常需要使用一個(gè)唯一鍵來(lái)標(biāo)識(shí)每一個(gè)數(shù)據(jù)項(xiàng)。舉個(gè)例子,假如數(shù)據(jù)項(xiàng)代表的是一些自定義的信息,你可能會(huì)使用自定義 ID 作為鍵去標(biāo)識(shí)每一個(gè)自定義數(shù)據(jù)。數(shù)據(jù)提供者能夠返回一個(gè)通過(guò) [[yii\data\DataProviderInterface::getModels()]] 返回的鍵與數(shù)據(jù)項(xiàng)相對(duì)應(yīng)的列表。例如,
use yii\data\ActiveDataProvider;
$query = Post::find()->where(['status' => 1]);
$provider = new ActiveDataProvider([
'query' => Post::find(),
]);
// 返回包含 Post 對(duì)象的數(shù)組
$posts = $provider->getModels();
// 返回與$posts 相對(duì)應(yīng)的主鍵值
$ids = $provider->getKeys();
在上面的例子中,因?yàn)槟闾峁┙o [[yii\data\ActiveDataProvider]] 一個(gè) [[yii\db\ActiveQuery]] 對(duì)象,它是足夠智能地返回一些主鍵值作為鍵。你也可以明確指出鍵值應(yīng)該怎樣被計(jì)算出來(lái),計(jì)算的方式是通過(guò)使用一個(gè)字段名或者一個(gè)可調(diào)用的計(jì)算鍵值來(lái)配置。例如,
// 使用 "slug" 字段作為鍵值
$provider = new ActiveDataProvider([
'query' => Post::find(),
'key' => 'slug',
]);
// 使用 md5(id)的結(jié)果作為鍵值
$provider = new ActiveDataProvider([
'query' => Post::find(),
'key' => function ($model) {
return md5($model->id);
}
]);
為了創(chuàng)建自定義的數(shù)據(jù)提供者類,你應(yīng)該實(shí)現(xiàn) [[yii\data\DataProviderInterface]] 接口。一個(gè)簡(jiǎn)單的方式是從 [[yii\data\BaseDataProvider]] 去擴(kuò)展,這種方式允許你關(guān)注數(shù)據(jù)提供者的核心邏輯。這時(shí),你主要需要實(shí)現(xiàn)下面的一些方法:
[[yii\data\BaseDataProvider::prepareModels()|prepareModels()]]:準(zhǔn)備好在當(dāng)前頁(yè)面可用的數(shù)據(jù)模型,并且作為一個(gè)數(shù)組返回它們。
[[yii\data\BaseDataProvider::prepareKeys()|prepareKeys()]]:接受一個(gè)當(dāng)前可用的數(shù)據(jù)模型的數(shù)組,并且返回一些與它們相關(guān)聯(lián)的鍵。
下面是一個(gè)數(shù)據(jù)提供者的例子,這個(gè)數(shù)據(jù)提供者可以高效地讀取 CSV 數(shù)據(jù):
<?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;
}
}