I am attempting to create a database query function which can take multiple parameters and be reused elsewhere, however tried a number of methods online similar to my approach and they are not working as expected.
function query($query, $bindings, $type)
I want to be able to run queries on the go with this single function, this type of function is a lot easier with the PDO driver as you could simply enter the binding inside ->execute($binding); however in this case I am forced to use MySQLi as the application currrently relies on it but wanting to upgrade it to use prepared statements.
An example of how I need to be able to use the function to ensure it is reusable and flexible:
$engine->query("SELECT * FROM accounts WHERE email = :mail AND id = :id",array(':mail' => $_POST['mail'], ':id' => 2))->fetch_assoc();
Lets split each of them up. First is the statement, then the array which is used to bind the parameters used, then the types of the parameters, and finally the type of fetch_ to utilize on the query (ASSOC, OBJECT, ARRAY) etc.
"SELECT * FROM accounts WHERE email = :mail AND id = :id",
array(':mail' => $_POST['mail'], ':id' => 2),
"si"
->fetch_assoc();
though to implement named parameters would be quite a task, the rest is pretty doable.
A PHP >= 5.6 variant, implementing splat operator:
function query($query, $params = NULL, $types = NULL)
{
if (!$params)
{
return $mysqli->query($query);
}
$statement = $this->mysqli->prepare($select);
$types = $types ?: str_repeat('s', count($params));
$statement->bind_param($types, ...$params);
$statement->execute();
return $statement->get_result();
}
used like this
$sql = "SELECT * FROM accounts WHERE email = ? AND id = ?";
$row = $engine->query($sql, [$_POST['mail'], 2])->fetch_assoc();
or, if you want to set types explicitly
$row = $engine->query($sql, [$_POST['mail'], 2], "si")->fetch_assoc();
Related
I have seen similar questions answered already but I can't seem to apply the same solutions to my code.
$a=1;
$results = DB::query('SELECT posts.`postbody`, posts.`filepost`, posts.`likes`, posts.`posted_at`, users.`id`, posts.`id_of_post` FROM posts, users WHERE posts.`post_id` = users.`id` ORDER BY id_of_post DESC LIMIT :a', array(':a'=>$a));
class DB {
private static function connect() {
$pdo = new PDO('mysql:host=127.0.0.1;dbname=SocialNetwork;charset=utf8', 'root', '');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return $pdo;
}
public static function query($query, $params = array()) {
$statement = self::connect()->prepare($query);
$statement->execute($params);
if (explode(' ', $query)[0] == 'SELECT') {
$data = $statement->fetchAll();
return $data;
}
}
}
For the record the following code works fine.
$results = DB::query('SELECT posts.`postbody`, posts.`filepost`, posts.`likes`, posts.`posted_at`, users.`id`, posts.`id_of_post` FROM posts, users WHERE posts.`post_id` = users.`id` ORDER BY id_of_post DESC LIMIT 1');
Not ideal, but you could do away with the PDO parameters.
$a = 1;
$sql = "SELECT stuff FROM table LIMIT {$a};";
Then run your query from the $sql string.
As stated in the previous answers if you do not define:
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
You have to define the parameter to be binded as integer:
foreach($params as $key => $value){
if(is_numeric($value))
$statement->bindParam($key,$value,PDO::PARAM_INT);
else
$statement->bindParam($key,$value,PDO::PARAM_STR);
}
$statement->execute();
This is still not a perfect solution, but if you trust the key value pairs(ie they are from code, not user input) it's good enough.
In MySQL's LIMIT clause, it's an error to do this:
LIMIT '1'
Because LIMIT must take an integer, not a string.
If PDO is configured to emulate prepare() (by interpolating values into your SQL string), it's likely to make the interpolated value a quoted string, which will cause an error.
To avoid this, you must use a native integer as your bound variable and you just specify PDO::PARAM_INT.
$statement = self::connect()->prepare($query);
$statement->bindParam('a', $a, PDO::PARAM_INT);
$statement->execute();
That will let the driver know to avoid putting quotes around the interpolated value.
You can also avoid the error if you set the PDO attribute to disable emulated prepares. I always do this, because I don't trust "emulated prepare."
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
See also my tests I wrote up here: Parametrized PDO query and `LIMIT` clause - not working
I'm looking to improve the security of database access on a website, and the general consensus seems to be to use prepared statements. I have an idea of how they work, but I want to generalize their use so the only things I need to supply are a query, the parameter types, and values. However, I haven't found any particularly good resources for this and as a result, I'm sort of lost as to how I should approach this.
Basically, what I want is as follows.
$query = "SELECT * FROM Table WHERE Column1 = ? AND Column2 = ?";
$array[0] = "string";
$array[1] = 5;
$parameters = "si";
$dbHandler = new mysqli("server", "user", "password", "database");
$stmt = $dbHandler->prepare($query);
$stmt->bind_param($parameters, $array);
$stmt->execute();
//Process results
I'm aware that this isn't the proper procedure but that's the problem. What can I do to make this work? The idea is that the number of variables within $array may change, as will the parameter list.
I need to supply are a query, the parameter types, and values.
Here you go:
function mysqli_query_params($mysqli, $query, $params, $types = NULL)
{
$statement = $mysqli->prepare($query);
$types = $types ?: str_repeat('s', count($params));
$statement->bind_param($types, ...$params);
$statement->execute();
return $statement;
}
which can be used exactly the way you described:
$query = "SELECT * FROM Table WHERE Column1 = ? AND Column2 = ?";
$params = ["string", 5];
$types = "si";
$mysqli = new mysqli("server", "user", "password", "database");
$data = mysqli_query_params($mysqli, $query, $params, $types)->get_result()->fetch_all();
Note that most of time you can avoid setting types explicitly and let them be mound as strings by default.
However, PDO indeed is way more usable than raw mysqli, and you better use it. You may learn it from this tutorial
P.S. You need PHP >=5.6 and mysqlnd installed for this code to run. Otherwise the amount of code will be increased tenfold. That's another reason to use PDO.
I am attempting to create a database query function which can take multiple parameters and be reused elsewhere, however tried a number of methods online similar to my approach and they are not working as expected.
function query($query, $bindings, $type)
I want to be able to run queries on the go with this single function, this type of function is a lot easier with the PDO driver as you could simply enter the binding inside ->execute($binding); however in this case I am forced to use MySQLi as the application currrently relies on it but wanting to upgrade it to use prepared statements.
An example of how I need to be able to use the function to ensure it is reusable and flexible:
$engine->query("SELECT * FROM accounts WHERE email = :mail AND id = :id",array(':mail' => $_POST['mail'], ':id' => 2))->fetch_assoc();
Lets split each of them up. First is the statement, then the array which is used to bind the parameters used, then the types of the parameters, and finally the type of fetch_ to utilize on the query (ASSOC, OBJECT, ARRAY) etc.
"SELECT * FROM accounts WHERE email = :mail AND id = :id",
array(':mail' => $_POST['mail'], ':id' => 2),
"si"
->fetch_assoc();
though to implement named parameters would be quite a task, the rest is pretty doable.
A PHP >= 5.6 variant, implementing splat operator:
function query($query, $params = NULL, $types = NULL)
{
if (!$params)
{
return $mysqli->query($query);
}
$statement = $this->mysqli->prepare($select);
$types = $types ?: str_repeat('s', count($params));
$statement->bind_param($types, ...$params);
$statement->execute();
return $statement->get_result();
}
used like this
$sql = "SELECT * FROM accounts WHERE email = ? AND id = ?";
$row = $engine->query($sql, [$_POST['mail'], 2])->fetch_assoc();
or, if you want to set types explicitly
$row = $engine->query($sql, [$_POST['mail'], 2], "si")->fetch_assoc();
In many places in our PHP code, (working with postgres if it matters)
we have stuff like:
$q = "SELECT DISTINCT a.id FROM alarms.current a, entities e, installations i ";
$q .= "WHERE i.\"entityId\"=e.id AND a.installationid=i.id AND ";
$q .= "e.id=".$entityId;
$stmt = $db->query($q);
$stmt->bindColumn("id", $alarmId);
if ($stmt->fetch(PDO::FETCH_ASSOC))
....etc
Now according to my reading of the docs, if you want your variables updated from their bound columns you ought to use PDO::FETCH_BOUND. But we don't, and no-one has complained about the performance as far as I'm aware.
Can anyone throw any light on why this apparently faulty code actually apparently works?
While the example in the PHP documentation for bindColumn uses PDO::FETCH_BOUND, which does suggest that this fetch style is necessary in order to use bindColumn, it does not explicitly state that this is a requirement. It only says
PDOStatement::bindColumn() arranges to have a particular variable bound to a given column in the result-set from a query. Each call to PDOStatement::fetch() or PDOStatement::fetchAll() will update all the variables that are bound to columns.
After some testing I determined that this will occur regardless of the fetch style that is used. I think the fact that the fetch call in your code is not actually fetched into a variable really just means that an associative array is created and assigned to nothing, while the side effect of the fetch populates the $alarmId variable.
Continuing from #DontPanic's comments, here is how I prefer to use bound parameters:
/**
* #param $id
*
* #return array|false
*/
public function retrieveImage($id)
{
$conn = $this->dbh->getInstance('LocalMSSQL');
$stmt = $conn->prepare("SELECT inputType, blob FROM images WHERE id = ?");
$stmt->bindValue(1, $id, PDO::PARAM_INT);
$stmt->execute();
$resultSet = [
'inputType' => null,
'blob' => null,
];
$stmt->bindColumn(1, $resultSet['inputType'], PDO::PARAM_STR);
$stmt->bindColumn(2, $resultSet['blob'], PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY);
if ($stmt->fetch()) {
return $resultSet;
} else {
return false;
}
}
Positional parameters become a nightmare when dealing with more than 3 or 4 parameters. Named parameters are verbose. I'm thinking of doing this:
query("SELECT * FROM users WHERE username = ", $username, " AND password = ", $password)
With dynamic parameters (using func_get_args()), every second one being transformed into a positional parameter.
I've never seen this before and wanted to know if anyone has done this before and why/why not?
Named parameters don't have to be verbose, at least not compared to positional parameters. You could use shortened versions that are still obvious:
$st = $dbh->prepare('SELECT * FROM users WHERE username = :u AND password = :p');
$st->bindValue(':u', $username);
$st->bindValue(':p', $password);
$st->execute();
It's a clever idea. The only problem I see is how to distinguish between SQL and passed-in variables. Unless you make an assumption that every second arg is a variable. I just think that assumption is fragile, and obfuscates things more than makes them clear.
Better way would probably be to use interpolation:
query("SELECT foo FROM bar WHERE id = #{id}", array("id" => "23"));
Then write logic to interpolate these.
I don't think positional parameters are so bad... this is my favorite method:
function mysql_safe_string($value) {
if(is_numeric($value)) return $value;
elseif(empty($value)) return 'NULL';
elseif(is_string($value)) return "'".mysql_real_escape_string($value)."'";
elseif(is_array($value)) return implode(',',array_map('mysql_safe_string',$value));
}
function mysql_safe_query($format) {
$args = array_slice(func_get_args(),1);
$args = array_map('mysql_safe_string',$args);
$query = vsprintf($format,$args);
$result = mysql_query($query);
if($result === false) echo '<div class="mysql-error"><strong>Error: </strong>',mysql_error(),'<br/><strong>Query: </strong>',$query,'</div>';
return $result;
}
// example
$result = mysql_safe_query('SELECT * FROM users WHERE username=%s', $username);