※2022/02/05: 追記 (->toArray()
する際の注意点)
ドキュメント
TableClass->find()...->all()
では ResultSet
が返り、ResultSet
を foreach で回すと PDOStatement::fetch
しDBから取得したデータがメモリ上に展開される
TableClass->find()...->all()
は\Cake\ORM\ResultSet
を返す- \Cake\ORM\Table::find は
\Cake\ORM\Query
を返す - \Cake\ORM\Query::all は
\Cake\Datasource\ResultSetInterface
を返す
- \Cake\ORM\Table::find は
\Cake\ORM\ResultSet
- 取得した時点では PDOStatement::execute を実行した段階
- この時点では、メモリ上には、配列として展開されてない (※1)
- 取得した時点では PDOStatement::execute を実行した段階
(※1)
ResultSet
に対して下記のような事を実行した際に、(fetch|fetchAll) しarray|object(=Entityオブジェクト)
としてメモリ上に展開される- foreach で回す
\Cake\ORM\ResultSet
は Iterator interface を実装したクラスなので、foreach で回す事ができる (※2)
->first()
->toArray()
(※3)
- foreach で回す
(※2) Iterator
- Iterator::current: 現在の要素を取り出す
\Cake\ORM\ResultSet
は \Cake\ORM\ResultSet::current でIterator::current
を実装しているCake\ORM\ResultSet::current
は\Cake\ORM\ResultSet::$_current
を返す- ここで PDOStatement::fetch して、メモリ上に Entity オブジェクトとして展開している
※3 ->toArray()
する際の注意点
\Cake\Datasource\QueryTrait::toArray
- PDOStatement::fetchAll して、全データをメモリ上にEntityオブジェクトの配列として 展開される
問題点
- データの件数が多い場合に、メモリオーバーになる事が可能性がある
- ※paginator 等で件数が指定されてるケースで
->toArray()
するのは問題ない
対策
- データ量が増えていくようなテーブルから、全件とってくるようなケース
->toArray()
せず、ResultSet
のまま処理する(ResultRest
を foreach で回して処理する)
- データ量が増えていくようなテーブルから、全件とってくるようなケース
PDO
https://dev-dub.hatenablog.com/entry/2022/02/05/120309 を参照
以下、コードを追った際のメモ
// in Controller /** @var ResultSetInterface $resultSet */ $resultSet = $this->Hoges //Class Hoges extends \Cake\ORM\Table // \Cake\ORM\Table::find // return \Cake\ORM\Query // - Table オブジェクトの find() を使う事で Query オブジェクトを生成 ->find() // \Cake\Database\Query::where // return \Cake\Database\Query ->where(...) // \Cake\ORM\Query::contain // return \Cake\ORM\Query ->contain(...) // \Cake\Database\Query::orderAsc // return \Cake\ORM\Query ->orderAsc(...) // \Cake\ORM\Query::all // return \Cake\ORM\ResultSet // - \Cake\ORM\ResultSet は \Cake\Datasource\ResultSetInterface を実装したクラスで、\Iterator を実装している // - \Iterator = PHP の Iterator インターフェース(https://www.php.net/manual/ja/class.iterator.php) // - Iterator インターフェイスを実装したオブジェクトは foreach 文でループ処理を行う事ができる ->all();
// in Controller $hoge = $this->Hoges //Class Hoges extends \Cake\ORM\Table ->find() ->all() // \Cake\ORM\ResultSet::first // return array|object|null ->first();
// in app/vendor/cakephp/cakephp/src/ORM/ResultSet.php /** * Get the first record from a result set. * * This method will also close the underlying statement cursor. * * @return array|object|null */ public function first() { // \Cake\ORM\ResultSet をループで回す事で、DBから最初のレコードを取得し、array|object として返す foreach ($this as $result) { if ($this->_statement !== null && !$this->_useBuffering) { // \Cake\Database\StatementInterface::closeCursor $this->_statement->closeCursor(); } // DBからfetchして、array|object で返す // array|object => メモリ上に展開 return $result; } return null; }
$resultArray = $this->Hoges ->find() // \Cake\Collection\CollectionTrait::toList // return $this->toArray(false); ->toList();
$resultArray = $this->Hoges ->find() // Returns a key-value array with the results of this query. // // \Cake\Datasource\QueryTrait::toArray // return $this->all()->toArray(); ※ `->all` してるので、\Cake\ORM\ResultSet の toArray()(=\Cake\Collection\CollectionTrait::toArray) を実行している // // \Cake\Collection\CollectionTrait::toArray // return iterator_to_array($this, $preserveKeys); // $this は \Cake\ORM\ResultSet なので、DBからレコードを取得して Array で返している ->toArray(); //\Cake\Datasource\QueryTrait::toArray