鍍金池/ 教程/ PHP/ 數(shù)據(jù)提供者
查詢構(gòu)建器
HTTP 緩存
單元測(cè)試
資源
數(shù)據(jù)庫(kù)遷移
Fixtures
收集列表輸入
認(rèn)證
助手類
緩存
數(shù)據(jù)緩存
最佳安全實(shí)踐
響應(yīng)格式
使用 Gii 生成代碼
服務(wù)定位器
性能優(yōu)化
資源
多模型的復(fù)合表單
控制器
Html 幫助類
運(yùn)行機(jī)制概述
快速入門
屬性(Property)
使用表單
配置測(cè)試環(huán)境
數(shù)據(jù)提供者
使用數(shù)據(jù)庫(kù)
授權(quán)
輸入驗(yàn)證
類自動(dòng)加載(Autoloading)
版本
響應(yīng)
Sessions 和 Cookies
數(shù)組助手類
創(chuàng)建你自己的應(yīng)用程序結(jié)構(gòu)
文件上傳
路由
收發(fā)郵件
模型
小部件
更上一層樓
頁(yè)面緩存
請(qǐng)求
片段緩存
排序
處理密碼
數(shù)據(jù)小部件
模塊
事件
控制器
從 Yii 1.1 升級(jí)
應(yīng)用組件
驗(yàn)收測(cè)試
入口腳本
總覽
Url 幫助類
行為
速率限制
控制臺(tái)命令
依賴注入容器
視圖
功能測(cè)試
錯(cuò)誤處理
過(guò)濾器
主題
應(yīng)用主體
引入第三方代碼
共享托管環(huán)境
測(cè)試
擴(kuò)展
路由
使用模板引擎
核心驗(yàn)證器(Core Validators)
分頁(yè)
數(shù)據(jù)庫(kù)訪問(wèn) (DAO)
配置
創(chuàng)建表單
日志
安裝 Yii
客戶端腳本使用
組件(Component)
說(shuō)聲 Hello
運(yùn)行應(yīng)用
數(shù)據(jù)格式器
認(rèn)證
錯(cuò)誤處理
別名(Aliases)
Active Record
啟動(dòng)引導(dǎo)(Bootstrapping)
國(guó)際化

數(shù)據(jù)提供者

PaginationSorting 部分,我們已經(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ā)布版本里面:

  • [[yii\data\ActiveDataProvider]]:使用 [[yii\db\Query]] 或者 [[yii\db\ActiveQuery]] 從數(shù)據(jù)庫(kù)查詢數(shù)據(jù)并且以數(shù)組項(xiàng)的方式或者Active Record 實(shí)例的方式返回。
  • [[yii\data\SqlDataProvider]]:執(zhí)行一段 SQL 語(yǔ)句并且將數(shù)據(jù)庫(kù)數(shù)據(jù)作為數(shù)組返回。
  • [[yii\data\ArrayDataProvider]]:將一個(gè)大的數(shù)組依據(jù)分頁(yè)和排序規(guī)格返回一部分?jǐn)?shù)據(jù)。

所有的這些數(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ù)提供者的使用方法。

活動(dòng)數(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)存在的 limitoffset 從句都將被終端用戶所請(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ù)連接。

SQL 數(shù)據(jù)提供者

[[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 BYLIMIT的 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ù)。

數(shù)組數(shù)據(jù)提供者

[[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 ProviderSQL Data Provider 這兩者進(jìn)行比較的話,會(huì)發(fā)現(xiàn)數(shù)組數(shù)據(jù)提供者沒(méi)有后面那兩個(gè)高效,這是因?yàn)閿?shù)組數(shù)據(jù)提供者需要加載所有的數(shù)據(jù)到內(nèi)存中。

數(shù)據(jù)鍵的使用

當(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ù)提供者

為了創(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;
    }
}