Bonjour,

Voila je rencontre un petit problème avec mon code.

Ce que je fais

Je travaille actuellement sur cakephp3 pour un site qui a une boutique en ligne.
Ma boutique est composée de 5 tables décritent ci-dessous:

  • categories (id, name)
  • products (id, title, content, price)
  • productimgs (id, product_id, name, ext)
  • items (id, product_id, price, impact, stock)
  • attributes (id, attributegroup_id, name)
  • attributegroups (id, name)

J'ai 2 relations HATBM au sein de ces tables, entre Items/Attributes et Categories/Products.
Je vais vous décrire mon problème en l'appliquant à une relation HATBM prenons la relation Categories/Products.
Sachant que cette relation HATBM et propre à mon projet et que l'on a pour habitude içi de parler des relations HATBM à travers une relation classique Posts/Tags alors je vais prendre ce cas particulier.

Ce que je veux

Imaginons que j'ai un Form avec des inputs de type checkbox dans une vue, chaque input correspond à un Tag.
Je peux donc séléctionner plusieurs Tags, je les POST vers une action. J'obtiens alors un tableau $d avec chaque Tag.id :

[
    (int) 0 => (int) 1,
    (int) 1 => (int) 2,
    (int) 2 => (int) 3,
]   

Je peux récupérer touts les Posts et leurs Tags:

$posts = $postsTable->find('all')->contain(['Tags'])->toArray();

Je peux récupérer touts les Posts qui ont un Tag.id contenue dans $d, j'y arrive:

$posts = $postsTable->find('all')       
                                ->contain(['Tags']) 
                                ->where(['Tags.id IN' => $d])           
                                ->toArray();

Maintenant, imaginons que je veux touts les Posts qui ont exactement touts les Tag.id contenue dans $d. Pas un de plus pas un de moins.
Dans l'esprit c'est la dernière requête avec le "->where(['Tags.id IN' => $d])" sauf que je veux pas un "IN" qui est un "OR" mais un "AND".
J'ai donc essayé d'introduire ->match([]) à ma requête pour tenter de récupérer tout les Posts qui ont tout leur Tag.id contenu dans $d.

$posts = $postsTable->find('all')       
                                ->contain(['Tags']) 
                                ->matching('Tags', function ($q) use ($d){
                                        return $q->where(['Tags.id IN' => $d]);
                                    })                  
                                ->toArray();

Le résultats est le même que la deuxième requête. Le fait de passer par match me sort chaque Post et tout ses Tags, la requête ne sort pas le Post qui contient exactement tout les Tag_id renseignés par $d.

J'ai essayé de traîter $posts après ma requête en suprimant chaque post qui n'avait pas au moins un des ses Tag.id contenu dans $d, en me disant que resterais que le post qui à tout les Tag.id contenue dans $d, sans succès...

$d = $this->request->data;
// Pour chaque posts 
foreach ($posts as $key => $post) {
                // Pour chaque Tag du post
                foreach($post->tags as $k => $tag){
                    // Si le Tag_id n'est pas dans $d
                    if(!in_array($tag->id, $d){
                        // Supprimer de $posts
                        unset($posts[$key]);
                    }
                }
}

Si quelqu'un à déjà voulu et réussi à resortir dans une relation HATBM les Posts qui ont exactement une série de Tag, je suis interessé. par sa philosophie.

Merci.

4 réponses


Salut je croie que pour commencer tu devrait aller sur la doc.
Elle est quand même très claire ici

Et est ce que tes table de relation existe dans ta bd ?
et est ce que tes relations sont créer dans tes class table ?

Oui, mes tables de relation existe dans ma BDD.
Oui, mes relations sont crées dans mes class table.
Dans ma relation HATBM Posts/Tags, je réussi à enregistrer un nouveau post et lui associer des tags existants ou non.
Je peux supprimer un Post et les relations dans la table de jointure.

Je flanche juste ce find() qui pourrait me sortir un post qui a une certaine list de Tag_id.

$d = $this->request->data;
$d = array_values($d); // Pour remettre les index à partir de 0.

$posts = $postsTable->find('all')->contain(['Tags'])->toArray();
// Pour tout les posts
foreach ($posts as $key => $post) {
    $post_Ids = []; // Je créer un tableau dans le quel je mettre tout les Tag.id liés au post
    foreach ($post->tags as $k => $tag) {
        array_push($post_Ids, $tag->id);
    }
    /*
        Si la différence entre le tableau $d et les $post_Ids est vide j'ai le post qui à exactement les tags demandé par $d
    */
    if(empty(array_diff($d, $post_Ids))){
        // J'ai accès a l'Id de ce post
        $post = $postsTable->find('all')
                    ->contain(['Tags'])
                    ->where(['Posts.id' => $post->id])
                    ->first();
        break;
    }               
}

J'arrive maintenant grâce à ce bout de code à récupérer le bon post. Mais j'avour que j'aurais aimé le matché directement par une requête.

Utilise le mot clé "AND" dans ta requête .

Un exemple à améliorer avec une boucle, mais que tu peux comprendre facilement.

$posts = $postsTable->find('all')       
    ->contain(['Tags']) 
    ->matching('Tags', function ($q) use ($d){
        return $q->where(['AND' => [
            'Tags.id' => $d[0],
            'Tags.id' => $d[1], // ...
        ]
        ]);
    })                  
    ->toArray();
'Tags.id' => $d[1], // ...

Avec cette solution c'est le dernier Tag.id qui est pris en compte pour le find et non pas tout les Tag.id déclaré.
Ainsi resorte tout les posts qui on le dernier Tag.id de renseigné dans 'AND' et non pas les posts qui ont tout les Tag.id de renseignés.

[
    'Tags.id' => $d[0],
    'Tags.id' => $d[1], // ...
 ]

Le tableau ci-dessus fournis à "AND" n'as pas toutes ses clés uniques (normal) puisqu'on déclare autant de fois que voulu la clé 'Tags.id' ainsi toute les précédentes sont écrasés et non prise en compte.