web-dev-qa-db-ja.com

EntityFieldQueryでORを使用する

今日までにこれを行う必要がありませんでしたが、db_orが選択クエリに使用されているため、EntityFieldQueryを使用してORクエリを実行することはできないようです。

例では、値がnullまたは今日より後の日付フィールドを持つすべてのエンティティを取得します。

私は何かまたはいくつかのトリックを逃していますか、またはこれは単にサポートされていませんか?

26
googletorp

この問題の解決策 を見ました。アイデアは、クエリでaddTag()を使用してhook_query_TAG_alter()を実装することです。この場合、古き良きSelectQueryオブジェクトがあります。

22
Michael

EntityFieldQueryをサブラスし、いくつかのメソッドをオーバーライドできます。

クラスEntityFieldQueryのオブジェクトに追加された条件(プロパティ条件など)が配列に追加されます。

_  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;
  }
_

クエリが作成されると、その配列は次のようなループで使用されます(コードは EntityFieldQuery :: propertyQuery() にあります)。

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

_$select_query_には、db_select()の呼び出しから返された値が含まれます。

12
kiamlaluno

残念ながら、ORはEntityFieldQueryクラスでネイティブにサポートされていません。

1つの方法として、->addTag()を使用してクエリにタグを追加し、次に hook_query_TAG_alter() を実装して、クエリの内部構造を手動で変更します。そのタグを含みます。

これを行うと、既存の条件をループして、必要な変更を加えてORロジックを追加できます。ただし、これを行うのはかっこいい方法ではありません。 ここの例 を見つけることができます。

5
Clive

クエリを2つに分割してマージする必要はありません。クエリを変更するだけです

シナリオを考えてみます。マシン名が2つのエンティティタイプでした。tincanステートメントとtincan_agentsです。

エンティティ上の5つのエンティティ参照フィールド

それらの4つは通常のエンティティ参照フィールドであり、5番目(tincan_object)はマルチエンティティタイプの参照フィールドで、各参照フィールドはタイプ「エージェント」のエンティティを参照します。

Tincan_object参照フィールドは、エージェントとアクティビティ(3番目のエンティティタイプ)を参照できます。エージェントにはプロパティobject_typeがあり、これはエージェントまたはグループのいずれかです。

参照フィールドのいずれかで、いくつかの可能なエージェントの1つを参照するステートメントを見つけたい。 OR演算子はfieldConditionsの間に必要ですが、複数エンティティタイプの参照フィールドのobject_typeを確認し、2つの可能性のいずれかであることを確認する必要もあります。

以下のコードは可能な限り最も単純なものを表しています。このソリューションでは、クエリに他の多くの条件、フィールドなどが含まれていたため、コードは条件の順序を考慮せず、またはこれらのすべてのフィールドがクエリされている場合でも必要です。

    $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:上記のEntityFieldQueryの通知

 $query->addTag('tincan_statement_get_agents');

これによりクエリにタグが付けられ、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

OPは日付がnullのエンティティをクエリしたいOR xよりも大きい、言語が定義されていないノードをクエリしたかったORユーザーの言語。addTag()は、実際のORステートメントを追加するための最良のソリューションですが、私の場合はやりすぎです。私の非常に単純なORは、次を使用して配列の言語プロパティを検索します。

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