2016-12-05 6 views
3

Я хочу реализовать функцию поиска рецептов и связанных с ними ингредиентов. Пользователь должен указать ингредиенты, которые он хочет исключить из поиска, и в то же время ингредиенты, которые содержатся в рецептах, которые он ищет.Cakephp3.1: Использование сопоставления() и notMatching() на одной и той же связанной модели сразу

Это мои два искатели:

public function findByContainingIngredients(Query $query, array $params) 
{ 
    $ingredients = preg_replace('/\s+/', '', $params['containing_ingredients']); 

    if($ingredients) { 
     $ingredients = explode(',', $ingredients); 
     $query->distinct(['Recipes.id']); 
     $query->matching('Ingredients', function ($query) use($ingredients) { 
      return $query->where(function ($exp, $query) use($ingredients) { 
       return $exp->in('Ingredients.title', $ingredients); 
      }); 
     }); 
    } 
    return $query; 
} 


public function findByExcludingIngredients(Query $query, array $params) 
{ 
    $ingredients = preg_replace('/\s+/', '', $params['excluding_ingredients']); 

    if($ingredients) { 
     $ingredients = explode(',', $ingredients); 
     $query->distinct(['Recipes.id']); 
     $query->notMatching('Ingredients', function ($query) use ($ingredients) { 
      return $query->where(function ($exp, $query) use ($ingredients) { 
       return $exp->in('Ingredients.title', $ingredients); 
      }); 
     }); 

    } 
    return $query; 
} 

В контроллере я зову:

  $recipes = $this->Recipes->find() 
      ->find('byExcludingIngredients', $this->request->data) 
      ->find('byContainingIngredients', $this->request->data); 

Если пользователь исключает компонент из поиска и определяет один руды более ингредиент, который он хочет включить , есть нулевые результаты. Когда я смотрю на сгенерированный SQL я вижу проблему:

SELECT 
    Recipes.id AS `Recipes__id`, 
    Recipes.title AS `Recipes__title`, 
    ..... 

FROM 
    recipes Recipes 
    INNER JOIN ingredients Ingredients ON (
    Ingredients.title IN (: c0) 
    AND Ingredients.title IN (: c1) 
    AND Recipes.id = (Ingredients.recipe_id) 
) 
WHERE 
    (
    Recipes.title like '%%' 
    AND (Ingredients.id) IS NULL 
) 
GROUP BY 
    Recipes.id, 
    Recipes.id 

Проблема является "AND (Ingredients.id) IS NULL". Эта линия приводит к исчезновению результатов от ингредиентов. Подходы:

  • Создание псевдонима при вызове функции notMatching() в ассоциации дважды. Я думаю, что это невозможно в Cake3.1
  • Использование левого соединения на PK/FK и исключенного заголовка и создание псевдонима. В основном написано моя функция notMatching. Это работает, но это не кажется правильным.

Есть ли другие решения?

ответ

1

Для кого подходит на эту страницу и заключая вы не можете комбинировать matching() и notMatching() в том же ассоциированном классе:

Да, это возможно (как и в случае с Тортом 3.4.9), чтобы сделать такую ​​находку. Но вы должны использовать другой псевдоним для целевой таблицы - это псевдоним, который отличается от обычного имени класса.

Таким образом, в ситуации OP, вы бы поставить это в RecipesTable.php:

public function initialize(array $config) { 
    ... usual stuff 

    $this->belongsToMany('Ingredients', [ 
     'foreignKey' => 'recipe_id', 
     'targetForeignKey' => 'ingredient_id', 
     'joinTable' => 'ingredients_recipes' 
    ]); 
    // the next association uses an alias, 
    // but is otherwise *exactly* the same as the previous assoc. 
    $this->belongsToMany('ExcludedIngredients', [ 
     'className' => 'Ingredients', 
     'foreignKey' => 'recipe_id', 
     'targetForeignKey' => 'ingredient_id', 
     'joinTable' => 'ingredients_recipes' 
    ]); 
} 

И вы должны быть в состоянии написать найти заявление, как это:

$this->find() 
    -> ... usual stuff 
    ->matching('Ingredients',function($q) use($okIngredients) { 
     ... check for ingredients ... 
    }) 
    ->notMatching('ExcludedIngredients', function($q) use($excludedIngredients) { 
     ... check for ingredients ... 
    }); 

Это делает работу. К сожалению, когда я использовал его в аналогичной ситуации с тысячами строк в моей таблице «Рецепты», на выполнение запроса заняло 40 секунд. Так что мне все равно пришлось вернуться и заменить notMatching() на ручное соединение.

1

Я думаю, что вы могли бы сделать вручную присоединиться ingridients таблицы еще раз с другим псевдонимом (http://book.cakephp.org/3.0/en/orm/query-builder.html#adding-joins), а затем использовать его в согласовании/notMatching

+1

Функции match/notMatching ожидают, что первым параметром будет имя assoiciation, а не имя таблицы или псевдоним. К сожалению, это не работает. – napolin