PHP [AJAX][MYSQL] Like-Button umsetzen

Timdaroxxa

Lieutenant
Registriert
März 2009
Beiträge
954
Hi Leute,

ich würde gerne einen Like-Button wie den Facebook-Button umsetzen.

Jetzt frage ich mich, welche Tabellen-Struktur etc ich nutzen sollte.
Am besten wäre es, wenn ein User nicht mehrmals "liken" kann.

Wie setzte ich das jetzt um?

Folgendes hab ich mir bis jetzt gedacht:

1. Da ich nur "Posts" "liken" kann, könnte ich in der Tabelle für Posts einfach eine Spalte `amount_likes` anlegen und so die "likes" zählen. Während einer Session könnte man dann ein Post nur einmal "liken".

2. Ich könnte zusätzlich zu den Posts eine Tabelle `likes` einbauen. Dann könnte ich jedes mal die Anzahl der Einträge zählen die auf ein Post zeigen. Außerdem ist es vielleicht sinnvoll die IP des Besuchers mit einzutragen um so doppeltes liken auszuschließen? Vorteil: Extrem gute Erweiterbarkeit. Deswegen find ich das natürlich besser...


Jetzt meine Frage: Wie wird das üblicherweise gehandhabt? Kennt jemand bessere Alternativen?
 
Deine Antwort ist leider zu kurz, als dass ich sie verstehen könnte... :D Kannste das noch mit 1,2 Sätzen erläutern?
 
er will sagen, dass du eine neue tabelle anlegst in der artikel-id mit user-id als primärschlüssel verknüpft werden. damit verhinderst du, dass user einen post mehrfach liken können.
 
Mahlzeit,

Sorry für die kurze Antwort ;-)
Dann hier ein wenig ausführlicher.

Wie gschulde schon schrieb, ist diese Tabellenstruktur dazu da, um doppeltes "liken" zu unterbinden.
Jeder Nutzer darf jeden Eintrag/Post/Artikel/wasauchimmer nur einmal "liken".
D.h. in der Tabelle darf kein Eintrag doppelt vorkommen.
Das lässt sich am einfachsten regeln, in dem ein eindeutiger Schlüssel (bei mysql "unique" oder "primary") über beide Spalten (Nicht: jeweils auf eine Spalte) gelegt wird.
Durch die extra Tabelle trennst du die "like"-Daten auch vom ganzen Rest ab und speicherst in dem Sinne nicht mehrfach die selben Daten. Ablegen könntest in der Tabelle zusätzlich z.B. noch wann ge-like-t wurde.
("dies sind die 10 neusten likes")

Über die Anzahl der Datensätze zu einem Artikel ermittelst du dann die "like"-Anzahl für diesen.
Code:
select count(*) from liketable where postId = ...

Im Prinzip lagst du mit deinem Vorschlag schon richtig ;-)
Um die Performance zu verbessern kannst du dir die like-Zahl dann nochmal separat (schnell abrufbar) ablegen, was dann allerdings zu Inkonsistenz führen kann, wenn die beiden Tabellen voneinander abweichen...

Die IP würde ich als Nutzerkennzeichen nicht nutzen.
Ne neue IP ist schnell bezogen (Router-Reset, Tor, etc.) und das ganze könnte dazu führen, dass ein Nutzer nicht "liken" kann, weil er zufällig die selbe IP hat wie jemand vor x Monaten.

Gruß
 
Ich weiß jetzt nicht ob du einen internen Bereich hast und wem du alles das liken erlauben möchtest, aber bei registrierten Benutzern macht die Umsetzung über IP's wenig sinn.

Deshalb:
Weitere Tabelle, in der Post-ID und User-ID abgelegt werden, anlegen und du kannst außerdem sagen, WER den entsprechenden Post geliked hat.
 
Hey, danke noch für die ausführliche Antwort! :)
Die Umsetzung, die du meinst, war mir schon klar. Mir gings eben darum, dass jeder "Liken" können soll, also auch nicht registrierte Besucher der Website und da gerne doppelte likes sogut wie möglich unterbinden will. Sry, dass das nicht aus meinem Ausgangspost klar geworden ist.

