You are here

class EntityDependencyIterator in Entity Dependency API 7

Iterator class which does the heavy lifting for detecting dependencies.

This iterator is reponsible for taking in an array of entity types and ids, figuring out all their dependencies, and returning an iterable object of all of them in a sane order (dependencies first). And since dependencies in theory are nested and recursive, we are using a recursive iterator here.

@todo We need to throw an exception when we detect a circular dependency.

Hierarchy

Expanded class hierarchy of EntityDependencyIterator

1 string reference to 'EntityDependencyIterator'
entity_dependency_entity_dependency_iterator in ./entity_dependency.module
Implements hook_entity_dependency_iterator().

File

./EntityDependencyIterator.inc, line 24
Entity Dependency classes.

View source
class EntityDependencyIterator implements RecursiveIterator {

  /**
   * The entities to be iterated over.
   *
   * @var array
   */
  public $entities = array();

  /**
   * The entity type of the entity currently being iterated over.
   *
   * @var string
   */
  public $entityType = NULL;

  /**
   * The entity ID of the entity currently being iterated over.
   *
   * @var string
   */
  public $entityId = NULL;

  /**
   * An array of dependencies to the entity being parsed.
   *
   * @var array
   */
  public $dependencies = array();

  /**
   * An array of belongings to the entity being parsed.
   *
   * @var array
   */
  public $belongings = array();

  /**
   * An array with information on the cause/reason why an entity exists in the
   * tree. Basically, the cause for term A's existance in the tree, might be
   * becasue node B depends on it.
   *
   * @var array
   */
  public $causes = array();

  /**
   * Keeps track of entities that have already been checked for dependencies.
   *
   * @var array
   */
  public $checked = array();

  /**
   * Keeps track of entities that have already been traversed (output).
   *
   * @var array
   */
  public $traversed = array();

  /**
   * Constructor.
   *
   * @param array $entities
   *   A structured array of entity ids and their entity types.
   * @param array $parent
   *   The parent array of the current entity.
   *
   * @see entity_dependency_iterator()
   */
  public function __construct($entities, &$parent = NULL) {
    $this->entities = array();
    foreach ($entities as $entity_arr) {
      $entity_obj = entity_load($entity_arr['type'], array(
        $entity_arr['id'],
      ));
      if (empty($entity_obj)) {
        if (isset($parent)) {
          $error_msg = 'Failed to load %type ID %id, which is a dependency of %parent_type ID %parent_id.';
          $error_vars = array(
            '%type' => $entity_arr['type'],
            '%id' => $entity_arr['id'],
            '%parent_type' => $parent->current['type'],
            '%parent_id' => $parent->current['id'],
          );
        }
        else {
          $error_msg = t('Failed to load requested %type ID %id.');
          $error_vars = array(
            '%type' => $entity_arr['type'],
            '%id' => $entity_arr['id'],
          );
        }
        watchdog('Entity Dependency', $error_msg, $error_vars, WATCHDOG_WARNING);
      }
      else {
        $this->entities[] = $entity_arr;
      }
    }
    if (empty($parent)) {
      foreach ($this->entities as $key => $entity) {
        $this->causes[$entity['type']][$entity['id']] = FALSE;
      }
    }
    else {
      $this->causes =& $parent->causes;
      $this->checked =& $parent->checked;
      $this->traversed =& $parent->traversed;
    }
  }

  /**
   * Returns TRUE if an iterator can be created for the current item in the
   * entities array.
   *
   * @return boolean
   */
  public function hasChildren() {
    $current = $this->current;

    // Don't check for dependencies twice.
    if (!empty($this->current['id']) && !isset($this->checked[$current['type']][$current['id']])) {

      // Load the current entity.
      if (!empty($current['revision_id'])) {
        $entity_info = entity_get_info($current['type']);
        if (!empty($entity_info['entity keys']['revision'])) {
          $conditions = array(
            $entity_info['entity keys']['revision'] => $current['revision_id'],
          );
        }
      }
      else {
        $conditions = array();
      }
      $entities = entity_load($current['type'], array(
        $current['id'],
      ), $conditions);
      $entity = reset($entities);

      // When $entity does not exist (may be the case with references
      // to deleted taxonomy terms), skip and remove it from $this->entites.
      if (!$entity) {
        watchdog('entity_dependency', "Missing entity %id of type %type", array(
          '%id' => $current['id'],
          '%type' => $current['type'],
        ), WATCHDOG_WARNING);

        // We can't do anything useful with the no-existent entity,
        // therfore we just remove it.
        array_shift($this->entities);
        return FALSE;
      }
      $this->dependencies = module_invoke_all('entity_dependencies', $entity, $current['type']);

      //$this->belongings = module_invoke_all('entity_belongings', $entity, $this->entityType);

      // Don't add dependencies that already were checked.
      foreach ($this->dependencies as $key => $dependency) {
        if ($dependency['type'] == $current['type'] && $dependency['id'] == $current['id'] || isset($this->checked[$dependency['type']][$dependency['id']])) {
          unset($this->dependencies[$key]);
        }
        else {
          $this->causes[$dependency['type']][$dependency['id']] = $current;
        }
      }

      // Let other modules have their say.
      drupal_alter('entity_dependencies', $this->dependencies, $entity, $current['type']);

      // Now mark this as checked.
      $this->checked[$current['type']][$current['id']] = TRUE;
      if (!empty($this->dependencies)) {
        return TRUE;
      }
    }
    return FALSE;
  }

