重寫yii2的數據提供器ArrayDataProvider類

首先看看ArrayDataProvider官方的doc:php

ArrayDataProvider implements a data provider based on a data array.
ArrayDataProvider實現了一個基於數據數組的數據提供器。html

The [[allModels]] property contains all data models that may be sorted and/or paginated.
[[allModels]]包含了須要排序和(或)分頁的全部數據模型。數據庫

ArrayDataProvider will provide the data after sorting and/or pagination.
ArrayDataProvider提供排序和(或)分頁後的數據。數組

You may configure the [[sort]] and [[pagination]] properties to
customize the sorting and pagination behaviors.
你能夠配置[[sort]][[pagination]]屬性自定義排序和分頁行爲。服務器

Elements in the [[allModels]] array may be either objects (e.g. model objects) or associative arrays (e.g. query results of DAO).
[[allModels]]數組中的元素也許是對象(如,model對象)也許是關聯數組(如,PDO的查詢結果)。yii2

Make sure to set the [[key]] property to the name of the field that uniquely identifies a data record or false if you do not have such a field.
確保設置的[[key]]屬性是惟一標識一條記錄的字段的名字,若是沒有這樣的字段,則設爲false。less

Compared to [[ActiveDataProvider]], ArrayDataProvider could be less efficient because it needs to have [[allModels]] ready.
[[ActiveDataProvider]]比較,ArrayDataProvider可能效率較低,由於它須要準備[[allModels]]yii

ArrayDataProvider may be used in the following way:
ArrayDataProvider能夠按照下面的方式使用:異步

$query = new Query;
$provider = new ArrayDataProvider([
    'allModels' => $query->from('post')->all(),
    'sort' => [
        'attributes' => ['id', 'username', 'email'],
    ],
    'pagination' => [
        'pageSize' => 10,
    ],
]);
// get the posts in the current page
$posts = $provider->getModels();

Note: if you want to use the sorting feature, you must configure the [[sort]] property
so that the provider knows which columns can be sorted.
注意:你給你想使用排序功能,你必須配置[[sort]]屬性。ide

@author Qiang Xue <qiang.xue@gmail.com>
@since 2.0

從上面的指南能夠看出,使用ArrayDataProvider須要準備好[[allModels]]數據,纔開始渲染視圖,並實現分頁。

ArrayDataProvider是先把數據拉渠道內存中,而後再根據已有數據進行分頁,這一點感受像JQuery的DataTables插件,可是DataTables插件支持異步獲取數據,也就是說能夠根據配置能夠分頁從數據庫中獲取數據,顯然,yii2自帶的ArrayDataProvider明顯不提供此功能。

先看看,yii2的ArrayDataProvider提供預處理models的方法,該方法處理排序和分頁:

/**
     * @inheritdoc
     */
    protected function prepareModels()
    {
        if (($models = $this->allModels) === null) {
            return [];
        }

        if (($sort = $this->getSort()) !== false) {
            $models = $this->sortModels($models, $sort);
        }

        if (($pagination = $this->getPagination()) !== false) {
            $pagination->totalCount = $this->getTotalCount();

            if ($pagination->getPageSize() > 0) {
                $models = array_slice($models, $pagination->getOffset(), $pagination->getLimit());
            }
        }

        return $models;
    }

對於分頁代碼,如過設置了pagination對象,也就是設置了分頁,則統計數據總條數,而後根據每頁的大小分片。

if (($pagination = $this->getPagination()) !== false) {

$pagination->totalCount = $this->getTotalCount();
  if ($pagination->getPageSize() > 0) {
      $models = array_slice($models, $pagination->getOffset(), $pagination->getLimit());
   }

}

再看看另外一個方法,yii2的ArrayDataProvider提供的數據統計總條數的方法:

/**
     * @inheritdoc
     */
    protected function prepareTotalCount()
    {
        return count($this->allModels);
    }

是的,ArrayDataProvider默認計算分頁總數是根據allModels數組計算的,而allModels的數據就是咱們查詢賦值給提供器的。

這裏面有兩個很重要的方法必須看看:

