4 votes

Cakephp3.1 : Utilisation simultanée de matching() et notMatching() sur le même modèle associé

Je veux mettre en place une fonction de recherche pour les recettes et les ingrédients associés. L'utilisateur doit spécifier les ingrédients qu'il veut exclure de la recherche et en même temps les ingrédients qui sont contenus dans les recettes qu'il recherche.

Ce sont mes deux viseurs :

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

Dans le contrôleur, j'appelle :

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

Si l'utilisateur exclut un ingrédient de la recherche et spécifie un ou plusieurs ingrédients qu'il souhaite inclure, il n'obtient aucun résultat. Lorsque je regarde le SQL généré, je vois le problème :

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

Le problème est "AND (Ingredients.id) IS NULL". Cette ligne fait disparaître les résultats des ingrédients inclus. Mes approches :

  • Création d'un alias lors de l'appel de notMatching() sur l'association deux fois. Je pense que ce n'est pas possible dans Cake3.1.
  • Utiliser une jointure gauche sur le PK/FK et le titre exclu et créer un alias. En fait, j'ai écrit ma propre fonction de non-concordance. Cela fonctionne, mais cela ne semble pas correct.

Existe-t-il d'autres solutions ?

2voto

Annabel Points 612

À tous ceux qui viennent sur cette page et concluent qu'il n'est pas possible de combiner une matching() y notMatching() sur la même classe associée :

Oui, il est possible (à partir de Cake 3.4.9 en tout cas) de faire une telle recherche. Mais vous devez utiliser un autre alias pour la table cible - c'est un alias différent du nom de classe habituel.

Donc, dans la situation de l'OP, vous mettriez ceci dans 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'
    ]);
}

Et vous devriez être capable d'écrire une déclaration de recherche comme celle-ci :

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

Este fait travail. Malheureusement, quand je l'ai utilisé dans une situation analogue avec milliers de rangs dans mon Recettes la requête a pris 40 secondes pour être exécutée. J'ai donc dû revenir en arrière et remplacer le notMatching() par un joint artisanal de toute façon.

1voto

Andrej Gr Points 56

Je pense que ce que vous pourriez faire est de joindre manuellement ingridients une fois de plus avec un alias différent ( http://book.cakephp.org/3.0/en/orm/query-builder.html#adding-joins ) et l'utiliser ensuite dans la mise en correspondance ou la non-remise en correspondance.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X