[PHP/MySQL] Vernünftiges Refsystem

flaschenkind

Well-known member
ID: 118459
L
20 April 2006
4.507
337
Da ich gerade langeweile habe schreibe ich mal eine kleine Anleitung dazu, wie man am besten ein Refsystem in PHP schreibt. Es gibt schließlich genug "dreckig" programmiere Script, besonders hier bei Klamm. Oft ist auch ein schlecht programmiertes Refsystem eines der Gründe, warum eine Seite soviele Resourcen frisst. Deswegen werde ich jetzt mal eine kleine Erkärung zur Programmierung eines Refsystems schreiben. Ich werde 3 Beispiele posten, 2 schlechte, ein vernünftiges. Es gibt bestimmt noch andere Refsysteme, die auch schlecht oder gut sind, könnt ihr auch gerne noch posten, aber ich gehe jetzt auf diese 3 ein ;)

Anmerkung: Ich werde keine Namen von Script nennen, aus denen ich die Beispiele habe, alleine schon um nciht nachher noch ne Klage wegen Rufmordes zu erhalten. Einige werden diese Strukturen bestimmt wieder erkennen, und ich bitte diese es für sich zu behalten.

In einem Script gibt es z.B. folgende Refstruktur:
Code:
Tabelle Refs:
user|ref1|ref2|ref3|ref4...
1    | 0 ...
2    | 0 ...
4    | 0 ....
5    | 3   | 2   | 0 ...
6    | 5   | 3   | 2 | 0 ...
In diesem Beispiel hab ich Userids genommen, im Originalscript werden Usernames verwendet, wovon ich sowieso abrate. Allgemein in jedem Script! Was ist, wenn z.B. jemand seinen Username geändert haben will? Das geht nur, wenn man sich durch sämtliche Tabellen hangelt und alle Usernames ändert. Mit einer Userid hat man dies Problem nicht, da die ID immer gleich bleibt und sich nur der Username bleibt.

Wenn man hier jetzt z.B. die Downline des Users 2 auslesen will, braucht man folgenden Code:
PHP:
for($i = 1;$i<=4;$i++){
  ${'ebene'.$i.'_refs'} = array();
  $result = mysql_query('SELECT `user` FROM `refs` WHERE `ref'.$i.'`=2');
  $row = mysql_fetch_array($result);
  ${'ebene'.$i.'_refs'}[] = $row['user'];
}
Dieser Code bräuchte, wenn man wie hier 4 Ebenen verwendet, 4 Querys für das Auslesen der Downline, wenn man genau wissen will wann welcher User in welcher Ebene ist. Man könnte natürlich auch eine Query machen alà
Code:
SELECT `user` FROM `refs` WHERE `ref1`=2 OR `ref2`=2 OR `ref3`=2 OR `ref4`=2
Damit hätte man auch seine komplette Downline, allerdings weiß man nicht genau wer in welcher Ebene ist.

Wenn man jetzt in ein Anmeldungssystem dieses Refsystem einbauen will, so muss man hingehen und von jedem Werber des Werbers den Werber auslesen. Also etwa so:
PHP:
$werber_1 = 2;
for($i = 2;$i<=4;$i++){ //nur damit jeder Werber auf 0 steht, weil die Schleife unten ja vorzeitig abgebrochen werden kann
  ${'werber_'.$i} = 0;
}
for($i = 2;$i<=4;$i++){
  ${'werber_'.$i} = 0;
  $result = mysql_query('SELECT `ref1` FROM `refs` WHERE `user`='.${'werber_'.($i-1)});
  if(!mysql_num_rows($result){
    break;
  }
  else{
  $row = mysql_fetch_array($result);
  ${'werber_'.$i} = $row['ref1'];
  }
}
mysql_query('INSERT INTO `refs` (`user`, `ref1`, `ref2`, `ref3`, `ref4`) VALUES (7, '.$werber_1.', '.$werber_2.', '.$werber_3.', '.$werber_4.')');
Also gibt es auch bei der Anmeldung keine Vorteile.

Vorteil: Man blickt leicht durch das System hindurch.
Nachteil: Sehr Performancelastig, sowohl beim Anmelden, als auch beim Downline auslesen.
Fazit: Dieses System kann man verwenden, es braucht zwar pro Ebene eine Abfrage beim Downline auslesen, sowie beim Anmelden, aber das ist verkraftbar, im Vergleich zu dem folgenden System. Trotzdem guckt euch nochmal das letzte System an, bevor ihr euch entscheided dieses zu nehmen :)



