Prepared Statements - Wie verwenden?

Anmerkungen, Fragen und Hinweise zur Konfiguration dürfen in diesem Forum gepostet werden. // Notes, questions, and hints on the configuration can be posted here.
Benutzeravatar
ma2604121
Beiträge: 349
Registriert: 24.01.2011, 23:42:18

Prepared Statements - Wie verwenden?

Beitrag von ma2604121 » 29.03.2011, 18:50:55

Hallo,

ich stelle mit dem ConnectionManager eine MySQLi-Verbindung zu meiner Datenbank her.

Code: Alles auswählen

$cM = &$this->__getServiceObject('core::database', 'ConnectionManager');
$SQL = &$cM->getConnection('MySQLi'); 
Die dazugehörige DEFAULT_connections.ini:

Code: Alles auswählen

[MySQLi]
DB.Host = "localhost"
DB.User = "user"
DB.Pass = "password"
DB.Name = "apf"
DB.Type = "MySQLi"
DB.Charset = "utf-8" 
Jetzt kann ich z.B. auf herkömmliche Weise Daten in die Datenbank einfügen:

Code: Alles auswählen

$insert = 'INSERT INTO user (nick) VALUES (\''.$nick.'\')';
$result = $SQL->executeTextStatement($insert); 
Klappt auch wunderbar. Nur möchte ich ganz gerne meine normalen Prepared Statements verwenden, an die mich im Laufe der Zeit gewöhnt habe. Leider ist mir unklar, wie ich das anstellen muss, da ich bereits bei der folgenden Zeile eine Fehlermeldung erhalte:

Code: Alles auswählen

$insert = 'INSERT INTO user (nick) VALUES (?)';
$stmt = $SQL->prepare($insert); 

Code: Alles auswählen

Fatal error: Call to undefined method MySQLiHandler::prepare() in /var/www/apf1/apps/test/pres/controller/register_controller.php on line 29 
Alternativ natürlich auch bei

Code: Alles auswählen

$insert = 'INSERT INTO user (nick) VALUES (?)';
$stmt = $cM->prepare($insert); 

Code: Alles auswählen

Fatal error: Call to undefined method ConnectionManager::prepare() in /var/www/apf1/apps/test/pres/controller/register_controller.php on line 29 
Wie muss ich also vorgehen, damit ich normale Prepared Statements verwenden kann?

Benutzeravatar
Screeze
Beiträge: 1920
Registriert: 05.08.2009, 09:49:04
Kontaktdaten:

Re: Prepared Statements - Wie verwenden?

Beitrag von Screeze » 29.03.2011, 18:56:05

Reicht dir das Codebeispiel hier? Ich hab den MySQLiHandler nie verwendet, daher kann ich da auch nur Hinweise aus der Doku geben :)

http://adventure-php-framework.org/Seit ... berschicht

Benutzeravatar
ma2604121
Beiträge: 349
Registriert: 24.01.2011, 23:42:18

Re: Prepared Statements - Wie verwenden?

Beitrag von ma2604121 » 29.03.2011, 19:08:03

Das war meine Grundlage, dennoch trete ich auf der Stelle:

Code: Alles auswählen

$nick = 'testuser';
$cM = &$this->__getServiceObject('core::database', 'ConnectionManager');
$SQL = &$cM->getConnection('MySQLi');
$insert = 'INSERT INTO user (nick) VALUES (?)';
$data = $SQL->executeBindStatement($insert, $nick); 
Da bekomme ich folgenden Fehler:

Code: Alles auswählen

Exception-ID:  	abb405747b4aff009220b8da45b6fb10
Type: 	DatabaseHandlerException
Message: 	[MySQLiHandler->getStatementFromFile()] There's no statement file with name "DEFAULT_testuser" for given namespace "config::INSERT INTO user (nick) VALUES (?)" and current context "test"! Root cause: [IniConfigurationProvider::loadConfiguration()] Configuration with namepace "INSERT INTO user (nick) VALUES (?)", context "test", language "de", environment "DEFAULT", and name "testuser" cannot be loaded!
Number: 	256
File: 	/var/www/apf1/apps/core/database/AbstractDatabaseHandler.php
Line: 	349
Alternativ (wie in der Doku):

Code: Alles auswählen

$cM = &$this->__getServiceObject('core::database', 'ConnectionManager');
$SQL = &$cM->getConnection('MySQLi');
$insert = 'INSERT INTO user (nick) VALUES (?)';
$data = $SQL->executeBindStatement($insert, array("testnick")); 
Das führt zu folgenden Fehlermeldungen:

Code: Alles auswählen

