[PHP] Créer des système de votes efficaces

WRInaute discret
Hey,

J'aimerais bien savoir quel est le moyen le plus efficace pour gérer des votes sur un site.

Quel moyen utilise les sites style Youtube ( noter une vidéo ) ou DiggLike ( Voter pr un article ) pour implémenter un vote unique d'une manière pas trop contraignante.

J'ai pas envie de stocker touss les votes dans une bdd pour éviter que l'internaute ne vote plusieurs fois de suite, ca devient vite ingérable non ?
Donc il reste quoi ? Stocker dans une $_SESSION ou un cookie ?
Comment faites-vous ?
Merci
:)
 
WRInaute impliqué
Chacho a dit:
J'ai pas envie de stocker touss les votes dans une bdd pour éviter que l'internaute ne vote plusieurs fois de suite, ca devient vite ingérable non ?
Donc il reste quoi ? Stocker dans une $_SESSION ou un cookie ?

Si tu veux restituer le résultat des votes aux autres visiteurs, tu seras bien obligé de stocker ces votes coté serveur.

Donc oublies le cookie ou la session, qui est concu pour stocker des informations associés à un utilisateur donné.

Sinon lorsque tu veux enregistrer un vote, tu n'es pas obligé d'enregistrer chaque vote, tu peux incrémenter une valeur dans la bdd

maintenant si tu veux empecher le multivote, tu peux enregistrer un cookie sur le post du client avec l'information comme quoi il a voté. mais bon dis toi que si il supprime ses cookies, il pourra revoter...

Donc la seule solution valable c'est de stocker l'ip et d'empecher 2 votes de la même ip espacé de moins de 5 minutes par exemple.
ou faire un triplet : ip/cookie/useragent par exemple
Enfin apres on peut coupler pas mal de techniques.
 
WRInaute discret
Oui bien sûr pour le stockage des votes, y'a pas de pb de mon coté pour ça. C'est juste pour empêcher de revoter plusieurs fois que je me demande qu'elle est la solution la plus facile.

Par exemple sur Scoopéo ou sur Youtube, on ne peut pas voter plusieurs enfin. Je sais pas si c'est un contrôle par ip ou quoi. Bref si qqun a déja du implémenter qqchose comme ça, ça pourrait aider :)

J'en aurais besoin pour un système de vote pour noter des vidéos et peut-être des articles plus tard.
 
WRInaute discret
Je n'étais aussi posé la question pour mon site ou les visiteurs peuvent voter pour des citations.
Pour chaque citation, je stocke dans ma base la liste des n dernières ip des votants. À chaque nouveau vote, je remplace la plus ancienne ip par la nouvelle, tout ça dans un champ text de ma BD.
En fonction de n et du nombre de votes sur mon site, je peux "régler" le temps que j'autorise entre chaque vote de la même ip pour la même citation.

J'avais préféré l'ip au cookie pour éviter les spammer motivés qui pourraient penser à effacer leurs cookies.

Voilà ma solution, c'est ptet pas la meilleure mais c'était assez simple à mettre en place. Si quelqu'un à quelque chose de mieux à suggérer, ça m'intéresse aussi. :wink:
 
WRInaute occasionnel
Si tu fais voter des utilisateurs enregistrés sur ton site (youtube, scoopeo, etc) alors la c'est facile il suffit de stocker l'id de l'utilisateur avec le vote.