Dann gibt es auch noch ein System, welches man nciht nutzen sollte, was aber auch manchmal zum Einsatz kommt (ich meine jetzt über mehrere Ebenen, über eine Ebene ist es ja ok)
Man hat in der Usertabelle eine Spalte "werber". Mehr nicht. Das Script hagelt dann durch die Tabellen, und erstellt ne Downline. Von diesem System ist noch mehr abzuraten, als das obige, da es wirklich unmengen an Abfragen, und somit auch Ressourcen, zieht.

So haben wir z.B. folgende Tabelle "user":
Code:
userid|werber
1      | 0
2      | 1
3      | 2
4      | 3
5      | 3
6      | 5
Sollte selbsterklärend sein.

Wenn man jetzt die Downline auslesen will, muss man rekursiv arbeiten, da es sonst nicht möglich ist das alles genau auszulesen. Man müsste für jeden Werber eine Abfrage nach dessen User machen, es sind also verdammt viele Abfragen. Deswegen ist davon absolut abzuraten. Hier mal ein Beispiel Code:
PHP:
function getDownline($userid, $ebene=1, $downline=null){
  $result = mysql_query('SELECT `userid` FROM `user` WHERE `werber`='.$userid);
  if(mysql_num_rows($result)){
    while($row = mysql_fetch_array($result)){
      echo $row['userid'].'|'.$ebene.'<br />';
      $downline['ebene'.$ebene][] = $row['userid'];
      $downline = getDownline($row['userid'], $ebene+1, $downline);
    }
  }
  return $downline;
}
$userid = 1;
$downline = getDownline($userid);
Dies ließe sich so lösen, aber wie man sieht, arbeitet diese Funktion rekursiv, wird also immer neu augerufen. So wird die Query für jeden User der Downline einzeln aufgerufen. So sind das bei der Downline von oben 6 Querys, was schon viel zu viel ist, besonders wenn man sich vorstellt, was passieren würde, wenn ein User über alle Ebenen 200 oder mehr User hat.
Allerdings benötigt man bei diesem Refsystem keine weiter Abfrage bei Anmeldungen, zum Werber eintragen oder ähnliches.


Vorteile: Man benötigt keine zusätzlichen Abfragen bei der Anmeldung
Nachteil: Sehr Performancelastig, da für jeden User der Downline eine eigene Abfrage durchgeführt wird.
Fazit: Dieses System ist überhaupt nicht zu empfehlen, da viel zu viele MySQL Abfragen braucht.



Und nun zu dem guten System, welches ich übrigens auch verwende.

Es gibt eine Tabelle "refs", die z.B. so aussieht.
Code:
userid | werber | ebene
1       | 0         | 1
2       | 1         | 1
3       | 2         | 1
3       | 1         | 2
4       | 3         | 1
4       | 2         | 2
4       | 1         | 3
5       | 4         | 1
5       | 3         | 2
5       | 2         | 3
5       | 1         | 4
Also es wird für jeden User genau abgespeichert wer der Werber welcher Ebene ist. Für Werberlose gibts den werber 0, der aber in späteren Ebenen nicht mehr aufgeführt wird.

