PDO MySql function to accept multiple parameters - php

I have below PDO function that executes a Mysql query.
public function run($sql, array $params = NULL) {
$statement = $this->pdo->prepare($sql);
if (!is_null($params)) {
foreach ($params as $key) {
$statement->bindParam(":n", $key);
}
}
$statement->execute();
return $statement->fetchAll(PDO::FETCH_CLASS);
}
This only works with single parameters (see my previous question)
So i tried modifying the foreach in the function to
foreach ($params as $key => $value) {
$statement->bindParam($key, $value);
}
I run the query as
$variation = $query->run(
"SELECT url, title, sale_price
FROM product
where category_id = :category
and url != :url",
[ ":category" => $data[0]->category_id, ":url" => $filePathArray[1] ]
);
It returns an empty set.

This function is not necessary. You can pass the array of parameters directly to $statement->execute() as explained in the documentation for PDOStatement::execute().
It is as safe as using bindParam(), it protects against SQL injection just the same.
The only downside is that you cannot specify the type of each parameter, but that isn't necessary in most cases, and in this example you were not using it anyway so you won't loose anything.

Related

PDO prepared statement returns empty set, Query works fine

I have below PDO function that executes a Mysql query. Its working correctly for all queries.
public function run($sql, array $params = NULL) {
$statement = $this->pdo->prepare($sql);
if (!is_null($params)) {
foreach ($params as $key) {
$statement->bindParam(":n", $key);
}
}
$statement->execute();
return $statement->fetchAll(PDO::FETCH_CLASS);
}
However when i run the below query its returns an empty set
$variation = $query->run(
"SELECT url, title, sale_price
FROM product
where category_id = :n
and url != :n",
[ $data[0]->category_id, $filePathArray[1] ]
);
Its works when i run the query manually in mysql client.
I tried type casting the category id but no joy:
(int)$data[0]->category_id
(since it was passing a string as opposed to Integer)
Here is the var_dump of $params from within the run function
array(2) {
[0]=> int(1)
[1]=> string(21) "light-resistance-band"
}
You need to add different named place-holder to bind corresponding values(not identical)
$variation = $query->run(
"SELECT url, title, sale_price
FROM product
where category_id = :n
and url != :n1",
[ ':n'=>$data[0]->category_id, ':n1'=>$filePathArray[1] ]
);
You can custommize your function checking the type of $key and bindParam like this:
public function run($sql, array $params = NULL) {
$statement = $this->pdo->prepare($sql);
if (!is_null($params)) {
foreach ($params as $key=>$value) {
$statement->bindParam(":".$key, $value);
}
}
$statement->execute();
return $statement->fetchAll(PDO::FETCH_CLASS);
}

MySQL PDO won't work with integers

I Have a basic function that looks like this:
public function query($query, $params = []) {
$statement = $this->db->prepare($query);
// Bind parameters based on value's type
foreach ($params as $key => $value) {
if(is_int($value)) {
$statement->bindParam($key + 1, $value, PDO::PARAM_INT);
} else {
$statement->bindParam($key + 1, $value, PDO::PARAM_STR);
}
}
$statement->execute();
return $statement;
}
For whatever reason, when I run something like this:
public static function photosByTag($tag, $user = null) {
$db = new DBConnection();
$query = "SELECT * FROM photos JOIN tags ON tags.photo = photos.pid WHERE tag LIKE ? AND owner = ?";
$params = [$tag, $user];
$result = $db->query($query, $params);
return $result->fetchAll();
}
photosByTag('city', 1)
It doesn't work. If I replace the AND owner = ? with AND owner = 1 it works fine. Something is wrong when binding integers as params, but I don't know what or why.
The problem isn't the bind, it is the loop. If you look at the manual, the second parameter for bindParam (&$variable) requires a reference. Your loop destroys that reference once it reassigns $value. The solution would be to use $params[$key] instead of $value in the bindParam()
Seems kind of redundant to do it this way when you can just use the execute() statement to bind the parameters.
$statement->execute($params);
Just let PDO handle how it assigns the variables. All your doing is checking what is submitted and then choosing the type, you're not enforcing a type, so it is probably similar to what PDO::execute does as is.

