Système de votes

Bonjour,
Je suis en train de faire un système de votes pour liker ou disliker un contenu. Mon contenu est la réponse à une question posé par un utlisateur. L’utilisateur qui a posé sa question peut liker ou disliker la ou les réponse(s) obtenue(s) par d’autres utilisateurs. Un autre utilisateur peut liker ou disliker une ou plusieurs réponse(s) s’il la trouve pertinente ou non.
J’utilise 3 tables de BD en guise d’exemple : users, reponses, votes

– Table users :

user_id
user_name

– Table réponses :

reponse_id
reponse_txt
user_id (fk)

– Table votes :

vote_id
vote_like (int)
vote_dislike (int)
user_id (fk)
reponse_id (fk)

Mon système de vote fonctionne quand je suis connecté et quand je clic sur like ou dislike. Mon problème est le fait que quand j’ai plusieurs réponses je ne peux pas liker ou disliker plus d’une réponse avec une même session utilisateur. Et même quand je change de session, je ne peux pas liker ou dislike car j’ai toujours le message “vous avez voté ce contenu auparavant”.

Or j’aimerais que d’autres utilisateurs connectés puissent voter. Si un utilisateur n’est pas connecté on le lui demande. Voici le résumé de mon code ci-dessous :

<!-- Partie HTML boutons like - dislike -->
<div class="vote" id="<?=isset($row['reponse_id'])?$row['reponse_id']:'';?>">
     <div class="vote_btn">
           <div class="btn_like"><img src="thumbs-up.png" alt="like"></div><span class="votes_like">0</span>
      </div>
      <div class="vote_btn">
           <div class="btn_dislike"><img src="thumb-down.png" alt="dislike"></div><span class="votes_dislike">0</span>
      </div>
</div>
// Partie jquery
$(document).ready(function() {
    $.each($('.vote'), function(){
        var id = $(this).attr("id");
        //console.log(id);
        post_d = {'id':id, 'vote':'fetch'};
        $.post('vote.php', post_d,  function(response) {
            $('#'+id+' .votes_like').text(response.vote_like);
            $('#'+id+' .votes_dislike').text(response.vote_dislike);
        },'json');
    });
    $(".vote .vote_btn").click(function(e) {
        var click_button = $(this).children().attr('class');
        var id = $(this).parent().attr("id");
        if (click_button==='btn_dislike') {
            post_d = {'id':id, 'vote':'dislike'};
            $.post('vote.php', post_d, function(data) {
                $('#'+id+' .votes_dislike').text(data);
                alert("Merci de votre vote !");
            }).fail(function(err) {
                alert(err.statusText);
            });
        } else if(click_button==='btn_like') {
            post_d = {'id':id, 'vote':'like'};
            $.post('vote.php', post_d, function(data) {
                $('#'+id+' .votes_like').text(data);
                alert("Merci de votre vote !");
            }).fail(function(err) {
                alert(err.statusText);
            });
        }
    });
});
// Partie php : vote.php
    // connexion à la BD
    require_once('db.php');
    $session_id = $_SESSION['user_id'];
    if($_POST) {
        $vote_type = trim($_POST["vote"]);
        $reponse_id = filter_var(trim($_POST["id"]), FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH);
        if(!isset($_SERVER['HTTP_X_REQUESTED_WITH']) AND strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) != 'xmlhttprequest') {
            die();
        }
        $sql = "SELECT COUNT(*) AS nbr FROM votes WHERE reponse_id='$reponse_id' AND user_id='$session_id'";
        $req = $db->prepare($sql);
        $req->execute() or die(print_r($db->errorInfo()));
        $query = $req->fetch(PDO::FETCH_ASSOC);
        switch ($vote_type) {
            case 'like':
                if ($query['nbr'] >= 1) {
                    header('HTTP/1.1 500 Vous avez vote auparavant !');
                    exit();
                } else {
                    $sql = "
                          SELECT vote_like
                          FROM votes v
                          INNER JOIN reponses rp
                          ON rp.reponse_id = v.reponse_id
                          WHERE v.reponse_id<>'$reponse_id'
                          AND user_id='$session_id'
                    ";
                    $req = $db->prepare($sql);
                    $req->execute() or die(print_r($db->errorInfo()));
                    $rows1 = $req->fetch(PDO::FETCH_ASSOC);   
                    if($rows1) {
                        $sql = "
                            UPDATE votes v
                            SET vote_like=vote_like+1
                            WHERE NOT EXISTS (SELECT reponse_id FROM reponse rp WHERE rp.reponse_id = v.reponse_id)
                            AND user_id='$session_id'
                        ";
                        $req = $db->prepare($sql);
                        $req->execute() or die(print_r($db->errorInfo()));
                    } else {
                        $sql = "INSERT INTO votes (user_id, reponse_id, vote_like) VALUES ('$session_id', '$reponse_id', 1)";
                        $req = $db->prepare($sql);
                        $req->execute() or die(print_r($db->errorInfo()));
                    }
                }
                echo ($rows1["vote_like"]+1);
                break;
            case 'dislike':
                if ($query['nbr'] >= 1) {
                    header('HTTP/1.1 500 Vous avez vote ce contenu auparavant !');
                    exit();
                } else {
                    $sql = "SELECT vote_dislike FROM votes WHERE reponse_id='$reponse_id'";
                    $req = $db->prepare($sql);
                    $req->execute() or die(print_r($db->errorInfo()));
                    $rows2 = $req->fetch(PDO::FETCH_ASSOC);
                    if ($rows2["vote_dislike"]) {
                        $sql = "UPDATE votes SET vote_dislike=vote_dislike+1 WHERE reponse_id='$reponse_id'";
                        $req = $db->prepare($sql);
                        $req->execute() or die(print_r($db->errorInfo()));
                    } else {
                        $sql = "INSERT INTO votes (user_id, reponse_id, vote_dislike) VALUES ('$session_id', '$reponse_id', 1)";
                        $req = $db->prepare($sql);
                        $req->execute() or die(print_r($db->errorInfo()));
                    }
                }
                echo ($rows2["vote_dislike"]+1);
                break;
            case 'fetch':
                $sql = "SELECT vote_like, vote_dislike FROM votes WHERE reponse_id='$reponse_id'";
                $req = $db->prepare($sql);
                $req->execute() or die(print_r($db->errorInfo()));
                $rows3 = $req->fetch(PDO::FETCH_ASSOC);
                $vote_like = ($rows3["vote_like"])?$rows3["vote_like"]:0;
                $vote_dislike = ($rows3["vote_dislike"])?$rows3["vote_dislike"]:0;
                $env_rep = array('vote_like'=>$vote_like, 'vote_dislike'=>$vote_dislike);
                echo json_encode($env_rep);
                break;
        }
    }

