ソースを参照

Implemented the select and subquery strategy for BelongsTo

Jose Lorenzo Rodriguez 12 年 前
コミット
e6f3e8ec29

+ 51 - 1
src/ORM/Association/BelongsTo.php

@@ -16,6 +16,7 @@ namespace Cake\ORM\Association;
 
 use Cake\Database\Expression\IdentifierExpression;
 use Cake\ORM\Association;
+use Cake\ORM\Association\SelectableAssociationTrait;
 use Cake\ORM\Entity;
 use Cake\ORM\Table;
 use Cake\Utility\Inflector;
@@ -28,6 +29,8 @@ use Cake\Utility\Inflector;
  */
 class BelongsTo extends Association {
 
+	use SelectableAssociationTrait;
+
 /**
  * Sets the name of the field representing the foreign key to the target table.
  * If no parameters are passed current field is returned
@@ -91,6 +94,23 @@ class BelongsTo extends Association {
 	}
 
 /**
+ * {@inheritdoc}
+ *
+ */
+	public function transformRow($row, $joined) {
+		if ($this->strategy() === $this::STRATEGY_JOIN) {
+			return parent::transformRow($row, $joined);
+		}
+
+		$sourceAlias = $this->source()->alias();
+		$nestKey = $this->_nestingKey();
+		if (isset($row[$nestKey])) {
+			$row[$sourceAlias][$this->property()] = $row[$nestKey];
+		}
+		return $row;
+	}
+
+/**
  * Takes an entity from the source table and looks if there is a field
  * matching the property name for this association. The found entity will be
  * saved on the target table for this association by passing supplied
@@ -157,7 +177,37 @@ class BelongsTo extends Association {
  * {@inheritdoc}
  *
  */
-	public function eagerLoader(array $options) {
+	protected function _linkField($options) {
+		$links = [];
+		$name = $this->name();
+
+		foreach ((array)$this->target()->primaryKey() as $key) {
+			$links[] = sprintf('%s.%s', $name, $key);
+		}
+
+		if (count($links) === 1) {
+			return $links[0];
+		}
+
+		return $links;
+	}
+
+/**
+ * {@inheritdoc}
+ *
+ */
+	protected function _buildResultMap($fetchQuery, $options) {
+		$resultMap = [];
+		$key = (array)$this->target()->primaryKey();
+
+		foreach ($fetchQuery->all() as $result) {
+			$values = [];
+			foreach ($key as $k) {
+				$values[] = $result[$k];
+			}
+			$resultMap[implode(';', $values)] = $result;
+		}
+		return $resultMap;
 	}
 
 }

+ 41 - 1
src/ORM/Association/ExternalAssociationTrait.php

@@ -25,7 +25,9 @@ use Cake\Utility\Inflector;
  */
 trait ExternalAssociationTrait {
 
-	use SelectableAssociationTrait;
+	use SelectableAssociationTrait {
+		_defaultOptions as private _selectableOptions;
+	}
 
 /**
  * Order in which target records should be returned
@@ -101,6 +103,44 @@ trait ExternalAssociationTrait {
 	}
 
 /**
+ * Returns the default options to use for the eagerLoader
+ *
+ * @return array
+ */
+	protected function _defaultOptions() {
+		return $this->_selectableOptions() + [
+			'sort' => $this->sort()
+		];
+	}
+
+/**
+ * {@inheritdoc}
+ *
+ */
+	protected function _buildResultMap($fetchQuery, $options) {
+		$resultMap = [];
+		$key = (array)$options['foreignKey'];
+
+		foreach ($fetchQuery->all() as $result) {
+			$values = [];
+			foreach ($key as $k) {
+				$values[] = $result[$k];
+			}
+			$resultMap[implode(';', $values)][] = $result;
+		}
+		return $resultMap;
+	}
+
+/**
+ * Returns the key under which the eagerLoader will put this association results
+ *
+ * @return void
+ */
+	protected function _nestingKey() {
+		return $this->_name . '___collection_';
+	}
+
+/**
  * Returns a single or multiple conditions to be appended to the generated join
  * clause for getting the results on the target table.
  *

+ 19 - 0
src/ORM/Association/HasMany.php

@@ -118,6 +118,25 @@ class HasMany extends Association {
 	}
 
 /**
+ * {@inheritdoc}
+ *
+ */
+	protected function _linkField($options) {
+		$links = [];
+		$name = $this->name();
+
+		foreach ((array)$options['foreignKey'] as $key) {
+			$links[] = sprintf('%s.%s', $name, $key);
+		}
+
+		if (count($links) === 1) {
+			return $links[0];
+		}
+
+		return $links;
+	}
+
+/**
  * Get the relationship type.
  *
  * @return string

+ 30 - 38
src/ORM/Association/SelectableAssociationTrait.php

@@ -38,13 +38,7 @@ trait SelectableAssociationTrait {
  *
  */
 	public function eagerLoader(array $options) {
-		$options += [
-			'foreignKey' => $this->foreignKey(),
-			'conditions' => [],
-			'sort' => $this->sort(),
-			'strategy' => $this->strategy()
-		];
-
+		$options = $options + $this->_defaultOptions();
 		$queryBuilder = false;
 		if (!empty($options['queryBuilder'])) {
 			$queryBuilder = $options['queryBuilder'];
@@ -60,6 +54,19 @@ trait SelectableAssociationTrait {
 	}
 
 /**
+ * Returns the default options to use for the eagerLoader
+ *
+ * @return array
+ */
+	protected function _defaultOptions() {
+		return [
+			'foreignKey' => $this->foreignKey(),
+			'conditions' => [],
+			'strategy' => $this->strategy()
+		];
+	}
+
+/**
  * Auxiliary function to construct a new Query object to return all the records
  * in the target table that are associated to those specified in $options from
  * the source table
@@ -141,20 +148,7 @@ trait SelectableAssociationTrait {
  * @param array $options
  * @return string
  */
-	protected function _linkField($options) {
-		$links = [];
-		$name = $this->name();
-
-		foreach ((array)$options['foreignKey'] as $key) {
-			$links[] = sprintf('%s.%s', $name, $key);
-		}
-
-		if (count($links) === 1) {
-			return $links[0];
-		}
-
-		return $links;
-	}
+	protected abstract function _linkField($options);
 
 /**
  * Builds a query to be used as a condition for filtering records in in the
@@ -187,19 +181,7 @@ trait SelectableAssociationTrait {
  * @param array $options The options passed to the eager loader
  * @return array
  */
-	protected function _buildResultMap($fetchQuery, $options) {
-		$resultMap = [];
-		$key = (array)$options['foreignKey'];
-
-		foreach ($fetchQuery->all() as $result) {
-			$values = [];
-			foreach ($key as $k) {
-				$values[] = $result[$k];
-			}
-			$resultMap[implode(';', $values)][] = $result;
-		}
-		return $resultMap;
-	}
+	protected abstract function _buildResultMap($fetchQuery, $options);
 
 /**
  * Returns a callable to be used for each row in a query result set
@@ -213,15 +195,16 @@ trait SelectableAssociationTrait {
 	protected function _resultInjector($fetchQuery, $resultMap) {
 		$source = $this->source();
 		$sAlias = $source->alias();
-		$tAlias = $this->target()->alias();
+		$keys = $this->type() === $this::ONE_TO_ONE ?
+			$this->foreignKey() :
+			$source->primaryKey();
 
 		$sourceKeys = [];
-		foreach ((array)$source->primaryKey() as $key) {
+		foreach ((array)$keys as $key) {
 			$sourceKeys[] = key($fetchQuery->aliasField($key, $sAlias));
 		}
 
-		$nestKey = $tAlias . '___collection_';
-
+		$nestKey = $this->_nestingKey();
 		if (count($sourceKeys) > 1) {
 			return $this->_multiKeysInjector($resultMap, $sourceKeys, $nestKey);
 		}
@@ -236,6 +219,15 @@ trait SelectableAssociationTrait {
 	}
 
 /**
+ * Returns the key under which the eagerLoader will put this association results
+ *
+ * @return string
+ */
+	protected function _nestingKey() {
+		return $this->property();
+	}
+
+/**
  * Returns a callable to be used for each row in a query result set
  * for injecting the eager loaded rows when the matching needs to
  * be done with multiple foreign keys

+ 12 - 2
tests/TestCase/ORM/QueryTest.php

@@ -98,14 +98,24 @@ class QueryTest extends TestCase {
 	}
 
 /**
+ * Provides strategies for associations that can be joined
+ *
+ * @return void
+ */
+	public function internalStategiesProvider() {
+		return [['join'], ['select'], ['subquery']];
+	}
+
+/**
  * Tests that results are grouped correctly when using contain()
  * and results are not hydrated
  *
+ * @dataProvider internalStategiesProvider
  * @return void
  */
-	public function testContainResultFetchingOneLevel() {
+	public function testContainResultFetchingOneLevel($strategy) {
 		$table = TableRegistry::get('articles', ['table' => 'articles']);
-		$table->belongsTo('authors');
+		$table->belongsTo('authors', ['strategy' => $strategy]);
 
 		$query = new Query($this->connection, $table);
 		$results = $query->select()