PHP 3 Fragen: Ersatz von Frames, Auto-Reload-Chat, Klassenkonzept

te one

Lt. Commander
Registriert
Apr. 2009
Beiträge
1.252
Hallo,

ich habe mir mal wieder ein Ziel gesetzt: Ein Browsergame soll her :)
Idee usw. ist schon vorhanden. Jetzt habe ich aber 3 Fragen bevor ich aktiv mit der Programmierung beginne:
1. Was nutzt man aktuell statt Frames? Habe schon häufig gelesen, dass die Technik ja veraltet sei und jetzt bin ich am überlegen was ich stattdessen nehmen soll (teilweiße müssen halt bereiche der seite per Javascript o. Ä. neu geladen werden können)
iFrames, normale include (geht dann ein reload u. Ä. noch per JS? Möglicherweiße auf DIV-Ebene?)
2. Wie bringe ich den Chat zum aktualisieren, sobald eine Nachricht geschrieben wurde? Ich könnte das ja alle 2-3 Sekunden abfragen und dann per Javascript neue Nachrichten anfügen, aber gibt es eine Möglichkeit das so zu lösen, dass der Webserver möglichst wenig belastet wird (oder ist das selbst bei möglicherweiße 100-1000 Benutzern online zu vernachlässigen, jede Sekunde zu aktualisieren?)
3. Suche ich aktuell noch nach einem Konzept um möglichst effektiv und übersichtlich zu programmieren. Wie würdet ihr solche Dinge wie zB die Benutzerattribute (also die Attribute des Spielercharakters zB. Lebenspunkte etc.) in PHP verwalten?
Dachte daran eine Klasse User o.Ä. zu machen und darauf dann NUR mit gettern und settern zu arbeiten, welche nicht auf das eigentliche Objekt, sondern auf die Datenbank zurückgreifen.
Wäre ja ganz gut umsetzbar. Nur wie mache ich es am einfachsten, dass ich auf verschiedene Nutzer im Skript zugreife? Hier mal meine Idee um das zu verdeutlichen:
PHP:
class User {
  public function get_Name ($id) {
    //return SQL SELECT Username FROM Users WHERE id=$id
  }
  public function get_HP ($id) {
    //return SQL SELECT HP FROM Users WHERE id=$id
  }
}

$dummie=new User; //Braucht man diesen Dummie?? Oder geht das auch direkt ohne ein Objekt anzulegen?
echo 'Dein Benutzername von Spieler 1 ist '.$dummie->get_Name(1);
Womöglich gibt es dafür einen Fachbegriff, jedoch kenne ich ihn nicht :D

Ich könnte das ganze natürlich ohne Klasse machen, jedoch würde dies einiges an Übersichtlichkeit schaffen.
Hätte dann halt eine Klasse User, NPC, Item, ...
Und dementsprechend kann ich dann super darauf zugreifen :) Bin auch gerne für ganz andere Vorschläge offen.

Danke schonmal :)
Gruß
Michael

Möchte es diesmal von Anfang an möglichst professionell aufziehen (Dritter Versuch. Die ersten beiden Games haben funktioniert, wurden aber schnell sehr unübersichtlich)
 
Zuletzt bearbeitet:
1.) div oder eines der anderen html-tags. Kann man genauso leicht mit JS refreshen lassen.
Ob das div per include aus einer anderen php-Datei einbettest oder direkt alles in einer Datei ist ja (für den Benutzer) egal, ist ja serverseitig.
2.) Der Client muss neu laden, der Server kann den Clienten nicht mitteilen, dass es was zu aktualisieren hat.
Ein aktualisieren per JS alle 2s und wenn der Benutzer was eingibt ist ganz ok für den Server.
3.) Ich weiß nicht genau was du willst. Per php und SQL umsetzten ist sicher das beste und einfachste. Wie du die Funktionen macht bleibt ja die überlassen, jeder hat da seine vorlieben.
 
te one schrieb:
1. Was nutzt man aktuell statt Frames?
...
(geht dann ein reload u. Ä. noch per JS? Möglicherweiße auf DIV-Ebene?)
Die Antwort heißt AJAX. Wer ein Browsergame spielt muss halt damit rechnen, dass er wenigstens JavaScript benötigt.

