Issue #2241655 by tim.plunkett: EntityStorageInterface::create() should always create a "new" entity.

8.0.x
Alex Pott 2014-04-16 21:57:51 +01:00
parent 12b53d008f
commit a62511ffbd
17 changed files with 126 additions and 40 deletions

View File

@ -823,6 +823,9 @@ function menu_link_rebuild_defaults() {
$link['customized'] = $existing_item->customized; $link['customized'] = $existing_item->customized;
$link['updated'] = $existing_item->updated; $link['updated'] = $existing_item->updated;
$menu_link = $menu_link_storage->createFromDefaultLink($link); $menu_link = $menu_link_storage->createFromDefaultLink($link);
// @todo Do not create a new entity in order to update it, see
// https://drupal.org/node/2241865
$menu_link->setOriginalId($existing_item->mlid);
// Convert the existing item to a typed object. // Convert the existing item to a typed object.
/** @var \Drupal\menu_link\MenuLinkInterface $existing_item */ /** @var \Drupal\menu_link\MenuLinkInterface $existing_item */

View File

@ -114,7 +114,7 @@ abstract class ConfigEntityBase extends Entity implements ConfigEntityInterface
public function setOriginalId($id) { public function setOriginalId($id) {
$this->originalId = $id; $this->originalId = $id;
return $this; return parent::setOriginalId($id);
} }
/** /**

View File

@ -925,6 +925,7 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
$duplicate = clone $this; $duplicate = clone $this;
$entity_type = $this->getEntityType(); $entity_type = $this->getEntityType();
$duplicate->{$entity_type->getKey('id')}->value = NULL; $duplicate->{$entity_type->getKey('id')}->value = NULL;
$duplicate->enforceIsNew();
// Check if the entity type supports UUIDs and generate a new one if so. // Check if the entity type supports UUIDs and generate a new one if so.
if ($entity_type->hasKey('uuid')) { if ($entity_type->hasKey('uuid')) {

View File

@ -67,6 +67,7 @@ abstract class ContentEntityStorageBase extends EntityStorageBase implements Fie
$bundle = $values[$this->bundleKey]; $bundle = $values[$this->bundleKey];
} }
$entity = new $entity_class(array(), $this->entityTypeId, $bundle); $entity = new $entity_class(array(), $this->entityTypeId, $bundle);
$entity->enforceIsNew();
foreach ($entity as $name => $field) { foreach ($entity as $name => $field) {
if (isset($values[$name])) { if (isset($values[$name])) {

View File

@ -502,6 +502,12 @@ abstract class Entity extends DependencySerialization implements EntityInterface
*/ */
public function setOriginalId($id) { public function setOriginalId($id) {
// By default, entities do not support renames and do not have original IDs. // By default, entities do not support renames and do not have original IDs.
// If the specified ID is anything except NULL, this should mark this entity
// as no longer new.
if ($id !== NULL) {
$this->enforceIsNew(FALSE);
}
return $this; return $this;
} }

View File

@ -238,6 +238,7 @@ class EntityDatabaseStorage extends EntityStorageBase {
$entity_class::preCreate($this, $values); $entity_class::preCreate($this, $values);
$entity = new $entity_class($values, $this->entityTypeId); $entity = new $entity_class($values, $this->entityTypeId);
$entity->enforceIsNew();
// Assign a new UUID if there is none yet. // Assign a new UUID if there is none yet.
if ($this->uuidKey && !isset($entity->{$this->uuidKey})) { if ($this->uuidKey && !isset($entity->{$this->uuidKey})) {

View File

@ -1281,7 +1281,11 @@ function comment_prepare_author(CommentInterface $comment) {
// The account has been pre-loaded by CommentViewBuilder::buildContent(). // The account has been pre-loaded by CommentViewBuilder::buildContent().
$account = $comment->getOwner(); $account = $comment->getOwner();
if (empty($account->uid->value)) { if (empty($account->uid->value)) {
// @todo Avoid creating a new entity by just creating a new instance
// directly, see https://drupal.org/node/1867228.
$account = entity_create('user', array('uid' => 0, 'name' => $comment->getAuthorName(), 'homepage' => $comment->getHomepage())); $account = entity_create('user', array('uid' => 0, 'name' => $comment->getAuthorName(), 'homepage' => $comment->getHomepage()));
// The anonymous user is not a new account, do not treat it as one.
$account->enforceIsNew(FALSE);
} }
return $account; return $account;
} }

View File

@ -226,10 +226,6 @@ class FieldAttachOtherTest extends FieldUnitTestBase {
// Update with different values, and check that the cache entry is wiped. // Update with different values, and check that the cache entry is wiped.
$values = $this->_generateTestFieldValues($this->field_2->getCardinality()); $values = $this->_generateTestFieldValues($this->field_2->getCardinality());
$entity = entity_create($entity_type, array(
'type' => $entity_type,
'id' => $entity->id(),
));
$entity->{$this->field_name_2} = $values; $entity->{$this->field_name_2} = $values;
$entity->save(); $entity->save();
$this->assertFalse(\Drupal::cache('entity')->get($cid), 'Cached: no cache entry on update'); $this->assertFalse(\Drupal::cache('entity')->get($cid), 'Cached: no cache entry on update');
@ -241,10 +237,6 @@ class FieldAttachOtherTest extends FieldUnitTestBase {
$this->assertEqual($cache->data[$langcode][$this->field_name_2], $values, 'Cached: correct cache entry on load'); $this->assertEqual($cache->data[$langcode][$this->field_name_2], $values, 'Cached: correct cache entry on load');
// Create a new revision, and check that the cache entry is wiped. // Create a new revision, and check that the cache entry is wiped.
$entity = entity_create($entity_type, array(
'type' => $entity_type,
'id' => $entity->id(),
));
$values = $this->_generateTestFieldValues($this->field_2->getCardinality()); $values = $this->_generateTestFieldValues($this->field_2->getCardinality());
$entity->{$this->field_name_2} = $values; $entity->{$this->field_name_2} = $values;
$entity->setNewRevision(); $entity->setNewRevision();

View File

@ -190,6 +190,9 @@ class FieldAttachStorageTest extends FieldUnitTestBase {
$entity = $this->entitySaveReload($entity); $entity = $this->entitySaveReload($entity);
$this->assertTrue($entity->{$this->field_name}->isEmpty(), 'Insert: NULL field results in no value saved'); $this->assertTrue($entity->{$this->field_name}->isEmpty(), 'Insert: NULL field results in no value saved');
// All saves after this point should be updates, not inserts.
$entity_init->enforceIsNew(FALSE);
// Add some real data. // Add some real data.
$entity = clone($entity_init); $entity = clone($entity_init);
$values = $this->_generateTestFieldValues(1); $values = $this->_generateTestFieldValues(1);

View File

@ -173,11 +173,14 @@ function file_copy(File $source, $destination = NULL, $replace = FILE_EXISTS_REN
$file->setFileUri($uri); $file->setFileUri($uri);
$file->setFilename(drupal_basename($uri)); $file->setFilename(drupal_basename($uri));
// If we are replacing an existing file re-use its database record. // If we are replacing an existing file re-use its database record.
// @todo Do not create a new entity in order to update it, see
// https://drupal.org/node/2241865
if ($replace == FILE_EXISTS_REPLACE) { if ($replace == FILE_EXISTS_REPLACE) {
$existing_files = entity_load_multiple_by_properties('file', array('uri' => $uri)); $existing_files = entity_load_multiple_by_properties('file', array('uri' => $uri));
if (count($existing_files)) { if (count($existing_files)) {
$existing = reset($existing_files); $existing = reset($existing_files);
$file->fid = $existing->id(); $file->fid = $existing->id();
$file->setOriginalId($existing->id());
$file->setFilename($existing->getFilename()); $file->setFilename($existing->getFilename());
} }
} }
@ -254,6 +257,7 @@ function file_move(File $source, $destination = NULL, $replace = FILE_EXISTS_REN
$existing = reset($existing_files); $existing = reset($existing_files);
$delete_source = TRUE; $delete_source = TRUE;
$file->fid = $existing->id(); $file->fid = $existing->id();
$file->uuid = $existing->uuid();
} }
} }
// If we are renaming around an existing file (rather than a directory), // If we are renaming around an existing file (rather than a directory),
@ -520,11 +524,14 @@ function file_save_data($data, $destination = NULL, $replace = FILE_EXISTS_RENAM
'status' => FILE_STATUS_PERMANENT, 'status' => FILE_STATUS_PERMANENT,
)); ));
// If we are replacing an existing file re-use its database record. // If we are replacing an existing file re-use its database record.
// @todo Do not create a new entity in order to update it, see
// https://drupal.org/node/2241865
if ($replace == FILE_EXISTS_REPLACE) { if ($replace == FILE_EXISTS_REPLACE) {
$existing_files = entity_load_multiple_by_properties('file', array('uri' => $uri)); $existing_files = entity_load_multiple_by_properties('file', array('uri' => $uri));
if (count($existing_files)) { if (count($existing_files)) {
$existing = reset($existing_files); $existing = reset($existing_files);
$file->fid = $existing->id(); $file->fid = $existing->id();
$file->setOriginalId($existing->id());
$file->setFilename($existing->getFilename()); $file->setFilename($existing->getFilename());
} }
} }
@ -946,11 +953,14 @@ function file_save_upload($form_field_name, $validators = array(), $destination
drupal_chmod($file->getFileUri()); drupal_chmod($file->getFileUri());
// If we are replacing an existing file re-use its database record. // If we are replacing an existing file re-use its database record.
// @todo Do not create a new entity in order to update it, see
// https://drupal.org/node/2241865
if ($replace == FILE_EXISTS_REPLACE) { if ($replace == FILE_EXISTS_REPLACE) {
$existing_files = entity_load_multiple_by_properties('file', array('uri' => $file->getFileUri())); $existing_files = entity_load_multiple_by_properties('file', array('uri' => $file->getFileUri()));
if (count($existing_files)) { if (count($existing_files)) {
$existing = reset($existing_files); $existing = reset($existing_files);
$file->fid = $existing->id(); $file->fid = $existing->id();
$file->setOriginalId($existing->id());
} }
} }

View File

@ -150,22 +150,19 @@ abstract class FileManagedTestBase extends WebTestBase {
* File entity. * File entity.
*/ */
function createFile($filepath = NULL, $contents = NULL, $scheme = NULL) { function createFile($filepath = NULL, $contents = NULL, $scheme = NULL) {
$file = new \stdClass(); // Don't count hook invocations caused by creating the file.
$file->uri = $this->createUri($filepath, $contents, $scheme); \Drupal::state()->set('file_test.count_hook_invocations', FALSE);
$file->filename = drupal_basename($file->uri); $file = entity_create('file', array(
$file->filemime = 'text/plain'; 'uri' => $this->createUri($filepath, $contents, $scheme),
$file->uid = 1; 'uid' => 1,
$file->created = REQUEST_TIME; ));
$file->changed = REQUEST_TIME; $file->save();
$file->filesize = filesize($file->uri);
$file->status = 0;
// Write the record directly rather than using the API so we don't invoke // Write the record directly rather than using the API so we don't invoke
// the hooks. // the hooks.
$file = (array) $file; $this->assertTrue($file->id() > 0, 'The file was added to the database.', 'Create test file');
$file['fid'] = db_insert('file_managed')
->fields($file) \Drupal::state()->set('file_test.count_hook_invocations', TRUE);
->execute(); return $file;
return entity_create('file', $file);
} }
/** /**

View File

@ -161,20 +161,19 @@ abstract class FileManagedUnitTestBase extends DrupalUnitTestBase {
* File entity. * File entity.
*/ */
function createFile($filepath = NULL, $contents = NULL, $scheme = NULL) { function createFile($filepath = NULL, $contents = NULL, $scheme = NULL) {
$file = new \stdClass(); // Don't count hook invocations caused by creating the file.
$file->uri = $this->createUri($filepath, $contents, $scheme); \Drupal::state()->set('file_test.count_hook_invocations', FALSE);
$file->filename = drupal_basename($file->uri); $file = entity_create('file', array(
$file->filemime = 'text/plain'; 'uri' => $this->createUri($filepath, $contents, $scheme),
$file->uid = 1; 'uid' => 1,
$file->created = REQUEST_TIME; ));
$file->changed = REQUEST_TIME; $file->save();
$file->filesize = filesize($file->uri);
$file->status = 0;
// Write the record directly rather than using the API so we don't invoke // Write the record directly rather than using the API so we don't invoke
// the hooks. // the hooks.
$this->assertNotIdentical(drupal_write_record('file_managed', $file), FALSE, 'The file was added to the database.', 'Create test file'); $this->assertTrue($file->id() > 0, 'The file was added to the database.', 'Create test file');
return entity_create('file', (array) $file); \Drupal::state()->set('file_test.count_hook_invocations', TRUE);
return $file;
} }
/** /**

View File

@ -108,9 +108,11 @@ function file_test_get_all_calls() {
* @see file_test_reset() * @see file_test_reset()
*/ */
function _file_test_log_call($op, $args) { function _file_test_log_call($op, $args) {
if (\Drupal::state()->get('file_test.count_hook_invocations', TRUE)) {
$results = \Drupal::state()->get('file_test.results') ?: array(); $results = \Drupal::state()->get('file_test.results') ?: array();
$results[$op][] = $args; $results[$op][] = $args;
\Drupal::state()->set('file_test.results', $results); \Drupal::state()->set('file_test.results', $results);
}
} }
/** /**

View File

@ -369,7 +369,10 @@ class MenuLink extends Entity implements \ArrayAccess, MenuLinkInterface {
$original['machine_name'] = $this->machine_name; $original['machine_name'] = $this->machine_name;
/** @var \Drupal\menu_link\MenuLinkStorageInterface $storage */ /** @var \Drupal\menu_link\MenuLinkStorageInterface $storage */
$storage = \Drupal::entityManager()->getStorage($this->entityTypeId); $storage = \Drupal::entityManager()->getStorage($this->entityTypeId);
// @todo Do not create a new entity in order to update it, see
// https://drupal.org/node/2241865
$new_link = $storage->createFromDefaultLink($original); $new_link = $storage->createFromDefaultLink($original);
$new_link->setOriginalId($this->id());
// Allow the menu to be determined by the parent // Allow the menu to be determined by the parent
if (!empty($new_link['parent']) && !empty($all_links[$new_link['parent']])) { if (!empty($new_link['parent']) && !empty($all_links[$new_link['parent']])) {
// Walk up the tree to find the menu name. // Walk up the tree to find the menu name.

View File

@ -564,7 +564,14 @@ function menu_ui_form_node_form_alter(&$form, $form_state) {
*/ */
function menu_ui_node_submit(EntityInterface $node, $form, $form_state) { function menu_ui_node_submit(EntityInterface $node, $form, $form_state) {
if (!empty($form_state['values']['menu'])) { if (!empty($form_state['values']['menu'])) {
$original_menu_id = !empty($node->menu) ? $node->menu->id() : NULL;
$node->menu = entity_create('menu_link', $form_state['values']['menu']); $node->menu = entity_create('menu_link', $form_state['values']['menu']);
// @todo Do not create a new entity in order to update it, see
// https://drupal.org/node/2241865
// If this menu had a previous menu link associated, mark it as not new.
if ($original_menu_id) {
$node->menu->setOriginalId($original_menu_id);
}
// Decompose the selected menu parent option into 'menu_name' and 'plid', if // Decompose the selected menu parent option into 'menu_name' and 'plid', if
// the form used the default parent selection widget. // the form used the default parent selection widget.
if (!empty($form_state['values']['menu']['parent'])) { if (!empty($form_state['values']['menu']['parent'])) {

View File

@ -515,8 +515,8 @@ class ConfigEntityStorageTest extends UnitTestCase {
->will($this->returnValue(array('baz'))); ->will($this->returnValue(array('baz')));
$entity = $this->getMockEntity(array('id' => 'foo')); $entity = $this->getMockEntity(array('id' => 'foo'));
$entity->enforceIsNew();
$entity->setOriginalId('baz'); $entity->setOriginalId('baz');
$entity->enforceIsNew();
$this->entityStorage->save($entity); $this->entityStorage->save($entity);
} }

View File

@ -15,7 +15,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
/** /**
* Tests the fieldable database storage. * Tests the fieldable database storage.
* *
* @see \Drupal\Core\Entity\ContentEntityDatabaseStorage * @coversDefaultClass \Drupal\Core\Entity\ContentEntityDatabaseStorage
* *
* @group Drupal * @group Drupal
* @group Entity * @group Entity
@ -36,7 +36,7 @@ class ContentEntityDatabaseStorageTest extends UnitTestCase {
/** /**
* Tests field SQL schema generation for an entity with a string identifier. * Tests field SQL schema generation for an entity with a string identifier.
* *
* @see \Drupal\Core\Entity\Controller\ContentEntityDatabaseStorage::_fieldSqlSchema() * @covers ::_fieldSqlSchema()
*/ */
public function testFieldSqlSchemaForEntityWithStringIdentifier() { public function testFieldSqlSchemaForEntityWithStringIdentifier() {
$field_type_manager = $this->getMock('Drupal\Core\Field\FieldTypePluginManagerInterface'); $field_type_manager = $this->getMock('Drupal\Core\Field\FieldTypePluginManagerInterface');
@ -112,4 +112,61 @@ class ContentEntityDatabaseStorageTest extends UnitTestCase {
$this->assertEquals($schema['test_entity__test_field']['fields']['revision_id']['type'], 'varchar'); $this->assertEquals($schema['test_entity__test_field']['fields']['revision_id']['type'], 'varchar');
} }
/**
* @covers ::create()
*/
public function testCreate() {
$language_manager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface');
$module_handler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
$entity_manager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
// @todo Add field definitions to test default values of fields.
$entity_manager->expects($this->atLeastOnce())
->method('getFieldDefinitions')
->will($this->returnValue(array()));
$container = new ContainerBuilder();
$container->set('language_manager', $language_manager);
$container->set('entity.manager', $entity_manager);
$container->set('module_handler', $module_handler);
\Drupal::setContainer($container);
$entity = $this->getMockForAbstractClass('Drupal\Core\Entity\ContentEntityBase', array(), '', FALSE, TRUE, TRUE, array('id'));
$entity_type = $this->getMock('Drupal\Core\Entity\EntityTypeInterface');
$entity_type->expects($this->atLeastOnce())
->method('id')
->will($this->returnValue('test_entity_type'));
$entity_type->expects($this->atLeastOnce())
->method('getClass')
->will($this->returnValue(get_class($entity)));
$entity_type->expects($this->atLeastOnce())
->method('getKeys')
->will($this->returnValue(array('id' => 'id')));
$entity_type->expects($this->atLeastOnce())
->method('hasKey')
->will($this->returnCallback(function ($key) {
return $key == 'id';
}));
$entity_manager->expects($this->atLeastOnce())
->method('getDefinition')
->with('test_entity_type')
->will($this->returnValue($entity_type));
$connection = $this->getMockBuilder('Drupal\Core\Database\Connection')
->disableOriginalConstructor()
->getMock();
$field_info = $this->getMockBuilder('\Drupal\field\FieldInfo')
->disableOriginalConstructor()
->getMock();
$entity_storage = new ContentEntityDatabaseStorage($entity_type, $connection, $field_info);
$entity = $entity_storage->create();
$entity->expects($this->atLeastOnce())
->method('id')
->will($this->returnValue('foo'));
$this->assertInstanceOf('Drupal\Core\Entity\EntityInterface', $entity);
$this->assertSame('foo', $entity->id());
$this->assertTrue($entity->isNew());
}
} }