make PDO from string - php

I'm in the process of procedurally making a PDO statement, so far i've got
$sql = "UPDATE users SET ";
$values_array = array();
foreach($non_empty_fields as $key => $value){
$sql .= $key;
$sql .= " = :".$key.", ";
$values_array[':'.$key] = $value;
}
$sql = substr($sql, 0, -2);
$sql .= " WHERE id = :id";
$values_array[':id'] = $user_id;
$sth = $this->conn->prepare($sql);
print_r($non_empty_fields);
print_r($values_array);
echo($sql);
$sth -> execute($values_array);
$num_affected_rows = $sth -> affected_rows;
$sth -> close();
and when i run it, i get
Array
(
[gender] => female
[device_id] => 1213423489ydasxas98y76
)
Array
(
[:gender] => female
[:device_id] => 1213423489ydasxas98y76
[:id] => 35
)
UPDATE users SET gender = :gender, device_id = :device_id WHERE id = :id<br />
<b>Fatal error</b>: Call to a member function execute() on a non-object in <b>/Sites/api/include/DbHandler.php</b> on line <b>280</b><br />
I suspect its because I'm not setting the prepared statement properly, so my question is: how do you make a prepared statement from a string?
UPDATE:
call_user_func so the code now looks like:
$sql = "UPDATE users SET ";
$values_array = array();
foreach($non_empty_fields as $key => $value){
$sql .= $key;
$sql .= " = ?, ";
$values_array[] = &$value;
}
$sql = substr($sql, 0, -2);
$sql .= " WHERE id = ?";
$values_array[] = &$user_id;
$sth = $this->conn->prepare($sql);
$params = array_merge(array(str_repeat('s', count($values_array))), array_values($values_array));
call_user_func_array(array(&$sth, 'bind_param'), $params);
$sth -> execute();
now the error I'm getting isn't represented, pdo just doesn't update the table.

Oh Mysqli? i thought you said it was PDO. Mysqli doesnt support named markers like :id, you have to use question marks like ?
This parameter can include one or more parameter markers in the SQL statement by embedding question mark (?) characters at the appropriate positions.
Reference
And for PDO you can use either
Prepares an SQL statement to be executed by the
PDOStatement::execute() method. The SQL statement can contain zero or
more named (:name) or question mark (?) parameter markers for which
real values will be substituted when the statement is executed. You
cannot use both named and question mark parameter markers within the
same SQL statement; pick one or the other parameter style. Use these
parameters to bind any user-input, do not include the user-input
directly in the query.
Manual

Related

PDO prepared statement bind parameters once for different queries

I am using PDO prepared statements to execute two queries:
SELECT count(*) FROM vocabulary WHERE `type` = :type AND `lesson` = :lesson;
SELECT * FROM vocabulary WHERE `type` = :type AND `lesson` = :lesson limit 100;
The first query to get the count works as expected and i get the row count.
$stmt = $this->connection->prepare($sql);
foreach ($params as $key => $value)
$stmt->bindValue(":" . $key, $value, PDO::PARAM_STR);
$stmt->execute();
$count = $stmt->fetchColumn();
$sql .= " limit $limit;";
$sql = str_replace("count(*)", $columns, $sql);
$stmt = $this->connection->prepare($sql);
$stmt->execute();
$result = $stmt->fetchAll(PDO::FETCH_CLASS, $class);
But when executing the second query i get:
SQLSTATE[HY093]: Invalid parameter number: no parameters were bound
Therefore, I would like to know, if I have multiple queries where the parameters are exactly the same ,if I need to bind the same parameters again using
foreach ($params as $key => $value)
$stmt->bindValue(":" . $key, $value, PDO::PARAM_STR);
or if there is a way to bind parameters only once.
If I have multiple queries where the parameters are exactly the same, do I need to bind the same parameters again using
Yes, of course.
Parameters are bound to each query, not to PDO or a database globally.
On a side note, with PDO you don't have to bind variables explicitly, so there is a solution to your "problem": just don't bind at all but send your data directly into execute() as it shown in the Dharman's excellent answer
There is no need to modify your SQL like this. Your code basically comes down to this:
$stmt = $this->connection->prepare('SELECT count(*) FROM vocabulary WHERE `type` = :type AND `lesson` = :lesson');
$stmt->execute($params);
$count = $stmt->fetchColumn();
$stmt = $this->connection->prepare('SELECT * FROM vocabulary WHERE `type` = :type AND `lesson` = :lesson limit 100');
$stmt->execute($params);
$result = $stmt->fetchAll(PDO::FETCH_CLASS, $class);

