[MySQLi] Einige Umstellungsfragen MySQL auf MySQLi zum Verständnis

filament

Erfahrenes Mitglied
Hallo Leute,

ich versuche gerade ein Projekt von mir auf PHP7 umzustellen und musste mich daher mit MySQLi beschäftigen. Habe ich heute auch etwas intensiver gemacht. Allerdings blicke ich einige Sachen noch nicht so genau und möchte daher mal nachfragen.

Bereits verstanden habe ich, dass es einen objektorientieren Stil und einen prozeduralen Stil gibt. Ich würde gern den objektorientierten Stil nutzen. Was ich bisher verstanden habe:

Verbindung herstellen, ist wohl selbsterklärend:
PHP:
$db = new mysqli($server, $user, $passwort, $datenbank);

/* Check Connection */
if (mysqli_connect_errno())
{
  printf("Verbindung fehlgeschlagen: %s\n", mysqli_connect_error());
  exit();
}

Ganz gewohnt kann ich nun den entsprechenden Query festlegen:

PHP:
$query = 'SELECT * FROM tabelle WHERE ...';
$query = 'INSERT INTO tabelle (name, vorname) VALUES (?, ?)';

Hier aber bereits die erste Ungeklärtheit (für mich zumindest). Wann nutze ich Fragezeichen und wann nicht? Ich habe SELECT Parameter gesehen wo steht WHERE id > 500, aber auch solche wo steht WHERE id > ? Was ist nun richtig?

Nach dem Query mache ich dann das Prepared Statement und binde die Parameter bzw. führe es aus:

PHP:
if ($stmt = $db->prepare($query))
{
 $stmt = $db->prepare($query); 
 $stmt->bind_param('i', $name, $vorname);
 $stmt->execute();

Als nächstes hole ich mir dann das Ergebnis wie gewohnt auch aus MySQL

PHP:
 $result = $stmt -> get_result();
 while ($row = $result -> fetch_object())
 {
  mache etwas
 }

Hier schon wieder die nächste Frage. Kann ich nun die oben gebundenen Variablen dafür nutzen? Sprich $name, $vorname in meinem Fall. Oder muss ich hier wieder mit $row->spalte arbeiten?

Danach dann logischerweise:

PHP:
 $stmt->close();
}
$db->close();

Was ich auch nicht verstanden habe ist warum ich eine Variable für das Statement habe ($stmt in meinem Beispiel)? Das scheint so wie ich es rauslesen konnte eine Sicherheitsabfrage zu sein, damit keine SQL Injections durchgeführt werden können.

Vielleicht könnte da Jemand etwas Licht ins Dunkle bringen.

Wie ist denn die gängige Vorgangsweise beim objektorientierten Stil, welche immer wieder folgt?



Nun habe ich auch gleich noch ein Beispiel mit einer doppelten Abfrage. Ich möchte News auslesen und mittels der ausgelesenen ID dann die Anzahl der Kommentare die in einer anderen Tabelle gespeichert sind. Früher konnte man hier die SQL Queries ineinander verschachteln und mittels der ausgelesenen News ID dann in der Kommentarfunktion einfach die Anzahl der Datensätze auslesen ganz kurz mit num_rows.

Wie ich gelesen habe, kann man in MySQLi nun Multi Queries nutzen und somit mit $query und $query. zwei Datenbankabfragen gleichzeitig abarbeiten. Mir ist nun aber nicht ganz bewusst wie das aussehen soll.

Was ich verstanden habe ist folgendes:

Datenbankabfrage ganz normal, dann Query wie eben beschrieben mit und einer do while Schleife.

PHP:
$query = '...........................';';
$query. = '..........................';

if ($db->multi_query($query))
{
 do
 {
  if ($result = $db->store_result())
  {
   while ($row = $result->fetch_object())
   {
    Ausgabe
   }
  }
  if ($db->more_results())
  {
   ...
  }
 }
 while ($db->next_result());
}

Aber was genau mach ich in der If Abfrage vom more_results ????

Dann noch eine generelle Frage:
Sollte ich noch wie früher die Abfrage nach den Datensätzen machen? Oder ist das mit if ($result = $db->query($query)) schon getan????

