shorthand PDO query - php

Currently to perform a query with PDO, I use the following lines of code:
$sql = "SELECT * FROM myTable WHERE id = :id";
$stmt = $conn->prepare($sql);
$stmt->bindParam(':id', $id);
$stmt->execute();
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
And after some research, I found a shorter way of executing the same command:
$stmt_test = $conn->prepare("SELECT * FROM status WHERE status_id = ?");
$stmt_test->execute([$id])->fetchAll(PDO::FETCH_ASSOC);
$result = $stmt_test->fetchAll(PDO::FETCH_ASSOC);
From there I thought I could possibly make it even shorter with the following code:
$stmt_test = $conn->prepare("SELECT * FROM status WHERE status_id = ?");
$result = $stmt_test->execute([$id])->fetchAll(PDO::FETCH_ASSOC);
But I get the following error:
Fatal error: Call to a member function fetchAll() on a non-object in
/home/.../index.php on line 20
QUESTION: Why am I getting this error? From my understanding, $stmt_test->execute([$id]) should be executing first, then the result of that would execute the ->fetchAll(PDO::FETCH_ASSOC) and from there return the array to $result, but since the error is happening, something must be flawed in my logic. What am I doing wrong? Also, does anyone know a better shorthand method to perform the previous query?

So you've got an answer for the question "Why I am getting this error", but didn't get one for the "shorthand PDO query".
For this we will need a bit of a thing called "programming".
One interesting thing about programming is that we aren't limited to the existing tools, like with other professions. With programming we can always create a tool of our own, and then start using it instead of a whole set of old tools.
And Object Oriented Programming is especially good at it, as we can take an existing object and just add some functionality, leaving the rest as is.
For example, imagine we want a shorthand way to run a prepared query in PDO. All we need is to extend the PDO object with a new shorthand method. The hardest part is to give the new method a name.
The rest is simple: you need only few lines of code
class MyPDO extends PDO
{
public function run($sql, $bind = NULL)
{
$stmt = $this->prepare($sql);
$stmt->execute($bind);
return $stmt;
}
}
This is all the code you need. You may store it in the same file where you store your database credentials. Note that this addition won't affect your existing code in any way - it remains exactly the same and you may continue using all the existing PDO functionality as usual.
Now you have to change only 2 letters in PDO constructor, calling it as
$conn = new MyPDO(...the rest is exactly the same...);
And immediately you may start using your shiny new tool:
$sql = "SELECT * FROM myTable WHERE id = :id";
$result = $conn->run($sql, ['id' => $id])->fetchAll(PDO::FETCH_ASSOC);
Or, giving it a bit of optimization,
$result = $conn->run("SELECT * FROM myTable WHERE id = ?", [$id])->fetchAll();
as you can always set default fetch mode once for all, and for just a single variable there is no use for the named placeholder. Which makes this code a real shorthand compared to the accepted answer,
$stmt_test = $conn->prepare("SELECT * FROM status WHERE status_id = ?");
$stmt_test->execute([$id]);
$result = $stmt_test->fetchAll(PDO::FETCH_ASSOC);
and even to the best answer you've got so far,
$result = $conn->prepare("SELECT * FROM status WHERE status_id = ?");
$result->execute([$id]);
not to mention that the latter is not always usable, as it fits for getting an array only. While with a real shorthand any result format is possible:
$result = $conn->run($sql, [$id])->fetchAll(); // array
$result = $conn->run($sql, [$id])->fetch(); // single row
$result = $conn->run($sql, [$id])->fetchColumn(); // single value
$result = $conn->run($sql, [$id])->fetchAll(PDO::FETCH_*); // dozens of different formats

$stmt_test->execute([$id]) returns a boolean value. That mean that
$result = $stmt_test->execute([$id])->fetchAll(PDO::FETCH_ASSOC);
isn't valid. Instead you should do
$stmt_test->execute([$id]);
$result = $stmt_test->fetchAll(PDO::FETCH_ASSOC);

The error you're getting comes form the way PDO was designed. PDOStatement::execute() doesn't return the statement, but a boolean indicating success. The shortcut you want therefore isn't possible.
See function definition in http://php.net/manual/en/pdostatement.execute.php
Additionally let me add that forEach() often (not always) is a code smell and takes relatively much memory as it has to store all rows as PHP values.

I believe PDO's execute() method returns either true or false. As the error already tells you: fetchAll() expects an object. Using three lines of code would be the shortest way.
Another option is to use an ORM like propel, it works really smooth and will save you alot of time.

Related

How to fetch results from a PDO prepare statement