Sinon si tu permet a tout le monde de voter, le mieux c'est ip + cookie je pense, mais ca n'empechera pas des motivés a tricher, (proxies) c'est juste que ca rend la tache plus difficile pour les neophites donc tu aura moins de triche.
Le mieux est de ne laisser voter que des utilisateurs enregistrés mais là encore l'utilisateur peut creer plusieurs comptes pour voter plusieurs fois comme c'est le cas sur youtube (oui j'avoue...).
 
WRInaute discret
D'accord et si je pars dans l'optique de laisser que des membres voter, je fais comment ?

Genre dans ma bdd pour mettons une vidéo, je crée un champs :
"voters" avec dedans la liste des ID des membres ?
Ex : 1,2,3,4,5,6,7

T'as une idée alliax sur la meilleure façon de stocker ça ? Sous quel format stockes-tu tes IP Toma ?
 
WRInaute impliqué
Chacho a dit:
D'accord et si je pars dans l'optique de laisser que des membres voter, je fais comment ?

Genre dans ma bdd pour mettons une vidéo, je crée un champs :
"voters" avec dedans la liste des ID des membres ?
Ex : 1,2,3,4,5,6,7

T'as une idée alliax sur la meilleure façon de stocker ça ? Sous quel format stockes-tu tes IP Toma ?

tu crées une table "voters" avec id/idVideo/idMembre/ip
et quand tu enregistres un nouveau vote tu fais un insert dans la table "voters"
et pour la protection et bien à chaque vote tu fais un "select * from voters where ip='ipduvisiteur' si tu as un retour tu dis que c pas possible

enfin tu vois l'idée now ?
 
WRInaute occasionnel
Précision, pour allèger les accès à la base de données, tu ne te sers de la table VOTERS que pour vérifier si l'utilisateur à déjà voter ou pas.
Ne fais pas un Select count(*) à chaque fois que tu affiches une vidéo pour montrer le nombre de votes!
Stocke le total des votes dans un champ de ta table videos, tu le recupere en meme temps que les autres infos de la video.
 
WRInaute occasionnel
moi je fais comme ça. Dans ma base j'ai un champ lastip et un champ note. La personne qui vote pour un élément la note se modifie et l'ip ce met dans le champ. On ajoute une condition pour que la note s'update que si l'ip de l'utilisateur n'est pas égal à l'ip qui se trouve dans le champ lastip. Cela empêche un utilisateur de modifier la note jusqu'à ce qu'un autre soit passé par là pour voter pour le même enregistrement de la base. Ca marche bien, et sans que les utilisateurs soient enregistrés.
 
WRInaute occasionnel
julien, ton truc marche pour la plupart des gens qui n'essaient pas de tricher, ou qui essaient simplement de cliquer deux fois, voient que ca ne marche pas et laissent tomber.
Mais avec ton systeme, il suffit de voter une fois normalement, une fois en passant par un proxy, une fois normalement, une fois par un proxy, etc.
mais c'est vrai que la plus simple des protections suffit a eliminer 99% des tentatives de triches, du moment que ton site n'est pas tres connu.
 
WRInaute occasionnel
entièrement d'accord avec ta remarque, j'en suis parfaitement conscient. On peut en effet améliorer. On peut mettre les 5 dernières ip au lieu de la dernière, et si l'ip est contenue dans la chaîne même chose. Bon d'accord si les enjeux sont commerciaux, financiers, mieux vaut s'orienter sur une structure plus solide, mais aussi plus complexe et plus lourde.
 
WRInaute occasionnel
Je pense que la solution de bozo est relativement facile à mettre en place et permets une sécurité accrue... Franchement, je ne pense pas que les votes de gens qui ne prennent pas la peine de s'enregistrer sur votre site soient à prendre en compte.

Il n'y a qu'une chose que je ne comprends pas c'est pourquoi tu mets id/idVideo/idMembre/ip... A mon avis, idVideo, idMembre, IP est suffisant et la clef est le couple idVideo, idMembre.
 
WRInaute impliqué
erestrebian a dit:
Il n'y a qu'une chose que je ne comprends pas c'est pourquoi tu mets id/idVideo/idMembre/ip... A mon avis, idVideo, idMembre, IP est suffisant et la clef est le couple idVideo, idMembre.

et bien une table doit avoir une primary key d'où ma colonne 'id'

Maintenant tu peux faire une primary composite sur le champ (idmembre+idvideo) mais bon je n'aime pas les primary key composite, en terme de performance je pense que c'est pas ça non plus.
Maintenant tu améliores aussi ton intégrité mais bon pour un table de gestion de vote, je pense qu'un bon vieux id en INTEGER suffit.
 
WRInaute discret
D'accord c'est pas mal effectivement :). J'ai pas encore le réflexe de créer plusieurs tables et j'ai tendance à vouloir tout mettre dans la même lol.
Par contre vous pensez que Youtube fais comme ça ? Si y'a une table avec une entrée pour chaque vote d'un membre par vidéo, elle doit être énorme la table non ? :eek:
 
