14 KiB
データプロバイダ
ページネーション と 並べ替え の節において、エンドユーザが特定のページのデータを選んで表示し、いずれかのカラムによってデータを並べ替えることが出来るようにする方法を説明しました。 データのページネーションと並べ替えは非常によくあるタスクですから、Yii はこれをカプセル化した一連の データプロバイダ を提供しています。
データプロバイダは yii\data\DataProviderInterface を実装するクラスであり、主として、ページ分割され並べ替えられたデータの取得をサポートするものです。 通常は、データウィジェット と共に使用して、エンドユーザが対話的にデータのページネーションと並べ替えをすることが出来るようにします。
Yii のリリースには次のデータプロバイダのクラスが含まれています。
- yii\data\ActiveDataProvider: yii\db\Query または yii\db\ActiveQuery を使ってデータベースからデータを取得して、配列または アクティブレコード インスタンスの形式でデータを返します。
- 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 に設定して、ページネーションや並べ替えの機能を無効にすることも出来ます。
データウィジェット、例えば yii\grid\GridView は、dataProvider
という名前のプロパティを持っており、これにデータプロバイダのインスタンスを受け取らせて、それが提供するデータを表示させることが出来ます。
例えば、
echo yii\grid\GridView::widget([
'dataProvider' => $dataProvider,
]);
これらのデータプロバイダの主たる相異点は、データソースがどのように指定されるかという点にあります。 次に続く項において、各データプロバイダの詳細な使用方法を説明します。
アクティブデータプロバイダ
yii\data\ActiveDataProvider を使用するためには、その yii\data\ActiveDataProvider::query プロパティを構成しなければなりません。 これは、yii\db\Query または yii\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,
]
],
]);
// Post オブジェクトの配列を返す
$posts = $provider->getModels();
上記の例における $query
が次のコードによって作成される場合は、提供されるデータは生の配列になります。
use yii\db\Query;
$query = (new Query())->from('post')->where(['status' => 1]);
Note|注意: クエリが既に
orderBy
句を指定しているものである場合、(sort
の構成を通して) エンドユーザによって与えられる並べ替えの指定は、既存のorderBy
句に追加されます。 一方、limit
とoffset
の句が存在している場合は、(pagenation
の構成を通して) エンドユーザによって指定されるページネーションのリクエストによって上書きされます。
デフォルトでは、yii\data\ActiveDataProvider はデータベース接続として db
アプリケーションコンポーネントを使用します。
yii\data\ActiveDataProvider::db プロパティを構成すれば、別のデータベース接続を使用することが出来ます。
SQL データプロバイダ
yii\data\SqlDataProvider は、生の SQL 文を使用して、必要なデータを取得します。
このデータプロバイダは、yii\data\SqlDataProvider::sort と yii\data\SqlDataProvider::pagination の指定に基づいて、SQL 文の ORDER BY
と OFFSET/LIMIT
の句を修正し、指定された順序に並べ替えられたデータを要求されたページの分だけ取得します。
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();
Info|情報: yii\data\SqlDataProvider::totalCount プロパティは、データにページネーションを適用しなければならない場合にだけ要求されます。 これは、yii\data\SqlDataProvider::sql によって指定される SQL 文は、現在要求されているページのデータだけを返すように、データプロバイダによって修正されてしまうからです。 データプロバイダは、総ページ数を正しく計算するためには、データアイテムの総数を知る必要があります。
配列データプロバイダ
yii\data\ArrayDataProvider は、一つの大きな配列を扱う場合に最も適しています。 このデータプロバイダによって、一つまたは複数のカラムで並べ替えた配列データの 1 ページ分を返すことが出来ます。 yii\data\ArrayDataProvider を使用するためには、全体の大きな配列として yii\data\ArrayDataProvider::allModels プロパティを指定しなければなりません。 この大きな配列の要素は、連想配列 (例えば DAO のクエリ結果) またはオブジェクト (例えば アクティブレコード インスタンス) とすることが出来ます。 例えば、
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();
Note|注意: アクティブデータプロバイダ および SQL データプロバイダ と比較すると、配列データプロバイダは効率の面では劣ります。 何故なら、全ての データをメモリにロードしなければならないからです。
データのキーを扱う
データプロバイダによって返されたデータアイテムを使用する場合、各データアイテムを一意のキーで特定しなければならないことがよくあります。 例えば、データアイテムが顧客情報を表す場合、顧客 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();
// $post に対応するプライマリキーの値を返す
$ids = $provider->getKeys();
上記の例では、yii\data\ActiveDataProvider に対して yii\db\ActiveQuery オブジェクトを供給していますから、キーとしてプライマリキーの値を返すのが理にかなっています。 キーの値の計算方法を明示的に指定するために、yii\data\ActiveDataProvider::key にカラム名を設定したり、キーの値を計算するコーラブルを設定したりすることも出来ます。 例えば、
// "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 読み出す CSV ファイルの名前
*/
public $filename;
/**
* @var string|callable キーカラムの名前またはそれを返すコーラブル
*/
public $key;
/**
* @var SplFileObject
*/
protected $fileObject; // ファイルの特定の行までシークするのに SplFileObject が非常に便利
/**
* @inheritdoc
*/
public function init()
{
parent::init();
// ファイルを開く
$this->fileObject = new SplFileObject($this->filename);
}
/**
* @inheritdoc
*/
protected function prepareModels()
{
$models = [];
$pagination = $this->getPagination();
if ($pagination === false) {
// ページネーションが無い場合、全ての行を読む
while (!$this->fileObject->eof()) {
$models[] = $this->fileObject->fgetcsv();
$this->fileObject->next();
}
} else {
// ページネーションがある場合、一つのページだけを読む
$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;
}
}