Error-ID:  	802e28bb07b2090dae62d3f868d2528f
Message: 	strripos() expects parameter 1 to be string, array given
Number: 	2
File: 	/var/www/apf1/apps/core/configuration/ConfigurationManager.php
Line: 	360

Error-ID:  	3f55f0051826fd43bc2f70920cb3686a
Message: 	preg_match() expects parameter 2 to be string, array given
Number: 	2
File: 	/var/www/apf1/apps/core/configuration/provider/BaseConfigurationProvider.php
Line: 	90


Exception-ID: 	dd7fa228b87cd202408d316ae11f49a2
Type: 	DatabaseHandlerException
Message: 	[MySQLiHandler->getStatementFromFile()] There's no statement file with name "DEFAULT_Array" for given namespace "config::INSERT INTO user (nick) VALUES (?)" and current context "test"! Root cause: [IniConfigurationProvider::loadConfiguration()] Configuration with namepace "INSERT INTO user (nick) VALUES (?)", context "test", language "de", environment "DEFAULT", and name "Array" cannot be loaded!
Number: 	256
File: 	/var/www/apf1/apps/core/database/AbstractDatabaseHandler.php
Line: 	349

Benutzeravatar
Screeze
Beiträge: 1920
Registriert: 05.08.2009, 09:49:04
Kontaktdaten:

Re: Prepared Statements - Wie verwenden?

Beitrag von Screeze » 29.03.2011, 19:10:00

Moment:
executeBindStatement() verhält sich analog zu MySQLx's executeStatement(), und führt ein statement aus, dass sich in einer .sql config-Datei befindet.
Was du suchst ist executeTextBindStatement()


steht in der Doku aber auch:

Code: Alles auswählen

// retrieve db connection 
$cm = &$this->__getServiceObject('core::database','ConnectionManager'); 
$conn = $cm->getConnection('MySQLi'); 

// execute textual statement with bind params
 $data = $conn->executeTextBindStatement( 'SELECT * FROM ent_user_2 WHERE FirstName LIKE ?', array('Christian') ); 
// execute statement within an sql file with bind params 
$data = $conn->executeBindStatement( 'my::module', 'notepad_entries.sql', array('date_from' => '2009-03-20 00:00:00','date_until' => '2010-04-10 00:00:00') ); 

Benutzeravatar
ma2604121
Beiträge: 349
Registriert: 24.01.2011, 23:42:18

Re: Prepared Statements - Wie verwenden?

Beitrag von ma2604121 » 29.03.2011, 19:23:21

Führt immerhin zu einer neuen Fehlermeldung:

Variante 1

Code: Alles auswählen

$nick = 'testuser';
$cM = &$this->__getServiceObject('core::database', 'ConnectionManager');
$SQL = &$cM->getConnection('MySQLi');
$insert = 'INSERT INTO user (nick) VALUES (?)';
$data = $SQL->executeTextBindStatement($insert, $nick); 

Code: Alles auswählen

Error-ID:  	4750add9998e371b7a280b2041389e8b
Message: 	Invalid argument supplied for foreach()
Number: 	2
File: 	/var/www/apf1/apps/core/database/MySQLiHandler.php
Line: 	368

Error-ID:  	ff1f2a1d6c008cf8932ee956a5bf67c8
Message: 	Wrong parameter count for mysqli_stmt::bind_param()
Number: 	2
File: 	/var/www/apf1/apps/core/database/MySQLiHandler.php
Line: 	377
Variante 2

Code: Alles auswählen

$cM = &$this->__getServiceObject('core::database', 'ConnectionManager');
$SQL = &$cM->getConnection('MySQLi');
$insert = 'INSERT INTO user (nick) VALUES (?)';
$data = $SQL->executeTextBindStatement($insert, array('testuser')); 
Fehler:

Code: Alles auswählen

Fatal error: Call to a member function fetch_field() on a non-object in /var/www/apf1/apps/core/database/MySQLiHandler.php on line 394 
Das SELECT-Beispiel aus der Dokumentation klappt hingegen problemlos.

Code: Alles auswählen

$cM = &$this->__getServiceObject('core::database', 'ConnectionManager');
$SQL = &$cM->getConnection('MySQLi');
$data = $SQL->executeTextBindStatement( 'SELECT nick, lastMod FROM user WHERE nick LIKE ?', array('testuser') );
print_r($data); 

Code: Alles auswählen

Array ( [0] => Array ( [nick] => testuser [lastMod] => 2011-03-29 19:14:08 ) [1] => Array ( [nick] => testuser [lastMod] => 2011-03-29 19:14:13 ) )  
Mir ist immer noch nicht klar, was ich nun falsch mache?