Ideen? :D


Noch eine andere Frage: Kann mir jemand von euch ein Buch empfehlen zu allgemeinen Programmierprinzipien in der Objektorientierten Programmierung? Sowas wie Subject-Observer z.B. sollte dort erklärt sein. (Also das kenn ich schon, ich suche nur weitere Muster, die einem das Leben leichter machen :D)!
 
Zuletzt bearbeitet:
Hmm,

Dann könntest du im ersten Schritt natürlich über die Session/Cookie gehen.
-> Problem: Browser-Cache / Cookie löschen

Im zweiten Schritt könntest die IP einschränken (x mal von der selben IP innerhalb von y Sekunden)
-> Problem: mehrere Nutzer über selbe IP, IP Wechsel

Im dritten Schritt könntest du die mitgesendeten Browser-Infos (User-Agent, Accept-Language) noch heranziehen.
-> Problem: Datenschutz?, ggf. bei mehreren Nutzern auch bei der selben IP gleich

Das würde ich dann allerdings nicht alleine über die DB abbilden, da ja dann das Primary-Key-Konstrukt nicht mehr greift...

Ansonsten gibt ja ggf. noch andere schlecht löschbare Daten (Flash-Cookie, HTML5 Storage?)
 
Zuletzt bearbeitet:
Naja, ich wähle ja PHP direkt nicht, weil ich unbedingt OO programmieren will :D
Ich lern aber auch noch Java... also keine Angst ^^


Danke für alle Antworten! Damit hat sich das Thema :)

EDIT: Doch noch nicht ganz. Wo ich hier schon nach Büchern frage: Kennt noch jemand eins zu nativer Android-Java Programmierung? Oder kennt jemand Bücher, die man zu OOP einfach gelesen haben sollte?
 
Zuletzt bearbeitet:
Hi,

ich kann nicht garantieren das das Skript direkt funktioniert. Hab es aus meinem Forum rausgeschnippelt.

Wird im like skript ein 'error' ausgegeben (echo 'error';) färbt sich der button Hintergrund rot und in dem Beispiel ändert sich der Text auf dem button zu 'Error';

Wenn du das noch auf deine Bedürfnisse anpasst und evtl. wie oben erwähnt mit primarykey auf TopicID:UserID arbeitest bisste schon n gutes Stück weiter. Nicht zwei Spalten sondern zusammen da die Felder Unique sein müssen.



Code:
like.php

<?php
session_start(); 

// nur für registrierte User
if ( !isset($_SESSION['User_ID'])){ echo 'error'; exit(); }

// bissl logging
class log {
    var $agent;
    var $ip;
    var $time;
    var $referer;
    var $hbyadrr;
    public function __construct() {
        $this->agent = $_SERVER['HTTP_USER_AGENT'];
        $this->ip    = $_SERVER['REMOTE_ADDR'];
        $this->referer = $_SERVER['HTTP_REFERER'];
        $this->time  = time();
        $this->hbyadrr = gethostbyaddr($_SERVER['REMOTE_ADDR']);
    }
}


// loggingobjekt erzeugen und Nutzer ID sowie die Topic ID extrahieren
$logging    = new log();  
$topic_id   = (int)$_POST['topic_to_like'];
$user_id    = $_SESSION['User_ID'];

// verbindung zur Datenbank
$db_user   = 'user';
$db_passwd = 'passwd';
$dbh       = new PDO("mysql:host=127.0.0.1;dbname=Database", $db_user, $db_passwd);

// abfragen ob der user schon geliked hat (zur Sicherheit sonst kann ja jeder kommen)
$sth = $dbh->prepare('SELECT user_liked_this_topic FROM like_control_table WHERE topic_id = ? AND user_id = ? ');
$sth->execute(array($topic_id,$user_id));
$r = $sth->fetch();
$result = $r['0'];