2. Wie bringe ich den Chat zum aktualisieren, sobald eine Nachricht geschrieben wurde? Ich könnte das ja alle 2-3 Sekunden abfragen und dann per Javascript neue Nachrichten anfügen, aber gibt es eine Möglichkeit das so zu lösen, dass der Webserver möglichst wenig belastet wird (oder ist das selbst bei möglicherweiße 100-1000 Benutzern online zu vernachlässigen, jede Sekunde zu aktualisieren?)
Wie viele User erwartest du? Was für Hardware hast du zur Verfügung?
In erster Linie heißt die Antwort auch hier AJAX ist der richtige Weg (beim Client). Nur muss man sich, je nach Usermenge, dann noch für die Servertechnik entscheiden. Bei nem nicht zu mickrig dimensionierten Server packste locker n paar Hundert User mit ner klassischen PHP-Lösung. Wenn es mehr werden soll, dann könntest du z.B. einen Schritt weiter gehen und HipHop einsetzen. Immerhin arbeitet Facebook damit (und hats entwickelt), kann also nicht so schlecht sein.
Die, meiner Meinung nach, Königsdisziplin der Client - Server - Kommunikation für solche asynchronen Aktionen wie einen Live-Chat ist dann aber schlichtweg ein Node.JS - Server. Mehr Skalierbarkeit geht nicht.

3. Suche ich aktuell noch nach einem Konzept um möglichst effektiv und übersichtlich zu programmieren. Wie würdet ihr solche Dinge wie zB die Benutzerattribute (also die Attribute des Spielercharakters zB. Lebenspunkte etc.) in PHP verwalten?
Ich würde nicht unbedingt jeden Getter/Setter direkt auf die Datenbank hetzen. Du könntest auch die ganzen Werte in erster Linie nach dem Login eines Users in einer Session lagern und manipulieren und evtl. ab und zu selbige Session in die Datenbank zurück schreiben.

Zu was ich dir, bei sehr vielen parallelen Anfragen, definitiv raten würde: Lass dir kein Hosting-Paket mit suPHP ans Bein nageln. Bei all den Vorteilen, die suPHP bietet (vor allem dem Hoster) hat es einen fetten Nachteil: Du kannst auf den Tod keine persistenten Datenbankverbindungen nutzen.
 
Lies dich erstmal in objektorientierte Programmierung ein. Dir scheinen schon die absoluten Grundlagen dafuer zu fehlen.

Mit PHP hast du dann auch noch eine Sprache an der Hand, die schlechten Programmierstil geradezu foerdert und auch noch lauter Fallen parat hat. Meiner Meinung nach alles andere als ideal zum Einstieg.
 
Zuletzt bearbeitet:
1) Okay, dann werde ich die Einbindung wohl in divs vornehmen.
Ja und JS ist schon okay. Wer das nicht aktivieren möchte, der kann gleich wieder gehen^^
2) Aktuell ist das alles nur Test und bei weitem (steht ja erst in den Startlöchern) noch nichts festes. Hardware steht also bis jetzt noch in den Sternen.
3) Das aber über die Session zu lösen hat auch immer wieder ein paar ungewisse Dinge. Wenn ich direkt mit der DB arbeite habe ich halt einen konsistenten Stand - zu jeder Zeit!
Genau static habe ich gesucht ;)

Ich denke eigentlich schon, dass ich vorerst dann mit einem kleinen Hostingpaket zurechtkomme. Und wenn es tatsächlich gut läuft und ich ein paar Spieler habe, kann ich ja durchaus expandieren :)

Wie man schon merkt habe ich bisher recht wenig mit OOP zutun gehabt. Aktuell bin ich beim lesen eines Buches zu JS und danach kommt ein kleines zu Ajax dran.
Mal sehen welche Fragen noch zu OOP im Laufe der Programmierung aufkommen.
 
Dein Ansatz, einen User mit einer statischen Klasse und entsprechend statischen Methoden zu modellieren, ist halt schon absurd. Du bombadierst deine DB mit Queries fuer jede einzelne Eigenschaft, die du irgendwann mal brauchst. Und das immer wieder. Die User-ID musst du auch staendig mit dir rumschleppen.

Deine Aussage zu Sessions ist auch fragwuerdig. Ob ein Objekt in einer Session vorgehalten wird, hat nicht zwangslaeufig damit zu, wie aktuell es ist.
 
Die Lösung mit statischen Klassen/Methoden wäre aber doch durchaus eine saubere Sache!?
Und das Problem ist halt, dass zB ein User womöglich irgendwie in einen Kampf verwickelt ist (-> HP ändern sich) was sich dann auch beim reload der Seite bei anderen Usern bemerkbar machen sollte. Und wenn ich das alles in eine Session packe, müssten diese Daten auch irgendwie zu den anderen Usern kommen. Und da sehe ich etwas das Problem...

Wie mache ich es denn dann besser? (Wäre mir halt jetzt so eingefallen, da es sehr übersichtlich ist)
 
Die Daten der User gehören in die Datenbank. Eine Session sollte nur Daten übertragen, welche eher zu vernachlässigen sind, wie Name im Spiel, Lebenspunkte uvm. Damit die Daten eines Spielers zum anderen gelangen, musst Du es eben über ein Datenbanksystem machen. (Sprichwort: MySQL)
 
