diff --git entity.module entity.module index 432d469..1f3ad94 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 $names + * An array of entity names or 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, $names = FALSE, $conditions = array(), $reset = FALSE) { + $entities = entity_load($entity_type, $names, $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..f5d9d9d 100644 --- entity.test +++ entity.test @@ -106,19 +106,19 @@ class EntityAPITestCase extends DrupalWebTestCase { function testExportables() { module_enable(array('entity_feature')); - $types = entity_load('entity_test_type', array('test', 'test2')); + $types = entity_load_by_name('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.'); // Test using a condition, which has to be applied on the defaults. - $types = entity_load('entity_test_type', FALSE, array('label' => 'label')); + $types = entity_load_by_name('entity_test_type', FALSE, array('label' => 'label')); $this->assertEqual($types['test']->label, 'label', 'Condition to default type applied.'); $types['test']->label = 'modified'; $types['test']->save(); // Ensure loading the changed entity works. - $types = entity_load('entity_test_type', FALSE, array('label' => 'modified')); + $types = entity_load_by_name('entity_test_type', FALSE, array('label' => 'modified')); $this->assertEqual($types['test']->label, 'modified', 'Modified type loaded.'); // Clear the cache to simulate a new page load. @@ -126,30 +126,30 @@ class EntityAPITestCase extends DrupalWebTestCase { // Test loading using a condition again, now they 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')); + $types = entity_load_by_name('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')); + $types = entity_load_by_name('entity_test_type', FALSE, array('label' => 'modified')); $this->assertEqual($types['test']->label, 'modified', 'Modified default type loaded by condition.'); - $types = entity_load('entity_test_type', array('test', 'test2')); + $types = entity_load_by_name('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.'); // Test rebuilding the defaults and make sure overridden entities stay. entity_defaults_rebuild(); - $types = entity_load('entity_test_type', array('test', 'test2')); + $types = entity_load_by_name('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.'); // Test reverting. $types['test']->delete(); - $types = entity_load('entity_test_type', array('test', 'test2')); + $types = entity_load_by_name('entity_test_type', array('test', 'test2')); $this->assertEqual($types['test']->label, 'label', 'Entity has been reverted.'); // Test loading an exportable by its numeric id. - $result = entity_load('entity_test_type', array($types['test']->id)); + $result = entity_load_by_name('entity_test_type', array($types['test']->id)); $this->assertTrue(isset($result['test']), 'Exportable entity loaded by the numeric id.'); // Test exporting an entity to JSON. @@ -187,7 +187,7 @@ class EntityAPITestCase extends DrupalWebTestCase { $this->assertTrue($_SESSION['entity_hook_test']['entity_test_type_insert'] == $insert, 'Hook entity_test_type_insert has been invoked.'); // Now override the 'test' entity and make sure it invokes the update hook. - $result = entity_load('entity_test_type', array('test')); + $result = entity_load_by_name('entity_test_type', array('test')); $result['test']->label = 'modified'; $result['test']->save(); @@ -202,7 +202,7 @@ class EntityAPITestCase extends DrupalWebTestCase { $this->assertTrue($_SESSION['entity_hook_test']['entity_test_type_delete'] == $delete, 'Hook entity_test_type_deleted has been invoked.'); // Now make sure 'test' is not overridden any more, but custom. - $result = entity_load('entity_test_type', array('test')); + $result = entity_load_by_name('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.'); @@ -219,7 +219,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 +239,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.'); } @@ -933,7 +933,7 @@ class EntityMetadataIntegrationTestCase extends DrupalWebTestCase { if ($return === FALSE) { continue; // No support for deleting. } - $return = entity_load($entity_type, array($id)); + $return = entity_load_by_name($entity_type, array($id)); $this->assertFalse($return, "$entity_type has been successfully deleted."); } } diff --git includes/entity.controller.inc includes/entity.controller.inc index 83fe55e..ba70a23 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,19 +300,17 @@ 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) { // 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); + field_attach_load_revision($this->entityType, $queried_entities); } else { - field_attach_load($this->entityType, $entities); + field_attach_load($this->entityType, $queried_entities); } } @@ -327,26 +322,20 @@ class EntityAPIController extends DrupalDefaultEntityController implements Entit // 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($queried_entities, $this->nameKey); + } + else { + $entities_by_name = $queried_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 { @@ -554,15 +543,17 @@ class EntityAPIController extends DrupalDefaultEntityController implements Entit */ public function view($entities, $view_mode = 'full', $langcode = NULL) { 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); + // 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); + + field_attach_prepare_view($this->entityType, $entities, $view_mode); + entity_prepare_view($this->entityType, $entities); } - 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 +566,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; }