  /**
   * Helper method to get entity dependencies.
   */
  public function getChildrenEntities() {
    $entities = array();
    $current = current($this->entities);
    if (!empty($this->dependencies)) {
      $entities = $this->dependencies;

      // In an iterator, having children means that the current key itself
      // isn't a part of the entities. However, we need that entity.. So we add
      // the parent as a part of the entities. And since children always should
      // go first, we add the parent last.
      $entities[] = $current;
    }
    return $entities;
  }

  /**
   * Returns an iterator for the current entry.
   *
   * @return EntityDependencyIterator
   */
  public function getChildren() {
    return new EntityDependencyIterator($this
      ->getChildrenEntities(), $this);
  }

  /**
   * Get the current entity formatted with some extra metadata according to
   * the OData protocol.
   *
   * @see http://www.odata.org/developers/protocols
   */
  public function current() {
    $current = current($this->entities);

    // Load the current entity.
    $entities = entity_load($current['type'], array(
      $current['id'],
    ));
    $entity = reset($entities);

    // Add necessary metadata to the entity.
    $cause = FALSE;
    if (!empty($this->causes[$current['type']][$current['id']])) {
      $cause = $this->causes[$current['type']][$current['id']]['type'] . '/' . $this->causes[$current['type']][$current['id']]['id'];
    }
    $entity->__metadata = array(
      'type' => $current['type'],
      'uri' => $current['type'] . '/' . $current['id'],
      'cause' => $cause,
    );

    // Now mark this as traversed.
    $this->traversed[$current['type']][$current['id']] = TRUE;
    return $entity;
  }

  /**
   * Returns the key of the current element.
   */
  public function key() {
    return key($this->entities);
  }

  /**
   * Moves the current position to the next element.
   */
  public function next() {
    do {
      $current = next($this->entities);
    } while (!empty($current) && isset($this->traversed[$current['type']][$current['id']]));
  }

  /**
   * Rewinds the Iterator to the first element.
   */
  public function rewind() {
    reset($this->entities);
  }

  /**
   * Checks if current position is valid.
   *
   * @return boolean
   */
  public function valid() {
    $current = current($this->entities);
    if (!empty($current) && is_array($current) && isset($current['type']) && isset($current['id']) && !isset($this->traversed[$current['type']][$current['id']])) {
      $this->current = $current;
      return TRUE;
    }
    return FALSE;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
EntityDependencyIterator::$belongings public property An array of belongings to the entity being parsed.
EntityDependencyIterator::$causes public property An array with information on the cause/reason why an entity exists in the tree. Basically, the cause for term A's existance in the tree, might be becasue node B depends on it.
EntityDependencyIterator::$checked public property Keeps track of entities that have already been checked for dependencies.
EntityDependencyIterator::$dependencies public property An array of dependencies to the entity being parsed.
EntityDependencyIterator::$entities public property The entities to be iterated over.
EntityDependencyIterator::$entityId public property The entity ID of the entity currently being iterated over.
EntityDependencyIterator::$entityType public property The entity type of the entity currently being iterated over.
EntityDependencyIterator::$traversed public property Keeps track of entities that have already been traversed (output).
EntityDependencyIterator::current public function Get the current entity formatted with some extra metadata according to the OData protocol.
EntityDependencyIterator::getChildren public function Returns an iterator for the current entry.
EntityDependencyIterator::getChildrenEntities public function Helper method to get entity dependencies.
EntityDependencyIterator::hasChildren public function Returns TRUE if an iterator can be created for the current item in the entities array.
EntityDependencyIterator::key public function Returns the key of the current element.
EntityDependencyIterator::next public function Moves the current position to the next element.
EntityDependencyIterator::rewind public function Rewinds the Iterator to the first element.
EntityDependencyIterator::valid public function Checks if current position is valid.
EntityDependencyIterator::__construct public function Constructor.