3 个关注者

数据提供者

分页排序 部分,我们描述了如何允许最终用户选择要显示的特定数据页并按某些列对它们进行排序。由于对数据进行分页和排序的任务非常普遍,Yii 提供了一组数据提供者类来封装它。

数据提供者是一个实现 yii\data\DataProviderInterface 的类。它主要支持检索分页和排序数据。它通常用于与 数据小部件 配合使用,以便最终用户可以交互式地对数据进行分页和排序。

Yii 版本中包含以下数据提供者类

所有这些数据提供者的使用都共享以下通用模式

// create the data provider by configuring its pagination and sort properties
$provider = new XyzDataProvider([
    'pagination' => [...],
    'sort' => [...],
]);

// retrieves paginated and sorted data
$models = $provider->getModels();

// get the number of data items in the current page
$count = $provider->getCount();

// get the total number of data items across all pages
$totalCount = $provider->getTotalCount();

您可以通过配置其 paginationsort 属性来指定数据提供者的分页和排序行为,它们分别对应于 yii\data\Paginationyii\data\Sort 的配置。您也可以将它们配置为 false 以禁用分页和/或排序功能。

数据小部件,例如 yii\grid\GridView,有一个名为 dataProvider 的属性,它可以接受一个数据提供者实例并显示它提供的数据。例如,

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

这些数据提供者主要在数据源的指定方式上有所不同。在以下小节中,我们将解释每个数据提供者的详细用法。

活动数据提供者

要使用 yii\data\ActiveDataProvider,您应该配置其 query 属性。它可以接受一个 yii\db\Queryyii\db\ActiveQuery 对象。如果前者,则返回的数据将是数组;如果后者,则返回的数据可以是数组或 活动记录 实例。例如,

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, 
        ]
    ],
]);

// returns an array of Post objects
$posts = $provider->getModels();

如果上面示例中的 $query 是使用以下代码创建的,那么数据提供者将返回原始数组。

use yii\db\Query;

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

注意: 如果查询已经指定了orderBy子句,则最终用户(通过sort配置)提供的新的排序指令将追加到现有的orderBy子句。任何现有的limitoffset子句将被来自最终用户(通过pagination配置)的分页请求覆盖。

默认情况下,yii\data\ActiveDataProvider 使用db应用程序组件作为数据库连接。您可以通过配置yii\data\ActiveDataProvider::$db 属性使用不同的数据库连接。

SQL 数据提供者

yii\data\SqlDataProvider 使用一个原始 SQL 语句来获取所需数据。根据sortpagination 的规格,提供者将相应地调整 SQL 语句的ORDER BYLIMIT 子句,以便仅按所需顺序获取请求的数据页。

要使用 yii\data\SqlDataProvider,您应该指定sql 属性以及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',
        ],
    ],
]);

// returns an array of data rows
$models = $provider->getModels();

信息: totalCount 属性仅在您需要对数据进行分页时才需要。这是因为通过 sql 指定的 SQL 语句将被提供者修改,以仅返回当前请求的数据页。提供者仍然需要知道数据的总数量,以便正确计算可用的页数。

数组数据提供者

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'],
    ],
]);

// get the rows in the currently requested page
$rows = $provider->getModels();

注意:Active Data ProviderSQL Data Provider 相比,数组数据提供者效率较低,因为它需要将所有数据加载到内存中。

使用数据键

当使用数据提供者返回的数据项时,您通常需要使用唯一的键来标识每个数据项。例如,如果数据项代表客户信息,您可能希望使用客户 ID 作为每个客户数据的键。数据提供者可以返回一个与yii\data\DataProviderInterface::getModels() 返回的数据项相对应的键列表。例如,

use yii\data\ActiveDataProvider;

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

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

// returns an array of Post objects
$posts = $provider->getModels();

// returns the primary key values corresponding to $posts
$ids = $provider->getKeys();

在上面的示例中,因为您向 yii\data\ActiveDataProvider 提供了一个yii\db\ActiveQuery 对象,它足够智能,可以返回主键值作为键。您也可以通过将yii\data\ActiveDataProvider::$key 配置为一个列名或一个可计算键值的闭包来显式指定如何计算键值。例如,

// use "slug" column as key values
$provider = new ActiveDataProvider([
    'query' => Post::find(),
    'key' => 'slug',
]);

// use the result of md5(id) as key values
$provider = new ActiveDataProvider([
    'query' => Post::find(),
    'key' => function ($model) {
        return md5($model->id);
    }
]);

创建自定义数据提供者

要创建自己的自定义数据提供者类,您应该实现yii\data\DataProviderInterface。更简单的方法是从yii\data\BaseDataProvider 扩展,这使您可以专注于核心数据提供者逻辑。特别是,您主要需要实现以下方法

  • prepareModels(): 准备将出现在当前页面中的数据模型,并将它们作为数组返回。
  • prepareKeys(): 接收当前可用的数据模型数组,并返回与其关联的键。
  • 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;
        }

        return array_keys($models);
    }
 
    /**
     * {@inheritdoc}
     */
    protected function prepareTotalCount()
    {
        $count = 0;
 
        while (!$this->fileObject->eof()) {
            $this->fileObject->next();
            ++$count;
        }
 
        return $count;
    }
}

使用数据过滤器过滤数据提供者

虽然您可以像数据小部件指南的Filtering DataSeparate Filter Form 部分中所述那样手动构建活动数据提供者的条件,但 Yii 拥有数据过滤器,如果您需要灵活的过滤器条件,这些过滤器非常有用。数据过滤器可以按如下方式使用

$filter = new ActiveDataFilter([
    'searchModel' => 'app\models\PostSearch'
]);

$filterCondition = null;

// You may load filters from any source. For example,
// if you prefer JSON in request body,
// use Yii::$app->request->getBodyParams() below:
if ($filter->load(\Yii::$app->request->get())) { 
    $filterCondition = $filter->build();
    if ($filterCondition === false) {
        // Serializer would get errors out of it
        return $filter;
    }
}

$query = Post::find();
if ($filterCondition !== null) {
    $query->andWhere($filterCondition);
}

return new ActiveDataProvider([
    'query' => $query,
]);

PostSearch 模型用于定义哪些属性和值允许用于过滤

use yii\base\Model;

class PostSearch extends Model 
{
    public $id;
    public $title;
    
    public function rules()
    {
        return [
            ['id', 'integer'],
            ['title', 'string', 'min' => 2, 'max' => 200],            
        ];
    }
}

数据过滤器非常灵活。您可以自定义如何构建条件以及允许哪些运算符。有关详细信息,请查看yii\data\DataFilter 上的 API 文档。

发现错别字或您认为此页面需要改进?
在 github 上编辑它 !