NACHTRAG
Ich sehe gerade, dass die Daten - trotz der Fehlermeldung - eingetragen werden... Zumindest in dieser Variante:

Code: Alles auswählen

$data = $SQL->executeTextBindStatement('INSERT INTO user (nick) VALUES (?)', array($nick)); 

Benutzeravatar
dave
Beiträge: 903
Registriert: 04.02.2011, 19:03:57
Wohnort: Berlin
Kontaktdaten:

Re: Prepared Statements - Wie verwenden?

Beitrag von dave » 29.03.2011, 20:14:33

Variante 1:
Der Testuser ist kein Array, daher liefert die foreach()-Schleife der Methode bindParams() aus der MySQLiHandler den fatal Error.
Mit dem

Code: Alles auswählen

array('testuser')
 
bzw.

Code: Alles auswählen

$nick = array('testuser')
 
hättest du schonmal keinen fatal Error aus der Codezeile 368 und 377 ;).

Zu Variante 2 habe ich auch erstmal kein Plan, wenn sich was ergibt, trage ich es nach.

Benutzeravatar
ma2604121
Beiträge: 349
Registriert: 24.01.2011, 23:42:18

Re: Prepared Statements - Wie verwenden?

Beitrag von ma2604121 » 29.03.2011, 20:20:24

hättest du schonmal keinen fatal Error aus der Codezeile 368 und 377 ;).
Danke! Stimmt, allerdings erhalte ich dann die gleiche Meldung wie unten (Daten werden ebenfalls in die DB eingetragen):

Momentan sieht es wie folgt aus:

Code: Alles auswählen

$cM = &$this->__getServiceObject('core::database', 'ConnectionManager');
$SQL = &$cM->getConnection('MySQLi');
$data = $SQL->executeTextBindStatement('INSERT INTO user (nick, pwd) VALUES (?, ?)', array($nick, $password)); 
Das führt dann leider zu folgender Fehlermeldung (auf weißer Seite -> also kompletter Abbruch der Verarbeitung):

Code: Alles auswählen

Fatal error: Call to a member function fetch_field() on a non-object in /var/www/apf1/apps/core/database/MySQLiHandler.php on line 394
Allerdings werden die Daten in die Datenbank eingetragen...

Benutzeravatar
dave
Beiträge: 903
Registriert: 04.02.2011, 19:03:57
Wohnort: Berlin
Kontaktdaten:

Re: Prepared Statements - Wie verwenden?

Beitrag von dave » 29.03.2011, 20:40:15

Ich habe mir mal die Methode executeTextBindStatement aus der MySQLiHandler.php angesehen und bin etwas "durchgetraced" ;).

Zu Beginn wird das Query aus deinem Statement erzeugt, alles wunderbar. DAnach wird es durchgeführt, dass heisst, dein INSERT wird ausgeführt. Daher ist der User dann auch in der DB vorhanden.

Anschliessend gehts mit dem Query in die Methode fetchBindResult().
Diese macht nun eigentlich einen Vorgang, den du zum Ausgeben von Inhalten ebnötigst. Mit fetch_field() werden Datensätze ausgelesen, wenn ich die PHP-Doku mal richtig verstanden habe. Ich persönlich nutze kein MySQLi, daher sind das nur Vermutungen, um dich auf die richtige Fährte zu führen.

Was du nun einfach mal versuchen könntest, wäre die Zeile 314 aus der MySQLiHandler.php auszukommentieren und nochmal zu versuchen. Nur du wirst dann von der Methode executeTextBindStatement nichts zurück bekommen, da diese genau das Ergebnis von fetchBindResult zurück gibt. Das heisst, deine Variable $data ist leer. Aber für nen INSERT brauchst du da ja auch nichts weiter ...

Am Ende kann leider nur Christian weiter helfen. Ich bin auch schon etwas neugierig, was es nun genau für ein Problem ist/war ;)

Benutzeravatar
Screeze
Beiträge: 1920
Registriert: 05.08.2009, 09:49:04
Kontaktdaten:

Re: Prepared Statements - Wie verwenden?

Beitrag von Screeze » 29.03.2011, 20:50:56

Aus meiner Sicht korrekte analyse dave, da fehlt wohl in MySQLiHandler::fetchBindResult() eine prüfung ob $query leer ist, in dem fall muss einfach nichts zurückgegeben werden, bzw. null oder so

Benutzeravatar
ma2604121
Beiträge: 349
Registriert: 24.01.2011, 23:42:18

Re: Prepared Statements - Wie verwenden?