Ja, dass die Daten in eine Datenbank müssen ist natürlich klar.
Die Frage ist nur ob ich bei jedem Zugriff auf Daten eben auf die DB zurückgreife oder das irgendwo cache (hier sei zB mal Session genannt).
Das Problem das ich hier aber sehe ist, dass sich die Daten in der DB schon geändert haben könnten und sie in der Session in einer veralteten Version noch vorhanden sind.

Also von der Programmierung her und um ständig einen aktuellen Stand zu haben würde ich das eben mit getern und setern lösen, die IMMER auf die DB zugreifen.
(Logisch ist, dass ich zB nach dem Login in der Session speichere, dass sich der Benutzer authentifiziert hat)
 
Eine Variante wäre z.B., dass du eine persistente Serversoftware schreibst, die immer alles "weiß", was gerade los ist. Das würde bei einmalig laufendem Serverdienst in etwa so aussehen:

- User will sich einloggen
- Server erlaubt Login, Server erzeugt in seinen eigenen Variablen einen User mit allen Attributen (durch einmaliges Lesen der DB)
- jede Attributsänderung durch Kämpfe oder sonstwas wird erst einmal nur in den Variablen gespeichert und nicht direkt in der DB. Regelmäßig wird der Status in die DB zurück geschrieben

Auf diese Weise hast du nicht jedes Mal, wenn irgend jemand sehen will, wie viele HP HansWust hat, einen DB-Zugriff sondern nur ne Anfrage an die Datenstruktur deines Serverprozesses.
Eine ähnliche Variante könnte man übrigens auch über NoSQL-Lösungen wie RedisDB erreichen. Hier wird ebenfalls etwas Persistenz für viel Performance geopfert.
 
Ein einfacher Ansatz wäre, die benötigten Objekte bei jedem Request aus der DB zu laden. Einmal. Du kannst mit einer Query einen kompletten User in ein Objekt laden und das dann verweden. Das lässt sich in Zukunft dann sehr gut mit erweiterten Caching-Mechanismen verbinden.

Du kannst dazu auch ein schon existierendes ORM-System verwenden.

Statische Methoden und Getter, die jedes mal die DB fragen, brauchst du dafuer nicht.

Edit: Wenn es komplexer wird, lies dich mal in Concurrency (mit Bezug zu PHP), Transactions und Locking ein.

Aber: Man kann Sachen auch unnötig beliebig kompliziert machen. Generell kann ja nur was passieren, wenn mehrere Requests sich überlappen UND das gleiche Datum in einer ungünstigen Reihenfolge verändern. Ein Request dauert aber nur wenige Millisekunden. Das ist schon sehr unwahrscheinlich.

Ohne Details deines Spiels zu kennen, kannst Aktionen aber auch so umsetzen, dass du Events in einer anderen Tabelle ablegst, z.B. "Spieler A baut auf Feld X ein Gebäude". Jetzt kommt Spieler B gleichzeitig an und will auf X auch ein Gebäude bauen. Daher kommt in die Tabelle als nächstes "Spieler B baut auf Feld X ein Gebäude" (ob A oder B "zuerst" dran war, darum kümmert sich die Datenbank für dich automatisch).

Diese Events arbeitest du dann irgendwo anders nacheinander ab. Dadurch kannst du etwaige Inkonsistenzen vermeiden. A hat das Feld zuerst bebaut, also kann B es nicht mehr bebauen. A freut sich, B bekommt eine Fehlermeldung.
 
Zuletzt bearbeitet:
Ich würde sagen, dass macht das ganze nur unnötig kompliziert.
Überlappende Requests werden an sich nicht/kaum vorkommen, und falls das doch mal passiert (wobei es wirklich nur ganz wenige Fälle geben wird, bei denen die gleichen Datensätze dann verändert werden) wird das in der Regel keine schweren Folgen haben (jedenfalls entstehen keine widersprüchlichen Einträge in der DB).

Im Endeffekt brauche ich erstmal folgende Daten aus der DB (beim Seitenaufruf):
- Welche anderen Spieler/NPCs stehen an der Stelle des Users? (IDs)
- Namen, evtl. aktuelle Lebenspunkte oder andere Details der Spieler/NPCs (anhand der IDs)
- Infos zum eigenen Charakter
Ist ja nicht so, dass ich da tausende Datensätze jedes mal auslese. Und jeder User wird das wohl nur alle 2-5 Sekunden (maximal) einmal machen.
Zzgl. kommt dann halt bei jeden noch alle 2 Sekunden eine Anfrage an die DB zwecks neuer Chatnachrichten sowie ein Update des Onlinestatus (vllt so alle 5-10 Sekunden).
 