I am using a PDO prepare statement to select the result.
My index.php file is:
include('operations.php');
$userprofileobj = new operations();
if(isset($_SESSION['user_email']))
{
$results = $userprofileobj->verify_user('account', $_SESSION['user_email'])->fetch(PDO::FETCH_ASSOC);
echo $results['username'];
}
My operations.php file is:
<?php
include('userclass.php');
class operations extends userclass
{
public function verify_user($table_name, $user_email)
{
$stmt = $this->con->prepare("select * from " . $table_name . " where username = :user_email");
$stmt->execute([
':user_email' => $user_email,
]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
}
I am trying to match email and the result should be fetched in index.php. But I got an error:
Fatal error: Call to a member function fetchColumn() on boolean in operations.php on line 84
Your result variable (besides being overwritten) is not what you think it is. It's a PDO statement.
Try this instead:
$stmt = $this->con->prepare("select * from ".$table_name." where username = :user_email");
$stmt->execute([
':user_email' => $user_email,
]);
if (false !== ($row = $stmt->fetchColumn()))
{
return $row;
}
However, this will only return the first column of the first row. Instead you probably want:
return $stmt->fetchAll(PDO::FETCH_ASSOC);
I changed $result to $stmt, because it's not a result. It's a Statement Object.
Original issues
In your original code (see below) you are overwriting it with the return from execute which is a Boolean.
// Old code (don't use this)
$result = $result->execute([
':user_email' => $user_email,
]);
//$result = TRUE|FALSE
if ($result->fetchColumn() !== false)
{
return $result;
}
And then you try to call a method of the Boolean, which, well, won't work. But the problems are deeper than just that. Let’s assume you don’t overwrite it.
// Old code (don't use this)
$result->execute([
':user_email' => $user_email,
]);
//$result = PDOStatment object.
if ($result->fetchColumn() !== false)
{
return $result;
}
Now the result is still your PDOStatement, which is good, but as I said, you're not saving the fetched data. This time you return the PDOStatement object. Which is not what you want.
Then further, as I stated earlier, if you do save it and return it, it's still probably not what you are after. Because fetchColumn() accesses only one row at a time and only one column.
But I have no way to know what you want. Maybe that is what you want? In that case your query is less than ideal. Maybe you just want to see if a user exists with a given email? In that case I would use this query.
$result = $this->con->prepare("SELECT id FROM ".$table_name." WHERE username = :user_email");
$result->execute([
':user_email' => $user_email,
]);
// There isn't any need to check it (see below)
return $result->fetchColumn();
PDOStatement::fetchColumn() returns a single column from the next row of a result set or FALSE if there are no more rows.
I can also tell by your stuff, that your database setup is probably wrong. That is, if you really need a dynamic table $table. The reason I can say this is you should not be duplicating any user data (or any data really, this is called normalization), and having the table dynamic implies that the email may exist separately in two (or more) tables.
If that is not the case then don't make it dynamic. Why is this an issue? Well, think what happens if a user changes their "email" now, because it exists in two tables (potentially). You'll have to update it in both places. But it's worse than that as it overcomplicates anything you do with the emails.
Without seeing the schema for your tables, I can only speculate on that, and how to fix it. But generally you would use a foreign key and associate the user record to that. Then using a JOIN you can access the email without duplication.
That said, there are a few cases where this may be acceptable, but I have no way to know if that is true in your case. Just a quick example would be a separate table for users and administrators (basically two-user systems).
Security
The last thing is be very very careful with this:
"select * from ".$table_name." where username = :user_email"
The issue here is it's open to SQL injection. Anytime you concatenate a variable into SQL, you open the door for injection attacks. Well, you may say I'm passing in a canned string account. Which is OK, but there is no validation at the point of failure. So maybe in five months you reuse this code and forget that you never validated the table name. Maybe not, but the fact remains that if user data could get into that argument, you have no protection whatsoever against injection on the table name. The possibility for it is there.
Something as simple as this:
public function verify_user($table_name,$user_email){
$allowed = ['account','users'];
if(!in_array($table_name, $allowed )) throw new Exception('Invalid table name');
}
See now it's virtually impossible to inject something into the table name. Further because it's in the same method (at the point of failure), you will never lose that protection. It's very easy to be in a rush latter and copy a piece of code change a few things and .... well you know.
Just my two cents.
UPDATE
So even if the chance is small that user input could get into $table you can not guarantee it 100%, because from within verify_user you have no way to know where the data came from, but you are trusting on faith that it's not user input. When it comes to SQL injection, you can't say well this is OK, because I will only call this method a certain way. It has to be 100% injection proof or as close as is humanly possible.
Why is this important, you ask? Imagine this.
$userprofileobj->verify_user('account --',$_SESSION['user_email']);
Those two little --s are like // in PHP, but for SQL, they comment out the rest of the line in SQL so your query becomes this.
"select * from account -- where username = :user_email"
Or (essentially)
"select * from account"
So we just modified what your query does. Now thankfully it's not really possible to run two queries at once in PDO. You can do it (with some work) in MySqli. But because of security reasons, they have mostly done away with this ability. The reason is this (or worse like creating database users).
$userprofileobj->verify_user('account; DROP TABLE account --',$_SESSION['user_email']);
Which, if you could do two queries, would do this:
SELECT * FROM account
DROP TABLE account
In any case this is dangerous stuff and is to be avoided at all costs. Being too lazy (and I am a lazy programmer, so don't take that wrong) to put a table name in is not an answer you want to give after your database has been compromised and you have exposed user data to a third party. It's just not an option.
All this does:
if(!in_array($table_name, ['table1', 'table2', ...])) throw new Exception('Invalid table name');
Is throw an error if "needle" $table_name is not in "haystack" - a canned list of table names. So if I do this (using our example above):
if(!in_array('account --', ['table1', 'table2', ...])) throw new Exception('Invalid table name');
It won't find account -- in our list of table1 and table2 and will blow up, thus preventing the injection attack.
$result = $this->con->prepare("select * from ".$table_name." where username = :user_email");
$result = $result->execute(..)
You're overwriting $result. The $this->con->prepare(..) sets $result to be a PDO Statement (See http://php.net/manual/en/pdo.prepare.php). A PDO Statement object has the method ->execute(...), which returns a boolean (true/false) as well as the ->fetchColumn() method. When you're doing your execute() you're overwriting your PDO Statement Object with the result of the execute() which is only a boolean and has no methods in it at all. As such that's why $result has no ->fetchColumn() method.

PDO Query Not Showing Data

I want to migrate a site from some poorly written MySQLi to clean PDO.
I have looked at three similar questions and their answers, and this is a straightforward question, but none of them are giving me results. Here's my code:
$state = "Alaska";
//trying to implement PDO here
$sql = "SELECT * FROM sales WHERE state = ? ORDER BY type";
$result = $conn->prepare($sql);
$result->execute(array($state));
/*
this was the old, successfully working way before
$sql = "SELECT * FROM sales WHERE state = '$state' ORDER BY type";
$result = $conn->query($sql);
*/
Previous questions on this site show me answers that look like my PDO implementation, yet mine doesn't work. I have made sure the PDO class exists and that the extension is loaded.
If you see the error, let me know!
The difference between the two, aside from difference in libraries, is that one is using a direct query() (the mysqli_*), while the other is using a prepared statement. Those are handled a bit different, regardless which API is running.
When using MySQLi, doing
$result = $conn->query($sql);
would have $result be a mysqli-result object, which holds the data. You can use mysqli_result::fetch_assoc() on that to fetch the data. However, when you're using PDO::prepare(), your $result variable will be a PDOStatement - which is a bit different. You'll need to run a fetch() method on it first, and then you can use the return-value of it, as such
$state = "Alaska";
$sql = "SELECT * FROM sales WHERE state = ? ORDER BY type";
$stmt = $conn->prepare($sql);
$stmt->execute(array($state));
$result = $stmt->fetch(PDO::FETCH_ASSOC);
Note that I've changed the names of your variables. Now $result is an array (if there are any results fetched), which you can use as you normally do when fetching associative-arrays. If there are no results, PDOStatement::fetch() will return a boolean false.
var_dump($result['state']);
You can loop the fetch() method as
while ($result = $stmt->fetch(PDO::FETCH_ASSOC)) {
if you expect more than one row. Use $result as you would without looping, as shown above.
Note that this assumes a valid PDO-connection. Beware that you cannot interchange any MySQL libraries, mysql_, mysqli_* and PDO are all different animals in the zoo.
PHP.net on PDOStatement::fetch()
Can I mix MySQL APIs in PHP?

Nesting MYSQLi functions

This question refers to nesting mysqli functions. I'm not sure the term "nesting" is the correct one here so i'll explain.
Look at this code for example:
$query = 'SELECT id FROM users WHERE id="' . $_POST['uid'] . '"';
$result = $mysqli->query($query);
$user_id = mysqli_fetch_row($result);
The code above can be turned into a shorthand version as follows:
$user_id = mysqli_fetch_row($mysqli->query('SELECT id FROM users WHERE id="' . $_POST['uid'] . '"'));
I know this works because I've tried it. I see that PHP is taking care of the code from "inside-out", is it basically a recursive method to write this code or am I completely off with the terms?
My question is this, does this work for all MYSQL commands or PHP functions? are there exceptions for this functionality? what are the drawbacks to using such form? (besides it being less readable by a programmer)
EDIT: I know the query is not a prepared statement.
In order to call a function or class method, PHP has to evaluate all of its supplied parameters.
In this case, given the statement:
$user_id = mysqli_fetch_row($mysqli->query("SELECT id FROM users WHERE id = $_POST[uid]"));
in order to call mysqli_fetch_row PHP will first evaluate its argument, that is $mysqli->query("SELECT id FROM users WHERE id = $_POST[uid]")
in order to call $mysqli->query PHP will first evaluate its argument, that is "SELECT id FROM users WHERE id = $_POST[uid]"
now PHP will pass the query string to $mysqli->query and return the result set (or error maybe)
that return value will be passed to mysqli_fetch_row
I would say that this is not PHP specific, but a quite general behaviour in parameter evaluation.
However it has one major drawback: you forego any kind of error checking which, especially when dealing with databases, is very important.

PHP DB2 Get Full Query after db2_execute()

We are using db2_prepare and db2_execute to prepare queries in a generic function. I am trying to have a debug method to get the full prepared query after the '?' values have been replaced by the db2_execute function.
Is there an efficient way of doing this besides manually replacing each '?' with the parameters I am passing in? i.e. is there a flag that can be set for db2_execute?
Example:
$params = array('xyz','123');
$query = "SELECT * FROM foo WHERE bar = ? AND baz = ?";
$sqlprepared = db2_prepare($CONNECTION, $query);
$sqlresults = db2_execute($sqlprepared,$params);
I would like the $sqlresults to contain the full prepared query:
"SELECT * FROM foo WHERE bar = 'xyz' AND baz = '123'";
I have looked through the docs and do not see any obvious way to accomplish this, but I imagine there must be a way.
Thank you!
db2_execute() does not replace parameter markers with values. Parameters are sent to the server and bound to the prepared statement there.
The CLI trace, which can be enabled on the client as explained here, will contain the actual parameter values. Keep in mind that the trace seriously affects application performance.
I ended up writing a loop to replace the '?' parameters with a simple preg_replace after and outputting the query in my 'debug' array key:
$debugquery = $query;
foreach($params as $param) {
$debugquery = preg_replace('/\?/',"'".$param."'",$debugquery,1);
}
return $debugquery;
This handled what I needed to do (to print the finalized query for debugging purposes). This should not be run in Production due to performance impacts but is useful to look at the actual query the server is trying to perform (if you are getting unexpected results).

2nd MySQLi Query causes a "Call to a member function execute() on a non-object"

I've got a strange problem. There are 2 queries.
$sql = "SELECT `content_auftrag_id`
FROM `content`
WHERE `content_id` = '".$content_id."' LIMIT 1";
$ergebnis = $db->prepare( $sql );
$ergebnis->execute();
$ergebnis->bind_result( $content_auftrag_vorhanden );
$content_auftrag_id = "test";
$sql = "UPDATE `content` SET `content_auftrag_id` = '".$content_auftrag_id."'
WHERE `content_id` = '".$content_id."'";
$ergebnis2 = $db->prepare( $sql );
$ergebnis2->execute();
When I use them both, then an error occurs for the second one. If I only run the the second, then it works fine. How can it be that both together cause an error?
All variables are there and correct.
Thanks!
Okay, I think you didn't quite got the idea behind PreparedStatements. You shouldn't directly insert the parameters in your SQL-String, but use ?-placeholders and bind them in the Query using the bind_param()-method.
Your error seams to appear here:
$ergebnis2 = $db->prepare( $sql );
This function returns false if it wasn't successful. You should check if the value of ergebnis2 is not false.
Also, you should use the error-method to see the last appeared MySQL-Error.
You can only work on one prepared query at a time, so to speak. See Mysqli::execute() method:
"When using mysqli_stmt_execute(), the mysqli_stmt_fetch() function must be used to fetch the data prior to performing any additional queries."
You can also use the store_result() method to remove this block as well to perform the next query.
Also, take heed from those who warn you about abusing prepared statements like your example. Though it works without error if you don't actually have any parameters to bind to, it basically throws sql injection prevention out the window.
If $ergebnis2 is 'not an object', then I guess it must be false. Which means the prepare() call failed for whatever reason.
What does $db->error return after you have called the 2nd prepare?
Always check your return values, its basic debugging

Categories