- 以前、hasMany の Association を contain すると 別SQL になるという内容の記事を書きました
- 今回は、どのような仕組みで別SQLになるのか、コアのコードを追ってみました
version
- CakePHP: 4.2.8
仕組み
\Cake\ORM\EagerLoader::loadExternal によって、別SQLになる
Decorates the passed statement object in order to inject data from associations that cannot be joined directly.
別SQLになるアソシエーション
のリストが \Cake\ORM\EagerLoader::$_loadExternal に格納されるA list of associations that should be loaded with a separate query
- how
// 流れ \Cake\Database\Driver::prepare \Cake\ORM\Query::sql \Cake\ORM\Query::_transformQuery \Cake\ORM\EagerLoader::attachAssociations \Cake\ORM\EagerLoader::attachableAssociations \Cake\ORM\EagerLoader::_resolveJoins
理解できていない事
- hasMany の Association を contain した場合は、別SQLにする、という設計になっている理由 (設計思想)
作業ログ
以下のような Learning Test を書いて、デバッグした
/** * @group leaning */ public function testHasMany(): void { //.. $query = $ArticlesTable->find(); // Articles belongsTo Authors // Articles hasMany Comments $query->contain(['Authors', 'Comments']); $result_set = $query->all(); // ... }
デバッグログ
// Leaning Test 内で \Cake\ORM\Query::all を実行
string(17) "get_class($query)"
string(14) "Cake\ORM\Query"
string(54) "↓ \Cake\ORM\Query::all $this->getRepository()::class"
string(28) "App\Model\Table\ArticlesTable"
string(69) "↓↓ \Cake\Datasource\QueryTrait::all $this->getRepository()::class"
string(28) "App\Model\Table\ArticlesTable"
string(65) "↓↓↓ \Cake\ORM\Query::_execute $this->getRepository()::class"
string(28) "App\Model\Table\ArticlesTable"
string(121) "!!!!!!!! \Cake\ORM\Query::_execute | $statement = $this->getEagerLoader()->loadExternal($this, $this->execute()) !!!!!!!!"
string(72) "↓↓↓↓ \Cake\Database\Query::execute $this->getRepository()::class"
string(28) "App\Model\Table\ArticlesTable"
string(30) "\Cake\Database\Connection::run"
string(34) "\Cake\Core\Retry\CommandRetry::run"
string(43) "\Cake\Database\Connection::run の callback"
string(34) "\Cake\Database\Connection::prepare"
string(34) "\Cake\Core\Retry\CommandRetry::run"
string(47) "\Cake\Database\Connection::prepare の callback"
string(34) "\Cake\Database\Driver::prepare (1)"
string(38) "\Cake\Database\Driver::prepare (2) sql"
string(1134) "SELECT ... FROM Articles Articles INNER JOIN Authors Authors ON (Authors.id = (Articles.author_id))"
string(60) "\Cake\Database\Driver::prepare (end) return new PDOStatement"
string(121) "↑↑↑↑ \Cake\Database\Query::execute (end) return \Cake\Database\StatementInterface | $this->getRepository()::class"
string(28) "App\Model\Table\ArticlesTable"
string(83) "↓↓↓↓↓ \Cake\ORM\EagerLoader::loadExternal $query->getRepository()::class"
string(28) "App\Model\Table\ArticlesTable"
string(81) "\Cake\ORM\EagerLoader::loadExternal (2) $external is not empty | count($external)"
int(1)
string(68) "\Cake\ORM\EagerLoader::loadExternal (3) foreach ($external as $meta)"
string(62) "\Cake\ORM\EagerLoader::loadExternal (3-1) ※foreach内 $alias"
string(7) "Articles"
string(54) "↓ \Cake\ORM\Query::all $this->getRepository()::class"
string(26) "App\Model\Table\CommentsTable"
string(69) "↓↓ \Cake\Datasource\QueryTrait::all $this->getRepository()::class"
string(26) "App\Model\Table\CommentsTable"
string(65) "↓↓↓ \Cake\ORM\Query::_execute $this->getRepository()::class"
string(26) "App\Model\Table\CommentsTable"
string(121) "!!!!!!!! \Cake\ORM\Query::_execute | $statement = $this->getEagerLoader()->loadExternal($this, $this->execute()) !!!!!!!!"
string(72) "↓↓↓↓ \Cake\Database\Query::execute $this->getRepository()::class"
string(26) "App\Model\Table\CommentsTable"
string(30) "\Cake\Database\Connection::run"
string(34) "\Cake\Core\Retry\CommandRetry::run"
string(43) "\Cake\Database\Connection::run の callback"
string(34) "\Cake\Database\Connection::prepare"
string(34) "\Cake\Core\Retry\CommandRetry::run"
string(47) "\Cake\Database\Connection::prepare の callback"
string(34) "\Cake\Database\Driver::prepare (1)"
string(38) "\Cake\Database\Driver::prepare (2) sql"
string(399) "SELECT ... FROM Comments Comments WHERE Comments.article_id in (:c0)"
string(60) "\Cake\Database\Driver::prepare (end) return new PDOStatement"
string(121) "↑↑↑↑ \Cake\Database\Query::execute (end) return \Cake\Database\StatementInterface | $this->getRepository()::class"
string(26) "App\Model\Table\CommentsTable"
string(83) "↓↓↓↓↓ \Cake\ORM\EagerLoader::loadExternal $query->getRepository()::class"
string(26) "App\Model\Table\CommentsTable"
string(78) "↑↑↑↑↑ \Cake\ORM\EagerLoader::loadExternal (1-end) $external is empty"
string(113) "↑↑↑ \Cake\ORM\Query::_execute (end) return new ResultSet($this, $statement) | $this->getRepository()::class"
string(26) "App\Model\Table\CommentsTable"
string(120) "↑↑ \Cake\Datasource\QueryTrait::all (end) return \Cake\Datasource\ResultSetInterface | $this->getRepository()::class"
string(26) "App\Model\Table\CommentsTable"
string(106) "↑ \Cake\ORM\Query::all (end) return \Cake\Datasource\ResultSetInterface | $this->getRepository()::class "
string(26) "App\Model\Table\CommentsTable"
string(126) "\Cake\ORM\EagerLoader::loadExternal (3-end) ※foreach内 $statement = new CallbackStatement($statement, $driver, $f) | $alias"
string(7) "Articles"
string(98) "↑↑↑↑↑ \Cake\ORM\EagerLoader::loadExternal (end) return \Cake\Database\StatementInterface"
string(113) "↑↑↑ \Cake\ORM\Query::_execute (end) return new ResultSet($this, $statement) | $this->getRepository()::class"
string(28) "App\Model\Table\ArticlesTable"
string(120) "↑↑ \Cake\Datasource\QueryTrait::all (end) return \Cake\Datasource\ResultSetInterface | $this->getRepository()::class"
string(28) "App\Model\Table\ArticlesTable"
string(106) "↑ \Cake\ORM\Query::all (end) return \Cake\Datasource\ResultSetInterface | $this->getRepository()::class "
string(28) "App\Model\Table\ArticlesTable"