public function getTotalCount()
    {
        if ($this->getPagination() === false) {
            return $this->getCount();
        } elseif ($this->_totalCount === null) {
            $this->_totalCount = $this->prepareTotalCount();
        }

        return $this->_totalCount;
    }

該方法就是統計數據總數的,相應的應該有一個設置數據總數的:

public function setTotalCount($value)
    {
        $this->_totalCount = $value;
    }

而在ArrayDataProvider及其分類中,並無一個public的totalCount屬性,所以yii在處理的時候,將totalCount經過魔法函數進行設置,由於yii2中全部的類都是Object的子類,關於魔法函數,這一塊內容參考深刻理解yii2.0,在此感謝做者帶咱們走的這麼深。

所以,無論你分頁不分頁,ArrayDataProvider並非服務器端分頁的,而是將已有數據分頁處理的。

這種狀況,若是數據量很大的時候,一點也很差,線上服務動輒上百萬的數據,一會兒拿出來分頁,服務器吃不消,你也耗不起這個等待時間。

下面,咱們須要重寫這兩個方法:

  • models預處理方法

取消對已有數據的分片處理,統計數據總數根據咱們的方式統計,好比數據庫中的總條數。

/*
     *  @inheritdoc
     */
    protected function prepareModels()
    {
        if (($models = $this->allModels) === null) {
            return [];
        }

        if (($sort = $this->getSort()) !== false) {
            $models = $this->sortModels($models, $sort);
        }

        if (($pagination = $this->getPagination()) !== false) {
            $pagination->totalCount = $this->getTotalCount();
        }

        return $models;
    }
  • 統計總數預處理函數

直接獲取經過getTotalCount()函數獲取傳遞給數據提供器的數據總和。

/*
     *       @inheritdoc
     */
    protected function prepareTotalCount()
    {
        return $this->getTotalCount();
    }

下面給出重寫後的完整ArrayDataProvider:

<?php
namespace backend\extensions;

use Yii;
use yii\base\Component;

class ArrayDataProvider extends \yii\data\ArrayDataProvider
{

    /*
     *  @inheritdoc
     */
    protected function prepareModels()
    {
        if (($models = $this->allModels) === null) {
            return [];
        }

        if (($sort = $this->getSort()) !== false) {
            $models = $this->sortModels($models, $sort);
        }

        if (($pagination = $this->getPagination()) !== false) {
            $pagination->totalCount = $this->getTotalCount();
        }

        return $models;
    }

    /*
     *       @inheritdoc
     */
    protected function prepareTotalCount()
    {
        return $this->getTotalCount();
    }

}

最後,來一個實際使用案例:

// TODO 業務邏輯
$data = ... // 數據數組或對象
$count = ... // 數據總條數,並非count($data)的值,是數據庫中符合條件的全部數據總數
$dataProvider = new \backend\extensions\ArrayDataProvider([
'allModels' => $data,
'totalCount' => isset($count) ? $count : 0,
'key' => 'ltime',
'sort' => [
    'attributes' => [
        'gmv',
        'ltime',
        'uv'
    ],
    'defaultOrder' => [
        'gmv' => SORT_DESC,
        'ltime' => SORT_DESC,
        'uv' => SORT_DESC,
    ],
],
'pagination' => [
    'pageSize' => 15,
],
]);

// 傳遞到test視圖渲染
return $this->render('test', ['model' => $model, 'dataProvider' => $dataProvider]);

在視圖層接收該數據提供器,傳遞給一個數據渲染插件,好比GridView:

echo GridView::widget([
    'dataProvider' => $dataProvider,
    'columns' => [
        ['class' => 'yii\grid\SerialColumn'],
        [
            'class' => 'yii\grid\DataColumn',
            'value' => function ($data) {
                if (isset($data['ltime']) && !empty($data['ltime'])) {
                    return date('Y-m-d', $data['ltime']);
                }
            },
            'label' => '日期',
            'format' => 'raw',
    ],
    'moneyPerUvOrder:raw:訂單UV單價',
    'moneyPerUvPurchase:raw:銷售UV單價'
    ]
]);

到此結束,若是幫到你,請點擊收藏!

相關文章
相關標籤/搜索