umprogrammierung von mysql zu mysqli macht probleme

ptron

Grünschnabel
Hallo,

ich bin gerade dabei meine Datenbankverbindung auf mysqli umzustellen und habe aber jetzt probleme mit meinem sessionhandler.

zunächst einmal meine alte Version:

db_connect.php
Code:
    $mysql_host = 'localhost';
    $mysql_user = 'root';
    $mysql_pass = '';
    $mysql_db = 'login';

    @mysql_connect($mysql_host,$mysql_user,$mysql_pass) OR die("Error: ".mysql_error());
    mysql_select_db($mysql_db) OR die("Error: ".mysql_error());

session_config.php ( nur ein Codeausschnitt )
Code:
 // Die db_connect.php und danach die session_config.php sind in der index.php eingebunden

    function session_read($session_id) {
        $sql = "SELECT
                                  session_value
                FROM
                                  sessions
                WHERE
                                  session_id = '".mysql_real_escape_string($session_id)."'
               ";
        $result = @mysql_query($sql);

        if (!$result)
            return '';

        if (!mysql_num_rows($result))
            return '';

        $row = mysql_fetch_assoc($result);
        return $row["session_value"];
    }

Jetzt zum eigentlich Problem:

ich habe meine db_connect.php umgeschrieben:
Code:
    $db = @new mysqli( 'localhost', 'root', '', 'login');
    if ($db->connect_errno) {
        die('Die Datenbank ist momentan nicht erreichbar!');
    }

nur brauche ich nun die $db in den Funktionen und müsste deswegen die db_connect.php in jede Funktion extra einbinden, was aber sehr unschön aussieht, gibt es da eine elegantere Möglichkeit das zu lösen ?
Und wo ich mir auch noch unsicher bin, sollte ich am Ende jeder Funktion die mysqli verbindung mit close trennen ?

ptron
 
ich tauche gerade erst in die Welt der OOP ein und kann daher damit noch fast nichts anfange. Gibt es noch eine Andere möglichkeit ? Wenn nicht muss ich halt in jede Funktion eine Verbindung zur Datenbank herstellen und wieder trennen, wird das viel Performance verbrauchen ?
 
Ja, das wird viel Performance kosten. Daher nimmt man ja das Singleton. Es hat den Vorteil, das man es von überall aus aufrufen kann, ohne die $db-Instanz global machen zu müssen (was immer ein Risiko ist).

Es funktioniert so:

PHP:
// Ungetestet
final class DB extends MySQLi
{
  private static $instance;

  // Die Instanz abholen, wenn noch nicht existent, eine erzeugen
  public static function getInstance()
  {
    if( ! self::$instance )
    {
      self::$instance = new self();
    }
    return self::$instance;
  }

  // Constructor muss privat sein, damit man von der Klasse DB keine Instanz manuell erzeugen kann
  private function __construct()
  {
    parent::__construct('localhost', 'root', '', 'login');
  }

  // Clonen unmöglich machen
  public function __clone() {}
}

Jetzt kannst in jeder Funktion (egal ob Klassen-Methode oder prozedurale Funktion) die DB-Instanz abholen. Dabei hast du automatisch eine Verbindung, wenn vorher noch keine existiert hat, wird sie erzeugt:

PHP:
function hello()
{
  $db = DB::getInstance();
  $db->query("SELECT * FROM tabelle WHERE spalte = 'wert'");
}

// Oder in einer Klasse
class Benutzer
{
  public function anmelden($benutzername, $passwort)
  {
    $db = DB::getInstance();
    $stmt = $db->prepare("SELECT * FROM benutzer WHERE benutzername = ? and passwort = ?");
    $stmt->bindValue(1, $benutzername);
    $stmt->bindValue(2, $passwort);
    $stmt->bindResult($ergebnis);
    $stmt->execute();

    if( $ergebnis )
    {
       // Hier Code ausführen, wenn Benutzer erfolgreich angemeldet
    }
    else
      throw new Exception("Benutzer konnte nicht angemeldet werden!");
  }
}

$benutzer = new Benutzer();
try
{
  $benutzer->anmelden($_POST['benutzername'], $_POST['passwort']);
}
catch($exception)
{
  echo $exception->getMessage();
}

/// usw. usf.

Wenn du richtig in OOP einsteigen willst, solltest du dich mit Entwurfsmustern vertraut machen und wissen, wann man sie einsetzt. Dadurch ersparst du dir ne Menge Arbeit und Nerven. Von der Schönheit des Codes reden wir mal gar nicht ;-)
 
Hey

vielen dank für den hilfreichen Code, er funktioniert. Werde aber noch eine weile brauchen bis ich den Code komplett verstanden habe. Habe jetzt meine db_connect umgeändert:

db_connect.php
Code:
final class DB extends MySQLi
{
  private static $instance;
 
