Browse Source

Fix EntityContext operations on collections.

There were a few cases where EntityContext would hit a fatal error when
dealing with collections/arrays. This fixes that and also introduces
a minor optimization for array properties.
mark_story 12 years ago
parent
commit
f031b3c9f9
2 changed files with 53 additions and 11 deletions
  1. 41 10
      src/View/Form/EntityContext.php
  2. 12 1
      tests/TestCase/View/Form/EntityContextTest.php

+ 41 - 10
src/View/Form/EntityContext.php

@@ -67,6 +67,14 @@ class EntityContext implements ContextInterface {
 	protected $_rootName;
 
 /**
+ * Boolean to track whether or not the entity is a
+ * collection.
+ *
+ * @var boolean
+ */
+	protected $_isCollection = false;
+
+/**
  * A dictionary of tables
  *
  * @var array
@@ -107,8 +115,8 @@ class EntityContext implements ContextInterface {
  */
 	protected function _prepare() {
 		$table = $this->_context['table'];
+		$entity = $this->_context['entity'];
 		if (empty($table)) {
-			$entity = $this->_context['entity'];
 			if (is_array($entity) || $entity instanceof Traversable) {
 				$entity = (new Collection($entity))->first();
 			}
@@ -126,6 +134,10 @@ class EntityContext implements ContextInterface {
 				'Unable to find table class for current entity'
 			);
 		}
+		$this->_isCollection = (
+			is_array($entity) ||
+			$entity instanceof Traversable
+		);
 		$alias = $this->_rootName = $table->alias();
 		$this->_tables[$alias] = $table;
 	}
@@ -178,8 +190,11 @@ class EntityContext implements ContextInterface {
 			return null;
 		}
 		$parts = explode('.', $field);
-		list($entity, $prop) = $this->_getEntity($parts);
-		return $entity->get(array_pop($parts));
+		list($entity) = $this->_getEntity($parts);
+		if ($entity instanceof Entity) {
+			return $entity->get(array_pop($parts));
+		}
+		return null;
 	}
 
 /**
@@ -194,8 +209,12 @@ class EntityContext implements ContextInterface {
  * @throws \RuntimeException When properties cannot be read.
  */
 	protected function _getEntity($path) {
+		$oneElement = count($path) === 1;
+		if ($oneElement && $this->_isCollection) {
+			return [false, false];
+		}
 		$entity = $this->_context['entity'];
-		if (count($path) === 1) {
+		if ($oneElement) {
 			return [$entity, $this->_rootName];
 		}
 
@@ -228,14 +247,20 @@ class EntityContext implements ContextInterface {
  * @return mixed
  */
 	protected function _getProp($target, $field) {
-		if (is_array($target) || $target instanceof Traversable) {
+		if (is_array($target) && isset($target[$field])) {
+			return $target[$field];
+		}
+		if ($target instanceof Entity) {
+			return $target->get($field);
+		}
+		if ($target instanceof Traversable) {
 			foreach ($target as $i => $val) {
 				if ($i == $field) {
 					return $val;
 				}
 			}
+			return false;
 		}
-		return $target->get($field);
 	}
 
 /**
@@ -251,12 +276,17 @@ class EntityContext implements ContextInterface {
 		$parts = explode('.', $field);
 		list($entity, $prop) = $this->_getEntity($parts);
 
+		$isNew = true;
+		if ($entity instanceof Entity) {
+			$isNew = $entity->isNew();
+		}
+
 		$validator = $this->_getValidator($prop);
 		$field = array_pop($parts);
 		if (!$validator->hasField($field)) {
 			return false;
 		}
-		$allowed = $validator->isEmptyAllowed($field, $entity->isNew());
+		$allowed = $validator->isEmptyAllowed($field, $isNew);
 		return $allowed === false;
 	}
 
@@ -353,10 +383,11 @@ class EntityContext implements ContextInterface {
 	public function error($field) {
 		$parts = explode('.', $field);
 		list($entity, $prop) = $this->_getEntity($parts);
-		if (!$entity) {
-			return [];
+
+		if ($entity instanceof Entity) {
+			return $entity->errors(array_pop($parts));
 		}
-		return $entity->errors(array_pop($parts));
+		return [];
 	}
 
 }

+ 12 - 1
tests/TestCase/View/Form/EntityContextTest.php

@@ -225,6 +225,9 @@ class EntityContextTest extends TestCase {
 
 		$result = $context->val('1.user.username');
 		$this->assertEquals('jose', $result);
+
+		$this->assertNull($context->val('nope'));
+		$this->assertNull($context->val('99.title'));
 	}
 
 /**
@@ -246,6 +249,9 @@ class EntityContextTest extends TestCase {
 		$this->assertFalse($context->hasError('1.title'));
 		$this->assertEquals(['Not long enough'], $context->error('1.body'));
 		$this->assertTrue($context->hasError('1.body'));
+
+		$this->assertFalse($context->hasError('nope'));
+		$this->assertFalse($context->hasError('99.title'));
 	}
 
 /**
@@ -265,6 +271,7 @@ class EntityContextTest extends TestCase {
 		$this->assertEquals('text', $context->type('1.body'));
 		$this->assertEquals('string', $context->type('0.user.username'));
 		$this->assertEquals('string', $context->type('1.user.username'));
+		$this->assertEquals('string', $context->type('99.title'));
 		$this->assertNull($context->type('0.nope'));
 
 		$expected = ['length' => 255, 'precision' => null];
@@ -288,10 +295,14 @@ class EntityContextTest extends TestCase {
 				'Users' => 'custom',
 			]
 		]);
+		$this->assertFalse($context->isRequired('nope'));
 
 		$this->assertTrue($context->isRequired('0.title'));
-		$this->assertFalse($context->isRequired('1.body'));
 		$this->assertTrue($context->isRequired('0.user.username'));
+		$this->assertFalse($context->isRequired('1.body'));
+
+		$this->assertTrue($context->isRequired('99.title'));
+		$this->assertFalse($context->isRequired('99.nope'));
 	}
 
 /**