web-dev-qa-db-fra.com

Utilisation de OR avec EntityFieldQuery

Je n'ai jamais eu besoin de le faire avant aujourd'hui, mais il ne semble pas que vous puissiez faire OR des requêtes avec EntityFieldQuery, puisque db_or est utilisé pour certaines requêtes.

Un exemple serait d'accéder à toutes les entités qui ont un champ de date où la valeur est nulle ou après aujourd'hui.

Suis-je en train de manquer quelque chose ou une astuce ou est-ce simplement non pris en charge?

26
googletorp

J'ai vu un solution de ce problème . L'idée est d'utiliser addTag() dans la requête et d'implémenter hook_query_TAG_alter(), où vous avez un bon ancien SelectQuery objet.

22
Michael

Vous pouvez sous-classer EntityFieldQuery et remplacer certaines méthodes.

Les conditions qui sont ajoutées à un objet de classe EntityFieldQuery (par exemple une condition de propriété) sont ajoutées à un tableau.

  public function propertyCondition($column, $value, $operator = NULL) {
    // The '!=' operator is deprecated in favour of the '<>' operator since the
    // latter is ANSI SQL compatible.
    if ($operator == '!=') {
      $operator = '<>';
    }
    $this->propertyConditions[] = array(
      'column' => $column, 
      'value' => $value, 
      'operator' => $operator,
    );
    return $this;
  }

Lorsque la requête est générée, ce tableau est ensuite utilisé dans une boucle similaire à la suivante (le code est présent dans EntityFieldQuery :: propertyQuery () ):

foreach ($this->propertyConditions as $property_condition) {
  $this->addCondition($select_query, "$base_table." . $property_condition['column'], $property_condition);
}

$select_query Contient la valeur renvoyée par un appel à db_select().

12
kiamlaluno

Vous ne pouvez pas, je le crains, les blocs opératoires ne sont pas pris en charge nativement par la classe EntityFieldQuery.

Une solution consiste à ajouter une balise à la requête avec avec ->addTag(), puis à implémenter hook_query_TAG_alter() pour modifier manuellement la structure interne de la requête pour les requêtes contenant cette balise.

Ce faisant, vous pourrez parcourir les conditions existantes et effectuer les modifications nécessaires pour ajouter votre logique OR. Ce n'est pas une jolie façon de le faire cependant; vous pouvez trouver un exemple ici .

5
Clive

Pas besoin de diviser les requêtes en 2 et de fusionner ou quelque chose comme ça. Il suffit de modifier la requête

Considérez le scénario: j'avais 2 types d'entité avec des noms de machine: les instructions tincan et tincan_agents

5 champs de référence d'entité sur l'entité

4 d'entre eux sont des champs de référence d'entité réguliers et le 5ème (tincan_object) est un champ de référence de type multi-entités, chaque champ de référence fait référence à des entités de type 'Agent'.