  // Die Instanz abholen, wenn noch nicht existent, eine erzeugen
  public static function getInstance()
  {
    if( ! self::$instance )
    {
      self::$instance = new self();
    }
    return self::$instance;
  }

  // Constructor muss privat sein, damit man von der Klasse DB keine Instanz manuell erzeugen kann
  private function __construct()
  {
    parent::__construct('localhost', 'root', '', 'login');
  }
 
  // Clonen unmöglich machen
  public function __clone() {}
}

    $db = DB::getInstance();
    if ($db->connect_errno) {
        die('Die Datenbank ist momentan nicht erreichbar!');
    }

Am Ende überprüfe ich die Datenbankverbindung, das heißt, dass ab jetzt eine Verbindung besteht und wenn jetzt mein sessionhändler eine änderung vornimmt, wird die Verbindung nicht erneut aufgebaut sondern die aktuelle benutzt und die Verbindung wird erst beendet wenn ich sie mit close schließe ?

Was ich mich noch frage, sollte ich in jeder Sessionfunktion die Verbindung überprüfen oder reicht es einmal am Anfang des Scriptes ?
 
Ich würde das ganz anders lösen: Du möchtest OOP verwenden, dann mach dir Exceptions zu nutze. Da geht man folgendermaßen vor: Wenn ein Fehler auftritt, wirft man eine Exception. Diese muss an entsprechender Stelle gefangen und verarbeitet werden, sonst wird das Script mit Fatal-Error beendet. Du kannst deine getInstance()-Methode erweitern:

PHP:
  // Die Instanz abholen, wenn noch nicht existent, eine erzeugen
  public static function getInstance()
  {
    if( ! self::$instance )
    {
      self::$instance = new self();
    }

    if( self::$instance->errno != 0)
    {
      // Fehlermeldung merken
      $connectionErrorString = self::$instance->error;
      // Instanz invalidieren
      unset( self::$instance );
      throw new Exception( $connectionErrorString );
    }
    return self::$instance;
  }

Jetzt musst du nicht mehr prüfen, ob eine Verbindung besteht. Wenn keine besteht, wird sie aufgebaut und bleibt offen, bis du sie Code-technisch schließt. Wenn der Aufbau nicht statt finden kann (z.B. Server nicht erreichbar), wird eine Exception geworfen. Diese muss dann nur noch abgefangen und dargestellt werden:

PHP:
try
{
  DB::getInstance();
}
catch( $exception )
{
  echo $exception->getMessage();
}

Das schönste daran kommt dann später -> Stichwort Transaktion & Rollback.
 
Erstmal vielen dank für die super Hilfe.

ich glaube du hast in deinem 2. Codeblock etwas vergessen.
müsste catch( $exception ) nicht catch(Exception $exception ) heißen ?

Ich finde es unschön, wenn ein fehler kommt, dass dann die ganzen Warnings ausgegeben werden, ich würde lieber einen Satz ausgeben: Die Datenbank ist momentan nicht erreichbar!
Habe versucht im catchblock echo $exception->getMessage(); mit die('Die Datenbank ist momentan nicht erreichbar!'); zu ersetzen, allerdings ohne Erfolg, der Satz wird bei einem verbindungsfehler nicht ausgegeben und es kommen immernoch die ganzen Warnungen ( habe zum Testen den mysql-server ausgeschalten )

hier die ganzen warnungen:
Warning: mysqli::mysqli() [mysqli.mysqli]: [2002] Es konnte keine Verbindung hergestellt werden, da der Zielcomputer die Verbindung verweigerte. (trying to connect via tcp://localhost:3306) in C:\xampp\htdocs\login3\db_connect.php on line 29

Warning: mysqli::mysqli() [mysqli.mysqli]: (HY000/2002): Es konnte keine Verbindung hergestellt werden, da der Zielcomputer die Verbindung verweigerte. in C:\xampp\htdocs\login3\db_connect.php on line 29

Warning: DB::getInstance() [db.getinstance]: Couldn't fetch DB in C:\xampp\htdocs\login3\db_connect.php on line 15

Warning: DB::getInstance() [db.getinstance]: Couldn't fetch DB in C:\xampp\htdocs\login3\db_connect.php on line 15

Warning: mysqli::prepare() [mysqli.prepare]: Couldn't fetch DB in C:\xampp\htdocs\login3\server_config.php on line 22

Fatal error: Call to a member function bind_param() on a non-object in C:\xampp\htdocs\login3\server_config.php on line 23
 
Mir ist gerade noch ein Fehler in deiner Singleton-Klasse aufgefallen, genauer gesagt was deine clone-Methode betrifft: du solltest sie als private deklarieren und nicht als public, denn so ist das etwas unsinnig.
 
Ihr habt beide recht ;-)

Ich hab das ohne es zu testen einfach aus dem Kopf runter schrieben. Hier mal eine etwas bessere überarbeitete Version, die ich kurz angetestet habe:

