データプロバイダ ================ [ページネーション](output-pagination.md) と [並べ替え](output-sorting.md) の節において、エンドユーザが特定のページのデータを選んで表示し、いずれかのカラムによってデータを並べ替えることが出来るようにする方法を説明しました。 データのページネーションと並べ替えは非常によくあるタスクですから、Yii はこれをカプセル化した一連の *データプロバイダ* を提供しています。 データプロバイダは [[yii\data\DataProviderInterface]] を実装するクラスであり、主として、ページ分割され並べ替えられたデータの取得をサポートするものです。 通常は、[データウィジェット](output-data-widgets.md) と共に使用して、エンドユーザが対話的にデータのページネーションと並べ替えをすることが出来るようにします。 Yii のリリースには次のデータプロバイダのクラスが含まれています。 * [[yii\data\ActiveDataProvider]]: [[yii\db\Query]] または [[yii\db\ActiveQuery]] を使ってデータベースからデータを取得して、配列または [アクティブレコード](db-active-record.md) インスタンスの形式でデータを返します。 * [[yii\data\SqlDataProvider]]: SQL 文を実行して、データベースのデータを配列として返します。 * [[yii\data\ArrayDataProvider]]: 大きな配列を受け取り、ページネーションと並べ替えの指定に基づいて、一部分を切り出して返します。 これら全てのデータプロバイダの使用方法は、次の共通のパターンを持っています。 ```php // ページネーションと並べ替えのプロパティを構成してデータプロバイダを作成する $provider = new XyzDataProvider([ 'pagination' => [...], 'sort' => [...], ]); // ページ分割されて並べ替えられたデータを取得する $models = $provider->getModels(); // 現在のページにあるデータアイテムの数を取得する $count = $provider->getCount(); // 全ページ分のデータアイテムの総数を取得する $totalCount = $provider->getTotalCount(); ``` データプロバイダのページネーションと並べ替えの振る舞いを指定するためには、その [[yii\data\BaseDataProvider::pagination|pagination]] と [[yii\data\BaseDataProvider::sort|sort]] のプロパティを構成します。 二つのプロパティは、それぞれ、[[yii\data\Pagination]] と [[yii\data\Sort]] の構成情報に対応します。 これらを false に設定して、ページネーションや並べ替えの機能を無効にすることも出来ます。 [データウィジェット](output-data-widgets.md)、例えば [[yii\grid\GridView]] は、`dataProvider` という名前のプロパティを持っており、これにデータプロバイダのインスタンスを受け取らせて、それが提供するデータを表示させることが出来ます。 例えば、 ```php echo yii\grid\GridView::widget([ 'dataProvider' => $dataProvider, ]); ``` これらのデータプロバイダの主たる相異点は、データソースがどのように指定されるかという点にあります。 次に続く項において、各データプロバイダの詳細な使用方法を説明します。 ## アクティブデータプロバイダ [[yii\data\ActiveDataProvider]] を使用するためには、その [[yii\data\ActiveDataProvider::query|query]] プロパティを構成しなければなりません。 これは、[[yii\db\Query]] または [[yii\db\ActiveQuery]] のオブジェクトを取ることが出来ます。 前者であれば、返されるデータは配列になります。 後者であれば、返されるデータは配列または [アクティブレコード](db-active-record.md) インスタンスとすることが出来ます。 例えば、 ```php 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` が次のコードによって作成される場合は、提供されるデータは生の配列になります。 ```php 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|sort]] と [[yii\data\SqlDataProvider::pagination|pagination]] の指定に基づいて、SQL 文の `ORDER BY` と `OFFSET/LIMIT` の句を修正し、指定された順序に並べ替えられたデータを要求されたページの分だけ取得します。 [[yii\data\SqlDataProvider]] を使用するためには、[[yii\data\SqlDataProvider::sql|sql]] プロパティだけでなく、[[yii\data\SqlDataProvider::totalCount|totalCount]] プロパティを指定しなければなりません。 例えば、 ```php 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|totalCount]] プロパティは、データにページネーションを適用しなければならない場合にだけ要求されます。 これは、[[yii\data\SqlDataProvider::sql|sql]] によって指定される SQL 文は、現在要求されているページのデータだけを返すように、データプロバイダによって修正されてしまうからです。 データプロバイダは、総ページ数を正しく計算するためには、データアイテムの総数を知る必要があります。 ## 配列データプロバイダ [[yii\data\ArrayDataProvider]] は、一つの大きな配列を扱う場合に最も適しています。 このデータプロバイダによって、一つまたは複数のカラムで並べ替えた配列データの 1 ページ分を返すことが出来ます。 [[yii\data\ArrayDataProvider]] を使用するためには、全体の大きな配列として [[yii\data\ArrayDataProvider::allModels|allModels]] プロパティを指定しなければなりません。 この大きな配列の要素は、連想配列 (例えば [DAO](db-dao.md) のクエリ結果) またはオブジェクト (例えば [アクティブレコード](db-active-record.md) インスタンス) とすることが出来ます。 例えば、 ```php 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: [アクティブデータプロバイダ](#active-data-provider) および [SQL データプロバイダ](#sql-data-provider) と比較すると、配列データプロバイダは効率の面では劣ります。 何故なら、*全ての* データをメモリにロードしなければならないからです。 ## データのキーを扱う データプロバイダによって返されたデータアイテムを使用する場合、各データアイテムを一意のキーで特定しなければならないことがよくあります。 例えば、データアイテムが顧客情報を表す場合、顧客 ID を各顧客データのキーとして使用したいでしょう。 データプロバイダは、[[yii\data\DataProviderInterface::getModels()]] によって返されたデータアイテムに対応するそのようなキーのリストを返すことが出来ます。 例えば、 ```php use yii\data\ActiveDataProvider; $query = Post::find()->where(['status' => 1]); $provider = new ActiveDataProvider([ 'query' => $query, ]); // Post オブジェクトの配列を返す $posts = $provider->getModels(); // $post に対応するプライマリキーの値を返す $ids = $provider->getKeys(); ``` 上記の例では、[[yii\data\ActiveDataProvider]] に対して [[yii\db\ActiveQuery]] オブジェクトを供給していますから、キーとしてプライマリキーの値を返すのが理にかなっています。 キーの値の計算方法を明示的に指定するために、[[yii\data\ActiveDataProvider::key]] にカラム名を設定したり、キーの値を計算するコーラブルを設定したりすることも出来ます。 例えば、 ```php // "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()|prepareModels()]]: 現在のページで利用できるデータモデルを準備して、それを配列として返します。 - [[yii\data\BaseDataProvider::prepareKeys()|prepareKeys()]]: 現在利用できるデータモデルの配列を受け取って、それと関連付けられるキーの配列を返します。 - [[yii\data\BaseDataProvider::prepareTotalCount()|prepareTotalCount]]: データプロバイダにあるデータモデルの総数を示す値を返します。 下記は、CSV ファイルを効率的に読み出すデータプロバイダのサンプルです。 ```php 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; } } ```