Beitrag von ma2604121 » 29.03.2011, 20:57:24

Wenn ich Zeile 314 in der MySQLiHandler.php ausklammere

Code: Alles auswählen

//$statementResult = $this->fetchBindResult($query);   
erhalte ich folgenden Fehler

Code: Alles auswählen

Error-ID:  	c5204927acb9b9313dcc3760a1acb2aa
Message: 	Undefined variable: statementResult
Number: 	8
File: 	/var/www/apf1/apps/core/database/MySQLiHandler.php
Line: 	322
Der Eintrag der Daten in die DB erfolgt.

Alternativ habe ich noch die Zeile 322 etwas verändert:

Code: Alles auswählen

return;// $statementResult; 
Damit würde der Eintrag wie gewohnt erstellt und es gäbe keinerlei Fehlermeldung. Aber ob das im Sinne des Erfinders ist?

Benutzeravatar
Screeze
Beiträge: 1920
Registriert: 05.08.2009, 09:49:04
Kontaktdaten:

Re: Prepared Statements - Wie verwenden?

Beitrag von Screeze » 29.03.2011, 21:08:15

Wie gesagt, da gehört eine Prüfung davor, schreib mal in
MySQLiHandler::fetchBindResult()
als allererstes ein var_dump($query)
und poste mal was für ein Datentyp das ist (ich vermute mal null oder false oder so, aber hab auch noch nicht mit MySQLi gearbeitet) anschließend ersetzen wir das var_dump() durch eine prüfung, ob das überhaupt einen rückgabewert hat, und geben return null; zurück, falls nicht, damit umgehen wir den rest der Funktion

Benutzeravatar
ma2604121
Beiträge: 349
Registriert: 24.01.2011, 23:42:18

Re: Prepared Statements - Wie verwenden?

Beitrag von ma2604121 » 29.03.2011, 21:48:24

MySQLiHandler.php | Zeile 311 - 316:

Code: Alles auswählen

// fetch the result set using the meta data returned by the query
$id = $statementId.' fetch';
$t->start($id);
var_dump($query);
//$statementResult = $this->fetchBindResult($query);
$t->stop($id); 
Ergibt:

Code: Alles auswählen

object(mysqli_stmt)#119 (0) { }   

Benutzeravatar
Screeze
Beiträge: 1920
Registriert: 05.08.2009, 09:49:04
Kontaktdaten:

Re: Prepared Statements - Wie verwenden?

Beitrag von Screeze » 29.03.2011, 21:54:48

Ahh sorry, hab was übersehen, du musst nicht nachschauen was $query ist, sondern was $metaData ist, also nach der Zeile:

Code: Alles auswählen

$metaData = $query->result_metadata();
ein var_dump auf $metaData, sorry

Benutzeravatar
ma2604121
Beiträge: 349
Registriert: 24.01.2011, 23:42:18

Re: Prepared Statements - Wie verwenden?

Beitrag von ma2604121 » 29.03.2011, 21:59:46

Meinst Du die folgenden Zeilen?

Code: Alles auswählen

private function fetchBindResult(&$query){
$metaData = $query->result_metadata();
var_dump($metaData); 
Da erhalte ich zunächst (logischerweise) gar keine Ausgabe - da wir den Aufruf zuvor auskommentiert hatten...

Ändere ich die Zeilen 311-315 wie folgt:

Code: Alles auswählen

// fetch the result set using the meta data returned by the query
$id = $statementId.' fetch';
$t->start($id);
$statementResult = $this->fetchBindResult($query);
$t->stop($id); 
erfolgt der Aufruf von fetchBindResult. Als Ergebnis von var_dump erhalte ich:

Code: Alles auswählen

bool(false)
Fatal error: Call to a member function fetch_field() on a non-object in /var/www/apf1/apps/core/database/MySQLiHandler.php on line 395 

Benutzeravatar
Screeze
Beiträge: 1920
Registriert: 05.08.2009, 09:49:04
Kontaktdaten:

Re: Prepared Statements - Wie verwenden?

Beitrag von Screeze » 29.03.2011, 22:43:09

Das dachte ich mir.
Wenn du jetzt die var_dump() zeile ersetzt durch:

Code: Alles auswählen

if($metaData === false) {
    return false;
}
 
und alle anderen Änderungen die du gemacht hast wieder rückgängig machst, sollte der Fehler nichtmehr auftreten, denke ich.
Ob das wirklich der korrekte Bugfix ist weis ich nicht, das soll Christian mal ansehen, aber ich denke vorerst solltest du damit weiterarbeiten können dann.

Antworten

Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder und 1 Gast