Newer
Older
Wolfgang Ziegler
committed
<?php
/**
* @file
* Provides a controller building upon the core controller but providing more
* features like full CRUD functionality.
*/
/**
* Interface for EntityControllers compatible with the entity API.
*/
interface EntityAPIControllerInterface extends DrupalEntityControllerInterface {
/**
* Delete permanently saved entities.
*
Wolfgang Ziegler
committed
* In case of failures, an exception is thrown.
*
Wolfgang Ziegler
committed
* @param $ids
* An array of entity IDs.
*/
public function delete($ids);
/**
* Invokes a hook on behalf of the entity. For hooks that have a respective
Wolfgang Ziegler
committed
* field API attacher like insert/update/.. the attacher is called too.
*/
public function invoke($hook, $entity);
/**
* Permanently saves the given entity.
*
Wolfgang Ziegler
committed
* In case of failures, an exception is thrown.
*
Wolfgang Ziegler
committed
* @param $entity
* The entity to save.
*
Wolfgang Ziegler
committed
* @return
Wolfgang Ziegler
committed
* SAVED_NEW or SAVED_UPDATED is returned depending on the operation
* performed.
Wolfgang Ziegler
committed
*/
public function save($entity);
/**
* Create a new entity.
*
Wolfgang Ziegler
committed
* @param array $values
Wolfgang Ziegler
committed
* An array of values to set, keyed by property name.
* @return
* A new instance of the entity type.
Wolfgang Ziegler
committed
*/
Wolfgang Ziegler
committed
public function create(array $values = array());
Wolfgang Ziegler
committed
Wolfgang Ziegler
committed
/**
Wolfgang Ziegler
committed
* Exports an entity as serialized string.
Wolfgang Ziegler
committed
*
* @param $entity
* The entity to export.
* @param $prefix
* An optional prefix for each line.
*
Wolfgang Ziegler
committed
* @return
Wolfgang Ziegler
committed
* The exported entity as serialized string. The format is determined by
* the controller and has to be compatible with the format that is accepted
* by the import() method.
Wolfgang Ziegler
committed
*/
public function export($entity, $prefix = '');
Wolfgang Ziegler
committed
/**
* Imports an entity from a string.
*
* @param string $export
* An exported entity as serialized string.
*
* @return
* An entity object not yet saved.
*/
public function import($export);
/**
* Builds a structured array representing the entity's content.
*
* The content built for the entity will vary depending on the $view_mode
* parameter.
*
* @param $entity
* An entity object.
* @param $view_mode
* View mode, e.g. 'full', 'teaser'...
* @param $langcode
* (optional) A language code to use for rendering. Defaults to the global
* content language of the current request.
* @return
* The renderable array.
*/
public function buildContent($entity, $view_mode = 'full', $langcode = NULL);
/**
* Generate an array for rendering the given entities.
*
* @param $entities
* An array of entities to render.
* @param $view_mode
* View mode, e.g. 'full', 'teaser'...
* @param $langcode
* (optional) A language code to use for rendering. Defaults to the global
* content language of the current request.
Wolfgang Ziegler
committed
* @param $page
* (optional) If set will control if the entity is rendered: if TRUE
* the entity will be rendered without its title, so that it can be embeded
* in another context. If FALSE the entity will be displayed with its title
* in a mode suitable for lists.
* If unset, the page mode will be enabled if the current path is the URI
* of the entity, as returned by entity_uri().
* This parameter is only supported for entities which controller is a
* EntityAPIControllerInterface.
* @return
Wolfgang Ziegler
committed
* The renderable array, keyed by entity name or numeric id.
*/
Wolfgang Ziegler
committed
public function view($entities, $view_mode = 'full', $langcode = NULL, $page = NULL);
Wolfgang Ziegler
committed
}
/**
Wolfgang Ziegler
committed
* A controller implementing EntityAPIControllerInterface for the database.
Wolfgang Ziegler
committed
*/
class EntityAPIController extends DrupalDefaultEntityController implements EntityAPIControllerInterface {
Wolfgang Ziegler
committed
protected $cacheComplete = FALSE;
Wolfgang Ziegler
committed
protected $bundleKey;
Wolfgang Ziegler
committed
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
/**
* Overridden.
* @see DrupalDefaultEntityController#__construct()
*/
public function __construct($entityType) {
parent::__construct($entityType);
// If this is the bundle of another entity, set the bundle key.
if (isset($this->entityInfo['bundle of'])) {
$info = entity_get_info($this->entityInfo['bundle of']);
$this->bundleKey = $info['bundle keys']['bundle'];
}
}
/**
* Builds and executes the query for loading.
*
* @return The results in a Traversable object.
*/
public function query($ids, $conditions, $revision_id = FALSE) {
// Build the query.
$query = $this->buildQuery($ids, $conditions, $revision_id);
$result = $query->execute();
if (!empty($this->entityInfo['entity class'])) {
$result->setFetchMode(PDO::FETCH_CLASS, $this->entityInfo['entity class'], array(array(), $this->entityType));
}
return $result;
}
/**
* Overridden.
* @see DrupalDefaultEntityController#load($ids, $conditions)
*
* In contrast to the parent implementation we factor out query execution, so
Wolfgang Ziegler
committed
* fetching can be further customized easily.
Wolfgang Ziegler
committed
*/
public function load($ids = array(), $conditions = array()) {
$entities = array();
// Revisions are not statically cached, and require a different query to
// other conditions, so separate the revision id into its own variable.
if ($this->revisionKey && isset($conditions[$this->revisionKey])) {
$revision_id = $conditions[$this->revisionKey];
unset($conditions[$this->revisionKey]);
}
else {
$revision_id = FALSE;
}
// Create a new variable which is either a prepared version of the $ids
// array for later comparison with the entity cache, or FALSE if no $ids
// were passed. The $ids array is reduced as items are loaded from cache,
// and we need to know if it's empty for this reason to avoid querying the
// database when all requested entities are loaded from cache.
$passed_ids = !empty($ids) ? array_flip($ids) : FALSE;
// Try to load entities from the static cache.
Wolfgang Ziegler
committed
if ($this->cache && !$revision_id) {
Wolfgang Ziegler
committed
$entities = $this->cacheGet($ids, $conditions);
// If any entities were loaded, remove them from the ids still to load.
if ($passed_ids) {
$ids = array_keys(array_diff_key($passed_ids, $entities));
}
}
// Support the entitycache module if activated.
if (!empty($this->entityInfo['entity cache']) && !$revision_id && $ids && !$conditions) {
$cached_entities = EntityCacheControllerHelper::entityCacheGet($this, $ids, $conditions);
// If any entities were loaded, remove them from the ids still to load.
$ids = array_diff($ids, array_keys($cached_entities));
$entities += $cached_entities;
// Add loaded entities to the static cache if we are not loading a
// revision.
if ($this->cache && !empty($cached_entities) && !$revision_id) {
$this->cacheSet($cached_entities);
}
}
Wolfgang Ziegler
committed
// Load any remaining entities from the database. This is the case if $ids
// is set to FALSE (so we load all entities), if there are any ids left to
Wolfgang Ziegler
committed
// load or if loading a revision.
Wolfgang Ziegler
committed
if (!($this->cacheComplete && $ids === FALSE && !$conditions) && ($ids === FALSE || $ids || $revision_id)) {
Wolfgang Ziegler
committed
$queried_entities = array();
foreach ($this->query($ids, $conditions, $revision_id) as $record) {
// Skip entities already retrieved from cache.
Wolfgang Ziegler
committed
if (isset($entities[$record->{$this->idKey}])) {
continue;
}
// For DB-based entities take care of serialized columns.
if (!empty($this->entityInfo['base table'])) {
$schema = drupal_get_schema($this->entityInfo['base table']);
Wolfgang Ziegler
committed
foreach ($schema['fields'] as $field => $info) {
if (!empty($info['serialize']) && isset($record->$field)) {
$record->$field = unserialize($record->$field);
// Support automatic merging of 'data' fields into the entity.
if (!empty($info['merge']) && is_array($record->$field)) {
foreach ($record->$field as $key => $value) {
$record->$key = $value;
}
unset($record->$field);
Wolfgang Ziegler
committed
}
}
}
}
Wolfgang Ziegler
committed
Wolfgang Ziegler
committed
$queried_entities[$record->{$this->idKey}] = $record;
Wolfgang Ziegler
committed
}
}
// Pass all entities loaded from the database through $this->attachLoad(),
// which attaches fields (if supported by the entity type) and calls the
// entity type specific load callback, for example hook_node_load().
if (!empty($queried_entities)) {
$this->attachLoad($queried_entities, $revision_id);
$entities += $queried_entities;
}
// Entitycache module support: Add entities to the entity cache if we are
// not loading a revision.
if (!empty($this->entityInfo['entity cache']) && !empty($queried_entities) && !$revision_id) {
EntityCacheControllerHelper::entityCacheSet($this, $queried_entities);
}
Wolfgang Ziegler
committed
if ($this->cache) {
// Add entities to the cache if we are not loading a revision.
if (!empty($queried_entities) && !$revision_id) {
$this->cacheSet($queried_entities);
Wolfgang Ziegler
committed
// Remember if we have cached all entities now.
if (!$conditions && $ids === FALSE) {
$this->cacheComplete = TRUE;
}
Wolfgang Ziegler
committed
}
}
// Ensure that the returned array is ordered the same as the original
// $ids array if this was passed in and remove any invalid ids.
if ($passed_ids && $passed_ids = array_intersect_key($passed_ids, $entities)) {
foreach ($passed_ids as $id => $value) {
$passed_ids[$id] = $entities[$id];
Wolfgang Ziegler
committed
}
$entities = $passed_ids;
}
return $entities;
}
public function resetCache(array $ids = NULL) {
$this->cacheComplete = FALSE;
Wolfgang Ziegler
committed
parent::resetCache($ids);
// Support the entitycache module.
if (!empty($this->entityInfo['entity cache'])) {
EntityCacheControllerHelper::resetEntityCache($this, $ids);
}
Wolfgang Ziegler
committed
}
/**
* Implements EntityAPIControllerInterface.
*/
public function invoke($hook, $entity) {
if (!empty($this->entityInfo['fieldable']) && function_exists($function = 'field_attach_' . $hook)) {
$function($this->entityType, $entity);
}
Wolfgang Ziegler
committed
Wolfgang Ziegler
committed
if (!empty($this->entityInfo['bundle of']) && entity_type_is_fieldable($this->entityInfo['bundle of'])) {
$type = $this->entityInfo['bundle of'];
Wolfgang Ziegler
committed
// Call field API bundle attachers for the entity we are a bundle of.
if ($hook == 'insert') {
field_attach_create_bundle($type, $entity->{$this->bundleKey});
}
elseif ($hook == 'delete') {
field_attach_delete_bundle($type, $entity->{$this->bundleKey});
}
Wolfgang Ziegler
committed
elseif ($hook == 'update' && $entity->original->{$this->bundleKey} != $entity->{$this->bundleKey}) {
field_attach_rename_bundle($type, $entity->original->{$this->bundleKey}, $entity->{$this->bundleKey});
Wolfgang Ziegler
committed
}
}
Wolfgang Ziegler
committed
// Invoke the hook.
module_invoke_all($this->entityType . '_' . $hook, $entity);
// Invoke the respective entity level hook.
if ($hook == 'presave' || $hook == 'insert' || $hook == 'update' || $hook == 'delete') {
module_invoke_all('entity_' . $hook, $entity, $this->entityType);
Wolfgang Ziegler
committed
}
Wolfgang Ziegler
committed
// Invoke rules.
if (module_exists('rules')) {
rules_invoke_event($this->entityType . '_' . $hook, $entity);
}
Wolfgang Ziegler
committed
}
/**
* Implements EntityAPIControllerInterface.
Wolfgang Ziegler
committed
*
* @param $transaction
* Optionally a DatabaseTransaction object to use. Allows overrides to pass
* in their transaction object.
Wolfgang Ziegler
committed
*/
Wolfgang Ziegler
committed
public function delete($ids, DatabaseTransaction $transaction = NULL) {
Wolfgang Ziegler
committed
$entities = $ids ? $this->load($ids) : FALSE;
if (!$entities) {
// Do nothing, in case invalid or no ids have been passed.
return;
}
Wolfgang Ziegler
committed
// This transaction causes troubles on MySQL, see
// http://drupal.org/node/1007830. So we deactivate this by default until
// is shipped in a point release.
// $transaction = isset($transaction) ? $transaction : db_transaction();
Wolfgang Ziegler
committed
try {
Wolfgang Ziegler
committed
$ids = array_keys($entities);
Wolfgang Ziegler
committed
db_delete($this->entityInfo['base table'])
Wolfgang Ziegler
committed
->condition($this->idKey, $ids, 'IN')
Wolfgang Ziegler
committed
->execute();
// Reset the cache as soon as the changes have been applied.
$this->resetCache($ids);
Wolfgang Ziegler
committed
Wolfgang Ziegler
committed
foreach ($entities as $id => $entity) {
Wolfgang Ziegler
committed
$this->invoke('delete', $entity);
Wolfgang Ziegler
committed
}
Wolfgang Ziegler
committed
// Ignore slave server temporarily.
db_ignore_slave();
}
catch (Exception $e) {
Wolfgang Ziegler
committed
if (isset($transaction)) {
$transaction->rollback();
}
Wolfgang Ziegler
committed
watchdog_exception($this->entityType, $e);
throw $e;
Wolfgang Ziegler
committed
}
}
/**
* Implements EntityAPIControllerInterface.
Wolfgang Ziegler
committed
*
* @param $transaction
* Optionally a DatabaseTransaction object to use. Allows overrides to pass
* in their transaction object.
Wolfgang Ziegler
committed
*/
Wolfgang Ziegler
committed
public function save($entity, DatabaseTransaction $transaction = NULL) {
$transaction = isset($transaction) ? $transaction : db_transaction();
Wolfgang Ziegler
committed
try {
// Load the stored entity, if any.
Wolfgang Ziegler
committed
if (!empty($entity->{$this->idKey}) && !isset($entity->original)) {
Wolfgang Ziegler
committed
// In order to properly work in case of name changes, load the original
// entity using the id key if it is available.
Wolfgang Ziegler
committed
$entity->original = entity_load_unchanged($this->entityType, $entity->{$this->idKey});
Wolfgang Ziegler
committed
}
Wolfgang Ziegler
committed
$this->invoke('presave', $entity);
Wolfgang Ziegler
committed
if (!empty($entity->{$this->idKey}) && empty($entity->is_new)) {
$return = drupal_write_record($this->entityInfo['base table'], $entity, $this->idKey);
Wolfgang Ziegler
committed
$this->resetCache(array($entity->{$this->idKey}));
Wolfgang Ziegler
committed
$this->invoke('update', $entity);
}
else {
$return = drupal_write_record($this->entityInfo['base table'], $entity);
$this->invoke('insert', $entity);
}
// Ignore slave server temporarily.
db_ignore_slave();
unset($entity->is_new);
unset($entity->original);
Wolfgang Ziegler
committed
return $return;
}
catch (Exception $e) {
Wolfgang Ziegler
committed
$transaction->rollback();
watchdog_exception($this->entityType, $e);
throw $e;
Wolfgang Ziegler
committed
}
}
/**
* Implements EntityAPIControllerInterface.
*/
Wolfgang Ziegler
committed
public function create(array $values = array()) {
Wolfgang Ziegler
committed
// Add is_new property if it is not set.
$values += array('is_new' => TRUE);
Wolfgang Ziegler
committed
if (isset($this->entityInfo['entity class']) && $class = $this->entityInfo['entity class']) {
return new $class($values, $this->entityType);
}
Wolfgang Ziegler
committed
return (object) $values;
Wolfgang Ziegler
committed
}
Wolfgang Ziegler
committed
/**
* Implements EntityAPIControllerInterface.
Wolfgang Ziegler
committed
*
* @return
* A serialized string in JSON format suitable for the import() method.
Wolfgang Ziegler
committed
*/
public function export($entity, $prefix = '') {
Wolfgang Ziegler
committed
$vars = get_object_vars($entity);
Wolfgang Ziegler
committed
unset($vars['is_new']);
Wolfgang Ziegler
committed
return entity_var_json_export($vars, $prefix);
}
/**
* Implements EntityAPIControllerInterface.
*
* @param $export
* A serialized string in JSON format as produced by the export() method.
*/
public function import($export) {
$vars = drupal_json_decode($export);
if (is_array($vars)) {
return $this->create($vars);
}
return FALSE;
Wolfgang Ziegler
committed
}
/**
* Implements EntityAPIControllerInterface.
*
* @param $content
* Optionally. Allows pre-populating the built content to ease overridding
* this method.
*/
public function buildContent($entity, $view_mode = 'full', $langcode = NULL, $content = array()) {
// Remove previously built content, if exists.
$entity->content = $content;
$langcode = isset($langcode) ? $langcode : $GLOBALS['language_content']->language;
// Add in fields.
if (!empty($this->entityInfo['fieldable'])) {
Wolfgang Ziegler
committed
// Perform the preparation tasks if they have not been performed yet.
// An internal flag prevents the operation from running twice.
$key = isset($entity->{$this->idKey}) ? $entity->{$this->idKey} : NULL;
field_attach_prepare_view($this->entityType, array($key => $entity), $view_mode);
$entity->content += field_attach_view($this->entityType, $entity, $view_mode, $langcode);
}
// Invoke hook_ENTITY_view() to allow modules to add their additions.
if (module_exists('rules')) {
rules_invoke_all($this->entityType . '_view', $entity, $view_mode, $langcode);
}
else {
module_invoke_all($this->entityType . '_view', $entity, $view_mode, $langcode);
}
Wolfgang Ziegler
committed
module_invoke_all('entity_view', $entity, $this->entityType, $view_mode, $langcode);
$build = $entity->content;
unset($entity->content);
return $build;
}
/**
* Implements EntityAPIControllerInterface.
*/
Wolfgang Ziegler
committed
public function view($entities, $view_mode = 'full', $langcode = NULL, $page = NULL) {
Wolfgang Ziegler
committed
// 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'])) {
Wolfgang Ziegler
committed
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();
Wolfgang Ziegler
committed
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
// generic one.
Wolfgang Ziegler
committed
// @see template_preprocess_entity()
Wolfgang Ziegler
committed
'#theme' => 'entity',
'#entity_type' => $this->entityType,
'#entity' => $entity,
'#view_mode' => $view_mode,
'#language' => $langcode,
Wolfgang Ziegler
committed
'#page' => $page,
);
// Allow modules to modify the structured entity.
Wolfgang Ziegler
committed
drupal_alter(array($this->entityType . '_view', 'entity_view'), $build, $this->entityType);
Wolfgang Ziegler
committed
$key = isset($entity->{$this->idKey}) ? $entity->{$this->idKey} : NULL;
$view[$this->entityType][$key] = $build;
}
return $view;
}
Wolfgang Ziegler
committed
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
}
/**
* A controller implementing exportables stored in the database.
*/
class EntityAPIControllerExportable extends EntityAPIController {
protected $entityCacheByName = array();
protected $nameKey, $statusKey, $moduleKey;
/**
* Overridden.
*
* Allows specifying a name key serving as uniform identifier for this entity
* type while still internally we are using numeric identifieres.
*/
public function __construct($entityType) {
parent::__construct($entityType);
// Use the name key as primary identifier.
$this->nameKey = isset($this->entityInfo['entity keys']['name']) ? $this->entityInfo['entity keys']['name'] : $this->idKey;
if (!empty($this->entityInfo['exportable'])) {
$this->statusKey = isset($this->entityInfo['entity keys']['status']) ? $this->entityInfo['entity keys']['status'] : 'status';
$this->moduleKey = isset($this->entityInfo['entity keys']['module']) ? $this->entityInfo['entity keys']['module'] : 'module';
}
}
/**
* Support loading by name key.
*/
protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
// Add the id condition ourself, as we might have a separate name key.
$query = parent::buildQuery(array(), $conditions, $revision_id);
if ($ids) {
// Support loading by numeric ids as well as by machine names.
$key = is_numeric(reset($ids)) ? $this->idKey : $this->nameKey;
$query->condition("base.$key", $ids, 'IN');
}
return $query;
}
Wolfgang Ziegler
committed
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
/**
* Overridden to support passing numeric ids as well as names as $ids.
*/
public function load($ids = array(), $conditions = array()) {
$entities = array();
// Only do something if loaded by names.
if (!$ids || $this->nameKey == $this->idKey || is_numeric(reset($ids))) {
return parent::load($ids, $conditions);
}
// Revisions are not statically cached, and require a different query to
// other conditions, so separate the revision id into its own variable.
if ($this->revisionKey && isset($conditions[$this->revisionKey])) {
$revision_id = $conditions[$this->revisionKey];
unset($conditions[$this->revisionKey]);
}
else {
$revision_id = FALSE;
}
$passed_ids = !empty($ids) ? array_flip($ids) : FALSE;
// Care about the static cache.
if ($this->cache && !$revision_id) {
$entities = $this->cacheGetByName($ids, $conditions);
}
// If any entities were loaded, remove them from the ids still to load.
if ($entities) {
$ids = array_keys(array_diff_key($passed_ids, $entities));
}
$entities_by_id = parent::load($ids, $conditions);
$entities += entity_key_array_by_property($entities_by_id, $this->nameKey);
// Ensure that the returned array is keyed by numeric id and ordered the
// same as the original $ids array and remove any invalid ids.
$return = array();
foreach ($passed_ids as $name => $value) {
if (isset($entities[$name])) {
$return[$entities[$name]->{$this->idKey}] = $entities[$name];
}
}
return $return;
}
/**
* Overridden.
* @see DrupalDefaultEntityController::cacheGet()
*/
protected function cacheGet($ids, $conditions = array()) {
if (!empty($this->entityCache) && $ids !== array()) {
$entities = $ids ? array_intersect_key($this->entityCache, array_flip($ids)) : $this->entityCache;
return $this->applyConditions($entities, $conditions);
}
return array();
}
/**
* Like cacheGet() but keyed by name.
*/
protected function cacheGetByName($names, $conditions = array()) {
if (!empty($this->entityCacheByName) && $names !== array() && $names) {
// First get the entities by ids, then apply the conditions.
// Generally, we make use of $this->entityCache, but if we are loading by
// name, we have to use $this->entityCacheByName.
$entities = array_intersect_key($this->entityCacheByName, array_flip($names));
return $this->applyConditions($entities, $conditions);
}
return array();
}
protected function applyConditions($entities, $conditions = array()) {
if ($conditions) {
foreach ($entities as $key => $entity) {
$entity_values = (array) $entity;
// We cannot use array_diff_assoc() here because condition values can
// also be arrays, e.g. '$conditions = array('status' => array(1, 2))'
foreach ($conditions as $condition_key => $condition_value) {
if (is_array($condition_value)) {
if (!isset($entity_values[$condition_key]) || !in_array($entity_values[$condition_key], $condition_value)) {
unset($entities[$key]);
}
}
elseif (!isset($entity_values[$condition_key]) || $entity_values[$condition_key] != $condition_value) {
unset($entities[$key]);
}
Wolfgang Ziegler
committed
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
}
}
}
return $entities;
}
/**
* Overridden.
* @see DrupalDefaultEntityController::cacheSet()
*/
protected function cacheSet($entities) {
$this->entityCache += $entities;
// If we have a name key, also support static caching when loading by name.
if ($this->nameKey != $this->idKey) {
$this->entityCacheByName += entity_key_array_by_property($entities, $this->nameKey);
}
}
/**
* Overridden.
* @see DrupalDefaultEntityController::attachLoad()
*
* Changed 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']) {
if ($revision_id) {
field_attach_load_revision($this->entityType, $queried_entities);
}
else {
field_attach_load($this->entityType, $queried_entities);
}
}
// Call hook_entity_load().
foreach (module_implements('entity_load') as $module) {
$function = $module . '_entity_load';
$function($queried_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.
// 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);
}
}
public function resetCache(array $ids = NULL) {
$this->cacheComplete = FALSE;
if (isset($ids)) {
foreach (array_intersect_key($this->entityCache, array_flip($ids)) as $id => $entity) {
unset($this->entityCacheByName[$this->entityCache[$id]->{$this->nameKey}]);
unset($this->entityCache[$id]);
}
}
else {
$this->entityCache = array();
$this->entityCacheByName = array();
}
}
/**
* Overridden to care about reverted entities.
*/
public function delete($ids, DatabaseTransaction $transaction = NULL) {
$entities = $ids ? $this->load($ids) : FALSE;
if ($entities) {
parent::delete($ids, $transaction);
foreach ($entities as $id => $entity) {
if (entity_has_status($this->entityType, $entity, ENTITY_IN_CODE)) {
entity_defaults_rebuild(array($this->entityType));
break;
}
}
}
}
Wolfgang Ziegler
committed
/**
* Overridden to care about reverted bundle entities and to skip Rules.
Wolfgang Ziegler
committed
*/
public function invoke($hook, $entity) {
if ($hook == 'delete') {
// To ease figuring out whether this is a revert, make sure that the
// entity status is updated in case the providing module has been
// disabled.
Wolfgang Ziegler
committed
if (entity_has_status($this->entityType, $entity, ENTITY_IN_CODE) && !module_exists($entity->{$this->moduleKey})) {
$entity->{$this->statusKey} = ENTITY_CUSTOM;
}
$is_revert = entity_has_status($this->entityType, $entity, ENTITY_IN_CODE);
}
if (!empty($this->entityInfo['fieldable']) && function_exists($function = 'field_attach_' . $hook)) {
$function($this->entityType, $entity);
}
Wolfgang Ziegler
committed
if (isset($this->entityInfo['bundle of']) && $type = $this->entityInfo['bundle of']) {
// Call field API bundle attachers for the entity we are a bundle of.
if ($hook == 'insert') {
field_attach_create_bundle($type, $entity->{$this->bundleKey});
Wolfgang Ziegler
committed
}
elseif ($hook == 'delete' && !$is_revert) {
Wolfgang Ziegler
committed
field_attach_delete_bundle($type, $entity->{$this->bundleKey});
}
elseif ($hook == 'update' && $id = $entity->{$this->nameKey}) {
if ($entity->original->{$this->bundleKey} != $entity->{$this->bundleKey}) {
field_attach_rename_bundle($type, $entity->original->{$this->bundleKey}, $entity->{$this->bundleKey});
}
}
Wolfgang Ziegler
committed
}
// Invoke the hook.
module_invoke_all($this->entityType . '_' . $hook, $entity);
// Invoke the respective entity level hook.
if ($hook == 'presave' || $hook == 'insert' || $hook == 'update' || $hook == 'delete') {
module_invoke_all('entity_' . $hook, $entity, $this->entityType);
Wolfgang Ziegler
committed
}
}
Wolfgang Ziegler
committed
/**
* Overridden to care exportables that are overridden.
*/
public function save($entity, DatabaseTransaction $transaction = NULL) {
// Preload $entity->original by name key if necessary.
if (!empty($entity->{$this->nameKey}) && empty($entity->{$this->idKey}) && !isset($entity->original)) {
$entity->original = entity_load_unchanged($this->entityType, $entity->{$this->nameKey});
}
// Update the status for entities getting overridden.
if (entity_has_status($this->entityType, $entity, ENTITY_IN_CODE) && empty($entity->is_rebuild)) {
$entity->{$this->statusKey} |= ENTITY_CUSTOM;
}
Wolfgang Ziegler
committed
return parent::save($entity, $transaction);
Wolfgang Ziegler
committed
}
/**
* Overridden.
*/
public function export($entity, $prefix = '') {
$vars = get_object_vars($entity);
unset($vars[$this->statusKey], $vars[$this->moduleKey], $vars['is_new']);
if ($this->nameKey != $this->idKey) {
unset($vars[$this->idKey]);
}
return entity_var_json_export($vars, $prefix);
}
/**
* Implements EntityAPIControllerInterface.
*/
Wolfgang Ziegler
committed
public function view($entities, $view_mode = 'full', $langcode = NULL, $page = NULL) {
$view = parent::view($entities, $view_mode, $langcode, $page);
Wolfgang Ziegler
committed
Wolfgang Ziegler
committed
if ($this->nameKey != $this->idKey) {
// Re-key the view array to be keyed by name.
$return = array();
foreach ($view[$this->entityType] as $id => $content) {
$key = isset($content['#entity']->{$this->nameKey}) ? $content['#entity']->{$this->nameKey} : NULL;
$return[$this->entityType][$key] = $content;
}
$view = $return;
}
Wolfgang Ziegler
committed
return $view;
}