Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Implementing eager loading for BelongsToMany when using composite keys

  • Loading branch information...
commit ff6eb98c5548b9e27011f052e3be9ed291f2a3c5 1 parent 16f7fe2
@lorenzo lorenzo authored
View
49 Cake/ORM/Association/BelongsToMany.php
@@ -33,6 +33,7 @@ class BelongsToMany extends Association {
use ExternalAssociationTrait {
_options as _externalOptions;
+ _addFilteringCondition as _addExternalConditions;
}
/**
@@ -183,7 +184,10 @@ public function junction($table = null) {
'foreignKey' => $this->targetForeignKey(),
'targetForeignKey' => $this->foreignKey()
]);
- $target->hasMany($junctionAlias)->target($table);
+ $target->hasMany($junctionAlias, [
+ 'targetTable' => $table,
+ 'foreignKey' => $this->targetForeignKey(),
+ ]);
}
if (!$source->association($table->alias())) {
@@ -296,7 +300,7 @@ public function eagerLoader(array $options) {
}
$resultMap = [];
- $key = $options['foreignKey'];
+ $key = (array)$options['foreignKey'];
$property = $this->target()->association($this->junction()->alias())->property();
$hydrated = $fetchQuery->hydrate();
@@ -308,7 +312,11 @@ public function eagerLoader(array $options) {
$result->dirty($this->_junctionProperty, false);
}
- $resultMap[$result[$this->_junctionProperty][$key]][] = $result;
+ $values = [];
+ foreach ($key as $k) {
+ $values[] = $result[$this->_junctionProperty][$k];
+ }
+ $resultMap[implode(';', $values)][] = $result;
}
return $this->_resultInjector($fetchQuery, $resultMap);
@@ -861,12 +869,10 @@ protected function _collectJointEntities($sourceEntity, $targetEntities) {
* @return \Cake\ORM\Query
*/
protected function _addFilteringCondition($query, $key, $filter) {
- return $query->contain([
- $this->_junctionAssociationName() => [
- 'conditions' => [$key . ' in' => $filter],
- 'matching' => true
- ]
- ]);
+ $name = $this->_junctionAssociationName();
+ return $query->matching($name, function($q) use ($key, $filter) {
+ return $this->_addExternalConditions($q, $key, $filter);
+ });
}
/**
@@ -877,7 +883,18 @@ protected function _addFilteringCondition($query, $key, $filter) {
* @return string
*/
protected function _linkField($options) {
- return sprintf('%s.%s', $this->_junctionAssociationName(), $options['foreignKey']);
+ $links = [];
+ $name = $this->_junctionAssociationName();
+
+ foreach ((array)$options['foreignKey'] as $key) {
+ $links[] = sprintf('%s.%s', $name, $key);
+ }
+
+ if (count($links) === 1) {
+ return $links[0];
+ }
+
+ return $links;
}
/**
@@ -926,19 +943,19 @@ protected function _junctionTableName($name = null) {
* @return void
*/
protected function _options(array $opts) {
- if (!empty($opts['through'])) {
- $this->junction($opts['through']);
+ $this->_externalOptions($opts);
+ if (!empty($opts['targetForeignKey'])) {
+ $this->targetForeignKey($opts['targetForeignKey']);
}
if (!empty($opts['joinTable'])) {
$this->_junctionTableName($opts['joinTable']);
}
+ if (!empty($opts['through'])) {
+ $this->junction($opts['through']);
+ }
if (!empty($opts['saveStrategy'])) {
$this->saveStrategy($opts['saveStrategy']);
}
- if (!empty($opts['targetForeignKey'])) {
- $this->targetForeignKey($opts['targetForeignKey']);
- }
- $this->_externalOptions($opts);
}
}
View
3  Cake/ORM/Association/ExternalAssociationTrait.php
@@ -273,7 +273,8 @@ protected function _addFilteringCondition($query, $key, $filter) {
$types[] = $defaults[$k];
}
}
- return $query->andWhere(new TupleComparison($key, $filter, $types, 'IN'));
+ $query->andWhere(new TupleComparison($key, $filter, $types, 'IN'));
+ return $query;
}
return $query->andWhere([$key . ' IN' => $filter]);
}
View
1  Cake/ORM/Association/HasMany.php
@@ -1,5 +1,6 @@
<?php
/**
+ *
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
View
48 Test/Fixture/SiteArticlesTagFixture.php
@@ -0,0 +1,48 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link http://cakephp.org CakePHP(tm) Project
+ * @since CakePHP(tm) v 3.0.0
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+namespace Cake\Test\Fixture;
+
+use Cake\TestSuite\Fixture\TestFixture;
+
+class SiteArticlesTagFixture extends TestFixture {
+
+/**
+ * fields property
+ *
+ * @var array
+ */
+ public $fields = array(
+ 'article_id' => ['type' => 'integer', 'null' => false],
+ 'tag_id' => ['type' => 'integer', 'null' => false],
+ 'site_id' => ['type' => 'integer', 'null' => false],
+ '_constraints' => [
+ 'UNIQUE_TAG2' => ['type' => 'primary', 'columns' => ['article_id', 'tag_id', 'site_id']]
+ ]
+ );
+
+/**
+ * records property
+ *
+ * @var array
+ */
+ public $records = [
+ ['article_id' => 1, 'tag_id' => 1, 'site_id' => 1],
+ ['article_id' => 1, 'tag_id' => 2, 'site_id' => 2],
+ ['article_id' => 2, 'tag_id' => 4, 'site_id' => 2],
+ ['article_id' => 4, 'tag_id' => 1, 'site_id' => 1],
+ ['article_id' => 1, 'tag_id' => 3, 'site_id' => 1]
+ ];
+
+}
View
45 Test/Fixture/SiteTagFixture.php
@@ -0,0 +1,45 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link http://cakephp.org CakePHP(tm) Project
+ * @since CakePHP(tm) v 3.0.0
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+namespace Cake\Test\Fixture;
+
+use Cake\TestSuite\Fixture\TestFixture;
+
+class SiteTagFixture extends TestFixture {
+
+/**
+ * fields property
+ *
+ * @var array
+ */
+ public $fields = [
+ 'id' => ['type' => 'integer'],
+ 'site_id' => ['type' => 'integer'],
+ 'name' => ['type' => 'string', 'null' => false],
+ '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id', 'site_id']]]
+ ];
+
+/**
+ * records property
+ *
+ * @var array
+ */
+ public $records = [
+ ['id' => 1, 'site_id' => 1, 'name' => 'tag1'],
+ ['id' => 2, 'site_id' => 2, 'name' => 'tag2'],
+ ['id' => 3, 'site_id' => 1, 'name' => 'tag3'],
+ ['id' => 4, 'site_id' => 2, 'name' => 'tag4']
+ ];
+}
+
View
94 Test/TestCase/ORM/CompositeKeysTest.php
@@ -34,7 +34,10 @@ class CompositeKeyTest extends TestCase {
*
* @var array
*/
- public $fixtures = ['core.site_article', 'core.site_author'];
+ public $fixtures = [
+ 'core.site_article', 'core.site_author', 'core.site_tag',
+ 'core.site_articles_tag'
+ ];
/**
* setUp method
@@ -62,7 +65,7 @@ public function strategiesProvider() {
* @dataProvider strategiesProvider
* @return void
*/
- public function testHasManyEagerCompositeKeys($strategy) {
+ public function testHasManyEager($strategy) {
$table = TableRegistry::get('SiteAuthors');
TableRegistry::get('SiteArticles');
$table->hasMany('SiteArticles', [
@@ -129,4 +132,91 @@ public function testHasManyEagerCompositeKeys($strategy) {
$this->assertEquals($table->association('SiteArticles')->strategy(), $strategy);
}
+/**
+ * Tests that BelongsToMany associations are correctly eager loaded.
+ * Also that the query object passes the correct parent model keys to the
+ * association objects in order to perform eager loading with select strategy
+ *
+ * @dataProvider strategiesProvider
+ * @return void
+ **/
+ public function testBelongsToManyEager($strategy) {
+ $articles = TableRegistry::get('SiteArticles');
+ $tags = TableRegistry::get('SiteTags');
+ $junction = TableRegistry::get('SiteArticlesTags');
+ $articles->belongsToMany('SiteTags', [
+ 'strategy' => $strategy,
+ 'targetTable' => $tags,
+ 'propertyName' => 'tags',
+ 'through' => 'SiteArticlesTags',
+ 'foreignKey' => ['article_id', 'site_id'],
+ 'targetForeignKey' => ['tag_id', 'site_id']
+ ]);
+ $query = new Query($this->connection, $articles);
+
+ $results = $query->select()->contain('SiteTags')->hydrate(false)->toArray();
+ $expected = [
+ [
+ 'id' => 1,
+ 'author_id' => 1,
+ 'title' => 'First Article',
+ 'body' => 'First Article Body',
+ 'site_id' => 1,
+ 'tags' => [
+ [
+ 'id' => 1,
+ 'name' => 'tag1',
+ '_joinData' => ['article_id' => 1, 'tag_id' => 1, 'site_id' => 1],
+ 'site_id' => 1
+ ],
+ [
+ 'id' => 3,
+ 'name' => 'tag3',
+ '_joinData' => ['article_id' => 1, 'tag_id' => 3, 'site_id' => 1],
+ 'site_id' => 1
+ ]
+ ]
+ ],
+ [
+ 'id' => 2,
+ 'title' => 'Second Article',
+ 'body' => 'Second Article Body',
+ 'author_id' => 3,
+ 'site_id' => 2,
+ 'tags' => [
+ [
+ 'id' => 4,
+ 'name' => 'tag4',
+ '_joinData' => ['article_id' => 2, 'tag_id' => 4, 'site_id' => 2],
+ 'site_id' => 2
+ ]
+ ]
+ ],
+ [
+ 'id' => 3,
+ 'title' => 'Third Article',
+ 'body' => 'Third Article Body',
+ 'author_id' => 1,
+ 'site_id' => 2
+ ],
+ [
+ 'id' => 4,
+ 'title' => 'Fourth Article',
+ 'body' => 'Fourth Article Body',
+ 'author_id' => 3,
+ 'site_id' => 1,
+ 'tags' => [
+ [
+ 'id' => 1,
+ 'name' => 'tag1',
+ '_joinData' => ['article_id' => 4, 'tag_id' => 1, 'site_id' => 1],
+ 'site_id' => 1
+ ]
+ ]
+ ],
+ ];
+ $this->assertEquals($expected, $results);
+ $this->assertEquals($articles->association('SiteTags')->strategy(), $strategy);
+ }
+
}
Please sign in to comment.
Something went wrong with that request. Please try again.