Le champ de référence tincan_object peut référencer des agents et des activités (un troisième type d'entité). Un agent a une propriété object_type, qui peut être agent ou groupe.

Je souhaite trouver toute déclaration faisant référence à l'un des agents possibles, dans l'un des champs de référence. Nous avons besoin d'un opérateur OR entre les fieldConditions, mais nous devons également vérifier le object_type du champ de référence de type multi-entité, et nous assurer qu'il s'agit de l'une des deux possibilités.

Le code ci-dessous représente le plus simple possible, dans notre solution la requête avait beaucoup d'autres conditions, champs, etc ... donc le code devait ne pas compter sur l'ordre des conditions, ou même si tous ces champs étaient interrogés.

    $query = new EntityFieldQuery();
    $query->entityCondition('entity_type', 'tincan_statement');

    $all_agents = array(4,10); //entity_ids to search for
    $query->addTag('tincan_statement_get_agents');
    $query->fieldCondition('tincan_actor', 'target_id', $all_agents, 'IN'); 
    //need OR between fields conditions
    $query->fieldCondition('tincan_authority', 'target_id', $all_agents, 'IN');
//need OR between fields conditions
    $query->fieldCondition('tincan_instructor', 'target_id', $all_agents, 'IN');
//need OR between fields conditions
    $query->fieldCondition('tincan_team', 'target_id', $all_agents, 'IN');
//need OR between fields conditions
//but then nested in the OR structure we need an AND for two columns of the multientity type reference field tincan_object
    $query->fieldCondition('tincan_object', 'target_id', $all_agents, 'IN');
    $query->fieldCondition('tincan_object', 'object_type', array('Agent', 'Group'), 'IN');
    $results = $query->$execute();

Solution: Avis dans l'EntityFieldQuery ci-dessus

 $query->addTag('tincan_statement_get_agents');

Cette balise la requête, permettant l'implémentation de hook_query_TAG_alter ()

/**
 * Implements hook_query_TAG_alter()
 * alters the query for finding agents with or without the related_agents flag
 * used for Statement API Get processor EntityFieldQuery
 */
function tincan_lrs_query_tincan_statement_get_agents_alter(QueryAlterableInterface $query) {
  //need to or the search for all the fields (actor, object, authority, instructor, team)
  // the object_type of the object field needs to be Agent OR Group

  $conditions =& $query->conditions();
  // dsm($conditions);  //dsm() is your friend! comes with devel module
  $agent_grouping_condition = db_or(); 
  $object_parameters = array();
  $x = 0;
  foreach ($conditions as $key => $condition) {
    if (is_numeric($key) && isset($condition['field']) && is_scalar($condition['field'])) {
      if ( (strpos($condition['field'], 'tincan_object_object_type') !== FALSE  ||
          strpos($condition['field'], 'tincan_object_target_id') !== FALSE ) && $condition['operator'] == 'IN') {
  //u
            unset($conditions[$key]);
            $object_parameters[$x]['field'] = $condition['field'];
            $object_parameters[$x]['value'] = $condition['value'];
            $object_parameters[$x]['operator'] = $condition['operator'];
            $x += 1;
          }

       if(strpos($condition['field'], 'tincan_actor_target_id') !== FALSE ||
          strpos($condition['field'], 'tincan_instructor_target_id') !== FALSE ||
          strpos($condition['field'], 'tincan_team_target_id') !== FALSE ||
          strpos($condition['field'], 'tincan_authority_target_id') !== FALSE ) {
            unset($conditions[$key]);
            $agent_grouping_condition->condition($condition['field'], $condition['value'], $condition['operator']);

      } 
    }
  }

  // create new AND condition to nest in our OR condition set for the object parameters
  $object_condition = db_and();
  foreach($object_parameters as $key => $param) {
    $object_condition->condition($param['field'], $param['value'], $param['operator']);
  }

  $agent_grouping_condition->condition($object_condition);

  $query->condition($agent_grouping_condition);

  //By default EntityFieldQuery uses inner joins, change to left
  $tables =& $query->getTables();

  foreach($tables as $key => $table) {
    if (strpos($key, 'field_data_tincan_object') !== FALSE ||
        strpos($key, 'field_data_tincan_actor') !== FALSE ||
        strpos($key, 'field_data_tincan_authority') !== FALSE ||
        strpos($key, 'field_data_tincan_instructor') !== FALSE ||
        strpos($key, 'field_data_tincan_team') !== FALSE ) {
          if(!is_null($table['join type'])) {
            $tables[$key]['join type'] = 'LEFT';
          }
    }
  }

}
5
jackrabbithanna

L'OP veut interroger des entités avec une date nulle OR plus grand que x, je voulais interroger des nœuds sans langue définie OR la langue de l'utilisateur. addTag() est la meilleure solution pour ajouter une véritable instruction OR, mais serait exagérée dans mon cas. Mon très simple OR peut être accompli par recherche de la propriété language dans un tableau à l'aide de:

$query->propertyCondition('language', array($GLOBALS['language']->language, LANGUAGE_NONE), 'IN');
2
lmeurs