Browse Source

Allow for multiple "WHEN" parts

Walther Lalk 11 years ago
parent
commit
d212505b4f

+ 107 - 43
src/Database/Expression/CaseExpression.php

@@ -25,72 +25,130 @@ use Cake\Database\ValueBinder;
  */
 class CaseExpression implements ExpressionInterface {
 
-	protected $_expression;
+/**
+ * A list of strings or other expression objects that represent the conditions of
+ * the case statement. For example one key of the array might look like "sum > :value"
+ *
+ * @var array
+ */
+	protected $_conditions = [];
 
-	protected $_isTrue;
+/**
+ * Values that are associated with the conditions in the $_conditions array.
+ * Each value represents the 'true' value for the condition with the corresponding key
+ *
+ * @var array
+ */
+	protected $_trueValues = [];
 
-	protected $_isFalse;
+/**
+ * The value to be used if none of the conditions match
+ *
+ * @var array|QueryExpression|string
+ */
+	protected $_defaultValue;
 
 /**
  * Constructs the case expression
  *
- * @param QueryExpression $expression The expression to test
- * @param mixed           $isTrue Value if the expression is true
- * @param mixed           $isFalse Value if the expression is false
+ * @param array|QueryExpression $conditions The conditions to test.
+ *                                          Must be a QueryExpression, or an array of QueryExpressions.
+ * @param string|array|QueryExpression $trueValues Value of each condition if that condition is true
+ * @param string|array|QueryExpression $defaultValue Default value if none of the conditiosn are true
  */
-	public function __construct(QueryExpression $expression, $isTrue = 1, $isFalse = 0) {
-		$this->_expression = $expression;
-		$this->_isTrue = $this->_getValue($isTrue);
-		$this->_isFalse = $this->_getValue($isFalse);
+	public function __construct($conditions = [], $trueValues = [], $defaultValue = 0) {
+		if (!empty($conditions)) {
+			$this->add($conditions, $trueValues);
+		}
+
+		$this->_defaultValue = $this->_parseValue($defaultValue);
 	}
 
 /**
- * Gets/sets the isTrue part
+ * Adds one or more conditions and their respective true values to the case object.
+ * Conditions must be a one dimensional array or a QueryExpression.
+ * The trueValues must be a similar structure, but may contain a string value.
  *
- * @param mixed $value Value to set
+ * @param array|QueryExpression $conditions Must be a QueryExpression, or an array of QueryExpressions.
+ * @param string|array|QueryExpression $trueValues Values of each condition if that condition is true
  *
- * @return array|mixed
+ * @return $this
  */
-	public function isTrue($value = null) {
-		return $this->_part('isTrue', $value);
+	public function add($conditions = [], $trueValues = []) {
+		if (!is_array($conditions)) {
+			$conditions = [$conditions];
+		}
+		if (!is_array($trueValues)) {
+			$trueValues = [$trueValues];
+		}
+
+		$this->_addExpressions($conditions, $trueValues);
+
+		return $this;
 	}
 
 /**
- * Gets/sets the isFalse part
+ * Iterates over the passed in conditions and ensures that there is a matching true value for each.
+ * If no matching true value, then it is defaulted to '1'.
  *
- * @param mixed $value Value to set
+ * @param array|QueryExpression $conditions Must be a QueryExpression, or an array of QueryExpressions.
+ * @param string|array|QueryExpression $trueValues Values of each condition if that condition is true
  *
- * @return array|mixed
- * @codeCoverageIgnore
+ * @return void
  */
-	public function isFalse($value = null) {
-		return $this->_part('isFalse', $value);
+	protected function _addExpressions($conditions, $trueValues) {
+		foreach ($conditions as $k => $c) {
+			$numericKey = is_numeric($k);
+
+			if ($numericKey && empty($c)) {
+				continue;
+			}
+
+			if (!$c instanceof QueryExpression) {
+				continue;
+			}
+
+			$trueValue = isset($trueValues[$k]) ? $trueValues[$k] : 1;
+
+			if ($trueValue === 'literal') {
+				$trueValue = $k;
+			} elseif (is_string($trueValue)) {
+				$trueValue = [
+					'value' => $trueValue,
+					'type' => null
+				];
+			} elseif (empty($trueValue)) {
+				$trueValue = 1;
+			}
+
+			$this->_conditions[] = $c;
+			$this->_trueValues[] = $trueValue;
+		}
 	}
 
 /**
- * Gets/sets the passed part
+ * Gets/sets the default value part
  *
- * @param string $part The part to get or set
- * @param mixed $value Value to set
+ * @param array|string|QueryExpression $value Value to set
  *
- * @return array|mixed
+ * @return array|string|QueryExpression
  */
-	protected function _part($part, $value) {
+	public function defaultValue($value = null) {
 		if ($value !== null) {
-			$this->{'_' . $part} = $this->_getValue($value);
+			$this->_defaultValue = $this->_parseValue($value);
 		}
 
-		return $this->{'_' . $part};
+		return $this->_defaultValue;
 	}
 
 /**
  * Parses the value into a understandable format
  *
- * @param mixed $value The value to parse
+ * @param array|string|QueryExpression $value The value to parse
  *
- * @return array|mixed
+ * @return array|string|QueryExpression
  */
-	protected function _getValue($value) {
+	protected function _parseValue($value) {
 		if (is_string($value)) {
 			$value = [
 				'value' => $value,
@@ -104,15 +162,14 @@ class CaseExpression implements ExpressionInterface {
 	}
 
 /**
- * Compiles the true or false part into sql
+ * Compiles the relevant parts into sql
  *
- * @param mixed       $part The part to compile
+ * @param array|string|QueryExpression $part The part to compile
  * @param ValueBinder $generator Sql generator
  *
  * @return string
  */
 	protected function _compile($part, ValueBinder $generator) {
-		$part = $this->{'_' . $part};
 		if ($part instanceof ExpressionInterface) {
 			$part = $part->sql($generator);
 		} elseif (is_array($part)) {
@@ -133,12 +190,13 @@ class CaseExpression implements ExpressionInterface {
  */
 	public function sql(ValueBinder $generator) {
 		$parts = [];
-		$parts[] = 'CASE WHEN';
-		$parts[] = $this->_expression->sql($generator);
-		$parts[] = 'THEN';
-		$parts[] = $this->_compile('isTrue', $generator);
+		$parts[] = 'CASE';
+		foreach ($this->_conditions as $k => $part) {
+			$trueValue = $this->_trueValues[$k];
+			$parts[] = 'WHEN ' . $this->_compile($part, $generator) . ' THEN ' . $this->_compile($trueValue, $generator);
+		}
 		$parts[] = 'ELSE';
-		$parts[] = $this->_compile('isFalse', $generator);
+		$parts[] = $this->_compile($this->_defaultValue, $generator);
 		$parts[] = 'END';
 
 		return implode(' ', $parts);
@@ -149,12 +207,18 @@ class CaseExpression implements ExpressionInterface {
  *
  */
 	public function traverse(callable $visitor) {
-		foreach (['_expression', '_isTrue', '_isFalse'] as $c) {
-			if ($this->{$c} instanceof ExpressionInterface) {
-				$visitor($this->{$c});
-				$this->{$c}->traverse($visitor);
+		foreach (['_conditions', '_trueValues'] as $part) {
+			foreach ($this->{$part} as $c) {
+				if ($c instanceof ExpressionInterface) {
+					$visitor($c);
+					$c->traverse($visitor);
+				}
 			}
 		}
+		if ($this->_defaultValue instanceof ExpressionInterface) {
+			$visitor($this->_defaultValue);
+			$this->_defaultValue->traverse($visitor);
+		}
 	}
 
 }

+ 10 - 46
tests/TestCase/Database/Expression/CaseExpressionTest.php

@@ -31,54 +31,16 @@ class CaseExpressionTest extends TestCase {
 	public function testSqlOutput() {
 		$expr = new QueryExpression();
 		$expr->eq('test', 'true');
-		$caseExpression = new CaseExpression($expr);
+		$caseExpression = new CaseExpression($expr, 'foobar');
 
-		$this->assertInstanceOf('Cake\Database\ExpressionInterface', $caseExpression);
-		$expected = 'CASE WHEN test = :c0 THEN 1 ELSE 0 END';
+		$expected = 'CASE WHEN test = :c0 THEN :c1 ELSE 0 END';
 		$this->assertSame($expected, $caseExpression->sql(new ValueBinder()));
-	}
-
-/**
- * Test that we can pass in things as the isTrue/isFalse part
- *
- * @return void
- */
-	public function testSetTrue() {
-		$expr = new QueryExpression();
-		$expr->eq('test', 'true');
-		$caseExpression = new CaseExpression($expr);
-		$expr2 = new QueryExpression();
-
-		$caseExpression->isTrue($expr2);
-		$this->assertSame($expr2, $caseExpression->isTrue());
-
-		$caseExpression->isTrue('test_string');
-		$this->assertSame(['value' => 'test_string', 'type' => null], $caseExpression->isTrue());
-
-		$caseExpression->isTrue(['test_string' => 'literal']);
-		$this->assertSame('test_string', $caseExpression->isTrue());
-	}
 
-/**
- * Test that things are compiled correctly
- *
- * @return void
- */
-	public function testSqlCompiler() {
-		$expr = new QueryExpression();
-		$expr->eq('test', 'true');
-		$caseExpression = new CaseExpression($expr);
 		$expr2 = new QueryExpression();
-		$expr2->eq('test', 'false');
-
-		$caseExpression->isTrue($expr2);
-		$this->assertSame('CASE WHEN test = :c0 THEN test = :c1 ELSE 0 END', $caseExpression->sql(new ValueBinder()));
-
-		$caseExpression->isTrue('test_string');
-		$this->assertSame('CASE WHEN test = :c0 THEN :c1 ELSE 0 END', $caseExpression->sql(new ValueBinder()));
-
-		$caseExpression->isTrue(['test_string' => 'literal']);
-		$this->assertSame('CASE WHEN test = :c0 THEN test_string ELSE 0 END', $caseExpression->sql(new ValueBinder()));
+		$expr2->eq('test2', 'false');
+		$caseExpression->add($expr2);
+		$expected = 'CASE WHEN test = :c0 THEN :c1 WHEN test2 = :c2 THEN 1 ELSE 0 END';
+		$this->assertSame($expected, $caseExpression->sql(new ValueBinder()));
 	}
 
 /**
@@ -94,8 +56,10 @@ class CaseExpressionTest extends TestCase {
 
 		$expr = new QueryExpression();
 		$expr->eq('test', 'true');
-		$caseExpression = new CaseExpression($expr);
+		$expr2 = new QueryExpression();
+		$expr2->eq('test', 'false');
+		$caseExpression = new CaseExpression([$expr, $expr2]);
 		$caseExpression->traverse($visitor);
-		$this->assertSame(2, $count);
+		$this->assertSame(4, $count);
 	}
 }