?
? ????? PHP ??? ???? ??? ?? ??
在?Pagination?和?Sorting?部分, 我們已經介紹了如何允許終端用戶選擇一個特定的數據頁面,根據一些字段對它們進行展現與排序。 因為分頁和排序數據的任務是很常見的,所以Yii提供了一組封裝好的data provider類。
數據提供者是一個實現了 yii\data\DataProviderInterface 接口的類。 它主要用于獲取分頁和數據排序。它經常用在?data widgets?小物件里,方便終端用戶進行分頁與數據排序。
下面的數據提供者類都包含在Yii的發(fā)布版本里面:
所有的這些數據提供者遵守以下模式:
// 根據配置的分頁以及排序屬性來創(chuàng)建一個數據提供者$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 和 yii\data\Sort。 你也可以對它們配置false來禁用分頁和排序特性。
Data widgets,諸如 yii\grid\GridView,有一個屬性名叫?dataProvider
?,這個屬性能夠提供一個 數據提供者的示例并且可以顯示所提供的數據,例如,
echo yii\grid\GridView::widget([
'dataProvider' => $dataProvider,
]);
這些數據提供者的主要區(qū)別在于數據源的指定方式上。在下面的部分,我們將詳細介紹這些數據提供者的使用方法。
為了使用 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
?用下面的代碼來創(chuàng)建,則數據提供者將返回原始數組。
use yii\db\Query;
$query = (new Query())->from('post')->where(['status' => 1]);
注意:假如查詢已經指定了?
orderBy
?從句,則終端用戶給定的新的排序說明(通過?sort
?來配置的)將被添加到已經存在的從句中。 任何已經存在的?limit
?和?offset
?從句都將被終端用戶所請求的分頁(通過?pagination
?所配置的)所重寫。
默認情況下,yii\data\ActiveDataProvider使用?db
?應用組件來作為數據庫連接。你可以通過配置 yii\data\ActiveDataProvider::db 的屬性來使用不同數據庫連接。
yii\data\SqlDataProvider 應用的時候需要結合需要獲取數據的SQL語句?;?yii\data\SqlDataProvider::sort 和 yii\data\SqlDataProvider::pagination 規(guī)格,數據提供者會根據所請求的數據頁面(期望的順序)來調整?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 的屬性只有你需要分頁數據的時候才會用到。 這是因為通過 yii\data\SqlDataProvider::sql 指定的SQL語句將被數據提供者所修改并且只返回當前頁面數據。 數據提供者為了正確計算可用頁面的數量仍舊需要知道數據項的總數。
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?這兩者進行比較的話, 會發(fā)現數組數據提供者沒有后面那兩個高效,這是因為數組數據提供者需要加載所有的數據到內存中。
當使用通過數據提供者返回的數據項的時候,你經常需要使用一個唯一鍵來標識每一個數據項。 舉個例子,假如數據項代表的是一些自定義的信息,你可能會使用自定義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 對象, 它是足夠智能地返回一些主鍵值作為鍵。你也可以明確指出鍵值應該怎樣被計算出來,計算的方式是通過使用一個字段名或者一個可調用的計算鍵值來配置。 例如,
// 使用 "slug" 字段作為鍵值$provider = new ActiveDataProvider([
'query' => Post::find(),
'key' => 'slug',
]);
// 使用md5(id)的結果作為鍵值$provider = new ActiveDataProvider([
'query' => Post::find(),
'key' => function ($model) {
return md5($model->id);
}
]);
為了創(chuàng)建自定義的數據提供者類,你應該實現 yii\data\DataProviderInterface 接口。 一個簡單的方式是從 yii\data\BaseDataProvider 去擴展,這種方式允許你關注數據提供者的核心邏輯。 這時,你主要需要實現下面的一些方法:
yii\data\BaseDataProvider::prepareModels():準備好在當前頁面可用的數據模型,并且作為一個數組返回它們。
yii\data\BaseDataProvider::prepareKeys():接受一個當前可用的數據模型的數組,并且返回一些與它們相關聯的鍵。
yii\data\BaseDataProvider::prepareTotalCount(): 在數據提供者中返回一個標識出數據模型總數的值。
下面是一個數據提供者的例子,這個數據提供者可以高效地讀取CSV數據:
<?phpuse?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;
? ? }
}