Das sieht auf den ersten Blick verdammt kompliziert aus, ist mit etwas Einarbeitung kein Problem. Im späteren Einsatz kann man dann mit einer (vernünftigen) Query eine perfekte Downline ausgeben.
Um sich in die Downline einzutragen, braucht es dann zwar ein paar Querys mehr, aber ich denke ja mal, dass die Downline häufiger ausgelesen wird, als dasss ein Werber gewechselt wird oder das man sich neu registriert.

Die Downline könnte man dann z.B. so auslesen:
PHP:
for($i=1;$i<=4;$i++){
  ${'ebene'.$i.'_userid'} = array();
}
$result = mysql_query('SELECT `userid`, `ebene` FROM `refs` WHERE `werber`=1');
while($row = mysql_fetch_array($result)){
  ${'ebene'.$row->ebene.'_userid'}[] = $row->userid;
}
So hat man die Downline schön in nem Array, nach Ebenen sortiert.


Vorteil: Gut für die Performance beim Downline auslesen
Nachteil: Schwer zu durchblicken, beim Anmelden oder Werber ändern kostet es ein paar Querys, aber da man das seltener macht als die Downline auslesen, ist es nicht weiter tragisch.
Fazit: Dieses System ist zu empfehlen, wegen seiner guten Performance .


Gesamtfazit: Nehmt das letzte System ;)

Vielen Dank fürs lesen! Ich hoff es hat euch gefallen.


Info am Rande: Ich saß jetzt einige Zeit an dem Tut, es ist spät und ich hatte nen Anstrengenden Tag. Nehmt Fehler hin und weißt mich bitte freundlich darauf hin, damit ich es ändern kann. Die Codes sind auch alle ungetestet, aber sollten funktionieren, bzw. nur kleine Denkfehler, oder Syntaxfehler haben ;)

Wenn der Thread in den FAQ Bereich verschoben wird, fänd ichs toll, damit ihn (hoffentlich) auch einige zu Gesicht bekommen :)
 
Zuletzt bearbeitet:

Stimme ich dir zu ;)
Auch wenn ich in einem anderem Thread mal das System Nr. 2 beschrieben habe, ist halt das einfachste und da der User nicht Informationen über die Anzahl der Ebenen usw. gemacht hat, wäre es bei einer Ebene finde ich auch das beste.

Aber trotzdem eine kl. Anmerkung: Pack es auch noch einmal in dem Thread der Codeschnipsel ;)
 
Wenn der Thread in den FAQ Bereich verschoben wird, fänd ichs toll, damit ihn (hoffentlich) auch einige zu Gesicht bekommen :)
Ich bin ja froh, dass sich einer mal die Mühe gemacht hat :)
Im Crashforum gammelt der alte Thread rum, den ich damals nicht rüberziehen konnte, weil ne Diskussion mit afair über 100 Posts ned so einfach zusammenfassbar is.

:arrow: Programmierung / FAQ und Archiv
Nehmt Fehler hin und weißt mich bitte freundlich darauf hin, damit ich es ändern kann. Die Codes sind auch alle ungetestet, aber sollten funktionieren, bzw. nur kleine Denkfehler, oder Syntaxfehler haben ;)
Mir is beim Durchlesen nur n Fehler im Syntaxhighlighting aufgefallen. Die fehlende Klammer hab ich dir schon eingefügt.

So und jetzt ne kleine Verständnisfrage von mir:
Für Werberlose gibts den werber 0, der aber in späteren Ebenen nicht mehr aufgeführt wird.
WTF sind Werberlose ? Gehts da um den Refback ?
 
Achso. "Werberlos" als Adjektiv. Sag das doch :ugly:

Brauchen die 'n Eintrag ? Ich würde sogar sagen, die dürften keinen haben, sonst wäre dein System ja nicht mehr konsistent, da du in höheren Ebenen das weglassen willst.
Wenn ich wissen will, ob es einen Werber gibt und die Datenbank liefert kein Resultat, dann bin ich werberlos.