PHP:
<?php
/**
 * Eine eigene Exception-Klasse für Datenbank-Aktionen
 */
final class DBException extends Exception
{
}

/**
 * Die MySQLi-Klasse als Singleton
 *
 * - Nicht erweiterbar durch Kind-Klasse (final)
 * - Implementiert Singleton-Design-Pattern
 * - Wirft Exceptions 
 */
final class DB extends MySQLi
{
	/**
	 * Singleton-Instanz
	 * 
	 * @var DB
	 */
	private static $instance;

	/**
	 * Singleton-Pattern getInstance()-Methode
	 * 
	 * @static
	 * @return DB
	 */
	public static function getInstance()
	{
		if( ! self::$instance )
		{
			self::$instance = new self();
		}
		
		return self::$instance;
	}

	/**
	 * Constructor
	 * 
	 * Um die Singleton-Pattern-Kriterien zu erfüllen
	 * muss das Teil privat sein
	 * 
	 * @throws DBException
	 */
	private function __construct()
	{
		@parent::__construct('127.0.0.1', 'root', '', 'mysql');
		
		if(mysqli_connect_errno())
		   throw new DBException("Die Datenbank ist momentan nicht erreichbar!");
	}

	/**
	 * Keine Kopien der Instanz erzeugen lassen
	 * 
	 * @access private
	 */
	private function __clone() {
	}
}

// Jetzt geht's los ;-)
try
{
	$db = DB::getInstance();
}
catch(DBException $exception)
{
	echo $exception->getMessage();
}
 
Zuletzt bearbeitet:
morgen,

hab deinen Code eingebunden, aber ich habe immer noch ein Problem, hab die ganze Nacht versucht das problem zu lösen, jedoch komme ich einfach nicht drauf.

Hier ist nun die db_connect.php
Code:
/**
 * Eine eigene Exception-Klasse für Datenbank-Aktionen
 */
final class DBException extends Exception
{
}
 
/**
 * Die MySQLi-Klasse als Singleton
 *
 * - Nicht erweiterbar durch Kind-Klasse (final)
 * - Implementiert Singleton-Design-Pattern
 * - Wirft Exceptions 
 */
final class DB extends MySQLi
{
    /**
     * Singleton-Instanz
     * 
     * @var DB
     */
    private static $instance;
 
    /**
     * Singleton-Pattern getInstance()-Methode
     * 
     * @static
     * @return DB
     */
    public static function getInstance()
    {
        if( ! self::$instance )
        {
            self::$instance = new self();
        }
        
        return self::$instance;
    }
 
    /**
     * Constructor
     * 
     * Um die Singleton-Pattern-Kriterien zu erfüllen
     * muss das Teil privat sein
     * 
     * @throws DBException
     */
    private function __construct()
    {
        @parent::__construct('127.0.0.1', 'root', '', 'login');

        
        if(mysqli_connect_errno())
           throw new DBException("Die Datenbank ist momentan nicht erreichbar!");
    }
 
    /**
     * Keine Kopien der Instanz erzeugen lassen
     * 
     * @access private
     */
    private function __clone() {
    }
}
try
{
    $db = DB::getInstance();
}
catch(DBException $exception)
{
    echo $exception->getMessage();
}

zum testen binde ich die db_connect.php und die server_config.php ( sessionhändler ) ein und erstelle eine Session und schreibe eine Variable rein.

Dann bekomme ich folgende Fehlermeldung:
Fatal error: Uncaught exception 'DBException' with message 'Die Datenbank ist momentan nicht erreichbar!' in C:\xampp\htdocs\login3\db_connect.php:55 Stack trace: #0 C:\xampp\htdocs\login3\db_connect.php(35): DB->__construct() #1 C:\xampp\htdocs\login3\server_config.php(19): DB::getInstance() #2 [internal function]: ses_read('npe8svefoe70fd0...') #3 C:\xampp\htdocs\login3\server_config.php(72): session_start() #4 C:\xampp\htdocs\login3\index.php(17): require_once('C:\xampp\htdocs...') #5 {main} thrown in C:\xampp\htdocs\login3\db_connect.php on line 55

Fatal error: Uncaught exception 'DBException' with message 'Die Datenbank ist momentan nicht erreichbar!' in C:\xampp\htdocs\login3\db_connect.php:55 Stack trace: #0 C:\xampp\htdocs\login3\db_connect.php(35): DB->__construct() #1 C:\xampp\htdocs\login3\server_config.php(38): DB::getInstance() #2 [internal function]: ses_write('npe8svefoe70fd0...', '') #3 {main} thrown in C:\xampp\htdocs\login3\db_connect.php on line 55

db_connect.php on line 55 :
Code:
throw new DBException("Die Datenbank ist momentan nicht erreichbar!");
 
Zurück