Skip to content
Permalink
Browse files

Implemented deep associations eager loading

  • Loading branch information...
lorenzo committed May 20, 2013
1 parent 0bda22e commit 4aa7f7a93cf7066c02ff43af8eaf72a3488d292a
@@ -80,21 +80,43 @@ public function attachTo(Query $query, array $options = []) {
return false;
}
public function eagerLoader($results, $options = []) {
$target = $this->target();
$source = $this->source();
$alias = $target->alias();
$fetchQuery = $target->find('all');
public function eagerLoader($parentKeys, $options = []) {
$options += [
'foreignKey' => $this->foreignKey(),
'conditions' => [],
'sort' => $this->sort()
];
$fetchQuery = $this->_buildQuery($parentKeys, $options);
$resultMap = [];
$key = $options['foreignKey'];
foreach ($fetchQuery->execute() as $result) {
$resultMap[$result[$key]][] = $result;
}
$source = $this->source();
$sourceKey = key($fetchQuery->aliasField(
$source->primaryKey(),
$source->alias()
));
$alias = $this->target()->alias();
$targetKey = key($fetchQuery->aliasField($this->property(), $source->alias()));
return function($row) use ($alias, $resultMap, $sourceKey, $targetKey) {
if (isset($resultMap[$row[$sourceKey]])) {
$row[$targetKey] = $resultMap[$row[$sourceKey]];
}
return $row;
};
}
protected function _buildQuery($parentKeys, $options) {
$target = $this->target();
$alias = $target->alias();
$fetchQuery = $target->find('all');
$options['conditions'] = array_merge($this->conditions(), $options['conditions']);
$key = sprintf('%s.%s in', $alias, $options['foreignKey']);
$fetchQuery
->where($options['conditions'])
->andWhere([$key => $results]);
->andWhere([$key => $parentKeys]);
if (!empty($options['fields'])) {
$fields = $fetchQuery->aliasFields($options['fields'], $alias);
@@ -111,23 +133,11 @@ public function eagerLoader($results, $options = []) {
$fetchQuery->order($options['sort']);
}
$resultMap = [];
$key = $options['foreignKey'];
foreach ($fetchQuery->execute() as $result) {
$resultMap[$result[$key]][] = $result;
if (!empty($options['contain'])) {
$fetchQuery->contain($options['contain']);
}
$sourceKey = key($fetchQuery->aliasField(
$source->primaryKey(),
$source->alias()
));
$targetKey = key($fetchQuery->aliasField($this->property(), $source->alias()));
return function($row) use ($alias, $resultMap, $sourceKey, $targetKey) {
if (isset($resultMap[$row[$sourceKey]])) {
$row[$targetKey] = $resultMap[$row[$sourceKey]];
}
return $row;
};
return $fetchQuery;
}
/**
@@ -148,6 +148,10 @@ protected function _addContainments() {
$contain = [];
foreach ($this->_containments as $table => $options) {
if (!empty($options['instance'])) {
$contain = (array)$this->_containments;
break;
}
$contain[$table] = $this->_normalizeContain(
$this->_table,
$table,
@@ -157,6 +161,8 @@ protected function _addContainments() {
$firstLevelJoins = $this->_resolveFirstLevel($this->_table, $contain);
foreach ($firstLevelJoins as $options) {
$table = $options['association']->target();
$this->_aliasMap[$table->alias()] = $table;
$this->_addJoin($options['association'], $options['options']);
}
@@ -179,7 +185,6 @@ protected function _normalizeContain(Table $parent, $alias, $options) {
$instance = $parent->association($alias);
$table = $instance->target();
$this->_aliasMap[$alias] = $table;
$extra = array_diff_key($options, $defaults);
$config = [
@@ -228,7 +233,11 @@ protected function _eagerLoad($statement) {
$statement->rewind();
foreach ($this->_loadEagerly as $association => $meta) {
$f = $meta['instance']->eagerLoader($keys[$alias], $meta['config']);
$contain = $meta['associations'];
$f = $meta['instance']->eagerLoader(
$keys[$alias],
$meta['config'] + compact('contain')
);
$statement = new CallbackStatement($statement, $this->connection()->driver(), $f);
}
@@ -177,7 +177,7 @@ public function testEagerLoaderWithOverrides() {
$keys = [1, 2, 3, 4];
$query = $this->getMock(
'Cake\ORM\Query',
['execute', 'where', 'andWhere', 'order', 'select'],
['execute', 'where', 'andWhere', 'order', 'select', 'contain'],
[null]
);
$this->article->expects($this->once())->method('find')->with('all')
@@ -213,10 +213,17 @@ public function testEagerLoaderWithOverrides() {
])
->will($this->returnValue($query));
$query->expects($this->once())->method('contain')
->with([
'Category' => ['fields' => ['a', 'b']],
])
->will($this->returnValue($query));
$association->eagerLoader($keys, [
'conditions' => ['Article.id !=' => 3],
'sort' => ['title' => 'DESC'],
'fields' => ['title', 'author_id']
'fields' => ['title', 'author_id'],
'contain' => ['Category' => ['fields' => ['a', 'b']]]
]);
}
@@ -438,4 +438,49 @@ public function testHasManyEagerLoadingOrder() {
$this->assertEquals($expected, $results);
}
/**
* Tests that deep associations can be eagerly laoded
*
* @return void
**/
public function testHasManyEagerLoadingDeep() {
$this->_insertTwoRecords();
$query = new Query($this->connection);
$table = Table::build('author', ['connection' => $this->connection]);
$article = Table::build('article', ['connection' => $this->connection]);
$table->hasMany('article', ['property' => 'articles']);
$article->belongsTo('author');
$results = $query->repository($table)
->select()
->contain(['article' => ['author']])
->toArray();
$expected = [
[
'id' => 1,
'name' => 'Chuck Norris',
'articles' => [
[
'id' => 1, 'title' => 'a title', 'author_id' => 1, 'body' => 'a body',
'author' => ['id' => 1 , 'name' => 'Chuck Norris']
]
]
],
[
'id' => 2,
'name' => 'Bruce Lee',
'articles' => [
[
'id' => 2, 'title' => 'another title',
'author_id' => 2,
'body' => 'another body',
'author' => ['id' => 2 , 'name' => 'Bruce Lee']
]
]
]
];
$this->assertEquals($expected, $results);
}
}

0 comments on commit 4aa7f7a

Please sign in to comment.
You can’t perform that action at this time.