Außerdem würde ein Join zu großen Problemen führen. Bsp:
"Selektiere die Loseguthaben und die Nicknames aller meiner Werber"
Für User 2 (aus deinem Bsp) alles ok. Dieselbe Abfrage für User 1 führt plötzlich zu komischen NULL-Werten, da ein User mit der ID 0 sicher nicht in der Usertabelle mit Nickname und Loseguthaben geführt wird.
 
Achso. "Werberlos" als Adjektiv. Sag das doch :ugly:
Stimmt, so hätt ichs ja viel einfacher ausdrücken können xD

Brauchen die 'n Eintrag ? Ich würde sogar sagen, die dürften keinen haben, sonst wäre dein System ja nicht mehr konsistent, da du in höheren Ebenen das weglassen willst.
Wenn ich wissen will, ob es einen Werber gibt und die Datenbank liefert kein Resultat, dann bin ich werberlos.
Stimmt, so könnt mans auch machen. Ich hab bis jetzt immer geguckt, obs nicht 0 ist, aber wenn kein Result da ist, weiß mans ja eigentlich auch ;)

Außerdem würde ein Join zu großen Problemen führen. Bsp:
"Selektiere die Loseguthaben und die Nicknames aller meiner Werber"
Für User 2 (aus deinem Bsp) alles ok. Dieselbe Abfrage für User 1 führt plötzlich zu komischen NULL-Werten, da ein User mit der ID 0 sicher nicht in der Usertabelle mit Nickname und Loseguthaben geführt wird.
Sowas hab ich noch nie gebraucht, deswegen hatte ich sonnen Fall auch noch nie bedacht. Das mit der 0 hab ich gespeichert, damit es für jeden User mindestens einen Eintrag gibt.
 
Das mit der 0 hab ich gespeichert, damit es für jeden User mindestens einen Eintrag gibt.
Das is aber nicht wichtig. Verbraucht dir eh nur Speicher.

Mal paar Beispiel, damit es klar wird:
Notierst du dir eine Auszahlungsanforderung über 0,00€, wenn der User grade keine Anforderung gemacht hat ?

Den einzigen Vorteil, den ich an der "Werber 0"-Variante seh, ist, dass du ohne zusätzlichen Join an alle werberlosen IDs rankommst.
Im Zeitalter von "Ich kaufe Geworbene ein" (wie kann man ein System nur dermaßen ad absurdum führen ? :wall: ) könnte es vielleicht von Nutzen sein. Nachteil is halt die Sonderbehandlung, weil du überall auf Werber!=0 checken musst.

Wie ich damals auf einer Loseseite dieses Referalsystem installiert hab, hab ich zusätzlich noch Spalten für prozentuales Refback, heutige Werberlose (jetzt Substantiv :mrgreen:) und die gesamt jemals erhaltenen Werberlose hinzugefügt.
Damit ergibt sich dann automatisch das Feature von mehrstufigen Refback, d.h. der Werber Stufe 2 (also der Werber meines Werbers) kann mir Refback geben.

Würde es jetzt Werber==0 geben, kommt das sehr komisch rüber, wenn ich in meinem UserCP seh:
Du hast insgesamt 1 Werber:
(0) Refback: [0%] - 0 Lose heute, 0 Lose gesamt
Der Nickname fehlt, weil der Join da NULL geliefert hat, der Link auf das Userprofil geht auf ne Fehlerseite, wo nur steht "User nicht gefunden" und auch sonst wundert mich das alles.

Wenn man alles abfängt, ok. Aber es is doch n großer Mehraufwand, weil überall, wo Refberechnungen (Downline, Statistiken, Refback, Refralley, kp, was man sich noch alles einfallen lässt) gemacht werden, der Sonderfall herausgefiltert werden muss.
 
Für ein Refsystem würde ich Nested Trees empfehlen. Und Spalten zu Nummerieren ist schon mal aus Prinzip schlecht und wird jedem mit Kenntnissen von DB Systemen Übelkeit hervorrufen.