J’aurais besoin d’aide. Merci par avance !

Bonjour.

Premièrement, je te conseille de regarder les requêtes préparées avec les variables non pas dans la requête, mais dans le execute (cf. doc PHP).

Sinon, que valent les valeurs $vote_type, $reponse_id, $session_id, $query (cf. exit + var_dump en mode crash test) ?

1 « J'aime »

Bonjour @kneelnrise,

Merci d’avoir pris le temps de me répondre.
Concernant mes requêtes préparées je n’ai pas eu de problème. Ça fonctionne normalement vu mon système de votes marche. Mais je n’arrive pas à faire ce que je voulais. Malgré que je lie chaque vote à un identifiant unique attribué à chaque réponse ainsi qu’à l’identifiant de la personne connectée. Cela ne permet pas donc par exemple à une même personne de voter pour 2 réponses, par exemple si elle trouve que les 2 sont utiles.

$session_id c’est l’id de la session utilisateur. Désolé pour l’erreur je viens de le corriger. C’est $_SESSION[‘user_id’] au lieu de $_SESSION[‘client_id’].
La variable $reponse_id correspond à l’id de la réponse sur laquelle on like ou dislike. Et $vote_type c’est la variable post du vote qu’on récupère.

Cdlt,

Pour les requêtes préparées, je parlais surtout de bonnes pratiques et de sécurité, pas de logique du code.

Je ne demandais pas la signification, mais bien la valeur des variables. Il se peut que tu aies une valeur null ou similaire qui invalide la logique.

Sinon, après relecture du code, je remarque :

INSERT INTO votes (user_id, reponse_id, vote_dislike) VALUES ('$session_id', '$reponse_id', 1)

UPDATE votes SET vote_dislike=vote_dislike+1 WHERE reponse_id='$reponse_id'

Dans un cas, tu ajoutes une ligne pour (session_id, reponse_id), dans l’autre, tu modifies l’ensemble des lignes de votes pour (reponse_id), quel que soit user_id. Ta logique me semble plutôt bizarre.

Si tu souhaites faire un système où les utilisateurs ne peuvent voter qu’une seule fois pour une réponse (ce qui semble être le cas), ta base de données devrait être comme suit :

users(user_id, user_name)
reponse(reponse_id, reponse_txt, user_id [fk])
votes(vote_id, reponse_id [fk], user_id [fk], vote_like [boolean])

Et réaliser des agrégations (ex: count) à la lecture pour compter le nombre de votes :

-- Adapter selon le langage (MySQL, PostgreSQL, Oracle, etc.)
SELECT
  (SELECT COUNT(*) as xxx FROM votes WHERE reponse_id='$reponse_id' AND vote_like = TRUE) as vote_like,
  (SELECT COUNT(*) as xxx FROM votes WHERE reponse_id='$reponse_id' AND vote_like = FALSE) as vote_dislike
FROM dual

Je ne vois pas pourquoi tu obtiens l’erreur “vous avez voté ce contenu auparavant” excepté en raison d’un problème de valeur, mais avec ces idées, peut-être que cela va résoudre ton problème.