PHP PDO, Object, bindParam

I don't manage to fill a table with a PHP Object with PDO and an object created from a JSON file. Do you see where the error comes from?
I use PHP5 & PostgreSQL
The code I wrote sucessfully adds the lines, but only the first column (field) of each row is filled, the others remain white.
My table structure looks like this:
CREATE TABLE ' . $infoTableName . ' (field text,type text,expefactor boolean,iduser boolean,idcontext boolean,idaction boolean,params boolean,comment text)
My object looks like this:
object(stdClass)[3]
public 'timestamp' =>
object(stdClass)[4]
public 'idagent' => boolean false
public 'idcontext' => boolean false
public 'idaction' => boolean false
public 'comment' => string 'ffff' (length=4)
public 'order' =>
object(stdClass)[5]
public 'idagent' => boolean false
public 'idcontext' => boolean false
public 'idaction' => boolean false
public 'comment' => string 'none' (length=4)
public 'test' =>
object(stdClass)[6]
public 'idagent' => boolean false
public 'idcontext' => boolean true
public 'idaction' => boolean false
public 'comment' => string 'y' (length=1)
And finally the PHP code:
$structure = json_decode($_POST['structure']);
$query = "INSERT INTO " . $infoTableName . " (field, iduser, idcontext, idaction, comment) VALUES (:field, :idagent, :idcontext, :idaction, :comment)"; //Prequery
$stmt = $db->prepare($query);
$stmt->bindParam(':field', $key);
$stmt->bindParam(':idagent', $value->idagent);
$stmt->bindParam(':idcontext', $value->idcontext);
$stmt->bindParam(':idaction', $value->idaction);
$stmt->bindParam(':comment', $value->comment);
foreach ($structure as $key => &$value) {
try {
var_dump($stmt->execute());
} catch (PDOException $e) {
var_dump($e->getMessage());
}
}
Do you see the error?
Thanks a lot.
EDIT: It looks like I am asking a too much with object in binding functions, still, here is a little workaround:
$stmt->bindParam(':field', $key);
$stmt->bindParam(':idagent', $idagent);
$stmt->bindParam(':idcontext', $idcontext);
$stmt->bindParam(':idaction', $idaction);
$stmt->bindParam(':comment', $comment);
foreach ($structure as $key => &$value) {
$idagent = $value->idagent;
$idcontext = $value->idcontext;
$idaction = $value->idaction;
$comment = $value->comment;
try {
var_dump($stmt->execute());
} catch (PDOException $e) {
var_dump($e->getMessage());
}
}
The reason bindParam() doesn't work is when I iterate thru the $structure, $value is reinstanciated, resulting in the references being changed... I first thought &$value would help, but it looks like not.
Like they do here: ...
No, they do not. Look again:
$stmt->bindParam(':name', $name);
$name = 'one';
$stmt->execute();
They are binding the variable $name by reference and then assign a value to $name.
You on the other hand are doing:
$stmt->bindParam(':idagent', $value->idagent);
foreach ($structure as $key => &$value) {
$stmt->execute();
}
You are binding $value->idagent by reference (I'm not actually sure if that works, creating an implicit object...?!), and then you're reference-overwriting $value. If at all, you should be assigning a value to $value->idagent, which was bound. Just replacing the $value object is not the same thing at all. PHP isn't so intelligent that it tracks that you bound the idagent attribute of that object and will reconstruct that after you have switched the underlying object. That's a little too meta.
I think in this case it's most useful to use bindValue inside your foreach loop and bind the existing values there.
$stmt = $db->prepare($query);
foreach ($structure as $key => $value) {
$stmt->bindValue(':field', $key);
$stmt->bindValue(':idagent', $value->idagent);
$stmt->bindValue(':idcontext', $value->idcontext);
$stmt->bindValue(':idaction', $value->idaction);
$stmt->bindValue(':comment', $value->comment);
$stmt->execute()
}
It looks like I am asking a too much with object in binding functions, still, here is a little workaround:
$stmt->bindParam(':field', $key);
$stmt->bindParam(':idagent', $idagent);
$stmt->bindParam(':idcontext', $idcontext);
$stmt->bindParam(':idaction', $idaction);
$stmt->bindParam(':comment', $comment);
foreach ($structure as $key => &$value) {
$idagent = $value->idagent;
$idcontext = $value->idcontext;
$idaction = $value->idaction;
$comment = $value->comment;
try {
var_dump($stmt->execute());
} catch (PDOException $e) {
var_dump($e->getMessage());
}
}
The reason bindParam() doesn't work is when I iterate thru the $structure, $value is reinstanciated, resulting in the references being changed... I first thought &$value would help, but it looks like not.

How make a Dynamic bindValue()?

Okay I have a function called sendQuery which sends your query.
I know how to do it with BindParams, but I can't really think of a way to make it work with bind values inside a execute.
This is the code:
public function sendQuery($query, array $value, $do_fetch)
{
$query_process = $this->db->prepare($query);
if(!$query_process->execute($binds))
{
throw new excpetion ("An error has occured!");
}
$this->insert = $this->db->lastInsertId();
$this->row_count = $query_process->rowCount();
if($fetch == true)
{
return $query_process->fetchAll();
}
}
As you see, it executes with $binds,
Works like (WHERE user = ?), but I want to send queries like this:
(WHERE user = :user) instead of a ' ? ', and multiple of them.
How do I do so?
You have to do exactly the same.
Just get rid of useless code and use consistent variable naming
public function sendQuery($query, array $binds, $do_fetch)
{
$stm = $this->db->prepare($query);
$stm->execute($binds);
$this->insert = $this->db->lastInsertId();
$this->row_count = $stm->rowCount();
if($do_fetch)
{
return $stm->fetchAll();
}
}
$sql = "SELECT * FROM t WHERE c1=:name AND c2=:age";
$param = array ("name" => $name,"age" => $age);
$data = $db->sendQuery($sql, $data, 1);
However, instead of just single function I would create a set of them:
query() to run non-select queries
getOne() preforms select and returns scalar value
getRow() returns a row
getAll returns all rows
it could be extremely handy

php pdo prepare repetitive variables

While writing a pdo statement, is it possible to repeat the value of a variable? I mean:
$query = "UPDATE users SET firstname = :name WHERE firstname = :name";
$stmt = $dbh -> prepare($query);
$stmt -> execute(array(":name" => "Jackie"));
Please note that I repeat the ":name" nameholder whereas I provide the value only once. How can I make this work?
The simple answer is: You can't. PDO uses an abstraction for prepared statements which has some limitations. Unfortunately this is one, you have to work-around using something like
$query = "UPDATE users SET firstname = :name1 WHERE firstname = :name2";
$stmt = $dbh -> prepare($query);
$stmt -> execute(array(":name1" => "Jackie", ":name2" => "Jackie"));
In certain cases, such as emulated prepared statements with some versions of the PDO/MySQL driver, repeated named parameters are supported; however, this shouldn't be relied upon, as it's brittle (it can make upgrades require more work, for example).
If you want to support multiple appearances of a named parameter, you can always extend PDO and PDOStatement (by classical inheritance or by composition), or just PDOStatement and set your class as the statement class by setting the PDO::ATTR_STATEMENT_CLASS attribute. The extended PDOStatement (or PDO::prepare) could extract the named parameters, look for repeats and automatically generate replacements. It would also record these duplicates. The bind and execute methods, when passed a named parameter, would test whether the parameter is repeated and bind the value to each replacement parameter.
Note: the following example is untested and likely has bugs (some related to statement parsing are noted in code comments).
class PDO_multiNamed extends PDO {
function prepare($stmt) {
$params = array_count_values($this->_extractNamedParams());
# get just named parameters that are repeated
$repeated = array_filter($params, function ($count) { return $count > 1; });
# start suffixes at 0
$suffixes = array_map(function ($x) {return 0;}, $repeated);
/* Replace repeated named parameters. Doesn't properly parse statement,
* so may replacement portions of the string that it shouldn't. Proper
* implementation left as an exercise for the reader.
*
* $param only contains identifier characters, so no need to escape it
*/
$stmt = preg_replace_callback(
'/(?:' . implode('|', array_keys($repeated)) . ')(?=\W)/',
function ($matches) use (&$suffixes) {
return $matches[0] . '_' . $suffixes[$matches[0]]++;
}, $stmt);
$this->prepare($stmt,
array(
PDO::ATTR_STATEMENT_CLASS => array('PDOStatement_multiNamed', array($repeated)))
);
}
protected function _extractNamedParams() {
/* Not actually sufficient to parse named parameters, but it's a start.
* Proper implementation left as an exercise.
*/
preg_match_all('/:\w+/', $stmt, $params);
return $params[0];
}
}
class PDOStatement_multiNamed extends PDOStatement {
protected $_namedRepeats;
function __construct($repeated) {
# PDOStatement::__construct doesn't like to be called.
//parent::__construct();
$this->_namedRepeats = $repeated;
}
/* 0 may not be an appropriate default for $length, but an examination of
* ext/pdo/pdo_stmt.c suggests it should work. Alternatively, leave off the
* last two arguments and rely on PHP's implicit variadic function feature.
*/
function bindParam($param, &$var, $data_type=PDO::PARAM_STR, $length=0, $driver_options=array()) {
return $this->_bind(__FUNCTION__, $param, func_get_args());
}
function bindValue($param, $var, $data_type=PDO::PARAM_STR) {
return $this->_bind(__FUNCTION__, $param, func_get_args());
}
function execute($input_parameters=NULL) {
if ($input_parameters) {
$params = array();
# could be replaced by array_map_concat, if it existed
foreach ($input_parameters as $name => $val) {
if (isset($this->_namedRepeats[$param])) {
for ($i=0; $i < $this->_namedRepeats[$param], ++$i) {
$params["{$name}_{$i}"] = $val;
}
} else {
$params[$name] = $val;
}
}
return parent::execute($params);
} else {
return parent::execute();
}
}
protected function _bind($method, $param, $args) {
if (isset($this->_namedRepeats[$param])) {
$result = TRUE;
for ($i=0; $i < $this->_namedRepeats[$param], ++$i) {
$args[0] = "{$param}_{$i}";
# should this return early if the call fails?
$result &= call_user_func_array("parent::$method", $args);
}
return $result;
} else {
return call_user_func_array("parent::$method", $args);
}
}
}
In my case this error appeared when I switched from dblib freedts to sqlsrv PDO driver. Dblib driver handled duplicate parameters names with no errors. I have quite complicated dynamic queries with lots of unions and a lot of duplicated params so I used following helper as a workaround:
function prepareMsSqlQueryParams($query, $params): array
{
$paramsCount = [];
$newParams = [];
$pattern = '/(:' . implode('|:', array_keys($params)) . ')/';
$query = preg_replace_callback($pattern, function ($matches) use ($params, &$newParams, &$paramsCount) {
$key = ltrim($matches[0], ':');
if (isset($paramsCount[$key])) {
$paramsCount[$key]++;
$newParams[$key . $paramsCount[$key]] = $params[$key];
return $matches[0] . $paramsCount[$key];
} else {
$newParams[$key] = $params[$key];
$paramsCount[$key] = 0;
return $matches[0];
}
}, $query);
return [$query, $newParams];
}
Then you can use it this way:
$query = "UPDATE users SET firstname = :name WHERE firstname = :name";
$params = [":name" => "Jackie"];
// It will return "UPDATE users SET firstname = :name WHERE firstname = :name1"; with appropriate parameters array
list($query, $params) = prepareMsSqlQueryParams($query, $params);
$stmt = $dbh->prepare($query);
$stmt->execute(params);

Categories