MySQLi dynamic prepared function with array binding - php

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

Parametrized PDO query for LIMIT clause

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

call_user_func_array for dynamic mysqli_bind_param [duplicate]

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();

Prepared statements using multiple values

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.

PDO bindColumn and PDO::FETCH_BOUND -- compulsory or optional?

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;
}
}

PDO Dynamic Query Building

I have been old school using mysql_query and starting out now using PDO. Which is great!
But in my old scripts I had build a dynamic query builder, and i'm having a tough time porting that over using PDO.
If anyone can give me some direction that would be great!
Here is the theory of it.
I have an array of
the DB Fields and Values (upon insert).
Create the query string to product a valid PDO transaction
Here is a portion of what i'm trying to do.
public $dbFields; // This is an array of the fields plus VALUES
public function select($where, $limit) {
// This is what I **had** before
$query = "SELECT ". implode(", ", $this->dbFields) ." FROM ". $this->table." WHERE ". $where ." ". $limit."";
// Now i need to convert that to PDO
$this->connection->beginTransaction();
# START Query
$select = $this->connection->prepare("SELECT {$this->fieldNames} FROM {$this->table}");
// I need to BIND my params and values, but i'm not sure the best route to take when I have a WHERE clause that includes, "AND" / "OR" operators.
# EXECUTE the query
$select->execute();
$this->connection->commit();
}
This is what I HAD before
$results = $db->select("userId = 111 OR userId = 222");
But what i'm thinking I need to do is use something more like
$results = $db->select(array("userId"=>111, "userId"=>222));
I know this is a tall order, and I hope it makes sense in what i'm trying to do, but any help in trying to build these queries would be greatly appreciated.
You'll need a separate $params parameter to your select method. I took the liberty of providing defaults for the method parameters. Like #userXxxx notes, you don't need a transaction just to do a SELECT.
<?php
class db {
public $connection; //PDO
public $dbFields; // This is an array of the fields plus VALUES
public function select($where = '1', $params = array(), $limit = '', $fetchStyle = PDO::FETCH_ASSOC) { //fetchArgs, etc
$fields = implode(', ', $this->dbFields);
//create query
$query = "SELECT $fields FROM {$this->table} WHERE $where $limit";
//prepare statement
$stmt = $this->connection->query($query);
$stmt->execute($params);
return $stmt->fetchAll($fetchStyle);
}
//...
}
$where = 'userId IN(:userId1, :userId2)';
$params = array(':userId1' => 111, ':userId2' => 2222);
$db->select($where, $params);
Notes:
If you really want, you can add additional method parameters to match up with all the flexibility of PDOStatement::fetchAll.
I'm not sure what you mean about $dbFields being "fields plus VALUES". Can you explain?
[Edit]
You might want to take a look at the docs/examples for PDOStatement::execute, since that seemed to be where your confusion was rooted--in particular, the $input_parameters method parameter.
What about this?
public function select($where, $limit) {
$query = "SELECT ". implode(", ", $this->dbFields) ." FROM ". $this->table." WHERE ". $where ." ". $limit."";
$this->connection->query($query);
}
//Use what you had before:
$results = $db->select("userId = 111 OR userId = 222");
Not sure why you want to use transaction (for all-or-nothing basis or catching exceptions and rollback) or prepared queries (for sending multiple queries)...

Categories