Te souhaitant bonne correction.

1 « J'aime »

Bonjour @kneelnrise,

Merci pour tes propositions. Je souhaite garder mes tables comme elles étaient. Seulement j’aimerais pouvoir gérer le vote de plusieurs utilisateurs. C’est-à-dire quand un utilisateur vote on vérifie s’il n’a pas déjà voté. Si c’est le cas on lui affiche un message, dans le cas contraire on insert le vote dans la table en fonction du type (like ou dislike). Maintenant quand un vote existe et qu’un autre utilisateur vote, c’est là ou je fais update du champ vote_like ou vote_dislike en incrémentant la valeur. Mais le problème c’est toujours l’id du premier utilisateur qui reste car l’insertion se fait sur la même ligne. Si je modifie le champ id j’écrase aussi le premier et ce n’est pas ce que je veux. J’aimerais aussi distinguer les votes par utilisateur. Si je fais insert partout, il va me rajouter une nouvelle ligne à chaque vote. Si c’est cette méthode qui est préférable comment afficher mes votes et les incrémenter sachant qu’actuellement j’ai un traitement ajax qui me permet d’incrémenter sans rechargement de page et que le calcule se fait sur une seule ligne.

Merci !
Cdlt,

Bonjour,

Tu peux en effet garder ces tables, cependant, tu devras quand même réaliser des agrégations et ne pas modifier les lignes, comme tu le fais.

Tu veux enregistrer le nombre de votes pour une réponse, après qu’un utilisateur ait voté (1 like) :

votes(vote_id, vote_like, vote_dislike, user_id, reponse_id)
=    (1,       1,         0,            1,       1)

Tu veux ensuite enregistrer le nombre de votes pour une réponse après que deux utilisateurs aient voté (1 like, 1 dislike) :

votes(vote_id, vote_like, vote_dislike, user_id, reponse_id)
=    (1,       1,         0,            1,       1)
=    (2,       0,         1,            2,       1)

Dans ce cas, le nombre de like et dislike pour la réponse 1, c’est 1 partout, car la somme des like et dislike est 1.

La requête pour compter sera donc :

SELECT
  SUM(vote_like) as vote_like,
  SUM(vote_dislike) as vote_dislike
FROM votes
WHERE reponse_id='$reponse_id'

Avec cette logique, tu ne fais donc que des insertions dans ta table et aucune mise à jour.

D’ailleurs, si tu règles ton problème initial (« Vous avez vote ce contenu auparavant ! »), tu remarqueras que quand tu votes une deuxième fois, tu modifies en réalité la première ligne de ta réponse, mais tu n’insères jamais l’information que l’utilisateur a voté (Cf. ton code).

À part revoir la logique de tes données et requêtes, je ne vois pas d’autres solutions pour répondre à ton besoin.

Et pour ton problème initial : Quelles sont les valeurs réelles de tes variables $vote_type, $reponse_id, $session_id et $query ? Ou au minimum, peux-tu me dire si tu n’as pas une valeur qui est toujours la même quel que soit le contexte (null ou autre valeur) ?

Les valeurs des variables : $session_id, $vote_type, $reponse_id et $query sont respectivements :
string ‘1’ (length=1)
string ‘fetch’ (length=5)
string ‘10’ (length=2)
string ‘1’ (length=1)
{“vote_like”:“1”,“vote_dislike”:“1”}


Pour toi c’est dans cette partie que je dois modifier ma requête :

$sql = "SELECT vote_like FROM votes WHERE reponse_id='$reponse_id'";
$req = $db->prepare($sql);
$req->execute() or die(print_r($db->errorInfo()));
$rows = $req->fetch(PDO::FETCH_ASSOC);
   
if ($rows["vote_like"]) {
     $sql = "UPDATE votes SET vote_like=vote_like+1 WHERE reponse_id='$reponse_id'";
     $req = $db->prepare($sql);
     $req->execute() or die(print_r($db->errorInfo()));
} else {
    $sql = "INSERT INTO votes (user_id, reponse_id, vote_like) VALUES ('$session_id', '$reponse_id', 1)";
   $req = $db->prepare($sql);
   $req->execute() or die(print_r($db->errorInfo()));
}

Et remplacer par :

$sql = "
SELECT
  SUM(vote_like) as vote_like,
  SUM(vote_dislike) as vote_dislike
FROM votes
WHERE reponse_id='$reponse_id' "
 $req = $db->prepare($sql);
 $req->execute() or die(print_r($db->errorInfo()));
$row = $req->fetch();

if ($row == 0) {
     $sql = "INSERT INTO votes (user_id, reponse_id, vote_like) VALUES ('$session_id', '$reponse_id', 1)";
     $req = $db->prepare($sql);
     $req->execute() or die(print_r($db->errorInfo()));
}

Et je fais pareil pour dislike et fetch ???

Cdlt,

1 « J'aime »
Human Coders - Le centre de formation recommandé par les développeur·se·s pour les développeur·se·s