MySQLi Wrapper -- will this slow down performance? - php

I found the following code on php.net. I'm trying to write a wrapper for the MySQLi library to make things incredibly simple. If this is going to slow down performance, I'll skip it and find another way, if this works, then I'll do that.
I have a single query function, if someone passes in more than one variable, I assume the function has to be prepared. The function that I would use to pass in an array to mysqli_stmt_bind_param is call_user_func_array, I have a feeling that is going to slow things down. Am I right?
<?php
/* just explaining how to call mysqli_stmt_bind_param with a parameter array */
$sql_link = mysqli_connect('localhost', 'my_user', 'my_password', 'world');
$type = "isssi";
$param = array("5", "File Description", "File Title", "Original Name", time());
$sql = "INSERT INTO file_detail (file_id, file_description, file_title, file_original_name, file_upload_date) VALUES (?, ?, ?, ?, ?)";
$sql_stmt = mysqli_prepare ($sql_link, $sql);
call_user_func_array('mysqli_stmt_bind_param', array_merge (array($sql_stmt, $type), $param);
mysqli_stmt_execute($sql_stmt);
?>

Nope. You're wrong.
1 call_user_func_array call can never be the performance bottle neck.
So if it performed slow - then your query runs slow.

Related

Insert SQL statements in php bind_param function

So I am trying to use sql commands preparation to avoid injection but I can't figure out how can I use SQL statements in the bind_param function. My code is this :
function saveContent($POST_DATA) {
$conn = new mysqli("HOST", "USER", "PASSWORD");
if ($conn->connect_error) {
return false;
} else {
$command = $conn->prepare("INSERT INTO events (title, dateHappening, time, topic,
subtopic, extraMessage) VALUES (?, ?, ?, ?, ?, ?)";
$command->bind_param("ssssss", $POST_DATA["inputTitre"], $POST_DATA["inputDate"],
$POST_DATA["inputHour"], $POST_DATA["selectTopic"], $POST_DATA["selectSubTopic"],
$POST_DATA["inputMessage"]);
$command->execute();
$command->close();
$conn->close();
}
}
The thing here is that I would like extraMessage to be NULL if it is blank (because this field is not mandatory). To optimize this code I would have liked to use something like
NULLIF($POST_DATA["inputMessage"], "")
Is inserting the statement above in the bind_param function possible ?
Question solved (see comment under my question) :
No. You have to write all the SQL expressions in the query and only insert the placeholder where the value goes. So, essentially, you would put NULLIF(?, "") in the query instead of just a ?. –

PHP and MySql - How can I make this insert query work?

So this is my code:
public function cadastrar(Family $family){
var_dump($family); //just to verify
$objDb = new db_con();
$conn = $objDb->getConn();
//REGISTER FAMILY INTO DATABASE
$sql = "insert into family(family_declaracao_renda,
familia_renda_mensal,
family_assinatura_local,
family_assinatura_dia,
family_assinatura_mes,
family_assinatura_ano,
family_deficiente_presente,
family_adaptacao_necessaria_imovel)
values( '$family->getDeclaracaoRendaFamiliar()',
'$family->getRendaBrutaMensal()',
'$family->getAssinaturaLocal()',
'$family->getAssinaturaDia()',
'$family->getAssinaturaMes()',
'$family->getAssinaturaAno()',
'$family->getDeficientePresente()',
'$family->getAdaptacaoImovelNecessaria()')";
//Executar query
if (mysqli_query($conn, $sql)){
echo 'SUCCESS!';
} else {
echo 'ERROR!';
}
}
So I know that you can put variables in between VALUES('variable1', 'variable2'). I'm calling class methods from this object, what am I missing?
Prepare your query, Its the best way to secure your DB against SQLInjection type attacks. It also takes care of a lot of quoting issues, and other things.
$sql = 'INSERT INTO family(
family_declaracao_renda,
familia_renda_mensal,
family_assinatura_local,
family_assinatura_dia,
family_assinatura_mes,
family_assinatura_ano,
family_deficiente_presente,
family_adaptacao_necessaria_imovel
)VALUES(
?,
?,
?,
?,
?,
?,
?,
?
)';
$stmt = mysqli_stmt_init($link);
mysqli_stmt_prepare($stmt, $sql);
mysqli_stmt_bind_param(
$stmt,
'ssssssss', //must be the same as number of argments, types s=string, i=int, d=double/float, b=blob
$family->getDeclaracaoRendaFamiliar(),
$family->getRendaBrutaMensal(),
$family->getAssinaturaLocal(),
$family->getAssinaturaDia(),
$family->getAssinaturaMes(),
$family->getAssinaturaAno(),
$family->getDeficientePresente(),
$family->getAdaptacaoImovelNecessaria()
);
//Executar query
if (mysqli_stmt_execute($stmt)){
echo 'SUCCESS!';
} else {
echo 'ERROR!';
}
It's not hard, even if the MySqli procedural interface is crap. Consider using the Object oriented interface, or switch to PDO (my preferred). MySqli's OOP interface, is a bit easier but I won't go into covering it just for the sake of length. You can look it up on PHP.net, or I am sure there are some tutorials out there.
One word of warning I haven't used MySqli in several years (maybe 6-8) and the procedural style even longer. So I can't gurantee that will work exactly as I have it here. In fact I don't think I ever did use the procedural style of MySqli. When I moved away from mysql_ (so I could do prepared statements) was right around the middle run of PHP5.3 (2010ish maybe a year after I finished web-design college), and I was getting into using OOP (classes and object) almost exclusively in my code. So most of the mysqli code I just copied from PHP.net.
http://php.net/manual/en/mysqli.prepare.php
In PDO:
$sql = 'INSERT INTO family(
family_declaracao_renda,
familia_renda_mensal,
family_assinatura_local,
family_assinatura_dia,
family_assinatura_mes,
family_assinatura_ano,
family_deficiente_presente,
family_adaptacao_necessaria_imovel
)VALUES(
:family_declaracao_renda,
:familia_renda_mensal,
:family_assinatura_local,
:family_assinatura_dia,
:family_assinatura_mes,
:family_assinatura_ano,
:family_deficiente_presente,
:family_adaptacao_necessaria_imovel
)';
try{
$stmt = mysqli_stmt_init($link);
$Pdo->prepare($sql)->execute([
':family_declaracao_renda' => $family->getDeclaracaoRendaFamiliar(),
':familia_renda_mensal' => $family->getRendaBrutaMensal(),
':family_assinatura_local' => $family->getAssinaturaLocal(),
':family_assinatura_dia' => $family->getAssinaturaDia(),
':family_assinatura_mes' => $family->getAssinaturaMes(),
':family_assinatura_ano' => $family->getAssinaturaAno(),
':family_deficiente_presente' => $family->getDeficientePresente(),
':family_adaptacao_necessaria_imovel'=> $family->getAdaptacaoImovelNecessaria(),
]);
}catch(PDOException $e){
echo "PDOException[{$e->getCode()}] {$e->getMessage()} in {$e->getFile()} on {$e->getLine()}";
}
In PDO you can use named place holders :name instead of ? as MySqli uses (you can still use the ? in PDO). What I typically do is copy the field names and add : to the front of them. Named placeholders make it easy to keep track of what value goes where, and the order of the data array (used in execute doesn't even matter). As you can see the mysqli version took around 4 calls, the PDO version took only 2, and with method chaining $Pdo->prepare($sql)->execute(...) you don't even need to put it on another line or set a local variable. That is equivalent to this:
//$Pdo->prepare($sql)->execute(...)
$stmt = $Pdo->prepare($sql); //returns PDOStatement
$stmt->execute(...); //makes a call on PDOStatement
Because $Pdo->prepare($sql) returns a PDOStatement if we don't need that object for anything else, we can just call ->execute(...) on the return value of the previous method in the chain. I felt I should explain that as you may not be used to using Objects. But, chaining keeps the code tidy (no superfluous variables) which makes it bit easier to keep track things (there are less things, to keep track of).
In short, its very easy to do it in both MySqli & PDO, but PDO is well worth learning how to use as it has many advantages. Better interface, better fetch methods, named placeholders, exceptions etc...
Cheers!
$sql = 'INSERT INTO family(
family_declaracao_renda,
familia_renda_mensal,
family_assinatura_local,
family_assinatura_dia,
family_assinatura_mes,
family_assinatura_ano,
family_deficiente_presente,
family_adaptacao_necessaria_imovel
)VALUES(
:family_declaracao_renda,
:familia_renda_mensal,
:family_assinatura_local,
:family_assinatura_dia,
:family_assinatura_mes,
:family_assinatura_ano,
:family_deficiente_presente,
:family_adaptacao_necessaria_imovel
)';
try{
$stmt = mysqli_stmt_init($link);
$Pdo->prepare($sql)->execute([
':family_declaracao_renda' => $family->getDeclaracaoRendaFamiliar(),
':familia_renda_mensal' => $family->getRendaBrutaMensal(),
':family_assinatura_local' => $family->getAssinaturaLocal(),
':family_assinatura_dia' => $family->getAssinaturaDia(),
':family_assinatura_mes' => $family->getAssinaturaMes(),
':family_assinatura_ano' => $family->getAssinaturaAno(),
':family_deficiente_presente' => $family->getDeficientePresente(),
':family_adaptacao_necessaria_imovel'=> $family->getAdaptacaoImovelNecessaria(),
]);
}catch(PDOException $e){
echo "PDOException[{$e->getCode()}] {$e->getMessage()} in {$e->getFile()} on {$e->getLine()}";
}