PHP string replace to prepare PDO SQL

I have some SQL queries that use PHP string variables to create the query before PDO prepare().
$connection = new PDO(...);
// Make variable placeholder for each column.
$params = array();
foreach ($row as $col => $value) {
$params[':' . $col] = $value;
}
$columns = implode(', ', array_keys($row));
$values = implode(', ', array_keys($params));
$query = "
INSERT INTO my_table ($columns)
VALUES ($values)
";
$statement = $connection->prepare($query);
$statement->execute($params);
Or something similar with SELECT:
$query = "
SELECT field
FROM my_table
WHERE id IN ($ids)
";
Where the query will become
$query = "
SELECT field
FROM my_table
WHERE id IN (:id0, :id1, :id2)
";
and then the execute() function will pass in the params like array(':id0' => 0, ...).
Is this vulnerable to injection if the part being inserted is just a bunch of placeholders to be used for query preparation? And is there a better way to do this in PHP with PDO?
When binding a dynamic number of parameters, I revert to the ? placeholders. You can do:
$placeholders = implode(',', array_fill(0, count($values), '?'));
$query = "SELECT field FROM my_table WHERE id IN ($placeholders)";
$stmt = $pdo->prepare($query);
$stmt->execute($values);
You still need to do string substitution if the column names are dynamic, as in your INSERT example. Those should be white-listed to prevent injection. But you can use the above mechanism for the values being inserted. You'll need to use
$values = array_values($params);
because ? placeholders can't be filled in from an associative array.

PDO Binding in foreach fails at prepare

I would like to create a query that may or may not have more than one part. This means, I will be including an array and looping through it, and appending a query to the main SQL query, then finally prepare it.
First of all, I have defined the sql query
$sql = '';
then, I defined a foreach looping value
$arrayLoopValue = 0;
after that, I have created a foreach loop. In which I increased the arrayLoopValue, appended the sql with a new query based on the array's index.
foreach($questionsArray as $questionAnswerRow){
$arrayLoopValue = $arrayLoopValue + 1;
$sql = $sql .
'INSERT INTO gosurveys_surveys_questions_answers
SET survey_id = :survey_id_' . $arrayLoopValue .
', question_id = :question_id_' . $arrayLoopValue .
', user_email = :user_email_' . $arrayLoopValue .
', answer_type = :answer_type_' . $arrayLoopValue .
', question_answer = :question_answer_' . $arrayLoopValue .
', question_answer_creation_date = UTC_TIMESTAMP(); ';
}
The database / example for this query is NOT important, as all fields match and it's already empty. Only the structure, which is provided above, is required.
This fails at the following line.
$query = $this->conn->prepare($sql);
I tried to echo the query and see if there's something wrong. I got the following output:
INSERT INTO gosurveys_surveys_questions_answers
SET survey_id = :survey_id_1,
question_id = :question_id_1,
user_email = :user_email_1,
answer_type = :answer_type_1,
question_answer = :question_answer_1,
question_answer_creation_date = UTC_TIMESTAMP();
INSERT INTO gosurveys_surveys_questions_answers
SET survey_id = :survey_id_2,
question_id = :question_id_2,
user_email = :user_email_2,
answer_type = :answer_type_2,
question_answer = :question_answer_2,
question_answer_creation_date = UTC_TIMESTAMP();
Which is correct. After this prepare, there's a second foreach loop. But the function does NOT reach after the prepare statement.
I would like to know the reason. MYSQL says the following:
Uncaught exception 'PDOException' with message 'SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'INSERT INTO gosurveys_surveys_questions_answers
SET survey_id = ?, ques'
The name of the parameter is not the important thing.
If you want to run a query more than once then preparing it is a great idea but the parameter names are almost irrelevant. the important thing is the binding of new values to the paramters.
So lets assume this is your query
$sql = "INSERT INTO gosurveys_surveys_questions_answers
SET survey_id = :a,
question_id = :b,
user_email = :c,
answer_type = :d,
question_answer = :e,
question_answer_creation_date = UTC_TIMESTAMP()";
Notice the parameter names are irrelevant as long as they are something unique in the query string.
So now you prepare that query. This passes the basic query to the database where it is compiled and optimized, but is not actually run.
$stmt = $this->conn->prepare($sql);
Now within a loop that gets you the parameter you can run that prepared query 1000, 1,000,000 times if you like, all you have to do is bind new values to the parameters and execute the query, which passes the parameter values to the already prepared (compiled and optimized query) and runs it with the data you pass on the execute()
foreach($inArray as $array) {
// build the array of parameters and values
$params = [ ':a' => $array['field1']
':b' => $array['field2']
':c' => $array['field3']
':d' => $array['field4']
':e' => $array['field5']
];
// execute the prepared query using the new parameters
$stmt->execute($params);
}
here is the way to insert multiple rows of data, you prepare once and insert execute multiple times one prepared statement
$dbh = new PDO($dsn, $user, $pass, $options);
$arrayOfData = [...];
$stmt = $dbh->prepare('INSERT INTO table SET col = :val');
$dbh->beginTransaction();
foreach($arrayOfData as $data) {
$stmt->bindValue(':val', $data);
$stmt->execute();
}
$dbh->commit();
The idea of a prepared statement is that the statement is prepared once and then executed multiple times. Something like this:
$sql = 'INSERT INTO gosurveys_surveys_questions_answers
SET survey_id = :survey_id,
question_id = :question_id,
user_email = :user_email,
answer_type = :answer_type,
question_answer = :question_answer,
question_answer_creation_date = UTC_TIMESTAMP()';
$query = $this->conn->prepare($sql);
foreach($questionsArray as $questionAnswerRow) {
$query->execute([
":survey_id" => $questionAnswerRow["survey_id"],
// etc.
]);
}

