數(shù)據(jù)提供器(Data Providers)

2018-02-24 15:40 更新

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

在?Pagination?和?Sorting?部分, 我們已經(jīng)介紹了如何允許終端用戶選擇一個特定的數(shù)據(jù)頁面,根據(jù)一些字段對它們進(jìn)行展現(xiàn)與排序。 因?yàn)榉猪摵团判驍?shù)據(jù)的任務(wù)是很常見的,所以Yii提供了一組封裝好的data provider類。

數(shù)據(jù)提供者是一個實(shí)現(xiàn)了 yii\data\DataProviderInterface 接口的類。 它主要用于獲取分頁和數(shù)據(jù)排序。它經(jīng)常用在?data widgets?小物件里,方便終端用戶進(jìn)行分頁與數(shù)據(jù)排序。

下面的數(shù)據(jù)提供者類都包含在Yii的發(fā)布版本里面:

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

所有的這些數(shù)據(jù)提供者遵守以下模式:

// 根據(jù)配置的分頁以及排序?qū)傩詠韯?chuàng)建一個數(shù)據(jù)提供者
$provider = new XyzDataProvider([
    'pagination' => [...],
    'sort' => [...],
]);

// 獲取分頁和排序數(shù)據(jù)
$models = $provider->getModels();

// 在當(dāng)前頁獲取數(shù)據(jù)項(xiàng)的數(shù)目
$count = $provider->getCount();

// 獲取所有頁面的數(shù)據(jù)項(xiàng)的總數(shù)
$totalCount = $provider->getTotalCount();

你可以通過配置 yii\data\BaseDataProvider::pagination 和 yii\data\BaseDataProvider::sort 的屬性來設(shè)定數(shù)據(jù)提供者的分頁和排序行為。屬性分別對應(yīng)于 yii\data\Pagination 和 yii\data\Sort。 你也可以對它們配置false來禁用分頁和排序特性。

Data widgets,諸如 yii\grid\GridView,有一個屬性名叫?dataProvider?,這個屬性能夠提供一個 數(shù)據(jù)提供者的示例并且可以顯示所提供的數(shù)據(jù),例如,

echo yii\grid\GridView::widget([
    'dataProvider' => $dataProvider,
]);

這些數(shù)據(jù)提供者的主要區(qū)別在于數(shù)據(jù)源的指定方式上。在下面的部分,我們將詳細(xì)介紹這些數(shù)據(jù)提供者的使用方法。

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

為了使用 yii\data\ActiveDataProvider,你應(yīng)該配置其 yii\data\ActiveDataProvider::query 的屬性。 它既可以是一個 yii\db\Query 對象,又可以是一個 yii\db\ActiveQuery 對象。假如是前者,返回的數(shù)據(jù)將是數(shù)組; 如果是后者,返回的數(shù)據(jù)可以是數(shù)組也可以是?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實(shí)例的數(shù)組
$posts = $provider->getModels();

假如在上面的例子中,$query?用下面的代碼來創(chuàng)建,則數(shù)據(jù)提供者將返回原始數(shù)組。

use yii\db\Query;

$query = (new Query())->from('post')->where(['status' => 1]);

注意:假如查詢已經(jīng)指定了?orderBy?從句,則終端用戶給定的新的排序說明(通過?sort?來配置的)將被添加到已經(jīng)存在的從句中。 任何已經(jīng)存在的?limit?和?offset?從句都將被終端用戶所請求的分頁(通過?pagination?所配置的)所重寫。

默認(rèn)情況下,yii\data\ActiveDataProvider使用?db?應(yīng)用組件來作為數(shù)據(jù)庫連接。你可以通過配置 yii\data\ActiveDataProvider::db 的屬性來使用不同數(shù)據(jù)庫連接。

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

yii\data\SqlDataProvider 應(yīng)用的時候需要結(jié)合需要獲取數(shù)據(jù)的SQL語句。基于 yii\data\SqlDataProvider::sort 和 yii\data\SqlDataProvider::pagination 規(guī)格,數(shù)據(jù)提供者會根據(jù)所請求的數(shù)據(jù)頁面(期望的順序)來調(diào)整?ORDER BY?和?LIMIT?的SQL從句。

