diff --git entity.module entity.module index 432d469..036fa90 100644 --- entity.module +++ entity.module @@ -71,6 +71,36 @@ function entity_type_supports($entity_type, $op) { } /** + * Wrapper to entity_load() to return the entities keyed by the name key, if it exists. + * + * @see entity_load() + * + * @param $entity_type + * The entity type to load, e.g. node or user. + * @param $ids + * An array of entity IDs, or FALSE to load all entities. + * @param $conditions + * (deprecated) An associative array of conditions on the base table, where + * the keys are the database fields and the values are the values those + * fields must have. Instead, it is preferable to use EntityFieldQuery to + * retrieve a list of entity IDs loadable by this function. + * @param $reset + * Whether to reset the internal cache for the requested entity type. + * + * @return + * An array of entity objects indexed by their names (or ids, if the entity + * type has no name key). + */ +function entity_load_by_name($entity_type, $ids = FALSE, $conditions = array(), $reset = FALSE) { + $entities = entity_load($entity_type, $ids, $conditions, $reset); + $info = entity_get_info($entity_type); + if (!isset($info['entity keys']['name'])) { + return $entities; + } + return entity_key_array_by_property($entities, $info['entity keys']['name']); +} + +/** * Permanently save an entity. * * In case of failures, an exception is thrown. @@ -327,6 +357,26 @@ function entity_access($op, $entity_type, $entity = NULL, $account = NULL) { } /** + * Converts an array of entities to be keyed by the given property. + * + * @param array $entities + * The array of entities to convert. + * + * @return array + * The same entities in the same order, but keyed by their $property values. + * Entities not having the specified property will be excluded. + */ +function entity_key_array_by_property(array $entities, $property) { + $ret = array(); + foreach ($entities as $entity) { + if (isset($entity->$property)) { + $ret[$entity->$property] = $entity; + } + } + return $ret; +} + +/** * Returns an array of entity info for the entity types provided via the entity CRUD API. */ function entity_crud_get_info() { @@ -492,11 +542,11 @@ function _entity_defaults_rebuild($entity_type) { drupal_alter($hook, $entities); // Remove defaults which are already overridden. - $overridden_entities = entity_load($entity_type, array_keys($entities), array($keys['status'] => ENTITY_OVERRIDDEN)); + $overridden_entities = entity_load_by_name($entity_type, array_keys($entities), array($keys['status'] => ENTITY_OVERRIDDEN)); $entities = array_diff_key($entities, $overridden_entities); // Determine already existing, custom entities and mark them as overridden. - $existing_entities = entity_load($entity_type, array_keys($entities), array($keys['status'] => ENTITY_CUSTOM)); + $existing_entities = entity_load_by_name($entity_type, array_keys($entities), array($keys['status'] => ENTITY_CUSTOM)); foreach ($existing_entities as $name => $entity) { $entity->{$keys['status']} |= ENTITY_OVERRIDDEN; $entity->{$keys['module']} = $entities[$name]->{$keys['module']}; diff --git entity.test entity.test index 09a2551..64457fd 100644 --- entity.test +++ entity.test @@ -107,57 +107,62 @@ class EntityAPITestCase extends DrupalWebTestCase { module_enable(array('entity_feature')); $types = entity_load('entity_test_type', array('test', 'test2')); - $this->assertEqual($types['test']->label, 'label', 'Default type loaded.'); - $this->assertTrue($types['test']->status & ENTITY_IN_CODE && !($types['test']->status & ENTITY_CUSTOM), 'Default type status is correct.'); + $type = reset($types); + $id = $type->id; + $this->assertEqual($id, key($types), 'Types loaded keyed by id.'); + $this->assertEqual($type->label, 'label', 'Default type loaded.'); + $this->assertTrue($type->status & ENTITY_IN_CODE && !($type->status & ENTITY_CUSTOM), 'Default type status is correct.'); // Test using a condition, which has to be applied on the defaults. $types = entity_load('entity_test_type', FALSE, array('label' => 'label')); - $this->assertEqual($types['test']->label, 'label', 'Condition to default type applied.'); + $this->assertEqual($types[$id]->label, 'label', 'Condition to default type applied.'); - $types['test']->label = 'modified'; - $types['test']->save(); + $types[$id]->label = 'modified'; + $types[$id]->save(); // Ensure loading the changed entity works. $types = entity_load('entity_test_type', FALSE, array('label' => 'modified')); - $this->assertEqual($types['test']->label, 'modified', 'Modified type loaded.'); + $this->assertEqual($types[$id]->label, 'modified', 'Modified type loaded.'); // Clear the cache to simulate a new page load. entity_get_controller('entity_test_type')->resetCache(); - // Test loading using a condition again, now they default may not appear any + // Test loading using a condition again, now the default may not appear any // more as it's overridden by an entity with another label. $types = entity_load('entity_test_type', FALSE, array('label' => 'label')); $this->assertTrue(empty($types), 'Conditions are applied to the overridden entity only.'); // But the overridden entity has to appear with another condition. $types = entity_load('entity_test_type', FALSE, array('label' => 'modified')); - $this->assertEqual($types['test']->label, 'modified', 'Modified default type loaded by condition.'); + $this->assertEqual($types[$id]->label, 'modified', 'Modified default type loaded by condition.'); $types = entity_load('entity_test_type', array('test', 'test2')); - $this->assertEqual($types['test']->label, 'modified', 'Modified default type loaded by id.'); - $this->assertTrue(entity_has_status('entity_test_type', $types['test'], ENTITY_OVERRIDDEN), 'Status of overridden type is correct.'); + $this->assertEqual($types[$id]->label, 'modified', 'Modified default type loaded by id.'); + $this->assertTrue(entity_has_status('entity_test_type', $types[$id], ENTITY_OVERRIDDEN), 'Status of overridden type is correct.'); // Test rebuilding the defaults and make sure overridden entities stay. entity_defaults_rebuild(); $types = entity_load('entity_test_type', array('test', 'test2')); - $this->assertEqual($types['test']->label, 'modified', 'Overridden entity is still overridden.'); - $this->assertTrue(entity_has_status('entity_test_type', $types['test'], ENTITY_OVERRIDDEN), 'Status of overridden type is correct.'); + $this->assertEqual($types[$id]->label, 'modified', 'Overridden entity is still overridden.'); + $this->assertTrue(entity_has_status('entity_test_type', $types[$id], ENTITY_OVERRIDDEN), 'Status of overridden type is correct.'); // Test reverting. - $types['test']->delete(); - $types = entity_load('entity_test_type', array('test', 'test2')); - $this->assertEqual($types['test']->label, 'label', 'Entity has been reverted.'); + $types[$id]->delete(); + $types = entity_load('entity_test_type', array('test')); + $type = reset($types); + $id = $type->id; + $this->assertEqual($type->label, 'label', 'Entity has been reverted.'); // Test loading an exportable by its numeric id. - $result = entity_load('entity_test_type', array($types['test']->id)); - $this->assertTrue(isset($result['test']), 'Exportable entity loaded by the numeric id.'); + $result = entity_load('entity_test_type', array($id)); + $this->assertTrue(isset($result[$id]), 'Exportable entity loaded by the numeric id.'); // Test exporting an entity to JSON. - $serialized_string = $types['test']->export(); + $serialized_string = $type->export(); $data = drupal_json_decode($serialized_string); $this->assertNotNull($data, 'Exported entity is valid JSON.'); $import = entity_import('entity_test_type', $serialized_string); - $this->assertTrue(get_class($import) == get_class($types['test']) && $types['test']->label == $import->label, 'Successfully exported entity to code.'); + $this->assertTrue(get_class($import) == get_class($types[$id]) && $types[$id]->label == $import->label, 'Successfully exported entity to code.'); $this->assertTrue(!isset($import->status), 'Exportable status has not been exported to code.'); } @@ -188,8 +193,9 @@ class EntityAPITestCase extends DrupalWebTestCase { // Now override the 'test' entity and make sure it invokes the update hook. $result = entity_load('entity_test_type', array('test')); - $result['test']->label = 'modified'; - $result['test']->save(); + $result = reset($result); + $result->label = 'modified'; + $result->save(); $this->assertTrue($_SESSION['entity_hook_test']['entity_update'] == array('test'), 'Hook entity_update has been invoked.'); $this->assertTrue($_SESSION['entity_hook_test']['entity_test_type_update'] == array('test'), 'Hook entity_test_type_update has been invoked.'); @@ -203,8 +209,9 @@ class EntityAPITestCase extends DrupalWebTestCase { // Now make sure 'test' is not overridden any more, but custom. $result = entity_load('entity_test_type', array('test')); - $this->assertTrue(!$result['test']->hasStatus(ENTITY_OVERRIDDEN), 'Entity is not marked as overridden any more.'); - $this->assertTrue(entity_has_status('entity_test_type', $result['test'], ENTITY_CUSTOM), 'Entity is marked as custom.'); + $result = reset($result); + $this->assertTrue(!$result->hasStatus(ENTITY_OVERRIDDEN), 'Entity is not marked as overridden any more.'); + $this->assertTrue(entity_has_status('entity_test_type', $result, ENTITY_CUSTOM), 'Entity is marked as custom.'); // Test deleting the remaining entities from DB. entity_delete_multiple('entity_test_type', array('test', 'test3')); @@ -219,7 +226,7 @@ class EntityAPITestCase extends DrupalWebTestCase { */ function testChanges() { module_enable(array('entity_feature')); - $types = entity_load('entity_test_type'); + $types = entity_load_by_name('entity_test_type'); // Override the default entity, such it gets saved in the DB. $types['test']->label ='test_changes'; @@ -239,7 +246,7 @@ class EntityAPITestCase extends DrupalWebTestCase { $this->assertEqual($types['test']->label, 'updated_presave_update', 'Changes have been determined.'); // Test the static load cache to be cleared. - $types = entity_load('entity_test_type'); + $types = entity_load_by_name('entity_test_type'); $this->assertEqual($types['test']->label, 'updated_presave', 'Static cache has been cleared.'); } diff --git includes/entity.controller.inc includes/entity.controller.inc index 83fe55e..d44f447 100644 --- includes/entity.controller.inc +++ includes/entity.controller.inc @@ -217,7 +217,7 @@ class EntityAPIController extends DrupalDefaultEntityController implements Entit $queried_entities = array(); foreach ($this->query($ids, $conditions, $revision_id) as $record) { // Skip entities already retrieved from cache. - if (isset($entities[$record->{$this->nameKey}])) { + if (isset($entities[$record->{$this->idKey}])) { continue; } @@ -237,7 +237,7 @@ class EntityAPIController extends DrupalDefaultEntityController implements Entit } } - $queried_entities[$record->{$this->nameKey}] = $record; + $queried_entities[$record->{$this->idKey}] = $record; } } @@ -286,9 +286,6 @@ class EntityAPIController extends DrupalDefaultEntityController implements Entit /** * Overridden. * @see includes/DrupalDefaultEntityController#cacheGet($ids, $conditions) - * - * If there is nameKey given, we index our entities by this key. This - * overrides cacheGet() to respect that when applying $conditions. */ protected function cacheGet($ids, $conditions = array()) { if (!empty($this->entityCache)) { @@ -303,14 +300,12 @@ class EntityAPIController extends DrupalDefaultEntityController implements Entit * Overridden. * @see DrupalDefaultEntityController::attachLoad() * - * Fixed to make attaching fields to entities having a name key work. + * Fixed to call type-specific hook with the entities keyed by name, if they + * have one. */ - protected function attachLoad(&$queried_entities, $revision_id = FALSE) { + protected function attachLoad(&$entities, $revision_id = FALSE) { // Attach fields. if ($this->entityInfo['fieldable']) { - // Field API assumes queried entities are keyed by the idkey, thus - // adapt the array accordingly for it. - $entities = $this->getEntitiesKeyedByNumericID($queried_entities); if ($revision_id) { field_attach_load_revision($this->entityType, $entities); } @@ -322,31 +317,25 @@ class EntityAPIController extends DrupalDefaultEntityController implements Entit // Call hook_entity_load(). foreach (module_implements('entity_load') as $module) { $function = $module . '_entity_load'; - $function($queried_entities, $this->entityType); + $function($entities, $this->entityType); } // Call hook_TYPE_load(). The first argument for hook_TYPE_load() are // always the queried entities, followed by additional arguments set in // $this->hookLoadArguments. - $args = array_merge(array($queried_entities), $this->hookLoadArguments); + // For entities with a name key, pass the entities keyed by name to the + // specific load hook. + if ($this->nameKey != $this->idKey) { + $entities_by_name = entity_key_array_by_property($entities, $this->nameKey); + } + else { + $entities_by_name = $entities; + } + $args = array_merge(array($entities_by_name), $this->hookLoadArguments); foreach (module_implements($this->entityInfo['load hook']) as $module) { call_user_func_array($module . '_' . $this->entityInfo['load hook'], $args); } } - /** - * Converts an array of entities in an array of entities keyed by numeric id, i.e. by id key. - */ - protected function getEntitiesKeyedByNumericID($entities) { - if ($this->nameKey != $this->idKey) { - $result = array(); - foreach ($entities as $entity) { - $result[$entity->{$this->idKey}] = $entity; - } - return $result; - } - return $entities; - } - public function resetCache(array $ids = NULL) { $this->cacheComplete = FALSE; parent::resetCache($ids); @@ -405,7 +394,7 @@ class EntityAPIController extends DrupalDefaultEntityController implements Entit try { db_delete($this->entityInfo['base table']) - ->condition($this->nameKey, array_keys($entities), 'IN') + ->condition($this->idKey, array_keys($entities), 'IN') ->execute(); // Reset the cache as soon as the changes have been applied. $this->resetCache($ids); @@ -458,7 +447,7 @@ class EntityAPIController extends DrupalDefaultEntityController implements Entit if (!empty($entity->{$this->idKey}) && empty($entity->is_new)) { $return = drupal_write_record($this->entityInfo['base table'], $entity, $this->idKey); - $this->resetCache(array($entity->{$this->nameKey})); + $this->resetCache(array($entity->{$this->idKey})); $this->invoke('update', $entity); } else { @@ -553,16 +542,21 @@ class EntityAPIController extends DrupalDefaultEntityController implements Entit * Implements EntityAPIControllerInterface. */ public function view($entities, $view_mode = 'full', $langcode = NULL) { + // For Field API and entity_prepare_view, the entities have to be keyed by + // (numeric) id. + $entities = entity_key_array_by_property($entities, $this->idKey); if (!empty($this->entityInfo['fieldable'])) { // Field API assumes queried entities are keyed by the idkey, thus // adapt the array accordingly for it. - field_attach_prepare_view($this->entityType, $this->getEntitiesKeyedByNumericID($entities), $view_mode); + // @todo This assumes $entities is keyed by name, not id. Does this still + // apply? + field_attach_prepare_view($this->entityType, $entities, $view_mode); } entity_prepare_view($this->entityType, $entities); $langcode = isset($langcode) ? $langcode : $GLOBALS['language_content']->language; $view = array(); - foreach ($entities as $key => $entity) { + foreach ($entities as $entity) { $build = entity_build_content($this->entityType, $entity, $view_mode, $langcode); $build += array( // If the entity type provides an implementation, use this instead the @@ -575,7 +569,7 @@ class EntityAPIController extends DrupalDefaultEntityController implements Entit ); // Allow modules to modify the structured entity. drupal_alter(array($this->entityType . '_view', 'entity_view'), $build, $this->entityType); - $view[$this->entityType][$key] = $build; + $view[$this->entityType][$entity->{$this->nameKey}] = $build; } return $view; } diff --git tests/entity_test.module tests/entity_test.module index c1cf2d5..8827dea 100644 --- tests/entity_test.module +++ tests/entity_test.module @@ -68,7 +68,7 @@ function entity_test_entity_info_alter(&$entity_info) { function entity_test_get_types($name = NULL) { $types = entity_load('entity_test_type', isset($name) ? array($name) : FALSE); if (isset($name)) { - return isset($types[$name]) ? $types[$name] : FALSE; + return $types ? reset($types) : FALSE; } return $types; }