Error: Call to a member function fetch() on array PDO not working

So I have seen quit a few similar questions, but none of the solutions for them worked for me so I am asking this.
This is the code:-
$sql = "SELECT sifra, IDartikli
FROM {$this->prefix}artikli
WHERE IDartikli = {$artikel_id};";
echo "$sql";
$stmt = $this -> db -> execute($sql);
print_r($stmt);
$table = $stmt->fetch(PDO::FETCH_ASSOC
//$table = $stmt;
// trenutne podatke vstavimo v tabelo artikli_stari
$sql = "INSERT INTO {$this->prefix}artikli_stari
(EAN,
IDartikli)
VALUES('{$table['sifra']}',
'{$table['IDartikli']}');";
$stmt = $this -> db ->prepare($sql);
$stmt->execute();
I get the error here $table = $stmt->fetch();
I tried the sql in phpmyadmin and it works fine there, and the function print_r($stmt); gets me this
Array
(
[status] => OK
[id] =>
[count] =>
)
I do not know why it is not executing. DB class is required and works for the other queries in the same file which are like this
$row = $stmt->fetch(PDO::FETCH_ASSOC) and they work correctly. I also tried PHP lint which can't find an error in my code.
You misunderstand how execute() works: You need to prepare a statement and then you pass an array with the placeholders values as the parameter. You don't pass an sql string.
So your first query should look like:
$sql = "SELECT sifra, IDartikli
FROM {$this->prefix}artikli
WHERE IDartikli = :articleId;";
$stmt = $this->db->prepare($sql);
$stmt->execute([':articleId' => $artikel_id]);
And your table name should be checked against a white-list if it comes from an unknown source as you cannot prepare a table name.
The function execute() takes the values for the statement markers
as arguments. See: PDOStatement::execute.
Before PDOStatement::execute you must call PDO::prepare, passing the sql statement as argument.
Always use markers for passing the values to the prepared statements.
Don't ever use semicolons (;) as part of sql statements.
But use semicolon after each PHP statement (you forgot it after $table = $stmt->fetch(PDO::FETCH_ASSOC) :-)
Always use the prepared statements together with exception handling (that's a complete answer posted by me today).
Good luck!
$sql = "SELECT sifra, IDartikli
FROM {$this->prefix}artikli
WHERE IDartikli = :artikel_id";
$stmt = $this->db->prepare($sql);
$stmt->execute(array(
':artikel_id' => $artikel_id,
));
$table = $stmt->fetch(PDO::FETCH_ASSOC);
$sifra = $table['sifra'];
$IDartikli = $table['IDartikli'];
$sql = "INSERT INTO {$this->prefix}artikli_stari (
EAN,
IDartikli
) VALUES (
':sifra',
':IDartikli'
)";
$stmt = $this->db->prepare($sql);
$stmt->execute(array(
':sifra' => $sifra,
':IDartikli' => $IDartikli,
));
//...

Not able to update rows using PDO

When I run the following code:
// Loop through each store and update shopping mall ID
protected function associateShmallToStore($stores, $shmall_id) {
foreach($stores as $store_id) {
$sql .= 'UPDATE my_table SET fk_shmallID = :shmall_id WHERE id = :store_id';
$stmt = $this->db->prepare($sql);
$stmt->bindParam(':shmall_id', $shmall_id);
$stmt->bindParam(':store_id', $store_id);
$stmt->execute();
}
}
I get the following message:
Warning: PDOStatement::execute() [pdostatement.execute]: SQLSTATE[HY093]: Invalid parameter number: mixed named and positional parameters
I've also tried the following without success (without $stmt->bindParam):
$stmt->execute( array($shmall_id, $store_id));
I don't understand what I'm doing wrong.
UPDATE
I've updated my code to reflect what I actually got in my source code. There should not be any typos here.
UPDATE 2
I tried this, but I still get the same error message.
protected function associateShmallToStore($stores, $shmall_id) {
$i = 0;
$sql .= "UPDATE sl_store ";
foreach($stores as $store_id) {
$i++;
$sql .= 'SET fk_shmallID = :shmall_id, lastUpdated = NOW() WHERE id = :store_id_'.$i.',';
}
$sql = removeLastChar($sql);
$stmt = $this->db->prepare($sql);
$stmt->bindParam(':shmall_id_'.$i, $shmall_id);
$i = 0;
foreach($stores as $store_id) {
$i++;
$stmt->bindParam(':store_id_'.$i, $store_id);
}
$stmt->execute();
}
This is the output of the SQL query:
UPDATE sl_store
SET fk_shmallID = :shmall_id, lastUpdated = NOW() WHERE id = :store_id_1,
SET fk_shmallID = :shmall_id, lastUpdated = NOW() WHERE id = :store_id_2
UPDATE 3
The code I endet up using was this:
foreach($stores as $store_id) {
$sql = "UPDATE sl_store SET fk_shmallID = :shmall_id WHERE id = :store_id";
$stmt = $this->db->prepare($sql);
$stmt->bindParam(':shmall_id', $shmall_id);
$stmt->bindParam(':store_id', $store_id);
$res = $stmt->execute();
}
It's just as the error says, you have mixed named and positional parameters:
:name (named)
:person_id (named)
? (positional)
More than that, you have the named parameter :person_id, but you're binding to :id.
These are your parameters, I'll call them P1, P2 and P3:
UPDATE my_table SET name = :name WHERE id = :person_id ?
^ P1 ^ P2 ^ P3
And this is where you bind them:
$stmt->bindParam(':name', $name); // bound to P1 (:name)
$stmt->bindParam(':id', $person_id); // bound to nothing (no such param :id)
You probably want to bind the second parameter to :person_id, not to :id, and remove the last positional parameter (the question mark at the end of the query).
Also, each iteration through the foreach loop appends more to the query, because you're using the concatenation operator instead of the assignment operator:
$sql .= 'UPDATE my_table SET name = :name WHERE id = :person_id ?';
You probably want to remove that . before =.
For more about this, take a look at the Prepared statements and stored procedures page in the PDO manual. You will find out how to bind parameters and what the difference is between named and positional parameters.
So, to sum it up:
Replace the SQL line with:
$sql = 'UPDATE my_table SET name = :name WHERE id = :person_id';
Replace the second bindParam() call with:
$stmt->bindParam(':person_id', $person_id);
Try:
$sql = 'UPDATE my_table SET name = :name WHERE id = :id';

Categories