Ich sage ja, dass du es nicht kompliziert machen sollst. Deswegen ist dein komischer statische Getter-Ansatz auch unnötig. Verwende einfach stinknormale Objekte. Dafür sind die da.

PHP:
class User {
  private $id, $name; // plus weitere Felder

  public static function getById($uid) {
    // SELECT ... FROM users WHERE id = :uid
    $data = ... // Daten aus der DB

    if (...) // User gefunden
      return new User($data);
    else
      return null;
  }

  public function __construct($data) {
    // Objekt mit Daten füllen
  }

  public function getName() {
    return $this->name;
  }

  // mehr getter, setter, anderer Kram
}

// Irgendwo anders dann:

$user = User::getById($id);

if (!$user)
  throw new Exception("Unbekannter Benutzer");

echo "Hallo ".htmlspecialchars($user->getName());
 
Zuletzt bearbeitet:
Hab da mal was gecodet.
Ich möchte ja nicht für alle User der DB ein Objekt erzeugen, sondern nur wenn es benötigt wird.
Das Skript sollte, falls noch keine Daten vorhanden sind das Objekt automatisch erzeugen und ansonsten soll es von selbst angelegt werden. Hier mal meine Überlegung:

PHP:
$Users=Array();
class User {
    private $id;
    private $name;
    //usw

    public static function getById($id) {
        global $Users;
        if (!isset($Users[$id])) {
            $Users[$id]=new User($id);
        }
        return ($Users[$id]);
    }

    public function __construct($id) {
        //Daten aus DB in Attribute schreiben
    }
    
    //Getter und Setter
}

Dann kann ich überall im Skript sagen:
User::getById(5)->getName();
oder??
 
Zuletzt bearbeitet:
Mein Beispiel lädt den User ja auch nur, wenn er benötigt wird. Genau dann, wenn du getById() aufrufst eben. Es wird natürlich immer nur der User mit der entsprechenden ID geladen, nicht alle.

Du hast jetzt noch einen Cache eingebaut, was durchaus keine schlechte Idee ist.

Ob du die eigentliche Datenbankabfrage in den Constructor oder in getById machst, ist dir überlassen. Ich bevorzuge aus diversen Gründen letzteres.

Statt einer globalen Variable nimmst du am besten eine static-Variable an der Klasse.

PHP:
class User {
    private static $users = array();

    public static function getById($id) {
        // schauen, ob User schon geladen wurde
        if (isset(self::$users[$id]))
            return self::$users[$id];

        // userdaten laden
        // wichtig: wenn userdaten nicht gefunden, irgendwie behandeln
        // objekt erzeugen
        // objekt in self::$users speichern
        // objekt zurückgeben
    }

    // ...
}

// irgendwo anders:
User::getById(5)->getName();  // crasht wenn user nicht gefunden wurde, musst du behandeln

Hier werden abgewandelte Factory- und Registry-Patterns verwendet.
 
Zuletzt bearbeitet:
Ja, diese Cache-Funktionalität möchte ich unbedingt einbauen.
Denn sonst müsste ich ja, wenn zB der Username des Users mit der ID 1 weiter oben schonmal abgefragt wurde, diese Variable entweder weiter unten wieder nutzen bzw. eben den Pointer aufs Objekt mitschleppen.

Und so würde ich die DB nur einmal belasten, wenn zB ein User auf dem eigenen Feld steht (Name, Lebenspunkte, ...) und weiter unten er nochmal (sei es im Chat, Freundesliste, oder was auch immer) erscheint.

Hätte die Daten aus der DB jetzt wegen der Logik her im Konstruktor geladen. Denn sonst müsste ich in getById erst den Konstruktor aufrufen und ihm entweder die Werte per Parameter übergeben oder eben nochmal extra per this->... einspeichern.
 
Kann man so machen, ja.

Ob du aber die User-ID (brauchst du ja bei getById) oder das eigentliche Objekt "mitschleppst", gibt sich nicht viel. Du kannst es auch in eine Instanzvariable deines Controller-Objekts stecken, dass Registry-Pattern mit einer separaten Registry umsetzen oder eben $_SESSION verwenden.

Jedenfalls hast du jetzt schon mal einen Ansatz. Viel Erfolg.
 
Jup, vielen Dank.

Wenn ich das Objekt hätte "mitgeschleppt" hätte ich halt auch ein Array für die Objekte anlegen müssen.
Denn an die IDs komme ich immer (z.B. welche IDs stehen auf meinem Feld? Wieviele HP hat Spieler mit ID xyz?...) Deshalb habe ich immer an der Stelle wo ich Daten benötige auch die entsprechende ID.
Ansonsten hätte ich halt $User_ObjectArray=Array(); oder so gemacht und dann dort wieder anhand der IDs die Objekte hinterlegt.
Aber die aktuelle Lösung ist top, das bleibt so.
 
Zurück
Oben