WRInaute impliqué
Chacho a dit:
D'accord c'est pas mal effectivement :). J'ai pas encore le réflexe de créer plusieurs tables et j'ai tendance à vouloir tout mettre dans la même lol.
Par contre vous pensez que Youtube fais comme ça ? Si y'a une table avec une entrée pour chaque vote d'un membre par vidéo, elle doit être énorme la table non ? :eek:

oui enfin là du coup on tombe dans des problématiques d'exploitation de grande entreprises.
Tu peux faire du table partitionning.

ou meme chez youtube les vidéos doivent etre repartis sur plusieurs bulles

un exemple simplifié : toutes les videos dont le titre commence par un A jusqu'à C (A....B....C) , je les mets sur le serveur 1 donc le serveur 1 ne contient que les votes des vidéos de A à C.
Les vidéos commencant par D à F tu les mets sur le serveur 2 etc etc etc
Tant que tu as pas un besoin d'accéder à tous tes votes en meme temps dans le meme contexte, tu peux partitionner.

De plus tu répartis le risque, si tu as un problème sur le serveur 1, le serveur 2 , 3 ... continuent de fonctionner.

Donc oui tant qu'on peut et qu'on en a le besoin, il faut pouvoir partionner.
Apres il existe des applications qui ne sont pas partionables et là les vrais problèmes d'optimisation commencent
 
WRInaute discret
Ok bozoleclown. C'est vrai que c'est plus même échelle là :)

Ok donc ce que je vais faire c'est dans mes tables de médias ( photos et vidéo par ex ) créer :
un champs : rating
un champs : nbVotes

Et créer une table voters avec :
un champs : id
un champs : idMedia
un champs : idMembre

Donc du coup à chaque fois qu'un membre affiche une vidéo ou une photo, je dois faire une requete MySql pour savoir s'il a déja voté pour ce média ? Ca fait un peu lourd non ?

Je peux peut-être créer une $_SESSION['votes'][] au premier média accédé par le membre qui contiendrais tous les ID des médias ou le membre a déja voté ce qui m'éviterais d'interroger ma base à chaque média afiché. Genre sur la page d'affichage faire un

