# 數據提供者
在?[Pagination](http://www.yiichina.com/doc/guide/2.0/output-pagination)?和?[Sorting](http://www.yiichina.com/doc/guide/2.0/output-sorting)?部分, 我們已經介紹了如何允許終端用戶選擇一個特定的數據頁面,根據一些字段對它們進行展現與排序。 因為分頁和排序數據的任務是很常見的,所以Yii提供了一組封裝好的*data provider*類。
數據提供者是一個實現了 yii\data\DataProviderInterface 接口的類。 它主要用于獲取分頁和數據排序。它經常用在?[data widgets](http://www.yiichina.com/doc/guide/2.0/output-data-widgets)?小物件里,方便終端用戶進行分頁與數據排序。
下面的數據提供者類都包含在Yii的發布版本里面:
* yii\data\ActiveDataProvider:使用 yii\db\Query 或者 yii\db\ActiveQuery 從數據庫查詢數據并且以數組項的方式或者?[Active Record](http://www.yiichina.com/doc/guide/2.0/db-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 和 yii\data\BaseDataProvider::sort 的屬性來設定數據提供者的分頁和排序行為。屬性分別對應于 yii\data\Pagination 和 yii\data\Sort。 你也可以對它們配置false來禁用分頁和排序特性。
[Data widgets](http://www.yiichina.com/doc/guide/2.0/output-data-widgets),諸如 yii\grid\GridView,有一個屬性名叫?`dataProvider`?,這個屬性能夠提供一個 數據提供者的示例并且可以顯示所提供的數據,例如,
~~~
echo yii\grid\GridView::widget([
'dataProvider' => $dataProvider,
]);
~~~
這些數據提供者的主要區別在于數據源的指定方式上。在下面的部分,我們將詳細介紹這些數據提供者的使用方法。
## 活動數據提供者
為了使用 yii\data\ActiveDataProvider,你應該配置其 yii\data\ActiveDataProvider::query 的屬性。 它既可以是一個 yii\db\Query 對象,又可以是一個 yii\db\ActiveQuery 對象。假如是前者,返回的數據將是數組; 如果是后者,返回的數據可以是數組也可以是?[Active Record](http://www.yiichina.com/doc/guide/2.0/db-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]);
~~~
> 注意:假如查詢已經指定了?`orderBy`?從句,則終端用戶給定的新的排序說明(通過?`sort`?來配置的)將被添加到已經存在的從句中。 任何已經存在的?`limit`?和?`offset`?從句都將被終端用戶所請求的分頁(通過?`pagination`?所配置的)所重寫。
默認情況下,yii\data\ActiveDataProvider使用?`db`?應用組件來作為數據庫連接。你可以通過配置 yii\data\ActiveDataProvider::db 的屬性來使用不同數據庫連接。
## SQL數據提供者
yii\data\SqlDataProvider 應用的時候需要結合需要獲取數據的SQL語句。基于 yii\data\SqlDataProvider::sort 和 yii\data\SqlDataProvider::pagination 規格,數據提供者會根據所請求的數據頁面(期望的順序)來調整?`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](http://www.yiichina.com/doc/guide/2.0/db-dao)查詢出來的結果)也可以是一些對象(例如:[Active Record](http://www.yiichina.com/doc/guide/2.0/db-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](http://www.yiichina.com/doc/guide/2.0/output-data-providers#active-data-provider)?和?[SQL Data Provider](http://www.yiichina.com/doc/guide/2.0/output-data-providers#sql-data-provider)?這兩者進行比較的話, 會發現數組數據提供者沒有后面那兩個高效,這是因為數組數據提供者需要加載*所有*的數據到內存中。
## 數據鍵的使用
當使用通過數據提供者返回的數據項的時候,你經常需要使用一個唯一鍵來標識每一個數據項。 舉個例子,假如數據項代表的是一些自定義的信息,你可能會使用自定義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);
}
]);
~~~
## 創建自定義數據提供者
為了創建自定義的數據提供者類,你應該實現 yii\data\DataProviderInterface 接口。 一個簡單的方式是從 yii\data\BaseDataProvider 去擴展,這種方式允許你關注數據提供者的核心邏輯。 這時,你主要需要實現下面的一些方法:
* yii\data\BaseDataProvider::prepareModels():準備好在當前頁面可用的數據模型,并且作為一個數組返回它們。
* yii\data\BaseDataProvider::prepareKeys():接受一個當前可用的數據模型的數組,并且返回一些與它們相關聯的鍵。
* yii\data\BaseDataProvider::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;
}
}
~~~
- 介紹(Introduction)
- 關于 Yii(About Yii)
- 從 Yii 1.1 升級(Upgrading from Version 1.1)
- 入門(Getting Started)
- 安裝 Yii(Installing Yii)
- 運行應用(Running Applications)
- 第一次問候(Saying Hello)
- 使用 Forms(Working with Forms)
- 玩轉 Databases(Working with Databases)
- 用 Gii 生成代碼(Generating Code with Gii)
- 更上一層樓(Looking Ahead)
- 應用結構(Application Structure)
- 結構概述(Overview)
- 入口腳本(Entry Scripts)
- 應用(Applications)
- 應用組件(Application Components)
- 控制器(Controllers)
- 模型(Models)
- 視圖(Views)
- 模塊(Modules)
- 過濾器(Filters)
- 小部件(Widgets)
- 前端資源(Assets)
- 擴展(Extensions)
- 請求處理(Handling Requests)
- 運行概述(Overview)
- 引導(Bootstrapping)
- 路由引導與創建 URL(Routing and URL Creation)
- 請求(Requests)
- 響應(Responses)
- Sessions and Cookies
- 錯誤處理(Handling Errors)
- 日志(Logging)
- 關鍵概念(Key Concepts)
- 組件(Components)
- 屬性(Properties)
- 事件(Events)
- 行為(Behaviors)
- 配置(Configurations)
- 別名(Aliases)
- 類自動加載(Class Autoloading)
- 服務定位器(Service Locator)
- 依賴注入容器(Dependency Injection Container)
- 配合數據庫工作(Working with Databases)
- 數據庫訪問(Data Access Objects): 數據庫連接、基本查詢、事務和模式操作
- 查詢生成器(Query Builder): 使用簡單抽象層查詢數據庫
- 活動記錄(Active Record): 活動記錄對象關系映射(ORM),檢索和操作記錄、定義關聯關系
- 數據庫遷移(Migrations): 在團體開發中對你的數據庫使用版本控制
- Sphinx
- Redis
- MongoDB
- ElasticSearch
- 接收用戶數據(Getting Data from Users)
- 創建表單(Creating Forms)
- 輸入驗證(Validating Input)
- 文件上傳(Uploading Files)
- 收集列表輸入(Collecting Tabular Input)
- 多模型同時輸入(Getting Data for Multiple Models)
- 顯示數據(Displaying Data)
- 格式化輸出數據(Data Formatting)
- 分頁(Pagination)
- 排序(Sorting)
- 數據提供器(Data Providers)
- 數據小部件(Data Widgets)
- 操作客戶端腳本(Working with Client Scripts)
- 主題(Theming)
- 安全(Security)
- 認證(Authentication)
- 授權(Authorization)
- 處理密碼(Working with Passwords)
- 客戶端認證(Auth Clients)
- 安全領域的最佳實踐(Best Practices)
- 緩存(Caching)
- 概述(Overview)
- 數據緩存(Data Caching)
- 片段緩存(Fragment Caching)
- 分頁緩存(Page Caching)
- HTTP 緩存(HTTP Caching)
- RESTful Web 服務
- 快速入門(Quick Start)
- 資源(Resources)
- 控制器(Controllers)
- 路由(Routing)
- 格式化響應(Response Formatting)
- 授權驗證(Authentication)
- 速率限制(Rate Limiting)
- 版本化(Versioning)
- 錯誤處理(Error Handling)
- 開發工具(Development Tools)
- 調試工具欄和調試器(Debug Toolbar and Debugger)
- 使用 Gii 生成代碼(Generating Code using Gii)
- TBD 生成 API 文檔(Generating API Documentation)
- 測試(Testing)
- 概述(Overview)
- 搭建測試環境(Testing environment setup)
- 單元測試(Unit Tests)
- 功能測試(Functional Tests)
- 驗收測試(Acceptance Tests)
- 測試夾具(Fixtures)
- 高級專題(Special Topics)
- 高級應用模版(Advanced Project Template)
- 從頭構建自定義模版(Building Application from Scratch)
- 控制臺命令(Console Commands)
- 核心驗證器(Core Validators)
- 國際化(Internationalization)
- 收發郵件(Mailing)
- 性能優化(Performance Tuning)
- 共享主機環境(Shared Hosting Environment)
- 模板引擎(Template Engines)
- 集成第三方代碼(Working with Third-Party Code)
- 小部件(Widgets)
- Bootstrap 小部件(Bootstrap Widgets)
- jQuery UI 小部件(jQuery UI Widgets)
- 助手類(Helpers)
- 助手一覽(Overview)
- Array 助手(ArrayHelper)
- Html 助手(Html)
- Url 助手(Url)