// wird kein Eintrag gefunden erzeugen wir einen
if( empty($result) ){
$sth = $dbh->prepare('INSERT INTO like_control_table VALUES(?,?,?,?,?,?)');
$sth->execute(array('auto',$topic_id,'true',$logging->agent,$logging->ip,$user_id));
$sth = $dbh->prepare('UPDATE topics SET count_likes = count_likes + 1 WHERE id = ?');
$sth->execute(array($topic_id));
}

// findet sich ein Eintrag kann man von manipulation ausgehen da der button bei einem Eintrag funktionslos ist
else{ echo 'error'; exit(); }
$dbh = NULL;
?>

// Skript für die Seite mit dem Button

<script type='text/javascript'>

$(function() {

   $('#likebutton').click(function() {

       var topicId = $(this).val();
       var button  = $(this);

       $.ajax({
           url: "/scripts/like.php",
           type: "post",
           data: "topic_to_like="+topicId,

           success: function(msg) {

               if(msg=='error'){ button.css('background-color','red').html('Error'); }
               else{ button.html('I Liked'); }

           },
           error:function(){ button.css('background-color','red').html('Error'); } 
       }); 

   });

});

</script>




/* Auch für das Skript auf dem der Like button zu finden ist */


<?php

   // Datenbank öffnen etc.

   $sth = $dbh->prepare('SELECT COUNT(*) FROM like_control_table WHERE topic_id = ? AND user_liked_this_topic = \'true\' AND user_id = ?');
   $sth->execute(array($topic_id, $user_id));
   $r = $sth->fetch();

   /* Create a no-func button if liked | Ternary operator */
   $likeButton = $r[0] > '0' ? '<button type=\'button\' name=\'liked\' id=\'liked\' class=\'l.button.nofunction\'>I Liked</button>' 
   : '<button type=\'button\' name=\'like_submit\' id=\'likebutton\' class=\'l.button\' value=\''.$topic_id.'\'>Like</button>';


?>
 
Zuletzt bearbeitet:
Nicht schlecht:
Was mir auffällt:
Z. 34: Du weißt, dass PDO eine Exception werfen kann, die dir gnadenlos die Verbindungsdetails offenlegt?
Lösung: try-catch verwenden

Z. 38 f.: fetch gibt false zurück, falls du am Ende bist, spart dir eine Zeilen und ein ['0'] (sowieso nicht so schön).

Z. 43: empty() auf ein Wert? Besser $r===false oder 1 speichern (select 1 as user_liked from... in Z. 37) und dann if($result)

Z. 45: Du speicherst ja fast so viel wie Facebook :)

Z. 51: Wie wäre header("HTTP/1.0 404 Not Found");exit(); Dadurch würdest du dir im Javascript auch ne Zeile sparen.


Z. 96:
Code:
SELECT count_likes like_control_table.topic_id as liked 
FROM topics 
LEFT OUTER JOIN like_control_table 
ON topics.topic_id = like_control_table.topic_id
WHERE topics.topic_id = ? AND user_id = ?
Und in Z 98 und 101 wie in Z. 43.

Es fehlt ja noch die Ausgabe "10 Personen gefällt das"
 
danke. Man lernt immer was dazu.

Z. 43: empty() auf ein Wert? Besser $r===false oder 1 speichern (select 1 as user_liked from... in Z. 37) und dann if($result)

Ich hab überlegt ob ich dass so hier einstell... Ehrlich. Ich weiß dass es kein gutes Licht auf mich wirft. Hab gehofft ich kann mich durchmogeln.

... if(!$r)

PS: Joins sind zuviel zum einsteigen.

Das mit den exceptions ruiniert mir mein Wochenende.:)
 
Zuletzt bearbeitet:
@Durchmogeln: ^^ Das funktioniert im Internet eher selten.
Aber, das ist ein sehr guter Weg, um was zu lernen.