Vielen Dank im Voraus für Hilfe!
 
Hi

Der Code zum Verbindung herstellen ist nicht ganz richtig.
PHP:
$db = new mysqli($server, $user, $passwort, $datenbank);
 
/* Check Connection */
if ($db->connect_errno)
{
  printf("Verbindung fehlgeschlagen: %s\n", mysqli_connect_error());
  exit();
}
Die Funktion mysqli_connect_error ist für den prozeduralen Fall (und bezieht sich immer auf den zeitlich letzten Verbindungsversuch. Falls man meherere Verbindungen hat evt. nicht das, was man will. Das ist zB. eienr der Gründe, warum die OO-Variante besser ist).

Die Fragezeichen:
Werden genau dann gemacht, wenn man etwas reinbinden will beim Prep.Statement. Wenn man "WHERE id > 500" immer fix in der Query drinhat kann man es ohne Fragezeichen reinschreiben, wenn die Zahl veränderbar ist und per Bindung eingefügt wird dann eben mit Fragezeichen.

Zu
PHP:
$stmt->bind_param('i', $name, $vorname);
i steht für integer/Zahl, d für double/Kommazahl, s für String, und b für Blob.
Ein Name wird wohl s sein, und da es zwei Variablen sind ss

Oder muss ich hier wieder mit $row->spalte arbeiten?
Ja. Queryparameter und Ergebnisse haben miteinander nichts zu tun.

Was ich auch nicht verstanden habe ist warum ich eine Variable für das Statement habe ($stmt in meinem Beispiel)?
Naja, wegen der Prepared Statements.

