Browse Source

Merge pull request #3030 from cakephp/3.0-entity-context-fixes

3.0 entity context fixes
Mark Story 12 years ago
parent
commit
2bf72df853

+ 5 - 1
src/Database/Query.php

@@ -1135,7 +1135,11 @@ class Query implements ExpressionInterface, IteratorAggregate {
 			$limit = 25;
 			$this->limit($limit);
 		}
-		$this->offset(($num - 1) * $limit);
+		$offset = ($num - 1) * $limit;
+		if (PHP_INT_MAX <= $offset) {
+			$offset = PHP_INT_MAX;
+		}
+		$this->offset((int)$offset);
 		return $this;
 	}
 

+ 25 - 1
src/Datasource/EntityTrait.php

@@ -98,6 +98,13 @@ trait EntityTrait {
 	protected $_accessible = ['*' => true];
 
 /**
+ * The alias of the repository this entity came from
+ *
+ * @var string
+ */
+	protected $_repositoryAlias;
+
+/**
  * Magic getter to access properties that has be set in this entity
  *
  * @param string $property name of the property to access
@@ -667,6 +674,22 @@ trait EntityTrait {
 	}
 
 /**
+ * Returns the alias of the repository from wich this entity came from.
+ *
+ * If called with no arguments, it returns the alias of the repository
+ * this entity came from if it is known.
+ *
+ * @param string the alias of the repository
+ * @return string
+ */
+	public function source($alias = null) {
+		if ($alias === null) {
+			return $this->_repositoryAlias;
+		}
+		$this->_repositoryAlias = $alias;
+	}
+
+/**
  * Returns a string representation of this object in a human readable format.
  *
  * @return string
@@ -688,7 +711,8 @@ trait EntityTrait {
 			'properties' => $this->_properties,
 			'dirty' => $this->_dirty,
 			'virtual' => $this->_virtual,
-			'errors' => $this->_errors
+			'errors' => $this->_errors,
+			'repository' => $this->_repositoryAlias
 		];
 	}
 

+ 2 - 0
src/ORM/Association/BelongsToMany.php

@@ -514,6 +514,7 @@ class BelongsToMany extends Association {
 		$targetPrimaryKey = (array)$target->primaryKey();
 		$sourcePrimaryKey = (array)$source->primaryKey();
 		$jointProperty = $this->_junctionProperty;
+		$junctionAlias = $junction->alias();
 
 		foreach ($targetEntities as $k => $e) {
 			$joint = $e->get($jointProperty);
@@ -535,6 +536,7 @@ class BelongsToMany extends Association {
 
 			$e->set($jointProperty, $joint);
 			$e->dirty($jointProperty, false);
+			$joint->source($junctionAlias);
 		}
 
 		return true;

+ 7 - 1
src/ORM/Entity.php

@@ -43,13 +43,15 @@ class Entity implements EntityInterface {
  * - markClean: whether to mark all properties as clean after setting them
  * - markNew: whether this instance has not yet been persisted
  * - guard: whether to prevent inaccessible properties from being set (default: false)
+ * - source: A string representing the alias of the repository this entity came from
  */
 	public function __construct(array $properties = [], array $options = []) {
 		$options += [
 			'useSetters' => true,
 			'markClean' => false,
 			'markNew' => null,
-			'guard' => false
+			'guard' => false,
+			'source' => null
 		];
 		$this->_className = get_class($this);
 		$this->set($properties, [
@@ -64,6 +66,10 @@ class Entity implements EntityInterface {
 		if ($options['markNew'] !== null) {
 			$this->isNew($options['markNew']);
 		}
+
+		if (!empty($options['source'])) {
+			$this->source($options['source']);
+		}
 	}
 
 }

+ 1 - 0
src/ORM/Marshaller.php

@@ -98,6 +98,7 @@ class Marshaller {
 		$tableName = $this->_table->alias();
 		$entityClass = $this->_table->entityClass();
 		$entity = new $entityClass();
+		$entity->source($this->_table->alias());
 
 		if (isset($data[$tableName])) {
 			$data = $data[$tableName];

+ 6 - 2
src/ORM/ResultSet.php

@@ -127,12 +127,13 @@ class ResultSet implements Countable, Iterator, Serializable, JsonSerializable {
  * @param \Cake\Database\StatementInterface $statement
  */
 	public function __construct($query, $statement) {
+		$repository = $query->repository();
 		$this->_query = $query;
 		$this->_statement = $statement;
 		$this->_defaultTable = $this->_query->repository();
 		$this->_calculateAssociationMap();
 		$this->_hydrate = $this->_query->hydrate();
-		$this->_entityClass = $query->repository()->entityClass();
+		$this->_entityClass = $repository->entityClass();
 		$this->_useBuffering = $query->bufferResults();
 
 		if ($statement) {
@@ -381,7 +382,9 @@ class ResultSet implements Countable, Iterator, Serializable, JsonSerializable {
 				continue;
 			}
 			$instance = $assoc['instance'];
-			$results[$alias] = $this->_castValues($instance->target(), $results[$alias]);
+			$target = $instance->target();
+			$results[$alias] = $this->_castValues($target, $results[$alias]);
+			$options['source'] = $target->alias();
 
 			if ($this->_hydrate && $assoc['canBeJoined']) {
 				$entity = new $assoc['entityClass']($results[$alias], $options);
@@ -391,6 +394,7 @@ class ResultSet implements Countable, Iterator, Serializable, JsonSerializable {
 			$results = $instance->transformRow($results);
 		}
 
+		$options['source'] = $defaultAlias;
 		$results = $results[$defaultAlias];
 		if ($this->_hydrate && !($results instanceof Entity)) {
 			$results = new $this->_entityClass($results, $options);

+ 1 - 0
src/ORM/Table.php

@@ -1170,6 +1170,7 @@ class Table implements RepositoryInterface, EventListener {
 				$event = new Event('Model.afterSave', $this, compact('entity', 'options'));
 				$this->getEventManager()->dispatch($event);
 				$entity->isNew(false);
+				$entity->source($this->alias());
 				$success = true;
 			}
 		}

+ 6 - 1
src/View/Form/EntityContext.php

@@ -120,7 +120,12 @@ class EntityContext implements ContextInterface {
 			if (is_array($entity) || $entity instanceof Traversable) {
 				$entity = (new Collection($entity))->first();
 			}
-			if ($entity instanceof Entity) {
+			$isEntity = $entity instanceof Entity;
+
+			if ($isEntity) {
+				$table = $entity->source();
+			}
+			if (!$table && $isEntity && get_class($entity) !== 'Cake\ORM\Entity') {
 				list($ns, $entityClass) = namespaceSplit(get_class($entity));
 				$table = Inflector::pluralize($entityClass);
 			}

+ 3 - 3
tests/Fixture/CommentFixture.php

@@ -33,10 +33,10 @@ class CommentFixture extends TestFixture {
 		'id' => ['type' => 'integer'],
 		'article_id' => ['type' => 'integer', 'null' => false],
 		'user_id' => ['type' => 'integer', 'null' => false],
-		'comment' => 'text',
+		'comment' => ['type' => 'text'],
 		'published' => ['type' => 'string', 'length' => 1, 'default' => 'N'],
-		'created' => 'datetime',
-		'updated' => 'datetime',
+		'created' => ['type' => 'datetime'],
+		'updated' => ['type' => 'datetime'],
 		'_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]]
 	);
 

+ 14 - 10
tests/TestCase/ORM/Association/BelongsToManyTest.php

@@ -1010,19 +1010,23 @@ class BelongsToManyTest extends TestCase {
 
 		$joint->expects($this->at(0))
 			->method('save')
-			->with(
-				new Entity(['article_id' => 1, 'tag_id' => 2], ['markNew' => true]),
-				$saveOptions
-			)
-			->will($this->returnValue($entity));
+			->will($this->returnCallback(function($e, $opts) use ($entity) {
+				$expected = ['article_id' => 1, 'tag_id' => 2];
+				$this->assertEquals($expected, $e->toArray());
+				$this->assertEquals(['foo' => 'bar'], $opts);
+				$this->assertTrue($e->isNew());
+				return $entity;
+			}));
 
 		$joint->expects($this->at(1))
 			->method('save')
-			->with(
-				new Entity(['article_id' => 1, 'tag_id' => 3], ['markNew' => true]),
-				$saveOptions
-			)
-			->will($this->returnValue($entity));
+			->will($this->returnCallback(function($e, $opts) use ($entity) {
+				$expected = ['article_id' => 1, 'tag_id' => 3];
+				$this->assertEquals($expected, $e->toArray());
+				$this->assertEquals(['foo' => 'bar'], $opts);
+				$this->assertTrue($e->isNew());
+				return $entity;
+			}));
 
 		$this->assertTrue($assoc->link($entity, $tags, $saveOptions));
 		$this->assertSame($entity->test, $tags);

+ 19 - 3
tests/TestCase/ORM/EntityTest.php

@@ -1009,16 +1009,32 @@ class EntityTest extends TestCase {
 		$entity = new Entity(['foo' => 'bar'], ['markClean' => true]);
 		$entity->accessible('name', true);
 		$entity->virtualProperties(['baz']);
+		$entity->dirty('foo', true);
 		$entity->errors('foo', ['An error']);
+		$entity->source('foos');
 		$result = $entity->__debugInfo();
 		$expected = [
-			'new' => true,
-			'accessible' => ['name'],
+			'new' => null,
+			'accessible' => ['name' => true],
 			'properties' => ['foo' => 'bar'],
 			'dirty' => ['foo' => true],
 			'virtual' => ['baz'],
-			'errors' => ['foo' => ['An error']]
+			'errors' => ['foo' => ['An error']],
+			'repository' => 'foos'
 		];
+		$this->assertSame($expected, $result);
+	}
+
+/**
+ * Tests the source method
+ *
+ * @return void
+ */
+	public function testSource() {
+		$entity = new Entity;
+		$this->assertNull($entity->source());
+		$entity->source('foos');
+		$this->assertEquals('foos', $entity->source());
 	}
 
 }

+ 1 - 0
tests/TestCase/ORM/MarshallerTest.php

@@ -110,6 +110,7 @@ class MarshallerTest extends TestCase {
 		$this->assertEquals($data, $result->toArray());
 		$this->assertTrue($result->dirty(), 'Should be a dirty entity.');
 		$this->assertNull($result->isNew(), 'Should be detached');
+		$this->assertEquals('Articles', $result->source());
 	}
 
 /**

+ 7 - 1
tests/TestCase/ORM/ResultSetTest.php

@@ -133,6 +133,8 @@ class ResultSetTest extends TestCase {
 		// Use a loop to test Iterator implementation
 		foreach ($results as $i => $row) {
 			$expected = new \Cake\ORM\Entity($this->fixtureData[$i]);
+			$expected->isNew(false);
+			$expected->source($this->table->alias());
 			$expected->clean();
 			$this->assertEquals($expected, $row, "Row $i does not match");
 		}
@@ -217,7 +219,11 @@ class ResultSetTest extends TestCase {
 	public function testGroupBy() {
 		$query = $this->table->find('all');
 		$results = $query->all()->groupBy('author_id')->toArray();
-		$options = ['markNew' => false, 'markClean' => true];
+		$options = [
+			'markNew' => false,
+			'markClean' => true,
+			'source' => $this->table->alias()
+		];
 		$expected = [
 			1 => [
 				new Entity($this->fixtureData[0], $options),

+ 4 - 5
tests/TestCase/ORM/TableTest.php

@@ -2692,9 +2692,7 @@ class TableTest extends \Cake\TestSuite\TestCase {
 		$article = $table->find('all')->where(['id' => 1])->contain(['tags'])->first();
 		$tags = $article->tags;
 		$this->assertNotEmpty($tags);
-		$tags[] = new \TestApp\Model\Entity\Tag([
-			'name' => 'Something New'
-		]);
+		$tags[] = new \TestApp\Model\Entity\Tag(['name' => 'Something New']);
 		$article->tags = $tags;
 		$this->assertSame($article, $table->save($article));
 		$tags = $article->tags;
@@ -2817,6 +2815,7 @@ class TableTest extends \Cake\TestSuite\TestCase {
 		$table = TableRegistry::get('articles');
 		$table->belongsToMany('tags');
 		$tagsTable = TableRegistry::get('tags');
+		$source = ['source' => 'tags'];
 		$options = ['markNew' => false];
 
 		$article = new \Cake\ORM\Entity([
@@ -2825,10 +2824,10 @@ class TableTest extends \Cake\TestSuite\TestCase {
 
 		$newTag = new \TestApp\Model\Entity\Tag([
 			'name' => 'Foo'
-		]);
+		], $source);
 		$tags[] = new \TestApp\Model\Entity\Tag([
 			'id' => 3
-		], $options);
+		], $options + $source);
 		$tags[] = $newTag;
 
 		$tagsTable->save($newTag);

+ 47 - 20
tests/TestCase/View/Form/EntityContextTest.php

@@ -92,7 +92,7 @@ class EntityContextTest extends TestCase {
  * @return void
  */
 	public function testIsCreateSingle() {
-		$row = new Entity();
+		$row = new Article();
 		$context = new EntityContext($this->request, [
 			'entity' => $row,
 		]);
@@ -132,6 +132,33 @@ class EntityContextTest extends TestCase {
 	}
 
 /**
+ * Tests that passing a plain entity will give an error as it cannot be matched
+ *
+ * @expectedException \RuntimeException
+ * @expectedExceptionMessage Unable to find table class for current entity
+ */
+	public function testDefaultEntityError() {
+		$context = new EntityContext($this->request, [
+			'entity' => new \Cake\ORM\Entity,
+		]);
+	}
+
+/**
+ * Tests that the table can be derived from the entity source if it is present
+ *
+ * @return void
+ */
+	public function testTableFromEntitySource() {
+		$entity = new Entity;
+		$entity->source('Articles');
+		$context = new EntityContext($this->request, [
+			'entity' => $entity,
+		]);
+		$expected = ['id', 'author_id', 'title', 'body', 'published'];
+		$this->assertEquals($expected, $context->fieldNames());
+	}
+
+/**
  * Test operations with no entity.
  *
  * @return void
@@ -200,14 +227,14 @@ class EntityContextTest extends TestCase {
  * @return array
  */
 	public static function collectionProvider() {
-		$one = new Entity([
+		$one = new Article([
 			'title' => 'First post',
 			'body' => 'Stuff',
 			'user' => new Entity(['username' => 'mark'])
 		]);
 		$one->errors('title', 'Required field');
 
-		$two = new Entity([
+		$two = new Article([
 			'title' => 'Second post',
 			'body' => 'Some text',
 			'user' => new Entity(['username' => 'jose'])
@@ -359,7 +386,7 @@ class EntityContextTest extends TestCase {
  * @return void
  */
 	public function testValBasic() {
-		$row = new Entity([
+		$row = new Article([
 			'title' => 'Test entity',
 			'body' => 'Something new'
 		]);
@@ -387,7 +414,7 @@ class EntityContextTest extends TestCase {
 			'title' => 'New title',
 			'notInEntity' => 'yes',
 		];
-		$row = new Entity([
+		$row = new Article([
 			'title' => 'Test entity',
 			'body' => 'Something new'
 		]);
@@ -406,7 +433,7 @@ class EntityContextTest extends TestCase {
  * @return void
  */
 	public function testValAssociated() {
-		$row = new Entity([
+		$row = new Article([
 			'title' => 'Test entity',
 			'user' => new Entity([
 				'username' => 'mark',
@@ -444,14 +471,14 @@ class EntityContextTest extends TestCase {
  * @return void
  */
 	public function testValAssociatedHasMany() {
-		$row = new Entity([
+		$row = new Article([
 			'title' => 'First post',
 			'user' => new Entity([
 				'username' => 'mark',
 				'fname' => 'Mark',
 				'articles' => [
-					new Entity(['title' => 'First post']),
-					new Entity(['title' => 'Second post']),
+					new Article(['title' => 'First post']),
+					new Article(['title' => 'Second post']),
 				]
 			]),
 		]);
@@ -473,7 +500,7 @@ class EntityContextTest extends TestCase {
  * @return void
  */
 	public function testValAssociatedDefaultIds() {
-		$row = new Entity([
+		$row = new Article([
 			'title' => 'First post',
 			'user' => new Entity([
 				'username' => 'mark',
@@ -499,7 +526,7 @@ class EntityContextTest extends TestCase {
  * @return void
  */
 	public function testValAssociatedCustomIds() {
-		$row = new Entity([
+		$row = new Article([
 			'title' => 'First post',
 			'user' => new Entity([
 				'username' => 'mark',
@@ -558,7 +585,7 @@ class EntityContextTest extends TestCase {
 			'rule' => 'numeric',
 		]);
 
-		$row = new Entity([
+		$row = new Article([
 			'title' => 'My title',
 			'comments' => [
 				new Entity(['comment' => 'First comment']),
@@ -585,7 +612,7 @@ class EntityContextTest extends TestCase {
 	public function testIsRequiredAssociatedValidator() {
 		$this->_setupTables();
 
-		$row = new Entity([
+		$row = new Article([
 			'title' => 'My title',
 			'comments' => [
 				new Entity(['comment' => 'First comment']),
@@ -615,7 +642,7 @@ class EntityContextTest extends TestCase {
 	public function testIsRequiredAssociatedBelongsTo() {
 		$this->_setupTables();
 
-		$row = new Entity([
+		$row = new Article([
 			'title' => 'My title',
 			'user' => new Entity(['username' => 'Mark']),
 		]);
@@ -640,7 +667,7 @@ class EntityContextTest extends TestCase {
 	public function testType() {
 		$this->_setupTables();
 
-		$row = new Entity([
+		$row = new Article([
 			'title' => 'My title',
 			'body' => 'Some content',
 		]);
@@ -663,7 +690,7 @@ class EntityContextTest extends TestCase {
 	public function testTypeAssociated() {
 		$this->_setupTables();
 
-		$row = new Entity([
+		$row = new Article([
 			'title' => 'My title',
 			'user' => new Entity(['username' => 'Mark']),
 		]);
@@ -685,7 +712,7 @@ class EntityContextTest extends TestCase {
 	public function testAttributes() {
 		$this->_setupTables();
 
-		$row = new Entity([
+		$row = new Article([
 			'title' => 'My title',
 			'user' => new Entity(['username' => 'Mark']),
 		]);
@@ -718,7 +745,7 @@ class EntityContextTest extends TestCase {
 	public function testHasError() {
 		$this->_setupTables();
 
-		$row = new Entity([
+		$row = new Article([
 			'title' => 'My title',
 			'user' => new Entity(['username' => 'Mark']),
 		]);
@@ -744,7 +771,7 @@ class EntityContextTest extends TestCase {
 	public function testHasErrorAssociated() {
 		$this->_setupTables();
 
-		$row = new Entity([
+		$row = new Article([
 			'title' => 'My title',
 			'user' => new Entity(['username' => 'Mark']),
 		]);
@@ -769,7 +796,7 @@ class EntityContextTest extends TestCase {
 	public function testError() {
 		$this->_setupTables();
 
-		$row = new Entity([
+		$row = new Article([
 			'title' => 'My title',
 			'user' => new Entity(['username' => 'Mark']),
 		]);

+ 2 - 2
tests/TestCase/View/Helper/FormHelperTest.php

@@ -1691,7 +1691,7 @@ class FormHelperTest extends TestCase {
 		$nested = new Entity(['foo' => 'bar']);
 		$nested->errors('foo', ['not a valid bar']);
 		$entity = new Entity(['nested' => $nested]);
-		$this->Form->create($entity);
+		$this->Form->create($entity, ['context' => ['table' => 'Articles']]);
 
 		$result = $this->Form->error('nested.foo');
 		$this->assertEquals('<div class="error-message">not a valid bar</div>', $result);
@@ -1709,7 +1709,7 @@ class FormHelperTest extends TestCase {
 		$nested = new Entity(['foo' => $inner]);
 		$entity = new Entity(['nested' => $nested]);
 		$inner->errors('bar', ['not a valid one']);
-		$this->Form->create($entity);
+		$this->Form->create($entity, ['context' => ['table' => 'Articles']]);
 		$result = $this->Form->error('nested.foo.bar');
 		$this->assertEquals('<div class="error-message">not a valid one</div>', $result);
 	}

+ 1 - 1
tests/test_app/TestApp/Model/Table/ArticlesTable.php

@@ -22,7 +22,7 @@ class ArticlesTable extends Table {
 	public function initialize(array $config) {
 		$this->belongsTo('authors');
 		$this->belongsToMany('tags');
-		$this->hasMany('articlesTags');
+		$this->hasMany('ArticlesTags');
 	}
 
 }

+ 1 - 1
tests/test_app/TestApp/Model/Table/TagsTable.php

@@ -22,7 +22,7 @@ class TagsTable extends Table {
 	public function initialize(array $config) {
 		$this->belongsTo('authors');
 		$this->belongsToMany('articles');
-		$this->hasMany('articlesTags', ['propertyName' => 'extraInfo']);
+		$this->hasMany('ArticlesTags', ['propertyName' => 'extraInfo']);
 	}
 
 }