為了使用 yii\data\SqlDataProvider,你應(yīng)該指定 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',
        ],
    ],
]);

// 返回包含每一行的數(shù)組
$models = $provider->getModels();

說明:yii\data\SqlDataProvider::totalCount 的屬性只有你需要分頁數(shù)據(jù)的時候才會用到。 這是因?yàn)橥ㄟ^ yii\data\SqlDataProvider::sql 指定的SQL語句將被數(shù)據(jù)提供者所修改并且只返回當(dāng)前頁面數(shù)據(jù)。 數(shù)據(jù)提供者為了正確計(jì)算可用頁面的數(shù)量仍舊需要知道數(shù)據(jù)項(xiàng)的總數(shù)。

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

yii\data\ArrayDataProvider 非常適用于大的數(shù)組。數(shù)據(jù)提供者允許你返回一個經(jīng)過一個或者多個字段排序的數(shù)組數(shù)據(jù)頁面。 為了使用 yii\data\ArrayDataProvider,你應(yīng)該指定 yii\data\ArrayDataProvider::allModels 屬性 作為一個大的數(shù)組。 這個大數(shù)組的元素既可以是一些關(guān)聯(lián)數(shù)組(例如:DAO查詢出來的結(jié)果)也可以是一些對象(例如: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)前請求頁的每一行數(shù)據(jù)
$rows = $provider->getModels();

注意:數(shù)組數(shù)據(jù)提供者與?Active Data Provider?和?SQL Data Provider?這兩者進(jìn)行比較的話, 會發(fā)現(xiàn)數(shù)組數(shù)據(jù)提供者沒有后面那兩個高效,這是因?yàn)閿?shù)組數(shù)據(jù)提供者需要加載所有的數(shù)據(jù)到內(nèi)存中。

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

當(dāng)使用通過數(shù)據(jù)提供者返回的數(shù)據(jù)項(xiàng)的時候,你經(jīng)常需要使用一個唯一鍵來標(biāo)識每一個數(shù)據(jù)項(xiàng)。 舉個例子,假如數(shù)據(jù)項(xiàng)代表的是一些自定義的信息,你可能會使用自定義ID作為鍵去標(biāo)識每一個自定義數(shù)據(jù)。 數(shù)據(jù)提供者能夠返回一個通過 yii\data\DataProviderInterface::getModels() 返回的鍵與數(shù)據(jù)項(xiàng)相對應(yīng)的列表。 例如,

use yii\data\ActiveDataProvider;

$query = Post::find()->where(['status' => 1]);

$provider = new ActiveDataProvider([
    'query' => Post::find(),
]);

// 返回包含Post對象的數(shù)組
$posts = $provider->getModels();

// 返回與$posts相對應(yīng)的主鍵值
$ids = $provider->getKeys();

在上面的例子中,因?yàn)槟闾峁┙o yii\data\ActiveDataProvider 一個 yii\db\ActiveQuery 對象, 它是足夠智能地返回一些主鍵值作為鍵。你也可以明確指出鍵值應(yīng)該怎樣被計(jì)算出來,計(jì)算的方式是通過使用一個字段名或者一個可調(diào)用的計(jì)算鍵值來配置。 例如,

// 使用 "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 接口。 一個簡單的方式是從 yii\data\BaseDataProvider 去擴(kuò)展,這種方式允許你關(guān)注數(shù)據(jù)提供者的核心邏輯。 這時,你主要需要實(shí)現(xiàn)下面的一些方法:

  • yii\data\BaseDataProvider::prepareModels():準(zhǔn)備好在當(dāng)前頁面可用的數(shù)據(jù)模型,并且作為一個數(shù)組返回它們。

  • yii\data\BaseDataProvider::prepareKeys():接受一個當(dāng)前可用的數(shù)據(jù)模型的數(shù)組,并且返回一些與它們相關(guān)聯(lián)的鍵。

  • yii\data\BaseDataProvider::prepareTotalCount(): 在數(shù)據(jù)提供者中返回一個標(biāo)識出數(shù)據(jù)模型總數(shù)的值。

下面是一個數(shù)據(jù)提供者的例子,這個數(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;
    }
}
以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號