Vier Varianten wie man was abfragen kann (evt. nicht direkt so ausführbar, nur um eine Idee zu bekommen):
PHP:
$db->query('SELECT bla from blub WHERE 1 > 2");
Fixe Abfrage, kein Problem so ohne Prepare, $stmt usw.

PHP:
$stmt = $db->prepare('SELECT bla from blub WHERE 1 > 2");
$stmt->execute();
$stmt->execute();
$stmt->execute();
$stmt->close();
Prepared-Variante.
Technisch braucht man hier nach dem Prepare etwas zum Aufrufen von Execute und Close, deswegen die Variable.
Der eigentliche Sinn davon ist, dass mehrere Ausführungen (hier zB. drei) auf diese Weise schneller sind als drei mal die erste Variante. (Auch wenn es meistens nicht viel Sinn macht, die komplett gleiche Query dreimal abzufragen)

PHP:
$db->query('SELECT bla from blub WHERE 1 > ' . $meinephpvariable);
Einfache Abfrage mit Variablen drin. Das Problem ist ein Sicherheitsrisiko: Wenn die Variable von "außerhalb" kommt, zB. Benutzereingaben in Formularen, und sie nicht wirklich gründlich geprüft ist, könnte der Inhalt die Abfrage verändern (= "Sql-Injection").

PHP:
$stmt = $db->prepare('SELECT bla from blub WHERE ? > ?");
%stmt->bind('ii', $meinephpvariable, $anderevariable);
$stmt->execute();
$anderevariable++;
$stmt->execute();
$anderevariable++;
$stmt->execute();
$stmt->close();
Außer Verschnellerung hat man hier noch den Zusatzvorteil, dass gebundene Variablen keind erartiges Risiko sind.

Sollte ich noch wie früher die Abfrage nach den Datensätzen machen?
Die Frage versteh ich leider nicht ganz.

Und zu Multi-Query:
Die bisherigen Varianten erlauben genau eine Sql-Anweisung, ja. Multiquery erlaubt mehrere, allerdings a) hintereinander statt gleichzeitig, und b) komplett unabhängig voneinander. Kein Unterscheid zu mehreren EInzelquerys, außer der Schreibweise.
Für die News-Sache ist eine Verschachtlung bzw. Join nach wie vor das Richtige.
 
Danke zunächst für die ausführliche Antwort!

Dann ist $stmt also eine Art interne Prüfung der Variable zur Verhinderung von Injections und hat mit der Ausgabe an sich gar nichts zu tun oder wie?

Denn die Ausgabe wird ja scheinbar nach wie vor dann über

PHP:
while ($row = $result->fetch_object())
{
 echo $row->spalte;
}

gesteuert. Nur verstehe ich halt daran dann bind_param nicht. Warum definiere ich das? Ist das lediglich zur Prüfung ob Integer, Zeichenkette etc.? Und im Anschluss übergebe ich dann mit $result = $stmt->get_result(); den Inhalt des Querys?

Zu dem was Du nicht verstehst:

Ich habe früher in MySQL Immer mit einer If Abfrage kontrolliert ob Datensätze vorhanden sind. (num_rows) Ansonsten kam gar keine While Schleife zum Einsatz.

Ich frage mich ob ich das unter MySQL ebenfalls machen sollte und wenn ja wie, da die Komplexität eines Prepared Statements natürlich ziemlich groß ist und nicht mit 3 Zeilen abgetan ist. Oder?


Zu der News und den Kommentaren nochmal: Auch hier viel Code durch die Statements. Oder kann ich hier ggfs. einsparen? Wie würdest Du denn diese Abfrage gestalten.

Unter MySQL waren das ja nur drei Zeilen. Da die Variable news_id auch ausgelesen wird könnte ich auch auf das Statement verzichten, oder nicht? Die Eingabe wird nicht vom User vorgenommen.
 
Dann ist $stmt also eine Art interne Prüfung der Variable zur Verhinderung von Injections und hat mit der Ausgabe an sich gar nichts zu tun oder wie?
Nochmal anders: Man hat im Wesentlichen zwei Möglichkeiten, um eine Abfrage zu machen:

Variante 1:

"$res = $db->query()" aufrufen, dabei die SQL-Anweisung übergeben an query(). Dann bei $res eben mit einer Schleife und fetch-irgendwas die Ergebnisse durchgehen.

Sicherheit dieser Variante:
Wenn die SQL-Anweisung nicht fix ist, sondern PHP-Variablen eingefügt werden, muss man slebst verhindern, dass SQL-Injections passieren. Als Idee:
PHP:
//Benutzer gibt
Hans
//ins Formular ein. Dann in PHP:
$q = 'SELECT count(*) from Benutzer where vorname="' . $_GET['vorname'] . '"';
//$q ist jetzt: SELECT count(*) from Benutzer where vorname="Hans"

//Benutzer gibt
Hans" OR ""="
//ins Formular ein
$q = 'SELECT count(*) from Benutzer where vorname="' . $_GET['vorname'] . '"';
//$q ist jetzt: SELECT count(*) from Benutzer where vorname="Hans" OR ""=""

//Und schon ist die Abfrage anders als sie sein sollte.

Geschwindigkeit:
Wenn man es einmal macht ist es ok.
Wenn man mehere Namen hat und die Anweisung für jeden einmal machen will ist es unnötig langsam.

Variante 2:

Zuerst mit prepare eine Abfrage mit Fragezeichen an die DB senden. Die DB kann schon erkennen dass die Tabelle Benutzer gebraucht wird, nach Vornamen gefiltert usw. Welcher Vorname ist aber noch nicht bekannt. In PHP bekommt man einen Variablenwert zurück (hier in "$stmt"), der die zwischengespeicherte Abfrage identifiziert. (zB. eine ID, die von der DB vergeben wird).

Mit bind kann man jetzt den Vornamen nachreichen und dann mit execute die Abfrage ausführen. Bei execute bekommt man dann wieder wie gewöhnt etwas zum durch-fetchen.

Anders als bei der ersten Variante kann man das jetzt aber mit einem anderen Vornamen wiederholen. Die Abfrage ist bei der DB noch immer bekannt. Wenn man dann fertig ist sollte man der DB sagen, dass die ABfrage nicht mehr gebraucht wird, und zwar mit close für $stmt.

Wie schon vermutet ist diese Variante schneller, wenn man mehr als einen Durchgang macht (schneller als mehere unabhängige query()). Und als Zusatzbonus gibts keine Injections: Der Vorname wird ja nicht so direkt in den Abfragenstring eingefügt. Die DB verarbeitet die Abfrage mit Fragezeichen vorher schon allein, und wenn der Vorname dann später hingeschickt wird ist klar, dass das der Vorname und sonst nichts sein soll.

---

Ja, das Fetchen dann Ausführung ist bei beiden Varianten gleich.

Zu num_rows: Wenn man so eine Schleife hat:
PHP:
while ($row = $result->fetch_object())
ist die Prüfung ob es überhaupt eine Zeile gibt überflüssig. Wenn es keine gibt wird die Schleife eben nie durchlaufen. Was man schon prüfen muss, egal ob mit oder ohne Prepare, ist, ob $result nicht null ist (wenn schon dann gab es einen Fehler beim Abfragen). Bei der Prepare-Variante sollte man nach prepare(), durch das $stmt ja seinen Wert bekommt, auch $stmt auf null prüfen

Und zu der News-Sache: Ich seh nicht, wie sich die Verschachtelung von SQL-Abfragen auf den Code hier auswirken sollte. Was nach SELECT geschrieben werden kann hat nichts mit dem PHP-Code zu tun. Ein zB.
SQL:
SELECT a from b where c in (SELECT d from e where f=g)
geht nach wie vor unverändert, man muss das geklammerte Select nicht loswerden.
 
Gut dann habe ich das jetzt einigermaßen verstanden.

Wenn ich die While Schleife nicht auf Datensätze prüfe und es kommt keine Ausgabe, dann ist der Besucher aber ggfs. verwirrt, weil er ja eine Ausgabe erwartet. Wie gebe ich denn dann aus, dass keine Datensätze vorhanden sind?


Unklar ist mir allerdings der Übergang zwischen der Variable des Statements $stmt zu der Ausgabe per While Schleife etc.

Spreche ich innerhalb der Schleife Jetzt $stmt an via

PHP:
$row = $stmt->fetch_object
???

Oder wie komme ich aus dem stmt zur Ausgabe?


Zu der Verschachtelung:

Im Normalfall kann ich Dir folgen. Aber dadurch, dass ich mittels der News ID nur aus der Kommentardatenbank die Datensätze auslese, kommt mir Dein Beispiel oder ein Join nicht zweckmäßig vor. Denn die Kommentare haben eigene IDs und eine Spalte für die entsprechende News ID, ich speichere aber nicht separat in der News Tabelle die Anzahl der Kommentare ab.

Damit ist mir nicht ganz klar, wie ich die dann auslesen kann?
 
Das Ergebnis-durchgehen:
Das ist nicht auf $stmt selber.
Oben ist von dir folgender Code:
PHP:
while ($row = $result->fetch_object())
{
 echo $row->spalte;
}
$result bekommt man entweder mit
PHP:
//Variante 1
$result = $db->query('SELECT...');
...
while ($row = $result->fetch_object())
...
oder
PHP:
//Variante 1
$stmt = $db->prepare('SELECT...');

...

$stmt->bind_irgendwas();
$result = $stmt->execute();
...
while ($row = $result->fetch_object())
...

dann ist der Besucher aber ggfs. verwirrt, weil er ja eine Ausgabe erwartet.
In dem Fall kann man natürlich ein if einbauen, ob es Ergebnisse gab

...

Zu den News:
Kannst du mal ein Beispiel schreiben? Wwelche Spalten die Tabellen haben, drei Datensätze rein, und wie das Abfrageergebnis ausschauen soll. Oder wie der Code vorher ausgeschaut hat.
 
Hallo nochmal,

sorry für die späte Antwort, war nicht früher zu Hause am Rechner. Hier das Beispiel des alten (MySQL) Codes:

PHP:
<?php

$verbindung = mysql_connect($server, $user, $passwort) or die ("Es konnte keine Verbindung zum Server hergestellt werden!");
mysql_select_db($datenbank) or die ("Diese Datenbank existiert nicht!");

$daten_news = mysql_query("select * from news ORDER BY id DESC LIMIT 0,10");
$news_vorhanden = mysql_num_rows($daten_news);

if ($news_vorhanden > 0)
{
  while ($row = mysql_fetch_object($daten_news))
  {
     $news_id = $row ->id;
     $news_schrift = $row ->schrift;
     $news_autor = $row ->autor;
     $news_text = $row ->text;
     $news_datum = date("d.m.Y", $row ->datum);

     echo "<div id='contentoben'>$news_datum | $news_autor | $news_schrift</div>

           <div id='contentunten'>$news_text";

     $news_kommentare = mysql_query("select * from newskommentare WHERE news = '$news_id'");
     $anzahl_kommentare = mysql_num_rows($news_kommentare);

     echo "<a href='kommentare.php?id=$news_id' class='comments'><img src='iconns/comment.png' class='icons' title='Kommentare' alt='Kommentare'/>$anzahl_kommentare Kommentar(e)</a></div>";
    }
   }
   else
   {
    echo "<div id='contentoben'>Leider noch keine Texte</div>

          <div id='contentunten'>Bisher wurden leider noch keine Texte geschrieben.</div>";
   }
   mysql_close($verbindung);
   ?>

Nochmal zwei ergänzende Fragen:

Du hast gesagt meine Errorausgabe wäre nicht ganz richtig. Was daran ist nicht richtig? Die habe ich so von PHP.net als objektorientiert übernommen?

Zum bind_param im Statement:
Ist das nur für den Fall gedacht, wenn ich auch Variablen übergebe z. B. Formular? Weil in meinem News Beispiel habe ich ja zum Beispiel bei der Ausgabe keine Variablen übergeben. Oder Muss ich dann die Variablen manuell definieren?


*UPDATE*

Ich habe jetzt mal die erste Ausgabe versucht wie folgt:

PHP:
/* Check Connection */
 if (mysqli_connect_errno())
 {
  printf("Verbindung fehlgeschlagen: %s\n", mysqli_connect_error());
  exit();
 }

 /* Ausgabe der Daten */
 $query = "SELECT id, autor, text, datum FROM shoutbox ORDER BY id DESC LIMIT 0,10";
 if ($stmt = $db->prepare($query))
 {
    $stmt->execute();
    $stmt->store_result();
    $datensatz_zahl = $db->query($query);
    if ($datensatz_zahl->num_rows > 0)
    {
     $stmt->bind_result('issi', $shout_id, $shout_autor, $shout_text, $shout_datum);
     $result = $stmt->execute();
     while ($row = $result->fetch_object())
     {
      $shout_id = $row->id;
      $shout_autor = $row->autor;
      $shout_text = $row->text;
      $shout_datum = date("d.m.Y", $row->datum);
      echo "<p>$shout_autor am $shout_datum";
      if (isset($_SESSION["user_id"]) AND $_SESSION["user_premium"] > 0)
      {
       echo "<a href='shoutdel.php?id=$shout_id' class='comments'><img src='iconns/close.png' title='Löschen' alt='Löschen'/></a>";
      }
      echo "</p><p>$shout_text</p>";
     }
    }
    else
    {
     echo "<p>Leider noch keine Texte vorhanden!</p>";
    }
    $stmt->free_result();
    $stmt->close();
 }

 $db -> close();

Ist das richtig so?
 
Zuletzt bearbeitet:
Zur Errorausgabe:
Die Funktion mysqli_connect_error ist für den prozeduralen Fall (und bezieht sich immer auf den zeitlich letzten Verbindungsversuch. Falls man meherere Verbindungen hat evt. nicht das, was man will. Das ist zB. eienr der Gründe, warum die OO-Variante besser ist).
Code:
if ($db->connect_errno)
  printf("Verbindung fehlgeschlagen: %s\n", $db->connect_error);

Zum bind_param im Statement:
Ist das nur für den Fall gedacht, wenn ich auch Variablen übergebe z. B. Formular?
Ja.

Und zur verschachtelten Abfrage: Auch so, verschachtelt in PHP...
Dass man da nach wie vor zwei Abfragen braucht ändert sich nicht.

Zum unteren Code: Ziemlich umständlich, aber falls es funktioniert...
 
Zuletzt bearbeitet:
Was genau ist denn umständlich? Wie könnte ich es besser machen?

Auf Funktionalität habe ich noch nicht geprüft. Wollte erstmal hier fragen. :)
 

Neue Beiträge

Zurück