if( isset($_SESSION['votes'][$idMedia]) ) { il a déja voté }
elseif { isset($_SESSION['votes']) { il n'a pas voté }
else { c'est le premier média consulté, on récupère les médias pour lequel il a voté}

Ca vous semble pas trop mal ?

Merci encore pour vos idées :)
 
WRInaute discret
Moi, j'ai choisi de laisser tout le monde voter pour avoir un maximum de votes et surtout parce que je n'ai quasiment pas de membres !

Le fait d'autoriser seulement les membres à voter permet de limiter les spams, mais si tu as un motivé, il pourra toujours créer plusieurs comptes. C'est vrai que c'est plus chiant que de juste changer d'ip, mais comme ça me faisait perdre plus de 90% des votes, j'ai préféré laisser tout le monde voter. J'ai pas fait de tests pour le pourcentage et c'est surement particulier à mon site car je n'incite pas vraiment à l'inscription ! :wink:

Comme tout le monde peut voter, la table aurait grandi trop vite en faisant une clé composite id_citation + ip. J'ai donc fait une table avec une ligne par citation et un champ text pour stocker les ips.
Concrètement j'ai une table dans ce genre : id_citation/note/pos/ips

pos est un int
ips est un tinytext qui peut contenir 15 ips sous la forme ip1;ip2;ip3;...;ip15 (15 suffit largement pour la fréquentation de mon site !)

pos indique la position de la prochaine ip à effacer.
Si qq vote, je regarde si sont ip est dans la liste des ips pour la citation avec un preg_match("/;".$ip.";/", ";".$ips.";"). Si l'ip y est, c'est fini, sinon je remplace l'ip à la position pos par l'ip de l'utilisateur (avec un explode implode) et j'incrémente pos de 1 modulo 15 (je mets aussi à jour la note).

Comme ça, ça fonctionne bien. Mais mon site est peut-être moins sujet au spam parce que les citations "n'appartiennent" pas aux votants. Ils n'ont donc pas grand chose à gagner en spammant.

A+
 
WRInaute discret
Je déterre le sujet car je vais justement implémenter un système de vote sur un nouveau site en faisant comme toi Toma.

Est-ce que tu as implémenté la fonction ip2long ? Dans ce cas, combien d'IP stockes-tu et quel type de champs utilises-tu désormais ?

Sinon je voulais savoir pourquoi as-tu crée une nouvelle table pour stocker les votes et pas seulement rajouté tes champs note/pos/ips à ta table citations ?

Mci
 
WRInaute discret
J'avoue j'ai pas eu le courage d'utiliser ip2long. Si mes calculs sont exacts cela permettait de stocker 23 (256/11) ips au lieu de 16 (256/16) (j'avais dit 15 mais c'était une erreur :wink: ) dans mon champ tinytext. Comme je n'éprouvais pas le besoin immédiat de stocker plus d'ips et que ça fonctionnait bien j'y ai pas touché.
De toute façon, ça sera jamais très optimisé de stocker des ips (sous n'importe quelle forme) dans un champ text. Mais je sais pas comment stocker une liste de quoi que ce soit dans autre chose qu'un champ text avec MySQL. L'idéal serait d'avoir des tableaux de long (long[] !) mais à ma connaissance ça n'existe pas.

Concernant le choix d'une base séparée, je l'ai fait parce que je trouvais ça plus "propre". Mais c'est vrai que mettre les votes dans la même base éviterait quelques jointures. L'avantage d'utiliser des tables séparées est de ne pas surcharger la table principale avec les votes. Ça limite aussi les écritures sur la table principale, ça peut pas faire de mal. D'autant plus que dans ma table votes, je stocke aussi le nombre affichages.
Je suis pas un expert de MySQL, j'ai un peu fait au feeling. À mon avis ça serait plus efficace de mettre les votes avec la table principale (à cause des jointures) mais je trouvais ça moins propre...

Voilà !

PS : si tu utilises ip2long fais attention à stocker un long non-signé pour pas avoir de signe moins qui te boufferait un caractère (ils en parlent dans la doc de la fonction)

Edit : en fait un stockage d'ips dans un champ text peut être optimal en créant et utilisant une fonction ip2char qui convertirait une ip en une suite de 4 caractères (en latin1). Du coup plus besoin de séparateur et donc on pourrait stocker 64 (256/4) ips dans un champ tinytext ! Et en plus avec une utilisation optimale de la mémoire. Mais bon faut en avoir l'utilité. Ptet je m'amuserai à faire ça le jour ou j'aurais plusieurs dizaines de millier visteurs quotidiens. :lol:
Et ptet même qu'en UTF8 on pourrait aller jusqu'à 128 ips dans un tinytext ! (mais là, je suis pas sur)
 
WRInaute impliqué
Toma a dit:
Mais je sais pas comment stocker une liste de quoi que ce soit dans autre chose qu'un champ text avec MySQL. L'idéal serait d'avoir des tableaux de long (long[] !) mais à ma connaissance ça n'existe pas.
Dans un enregistrement (ou tuple), un champ a une valeur unique. C'est le principe de base.
Si ce que tu veux faire est stocker toutes les IP qui ont répondu à un sondage, il faut insérer autant de tuples que d'IP qui ont participé.
Exemple: 2 champs, "id_sondage", "ip".
Chaque fois qu'une IP répond à un sondage, tu enregistre une nouvelle ligne.
Penser aux index également... sur "id_sondage" et peut-être sur "ip" s'il y a beaucoup d'IPs par sondage.

Toma a dit:
PS : si tu utilises ip2long fais attention à stocker un long non-signé pour pas avoir de signe moins qui te boufferait un caractère (ils en parlent dans la doc de la fonction)
Les types numériques... ne sont pas des types texte. Donc on ne parle pas de caractères.
Un entier signé (sur 32 bits, le plus courant) permet simplement de stocker un nombre entier entre -2147483648 et 2147483647.
Alors qu'un entier non signé commence à 0 (on ne stocke donc que des valeurs positives), et permet donc d'atteindre l'entier 4294967295.

Ce dernier type correspond bien à ce qu'il faut pour stocker une IP (v4), puisque l'IP la "plus petite" est 0.0.0.0 représentée par 0, et la "plus grande" théoriquement possible est 255.255.255.255, qui est représenté par via ip2long par 4294967295.
Sauf que ces 2 IPs sont particulières, et qu'on ne pourra pas les croiser sur un réseau (bugs ip2long dans certaines versions de PHP avec ces IP).

Mais maintenant il y a aussi les adresses du protocole IP version 6 :lol:
Tout se complique puisque ces IPs font 128 bits, soit 2 fois plus que le plus grand type géré par MySQL (bigint, sur 64 bits). Donc il faudrait découper ces IP en 2 :p pfiou lol
Mais bon si votre serveur n'accepte pas l'IPv6, pas de soucis pour l'instant, mais il faudra bien y passer un jour.
 
WRInaute discret
FloBaoti a dit:
Dans un enregistrement (ou tuple), un champ a une valeur unique. C'est le principe de base.
Si ce que tu veux faire est stocker toutes les IP qui ont répondu à un sondage, il faut insérer autant de tuples que d'IP qui ont participé.
Exemple: 2 champs, "id_sondage", "ip".
Chaque fois qu'une IP répond à un sondage, tu enregistre une nouvelle ligne.
Penser aux index également... sur "id_sondage" et peut-être sur "ip" s'il y a beaucoup d'IPs par sondage.

Je suis d'accord, c'est la bonne façon de faire en MySQL. Le seul problème c'est que ça te fait un nombre de lignes assez impressionnant avec une création de ligne à chaque vote ! Après tout, MySQL est fait pour ça. Mais avec ma méthode certes pas très "pure" on a une ligne par enregistrement et ça bouge plus. Ça me paraissait plus simple pour un site artisanal.

FloBaoti a dit:
Les types numériques... ne sont pas des types texte. Donc on ne parle pas de caractères.

Ça je le sais bien. Je parle ici de caractères parce qu'on évoque la possibilité de stocker des longs dans un champ text.



Avec le recul je pense que ma méthode de stockage n'est pas la meilleure façon de faire avec une base MySQL (et j'ai jamais prétendu le contraire !). Une table avec une primary key id_video/ip me parait être plus conventionnelle pour enregistrer les votants. Néanmoins ma méthode est assez intuitive et permet de stocker en même temps toutes les infos de vote. J'ai sans doute aussi fait comme ça parce que je suis plus à l'aise en php qu'en MySQL. :wink:
 
WRInaute discret
Merci de vos retours :)

Bon j'ai implémenté pour l'instant un champs des dernières IP dans la même table que les éléments pour lesquels les internautes votent :)