Exceptions sind toll, sehe es als eleganten Weg, Fehler abzuarbeiten.
Fürs erste
Code:
try{
$db=new PDO();
}catch(Exception $e){
header(HTTP/1.0 500 Internal Server Error");
die("Database error");
}

Joins sind das, was eine Datenbank cool macht.

Prinzipiell macht ein Join eine Verbindung zwischen einer Tabelle und einer anderen auf Datensatzebene.

Grob gesagt:
Code:
SELECT ...
FROM erste_tabelle
JOIN zweite_tabelle
ON erste_tabelle.irgendeine_id = zweite_tabelle.das_passende_gegenstück
Das Ergebnis ist dann eine Abfrage mit allen Spalten der ersten und der zweiten Tabelle, wobei für jede Zeile der ersten Tabelle die passende Zeile aus der zweiten dabei ist. Falls die zweite Tabelle keine passende hat, kommt die Zeile gar nicht => nicht immer so gewollt, daher gibt es verschiedene JOINs:


JOIN oder INNER JOIN: Es muss in beiden Tabellen EIN passenden Eintrag geben
LEFT (OUTER) JOIN: Falls es in der zweiten Tabelle kein Eintrag gibt, werden alle Werte als NULL zurückgegeben (verwende ich in meine Vorschlag)
FULL OUTER JOIN: JEDE Kombination von passenden Einträgen wird ausgegeben (mach das mal mit 1000 auf beiden Seiten, die zusammenpassen => 1 Mio. Zeilen).
RIGHT JOIN: Gleiche wie LEFT bloß verdreht, wird von manchen DBs nicht unterstützt.
 
Ach Gott. Ohne Joins läuft nix. Hier hat allerdings jemand gefragt wie er ein Likebutton umsetzen kann. Ihn dann mit Joins umhauen?

Ich würde bei deinem Beispiel noch bissl kürzen. So rein aus trotz.

Code:
    SELECT t.count_likes l.topic_id as liked
    FROM topics t
    LEFT OUTER JOIN like_control_table l
    ON t.topic_id =l.topic_id
    WHERE t.topic_id = ? AND user_id = ?
 
Das ist natürlich kürzer. Da fehlt ein ","

Ich denke schon, dass man jemandem, der nach einer Funktion fragt, auch elegante (und effiziente) Wege zeigen darf. Ob er die dann umsetzt, egal.
Und: Ich persönlich kann am Besten an echten Problemstellungen lernen, wie was funktioniert und warum das praktisch ist.
 
PHP OOP, ternary operator, ajax, prepared statements, joins ... Das ist ganz schön was zu knabbern wen man noch am Anfang ist.

Wobei es gut ist JOINs an einem Beispiel erklärt zu bekommen das zum eigenen Projekt Bezug hat.

Wenns dann noch in Details geht... JOINS die gar nix zurückgeben wenn der Usereintrag fehlt, JOINS die zumindest eine Seite zurückgeben etc.
 
Genau, und da ist es doch super, wenn man eine eigene Datenbank hat (wo man die Beziehungen selbst zusammengestellt hat) und sehen kann, was passiert, wenn man in phpMyAdmin mal den einen oder den anderen Join einsetzt (und welchen man wollte).

An sich ist "der Like-Button für Mitglieder" eine gute Programmierübung: m:n Relations, Joins, NULLs ...
 
PS: Nachdem ich die Tage jquery aktualisiert hab wissend das es mit Arbeit verbunden ist noch ein paar Anmerkungen:

success: und error werden nicht mehr genutzt.

Ich werd den obigen Code nicht aktualisieren lediglich ergänzen wie man ihn anpassen muss:

PS: object beinhalted mögliche Rückgabewerte des aufgerufenen Skripts. Zum Beispiel die neue Anzahl an likes.

Code:
var topicID = Topic_NR_1;
$.ajax({
  type:'POST',
  url:'/like_submit.php',
  data: { topic_to_like: topicID }
})
.done(function (object) { 
    alert('Liked');
    $('#like_counter').html(object); 
})
.fail(function (jqXHR, textStatus, errorThrown) { alert('failed'); })
.always(function (a, textStatus, b) { alert('XYZ'); });
 
Zurück
Oben