Datenbankverbindung aus Session
Stefan
- php
0 Henrico Hamstar0 Stefan
0 dedlfix
Hallo,
Ich habe mich damit beschäftigt eine Klasse zu basteln, die für den Zugriff auf die Datenbank zuständig ist. Eine Instanz dieser Klasse sollte dann in der Session gespeichert werden, damit ich nur einmal eine Verbindung aufbauen muss. So war der Plan aber inzwischen bin ich mir nicht mehr so sicher, ob der so ideal war (vor allem nach Svens Artikel).
Jetzt habe ich jedoch meiner Meinung nach alle nötigen Funktionen der Klasse hinzugefügt, um sie nach Versand mittels $_SESSION zu einem anderen Skript die Verbindung zur Datenbank wieder aufbauen zu lassen. Das geschieht jedoch nicht und mein Ehrgeiz möchte wissen, warum nicht.
Das ist meine Klasse:
class db_handler
{
// VARIABLEN
protected $mysqli;
private static $uniqueInstance = NULL;
// FUNKTIONEN
protected function __construct()
{
echo "<br><b>__construct</b><br>";
print_r($this);
$this->connect();
}
private function connect()
{
echo "<br><b>connect</b><br>";
try
{
$conf = simplexml_load_file("inc/dbconf.xml");
$this->mysqli = new mysqli($conf['host'], $conf['uid'], $conf['pwd'], $conf['db']);
if(mysqli_connect_errno())
{
throw new Exception(mysqli_connect_errno() . " Could not establish connection to database");
}
}
catch(Exception $e)
{
echo $e->getMessage();
$this->mysqli = FALSE;
exit();
}
print_r($this);
}
private final function __clone() {}
public static function getInstance()
{
if(self::$uniqueInstance === NULL)
{
self::$uniqueInstance = new db_handler;
}
return self::$uniqueInstance;
}
function __destruct()
{
$this->close();
}
function __sleep()
{
echo "<br><b>__sleep</b><br>";
print_r($this);
$this->close();
echo "<br><b>__sleep2</b><br>";
print_r($this);
return array();
}
function __wakeup()
{
echo "<br><b>__wakeup</b><br>";
print_r($this);
self::$uniqueInstance = new db_handler;
}
}
Der Code meines ersten Skriptes sieht so aus (in temp.php ist die Klassendefinition):
include('temp.php');
$t = db_handler::getInstance();
session_start();
$a = $t->queryObjectArray("SELECT * FROM member;");
/*print_r($a);
echo "<br>";*/
$_SESSION['db'] = $t;
//header('Location: test2.php');
und liefert diese Ausgaben:
__construct
db_handler Object ( [mysqli:protected] => )
connect
db_handler Object ( [mysqli:protected] => mysqli Object ( ) )
__wakeup
db_handler Object ( [mysqli:protected] => )
__construct
db_handler Object ( [mysqli:protected] => )
connect
db_handler Object ( [mysqli:protected] => mysqli Object ( ) )
__sleep
db_handler Object ( [mysqli:protected] => )
__sleep2
db_handler Object ( [mysqli:protected] => )
Wenn ich dann auf eine andere Seite mit dem Code
include('temp.php');
session_start();
$t = $_SESSION['db'];
echo "<br><br><br>In test2.php<br>";
print_r($t);
$t->execute("SELECT * FROM member;");
echo "<br>§§§<br>";
weiterleiten lasse, dann bekomme ich folgende Ausgabe:
__wakeup
db_handler Object ( [mysqli:protected] => )
__construct
db_handler Object ( [mysqli:protected] => )
connect
db_handler Object ( [mysqli:protected] => mysqli Object ( ) )
In test2.php
db_handler Object ( [mysqli:protected] => )
Fatal error: Call to a member function real_query() on a non-object in C:\Programme\Apache Group\Apache2\htdocs\kendo-do\temp.php on line 130
__sleep
db_handler Object ( [mysqli:protected] => )
__sleep2
db_handler Object ( [mysqli:protected] => )
Die Verbindung wird also in der Funktion connect() aufgebaut aber das in der Session vorhandene Objekt scheint es nicht mitzubekommen.
Was mache ich falsch?
Viele Grüße und danke an alle, die bis zu dieser Zeile durchgehalten haben,
Stefan
Eine Instanz dieser Klasse sollte dann in der Session gespeichert werden, damit ich nur einmal eine Verbindung aufbauen muss.
Nur dazu etwas:
Eine Session ist eine sitzungsbezogene Datenhaltung. Eine Sitzung ist eine erkannte Anforderungsfolge durch ein und denselben Nutzer. ein Nutzer kann also bspw. um 13:12, um 13:34 und um 14:34 Uhr zugreifen. Um 14:34 Uhr kriegt er vielleicht nur noch eine abblockende Antwort, da Sitzungen fast immer zeitlich begrenzt sind. Sitzungen werden ueber das Hin- und Hersenden einer eindeutigen Kennung identifiziert, die beim Erstzugriff vergeben wird.
Was willst Du jetzt in diesem Szenario eigentlich genau machen? Willst Du eine Datenbankverbindung offenhalten? Zwischen welchen Diensten? Mit welchem Ziel?
Wie wuerdest Du die Sache nicht OO-maessig formulieren?
Hallo Henrico,
Was willst Du jetzt in diesem Szenario eigentlich genau machen? Willst Du eine Datenbankverbindung offenhalten? Zwischen welchen Diensten? Mit welchem Ziel?
Wie wuerdest Du die Sache nicht OO-maessig formulieren?
Ein Benutzer meldet sich an die Datenbank meiner Anwendung (aka Spielwiese) an. Die dabei aufgebaute Verbindung zur Datenbank sollte offen gehalten werden, damit er dann Datenbankeinträge abfragen, einfügen, verändern und löschen kann.
Der Plan war also, dass ich anfangs einmal
include('classes/class.db_handler.$oDBHandler = db_handler::getInstance();
session_start();
$_SESSION['oDBHandler'] = $oDBHandler;
ausführe und dann in jeder Funktion, die CRUDL macht, "nur"
$oDBHandler = $_SESSION['oDBHandler'];
$SQL = irgendwas;
$res = $oDBHandler->execute($SQL);
aufrufen muss und nicht etwas in der Art
$connection = mysqli_connect($db['host'],$db['uid'],$db['pwd'], $db['db']);
if($connection)
{
$sql = '...';
$result = mysqli_query($sql);
...
}
Anfangs dachte ich, dass ich durch OO und das Speichern der Datenbankverbindung Quellcode spare bzw. dass das besonders toll wäre. Nach meinen bisherigen Versuchen und weiterer Recherche komme ich langsam zu der Erkenntnis, dass es vielleicht doch nicht der Weisheit letzter Schluss ist. Zumal ich ja gar keinen Quellcode spare (Klassendefinition muss immer eingebunden werden, Session muss immer gestartet werden).
Den jetzigen Versuch habe ich wegen Deiner Antwort (letzter Absatz) unternommen.
Viele Grüße,
Stefan (weiter durch die Gegend programmierend :))
Ein Benutzer meldet sich an die Datenbank meiner Anwendung (aka Spielwiese) an. Die dabei aufgebaute Verbindung zur Datenbank sollte offen gehalten werden, damit er dann Datenbankeinträge abfragen, einfügen, verändern und löschen kann.
Besser ist es die Verbindung bei jedem Datenzugriff (bzw. fuer jedes Scripts) neu aufzubauen. Wenn wir eine Webanwendung mit Sessions haben, und die haben wir anscheinend, dann sollte die Verbindung nicht zu lange offen bleiben. Ausserdem ist mir unklar wie Du bspw. mit PHP auf eine Verbindung zugreifen moechtest, die von einem anderen PHP-Script aufgebaut worden ist. Einige (alle?) Datenserver bauen alte Verbindungen zudem selbststaendig ab.
Ausserdem ist mir unklar wie Du bspw. mit PHP auf eine Verbindung zugreifen moechtest, die von einem anderen PHP-Script aufgebaut worden ist.
Da war ich vielleicht etwas unklar. In der Session wird das Objekt gespeichert, das zur Kommunikation mit der Datenbank dienen soll. Die Verbindung zur Datenbank wird bei seiner Serialisierung gekappt. Wenn das Objekt aus der Session geholt wird, wird dessen Funktion __wakeup() aufgerufen. Das Objekt hätte genügend Informationen, um in dieser Funktion die Verbindung wieder aufzubauen.
echo $begrüßung;
Du solltest nicht zu sehr auf den Hamster/Hamstar hören. Er hat zwar grundlegende Datenbankahnung kennt sich jedoch mit den Details zu MySQL und PHP nicht weiter aus. Das hält ihn aber nicht vom Antworten ab, was dann teilweise zu derben Fehlaussagen führt.
Zunächst etwas Kleinkram:
catch(Exception $e)
{
echo $e->getMessage();
$this->mysqli = FALSE;
exit();
}
Wenn du sowieso abbrichst (exit()), brauchst du $this->mysqli nicht mehr durch das Zuweisen von false zum Destrukten zu bewegen, das passiert durch das Scriptende sowieso. Außerdem gehört es sich nicht für die Datenbankklasse, das Script zu beenden. Auftretende Fehler sollte sie an die aufrufende Instanz weitergeben, damit diese angemessen reagieren kann, z.B. eine (weniger detaillierte, denn diese Information interessiert den Anwender nicht) Fehlermeldung im Layout der restlichen Anwendung ausgeben.
include('temp.php');
include ist ein Sprachkonstrukt, keine Funktion. Es benötigt keine Klammern um den Dateinamen. Die Klammern bewirken hier nur, dass der darin enthaltene Ausdruck, der nur aus dem String besteht, berechnet wird, und dann erst dem include übergeben wird.
Nun zum eigentlichen Problem:
Was mache ich falsch?
Der Fehler sollte deutlicher sichtbar werden, wenn du var_dump() statt print_r() zur Kontrollausgabe verwendetest. Statt "db_handler Object" wird dann "object(db_handler)#1" angezeigt. Die #1 gibt dabei an, um welche Instanz es sich handelt. Probier das erstmal aus, bevor du weiterliest ...
function __wakeup()
{
echo "<br><b>__wakeup</b><br>";
print_r($this);
self::$uniqueInstance = new db_handler;
}
In __wakeup() lässt du dir mit $this das eben aus der Session wiederhergestellte Objekt #1 anzeigen. Danach erzeugst du ein neues Objekt #2 (inklusive __construct() und connect()) und weist es self::$uniqueInstance zu. Die aufgeweckte #1 hat __construct() und damit auch connect() nicht durchlaufen. Mit $_SESSION['db'] greifst du auf das wiederhergestellte Objekt #1 zu, nicht auf das konnektierte #2 in self::$uniqueInstance.
Allgemein wäre es besser, wenn du den Connect erst dann ausführst, wenn er wirklich benötigt wird, nicht schon prophylaktisch beim Konstruieren des Objekts. Jede Methode, die eine Datenbankverbindung braucht, muss dazu connect() aufrufen, welches die Verbindungskennung (bzw. das mysqli-Objekt) zurückliefert. Werden nur Methoden aufgerufen die keine Verbindung brauchen, gibt es kein unnötiges Connect.
Das Singleton-Pattern brauchst du dann nur für die über connect() abrufbare Verbindungskennung (bzw. das mysqli-Objekt), die in einer Klassenvariable (self::$connection) abgelegt wird. DB-Handler-Objekte kann es durchaus mehrere geben, z.B. für jedes Statement eine Instanz. Die Result-Kennung wäre dann eine Objekt- bzw. Instanzvariable. Damit kannst du eine Abfrage starten, und noch bevor das Ergebnis komplett ausgelesen ist, kannst du eine weitere Abfrage starten, deren Result-Kennung in ihrer eigenen Instanz gehalten wird. (Klappt nur nicht bei ungepufferten Abfragen. Also entweder mysqli::query() ohne zweiten Parameter bzw. mit MYSQLI_STORE_RESULT oder mysqli::real_query() mit mysqli::store_result() verwenden.)
Das Datenbankzugriffsobjekt solltest du nicht in der Session ablegen. Die Verbindung bleibt, wie du weißt, nicht erhalten, sie wird zum Scriptende geschlossen. Beim erneuten Start der Session konnektierst du sowieso erneut. Auch die Result-Kennung nützt dir nichts, da nur zusammen mit der Verbindung Daten von der DB gelesen werden können. Wenn du etwas in der Session ablegen möchtest, sollten das fertig abgefragte Daten (eigenes Objekt, Array oder sonstige Struktur) sein, von denen du ausgehen kannst, dass sie sich während der Session-Laufzeit nicht in der Datenbank ändern. Ansonsten solltest du andere Cache-Mechanismen verwenden, die auch sessionübergreifend bzw. -unabhängig funktionieren, und damit Daten als unbrauchbar, weil geändert, kennzeichnen können.
echo "$verabschiedung $name";