Je n'ai pas crée un champs avec la prochaine IP à virer par contre. Je vire toujours la dernière et je décale tout d'un cran pour rajouter la nouvelle ip en premier :) Voilà mon code SQL si ça peut aider ( avec un champs d'ips séparés par des / )

Code:
UPDATE maTable SET ips=CONCAT('$ip/',SUBSTRING_INDEX(ips,'/',$nb_ips-1)) WHERE id='$def'

où $nb_ips est une variable où j'indique le nombre d'ips que je souhaite stocker pour chaque élément de la table.
 
WRInaute discret
Euh, quasiment 2 ans après, je (re)déterre ce topic :eek:

Alors qu'à l'époque j'ai donc suivi une des solutions que l'on m'a proposé (stocker les IP des derniers votants), j'ai changé cela hier et désormais je ne laisse que les membres voter (du coup chaque définition va repasser à 1 vote dès qu'un nouveau vote sera engistré :( ).

Eh oui, je sais bien que je risque d'avoir beaucoup moins de votes mais je pense que j'y gagne fortement en fiabilité. J'ai notamment récemment implanté Facebook et j'espère un certain équilibre (inscription -- vitesse).

Je poste ceci afin de faire un retour mais aussi une question :

- Comment est-ce que j'ai implémenté ceci :
A la connexion du membre, je récupère tous les votes qu'il a effectué et je les mets dans une variable $_SESSION.
Du coup, lorsque le membre regarde des définitions, je n'ai pas besoin d'interroger à nouveau ma base SQL. Je sais directement s'il a voté ou non et je peux réafficher son vite.

- Ma question ?
Est-ce que les variables $_SESSION sont très consommatrices en mémoire ? Risque-je d'avoir des problèmes plus tard si des membres ont votés pour beaucoup de définitions ? Qu'en pensez-vous ?

EDIT: faute de temps (je pensais implémenter tout ça plus rapidemment), si vous essayez de voter tout en n'étant pas connecté, une popup plus que vide s'affichera (mon idée finale étant de proposer des liens vers l'inscription/connexion)
 
Discussions similaires
Haut