PHP PDO Prepared Statements and Value Binding Gives Invalid Parameter Number Error

I'm having a slight problem with the PHP PDO library and prepared statements. As far as I can see the prepared statement below should work but it doesn't, instead I get: "PDOStatement::execute(): SQLSTATE[HY093]: Invalid parameter number: number of bound variables does not match number of tokens".
My PHP code for this section looks like:
$sql = 'INSERT INTO '.POLYGON_TABLE.' (user_id, polygon, polygon_type) VALUES (:userId, PolygonFromText(\'POLYGON((:polygonArea))\'), :polygonType)';
$sth = $this->pdo->prepare($sql);
$sth->bindValue(':userId', $polygon->getUserId(), \PDO::PARAM_INT);
$sth->bindValue(':polygonArea', $polygon->getPolygonAsText(), \PDO::PARAM_STR);
$sth->bindValue(':polygonType', $polygon->getPolygonType(), \PDO::PARAM_STR);
if($sth->execute()) {
return true;
} else {
return false;
}
I have done a var_dump of $polygon->getUserId(), $polygon->getPolygonAsText() and $polygon->getPolygonType() and get the following:
string(1) "1"
string(226) "53.897910476098765 -1.739655277929728, 53.865530797116 -2.080231449804728, 53.67235280490181 -2.006073734960978, 53.68862047002787 -1.621552250585978, 53.89305512284903 -1.539154789648478, 53.897910476098765 -1.739655277929728"
string(7) "commute"
The issue is with $polygon->getPolygonAsText() as commenting out this particular bindValue call and the PolygonFromText(\'POLYGON((:polygonArea))\') from the SQL statement causes the query to work.
I'm now completely at a loss. Anyone know what's wrong here? I can't see anything wrong with the text contained within $polygon->getPolygonAsText(). I have searched high and low for a solution to this and spent several hours this evening tinkering with the code but to no avail.
I have even tried the suggestions in these 2 stack overflow topics but they didn't work either:
Invalid parameter number on PDO Prepared Statement
PHP PDO prepared statements
Any help would be much appreciated...
Did you try passing in the entire expression as the bind value?
$sql = 'INSERT INTO '.POLYGON_TABLE.' (user_id, polygon, polygon_type) VALUES (:userId, PolygonFromText(:polygonArea), :polygonType)';
$sth = $this->pdo->prepare($sql);
$area = sprintf("POLYGON((%s))", $polygon->getPolygonAsText());
$sth->bindValue(':userId', $polygon->getUserId(), \PDO::PARAM_INT);
$sth->bindValue(':polygonArea', $area, \PDO::PARAM_STR);
$sth->bindValue(':polygonType', $polygon->getPolygonType(), \PDO::PARAM_STR);
It appears that you're trying to use a named parameter inside a string:
PolygonFromText(\'POLYGON((:polygonArea))\')
This would be akin to doing something like this:
UPDATE foo SET bar = 'blah blah :wontwork blah blah'
What you should try instead is binding the whole string in the query:
PolygonFromText(:polygonArea)
And then including the rest of the string in the bound value:
$sth->bindValue(':polygonArea', 'POLYGON((' . $polygon->getPolygonAsText() . '))', \PDO::PARAM_STR);
Last resort you could do this:
$sql = "INSERT INTO ".POLYGON_TABLE." (user_id, polygon, polygon_type) "
."VALUES (:userId, PolygonFromText('POLYGON(". $polygon->$getPolygonAsText
.")'),:polygonType)";
But I think you should try the ? params first and see how that goes.
$sql = "INSERT INTO ".POLYGON_TABLE." (user_id, polygon, polygon_type) "
."VALUES (?, PolygonFromText('POLYGON(?)'), ?);";
$data = array($polygon->getUserId(), $polygon->getPolygonAsText(), $polygon->getPolygonType());
$query->execute($data);
Btw, I also think those single quotes around the POLYGON(?) function are dodgy... usually you don't quote a method call do you?

PDO bindParam into one statement?

Is there a way I can put these bindParam statements into one statement?
$q = $dbc -> prepare("INSERT INTO accounts (username, email, password) VALUES (:username, :email, :password)");
$q -> bindParam(':username', $_POST['username']);
$q -> bindParam(':email', $_POST['email']);
$q -> bindParam(':password', $_POST['password']);
$q -> execute();
I was using mysqli prepared before where it was possible, I switched to PDO for assoc_array support. On the php.net website for PDO it shows them on seperate lines, and in all examples I have seen it is on seperate lines.
Is it possible?
Example 2 on the execute page is what you want:
$sth->execute(array(':calories' => $calories, ':colour' => $colour));
You may want to look at the other examples too. With question mark parameters, it would be:
$q = $dbc -> prepare("INSERT INTO accounts (username, email, password) VALUES (?, ?, ?)");
$q->execute(array($_POST['username'], $_POST['email'], $_POST['password']));
If those are the only columns, you can just write:
$q = $dbc -> prepare("INSERT INTO accounts VALUES (?, ?, ?)");
$q->execute(array($_POST['username'], $_POST['email'], $_POST['password']));
helper function is a function that makes you help to avoid writing bunch of repetitive code every time you want to run a query.
This is called "programming" and there is almost none of it on this site, at least under "PHP" tag.
While many peiople thinks that programming stands for copy/pasting chunks of code from manual examples, it's somewhat different.
Although it's hard to learn but really worth it, especially if you're devoting yourself to web-developing.
As you can see, no accepted answer did no real help for you, as you still have to write something like
$sth->execute(array(':username' => $_POST['username'],
':email' => $_POST['email']
':password' => $_POST['password']);
as many times as many fields in your table, which makes not much difference from your initial approach, still makes you write each field name FOUR times.
But being a programmer, you can use powers of programming. A loop, for example - one of cornerstone programming operators.
Every time you see repetitions, you know there should be a loop.
for example, you can set up a list of fields, naming them only once.
And let a program do the rest.
Say, such a function like this one
function pdoSet($fields, &$values, $source = array()) {
$set = '';
$values = array();
if (!$source) $source = &$_POST;
foreach ($fields as $field) {
if (isset($source[$field])) {
$set.="`$field`=:$field, ";
$values[$field] = $source[$field];
}
}
return substr($set, 0, -2);
}
being given an array of field names, it can produce both insert statement and data array for you. Programmatically. So, your code become no more than these 3 short lines:
$fields = array('username', 'email', 'password');
$stmt = $dbh->prepare("INSERT INTO accounts SET ".pdoSet($fields,$values));
$stmt->execute($values);
Your Common Sense is totally right that the aim of coding is to save typing... but his solution doesn't help with the BindParams bit. I couldn't find anything else about this online, so here's something I finally just persuaded to work - I hope it's useful for someone!
//First, a function to add the colon for each field value.
function PrepareString($array){
//takes array (title,author);
//and returns the middle bit of pdo update query :title,:author etc
foreach($array as $k =>$v){
$array[$k]=':'.$v;
}
return implode(', ', $array);
}
Then...
function PdoInsert($table_name,$array){
$db = new PDO(); //however you create your own pdo
//get $fields and $vals for statement
$fields_vals=array_keys($array);
$fields=implode(',',$fields_vals);
$vals=PrepareString($fields_vals);
$sql = "INSERT INTO $table_name($fields) VALUES ($vals)";
$qwe=$db->prepare($sql);
foreach ($array as $k =>$v ){
//add the colon to the key
$y=':'.$k;
//god knows why it doesn't like $qwe->bindParam($y,$v,PDO::PARAM_STR);
// but it really doesn't! So we refer back to $array.
//add checks for different binding types here
(see PDO::PARAM_INT is important in bindParam?)
$qwe->bindParam($y,$array[$k],PDO::PARAM_STR);
}
if ($qwe->execute()==true){
return $db->lastInsertId();
}
else {
return $db->errorCode();
}
}
Then you can insert anything by doing
PdoInsert('MyTableName',array('field1'=>$value1,'field2'=>$value2...));
Having previously sanitized your values of course.
+1 to Matthew Flaschen for the accepted answer, but I'll show you another tip. If you use SQL parameters with names the same as the entries in $_POST, you could take advantage of the fact that $_POST is already an array:
$q->execute($_POST);
The SQL parameter names are prefixed with a colon (:) but the keys in the $_POST array are not. But modern versions of PDO account for this - you no longer need to use colon prefixes in the keys in the array you pass to execute().
But you should be careful that anyone can add extra parameters to any web request, and you should get only the subset of $_POST params that match parameters in your query.
$q = $dbc -> prepare("INSERT INTO accounts (username, email, password)
VALUES (:username, :email, :password)");
$params = array_intersect_key($_POST, array("username"=>1,"email"=>1,"password"=>1));
$q->execute($params);
Personally, I prefer to use a wrapper function for all of pdo, which simplifies the code necessary substantially.
For example, to run bound queries (well, all my queries), I do this:
$iterable_resultset = query("INSERT INTO accounts (username, email, password) VALUES (:username, :email, :password)", array(':username'=>'bob', ':email'=>'bob#example.com', ':password'=>'bobpassword'));
Note that not only is the sql simply a string, but it's actually a reusable string, as you can simply pass the sql as a string and change the array of variables to pass in if you want to perform a similar insert right after that one (not applicable to this situation, but applicable to other sql use cases).
The code that I use to create this wrapper function is as below:
/**
* Run bound queries on the database.
*
* Use: query('select all from players limit :count', array('count'=>10));
* Or: query('select all from players limit :count', array('count'=>array(10, PDO::PARAM_INT)));
*
* Note that it returns foreachable resultset object unless an array is specifically requested.
**/
function query($sql, $bindings=array(), $return_resultset=true) {
DatabaseConnection::getInstance(); // Gets a singleton database connection
$statement = DatabaseConnection::$pdo->prepare($sql); // Get your pdo instance, in this case I use a static singleton instance. You may want to do something simpler.
foreach ($bindings as $binding => $value) {
if (is_array($value)) {
$first = reset($value);
$last = end($value);
// Cast the bindings when something to cast to was sent in.
$statement->bindParam($binding, $first, $last);
} else {
$statement->bindValue($binding, $value);
}
}
$statement->execute();
if ($return_resultset) {
return $statement; // Returns a foreachable resultset
} else {
// Otherwise returns all the data an associative array.
return $statement->fetchAll(PDO::FETCH_ASSOC);
}
}
// Wrapper to explicitly & simply get a multi-dimensional array.
function query_array($sql_query, $bindings=array()) {
return query($sql_query, $bindings, false); // Set return_resultset to false to return the array.
}
As noted in the comments, you'd want to use your own method for setting up a database connection and getting an initialized pdo, but in general it allows your bound sql to be cut down to just a single line.

Mysqli Prepared Statement in bindParam() not working

Just as usual i was looking around best practices with PHP, and prepared statements seems the kind of stuff i should now how do with my eyes closed. So i started playing around with some examples i've found.
I've got this error when running the script:
Fatal error: Call to a member function
bindParam() on a non-object in
/opt/lampp/htdocs/phpSecurity/PreparedStatments/Insert-Multi-Binded-Params/Insert
Simple Method.php on line 10
Here it goes the code.
Insert Simple Method.php
<?php
require_once '../config.php';
$stmt = $db->prepare("INSERT INTO coisas (nome, telefone, bi) VALUES (?, ?, ?)");
$nome = 'Fabio Antunes';
$telefone = 916810641;
$bi = 123093456;
$stmt->bindParam(1, $nome);
$stmt->bindParam(2, $telefone);
$stmt->bindParam(3, $bi);
$stmt->execute();
$stmt->close();
$db->close();
?>
config.php
<?php
$server_host = 'localhost';
$server_user = 'root';
$server_password = '';
$server_db = 'PreparedStatements';
$db = new mysqli($server_host, $server_user, $server_password, $server_db);
?>
Not sure what i'm doing wrong here, this is similar example found at php.net, why isn't working?
PS: I think the mysqli connection isn't the problem because I've used it to do some prepared statements with SELECT SQL commands. And worked pretty well.
EDIT
The Resolution and why.
Well in the example i should use bind_param() for each value in the query. But thanks to Bart, he managed to solve the problem with my code.
Where it is:
$stmt->bindParam(1, $nome);
$stmt->bindParam(2, $telefone);
$stmt->bindParam(3, $bi);
It should be:
$stmt->bind_param("sii", $nome, $telefone, $bi);
Now for those who might wondering what is "sii".
Well bind_param for what i see it binds the "$var" to each question mark "?" in order.
So with one bind_param() i can bind them all at the same time, and the normal use of bind_param() requires to specify the type of data being binded.
My first value to be binded is $nome a String, specified by the "s";
And the others $telefone and $bi are Integers for that he have "i";
For others that have a similar problem here it goes other data types (from php.net).
i = Integer;
s = String;
d = Double;
b = Blob;
If someone as a better explanation please post it or comment. So i can improve my own.
Thanks.
You may think there's nothing wrong with the connection, but you should check to make sure:
$db = new mysqli($server_host, $server_user, $server_password, $server_db);
if (mysqli_connect_errno()) {
printf("DB error: %s", mysqli_connect_error());
exit();
}
EDIT:
What happens when you do:
$stmt = $db->prepare("INSERT INTO coisas (nome, telefone, bi) VALUES (?, ?, ?)");
$stmt->bind_param("sii", $nome, $telefone, $bi);
$stmt->execute();
?
Is the table coisas spelled properly?
do a print_r on $stmt after you get it back on line 4. Is it a real object? I am guessing no.

Categories