Iterating over results of PDO query - php

I want to run a query using PDO, based on data in the URL paramater (yes, I know that this is prone to attacks, but its internal code for a utility).
$user = 'USER';
$pass = 'PASSWORD';
$dsn = 'mysql:dbname=PRODUCTS;host=HOST';
try {
$productDB = new PDO($dsn, $user, $pass);
$productDB->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
catch(PDOException $e) {
$msg = 'PDO ERROR' . $e->getFile() . ' L.' . $e->getLine() . ' : ' . $e->getMessage();
die($msg);
}
if(isset($_GET['cat']))
{
$cat = $_GET['cat'];
print "cat = $cat <br>";
$products = $productDB->prepare('SELECT * FROM products WHERE cat_id LIKE ?');
$products->execute(array($cat));
$rows = $products->rowCount();
print "$rows rows returned";
?>
<table border="1">
<tr>
<td>product_id</td>
<td>product_name</td>
</tr>
<?php
foreach ($products->fetchAll() as $row) {
$id = $row['product_id'];
$product_name = $row['product_name'];
print "<tr>";
print "<th scope=\"row\"><b>$id</b></th>";
print "<td> $product_name </td>";
print "<tr>";
}
print "</table>";
}
?>
When I run this code, it prints the correct number of rows depending on the query, but does not populate the table.
I have also tried replacing the prepare and execute lines with:
$products = $productDB->query("SELECT * FROM products WHERE cat_id LIKE $cat");
Which returns the correct row count, but doesn't otherwise help.
And finally, I've tried replacing the foreach line with something like:
$rows = $products->fetchAll();
foreach ($rows as $row) {
My attempts to do the same with a fixed query all work fine, but I am having trouble working out how to place a variable element in a query, and then iterate over the results.

You're not doing anything to store the result:
$products->execute(array($cat));
needs to go in a variable:
$result = $products->execute(array($cat));
Then, instead of calling $products->fetchAll(), use $results->fetchAll():
foreach ($result->fetchAll() as $row)
I find it easier to use a $query variable (for prepare, etc) and then get the result into something like $result or $product. Makes the code a bit easier to read.

Try this (If I understood correctly) :
$products = $productDB->prepare("SELECT * FROM products WHERE cat_id LIKE :cat");
// Now, you can either do this :
$products->bindParam('cat', '%'.$cat.'%');
$products->execute();
// or you can call execute with an associative array of your parameterized query.
$products->execute(array('cat' => '%'.$cat.'%'));
// Then, get all the results like this :
$rows = $products->fetchAll();
foreach ($rows as $row) {
// Do work here ..
}
// Or, like this :
while ($row = $products->fetch(PDO::FETCH_ASSOC)) {
// Do work here ..
}
I personaly prefer the while, because you don't fetch the whole query in one var, reducing the amount of memory needed.
I also recommend you to use the FETCH_* parameter, to get only the kind of array you want.
By the way, you need to know that rowCount should not be used to count the rows returned by a SELECT. As said by php.net :
If the last SQL statement executed by the associated PDOStatement was a SELECT statement, some databases may return the number of rows returned by that statement. However, this behaviour is not guaranteed for all databases and should not be relied on for portable applications.

Related

I cannot find a way to display "No Results."

The code below works as written provided a name in the database is entered in the search box. If a name not in the data base is entered, the error 'Warning: Invalid argument supplied for foreach() ….. on line 201.' Rather than this generic error I want something like “No Results” to display. Any suggestions anyone? I am aware that this question has been asked before but none of the answers seem to match the type of output I am using here.
enter code here
<?php
include 'connect.php';
if (isset($_POST['submit-keyword'])) {
$keyword = $_POST['keyword'];
}
try {
//first pass just gets the column names
print "<table>";
$result = $con->query("SELECT * FROM Bath_Wells_NBR WHERE Founder LIKE '%$keyword%' ORDER BY DATE");
//return only the first row (we only need field names)
$row = $result->fetch(PDO::FETCH_ASSOC);
print " <tr>";
foreach ($row as $field => $value){
print " <th>$field</th>";
}
// end foreach
print " </tr>";
//second query gets the data
$data = $con->query("SELECT * FROM Bath_Wells_NBR WHERE Founder LIKE '%$keyword%' ORDER BY DATE");
$data->setFetchMode(PDO::FETCH_ASSOC);
foreach($data as $row){
print " <tr>";
foreach ($row as $name=>$value){
print " <td>$value</td>";
} // end field loop
print " </tr>";
} // end record loop
print "</table>";
} catch(PDOException $e) {
echo 'ERROR: ' . $e->getMessage();
} // end try
?>
the first thing you need to do is make use of the PDO object and actually use the prepared statement to avoid sql injection like this:
$keyword = '%' . $keyword . '%';
$q = $con->prepare("SELECT * FROM Bath_Wells_NBR WHERE Founder LIKE :keyword ORDER BY DATE");
$q->bindParam(':keyword',$keyword,PDO::PARAM_STR);
$q->execute();
//here we will use fetchAll to get the full dataset of results, or an empty array
$result = $q->fetchAll();
Now you have $result which is an array either of your data, or empty so you can solve your problem by saying:
if(count($result) < 1) {
//output an error message
} else {
//output your table
}
Now you have your solution, let's streamline the code a bit. Remember, programmers want DRY code, so we don't want to do the same query twice. Lets assume our result count was greater than 0 so we are inside our else block.
{
//get all the array keys from the first entry in the result. These are the column names from the database which we want to use for our headings
$headings = array_keys($result[0]);
//now we want to do the same thing to each element, i.e. wrap it in html tags so let's make use of array_walk. We use the & symbol to pass the values by reference so we can amend them
array_walk($headings, function(&$field, &$key) {
$field = <th>$field</th>
});
//so now we have an array of table headings, so lets quickly implode them, we don't want to write a whole loop for this
echo <tr> . implode('',$headings) . </tr>;
now you can do your original loop and just output the values hey presto, dryer simpler code! You could also try and experiment with array_walk_recursive and see if you can do the same to the values for your nested loop, after all you just want to wrap them in <td> tags!
Note, I've not tested this so code, but you should be able to use it pretty much as it is, just have a little play and experiment with it.
happy coding!

Nesting foreach loops with PDO statements

I'm trying to nest foreach loops with PDO statements (this previously worked for me in mysql,btw). The first example works and the second one doesn't. However I'd prefer not to run a SQL query every time (isn't that the point of PDO?) and would prefer to use something more like example 2. However, it doesn't 'nest' the loop inside the other, it seems, rather it runs the first then the next.
Example 1)
foreach($db->query('SELECT country FROM db GROUP BY `country`') as $row1) {
echo $row1['country']."<br/>";
foreach($db->query('SELECT * FROM db') as $row2) {
if ($row1['country']==$row2['country']){
echo $row2['name']."<br/>";
}
}
}
Example 2)
$cntry = $db->query('SELECT country FROM db GROUP BY `country` ');
$rslts = $db->query('SELECT * FROM db');
foreach ($cntry as $row1) {
echo "<div id='".$row1['country']."'>".$row1['country']."<br/>";
foreach($rslts as $row2) {
if ($row1['country']==$row2['country']){
echo $row2['name']."<br/>";
}
};
echo "</div>";
}
(isn't that the point of PDO?)
No. The point of PDO is to send your query to database server and to return the results back. But PDO cannot reduce the number of queries executed.
So, here goes the proper solution:
$stmt = $db->query('SELECT country, name FROM db ORDER BY country, name');
$data = $stm->fetchAll(PDO::FETCH_COLUMN|PDO::FETCH_GROUP);
foreach ($data as $country => $row)
{
echo $country."<br/>\n";
foreach ($row as $name)
{
echo $name."<br/>\n";
}
}
As a matter of fact, fetchAll() is just a syntax sugar for the code like this:
$data = array();
while ($row = $stmt->fetch())
{
$data[] = $row;
}
it just creates a regular PHP array out of query result. And of course you can loop over this array as many times as you wish. Means you an always replace fetchAll() with manual looping over results, and of course you may group the results whatever way you wish.
While using foreach on $stmt is just a syntax sugar again, intended to confuse PHP users. Because $stmt is not an array but a way more complex structure.
not sure, but as I remember it would be like this
$cntry = $db->query('SELECT country FROM db GROUP BY `country` ')->fetchAll (PDO::FETCH_COLUMN);
$rslts = $db->query('SELECT * FROM db')->fetchAll(PDO::FETCH_COLUMN);
foreach ($cntry as $row1) {
echo $row1['country']."<br/>";
foreach($rslts as $row2) {
if ($row1['country']==$row2['country']){
echo $row2['name']."<br/>";
}
}
}

Joomla Mysql query in foreach loop

I'm trying to build a simple select list in Joomla 2.5 with php and mysql, but I've got a problem: it doesen't load the options name... are there any error?
<?php
$db = JFactory::getDBO();
$query = $db->getQuery(true);
$query->select('optValue');
$query->from('#__sobipro_field_option');
$query->where("fid='38'");
$db->setQuery((string)$query);
$results = $db->loadObjectList();
if ($results){
echo "<select>";
foreach($results as $result){
foreach($result as $value) {
$query->select('sValue');
$query->from('#__sobipro_language');
$query->where("fid='38' and language='it-IT' and sKey='".$value."'");
$db->setQuery((string)$query);
$name = $db->loadResult();
echo "<option value=\"$value\">".$name."</option>";
}
}
echo "</select>";
}
else {
echo 'Error';
}
?>
Whenever you're trying to debug Joomla! code turn on debug mode (Global Configuration->System->Debug Settings) and turn Error Reporting up to Development (Global Configuration->Server->Error Reporting), then you would have seen your problem straight away.
$query is a actually an object of type JDatabaseQuery and should be used as is in the $db->setQuery($query) call without casting to a (string).
When referencing a database, table or column name it's a good idea to use Joomla's mechanism for wrapping the name is the correct quotes e.g.
$query-select($db->quoteName('optValue'))
Amongst other things this will insure that SQL keywords aren't a problem.
Likewise for values, use the Joomla! provided $db->quote() method. e.g.
$query->where($db->quoteName('fid') . '=' . $db->quote('38'));
When building a WHERE query with multiple values ANDed together you can simply add each item as it's own ->where() clause.
So your where before I would tend to write as:
$query->where($db->quoteName('fid') . '=' . $db->quote('38'));
$query->where($db->quoteName('language') . '=' . $db->quote('it-IT'));
$query->where($db->quoteName('sKey') . '=' . $db->quote($value));
Finally, as you've said both of your $queries work by themselves, but when put together in your code as shown they don't.
The reason for this is that after you configure $query for your first statement (and run it), you continue to add to the second statement to the original $query object. So, that when you do the second, third etc setQuery($query) the SQL is getting longer and longer and unlikely to return any results.
As you don't test for success of each $db->loadResult() in the inner-most foreach loop you never see the error.
At the very least you need to add a new $db->getQuery(true) at the beginning of your inner loop, like this:
<?php
// Get default database object
$db = JFactory::getDBO();
// Get a new JDatabaseQuery object
$query = $db->getQuery(true);
// Build the query
$query->select($db->quoteName('optValue'));
$query->from($db->quoteName('#__sobipro_field_option'));
$query->where($db->quoteName('fid') .'=' . $db->quote('38'));
// Set the query for the DB oject to execute
$db->setQuery($query);
// Get the DB object to load the results as a list of objects
$results = $db->loadObjectList();
if ($results){
echo "<select>";
foreach($results as $result)
{
foreach($result as $value)
{
// Get a new query object
$query = $db->getQuery(true);
// Build the query
$query->select($db->quoteName('sValue'));
$query->from($db->quoteName('#__sobipro_language'));
$query->where($db->quoteName('fid') .'=' . $db->quote('38'));
$query->where($db->quoteName('language') .'=' . $db->quote('it-IT'));
$query->where($db->quoteName('sKey') .'=' . $db->quote($value));
$db->setQuery($query);
$name = $db->loadResult();
echo "<option value=\"$value\">$name</option>";
}
}
echo "</select>";
}
else
{
echo 'Error';
}
This is going to potentially create a lot of objects, so, you should consider if there's a way to restructure it all into one SQL query this will make it significantly faster and reduce the load on your server.

Loop results PDO PHP

I'm trying to fetch all records from my table on my site, I have the following
$sth = $conn->prepare("SELECT * FROM directory WHERE user_active != ''");
$sth->execute();
/* Exercise PDOStatement::fetch styles */
$result = $sth->fetch(PDO::FETCH_ASSOC);
foreach ($sth->fetchAll(PDO::FETCH_ASSOC) as $result) {
echo $result[First_Name];
}
Only its not returning all records, only my first, Can anybody see where I'm going wrong?
You need to use a loop:
while ($result = $sth->fetch(PDO::FETCH_ASSOC)) {
echo $result[First_Name];
echo ' ' . $result[Surname];
}
Or you could use fetchAll method:
foreach ($sth->fetchAll(PDO::FETCH_ASSOC) as $result) {
echo $result[First_Name];
echo ' ' . $result[Surname];
}
And Note: If First_Name and Surname are not constants, then you should use the string as the key name.
$result['First_Name'] and $result['Surname']
Well, you only call $sth->fetch once. You need to loop over the results.
while ($row = $sth->fetch(PDO::FETCH_ASSOC)) {
echo $row['First_Name'] . ' ' . $row['Surname'] . "\n";
}
Also don't call array string indexes without braces. This way PHP checks if the key is a CONSTANT, then casts it to string. This is just bad practice and might lead to unexpected bahavior.
If this returns only one row, you probably have only one row in the database (or the result set). Show us more code!

Convert PHP while loop to use PDO

I'm currently updating my app by switching to PDO. I have the following code:
$stmt = $db->prepare("select * from `product` where productid in (:productidLst)");
$stmt->bindParam(":productidLst",$productidLst, PDO::PARAM_INT);
$stmt->execute();
The var $productidLst is 1,2 after the above code I would like to use the PDO equivalent of this:
while($rs=mysql_fetch_assoc($res)){
$rs['qty']=$_SESSION['basket'][$rs['productid']];
$rs['total'] += $rs['qty']*$rs['price'];
$total += $rs['total'];
$a[] = $rs;
}
I have tried numerous combinations but not been successful so any help with this would be appreciated (in the 2nd code block $res was the sql). Secondly I have set the Parameter $productidLst to INT is this correct or should it be a string?
--------------------UPDATE 1----------------------------------------------------
I have tried the following code:
$stmt = $db->prepare("select * from `product` where productid in (:productidLst)");
foreach ($stmt->execute(array(':productidLst' => $productidLst)) as $row)
{
$total += $row['total'];
}
Which returns: Invalid argument supplied for foreach() error
The standard documentation in the PHP manual is usually pretty helpful. There is an example of executing a for loop with PDO in the PHP manual, PDO Details.
function getFruit($conn) {
$sql = 'SELECT name, color, calories FROM fruit ORDER BY name';
foreach ($conn->query($sql) as $row) {
print $row['name'] . "\t";
print $row['color'] . "\t";
print $row['calories'] . "\n";
}
}
With a few changes, the example can be made to use a prepared statement.
function getFruit($conn) {
$query = $conn->prepare('SELECT name, color, calories FROM fruit WHERE kind=:kind ORDER BY name');
$query->execute(array(':kind' => 'drupe'));
// alternatively you could use PDOStatement::fetchAll() and get rid of the loop
// this is dependent upon the design of your app
foreach ($query as $row) {
print $row['name'] . "\t";
print $row['color'] . "\t";
print $row['calories'] . "\n";
}
}
You can also use a while loop and PDOStatement::fetch to get each row.
function getFruit($conn) {
$query = $conn->prepare('SELECT name, color, calories FROM fruit WHERE kind=:kind ORDER BY name');
$query->execute(array(':kind' => 'drupe'));
// alternatively you could use PDOStatement::fetchAll() and get rid of the loop
// this is dependent upon the design of your app
while ($row = $query->fetch(PDO::FETCH_ASSOC)) {
print $row['name'] . "\t";
print $row['color'] . "\t";
print $row['calories'] . "\n";
}
}
The PHP manual remains quite helpful in providing all the necessary information to create the latter two versions.
Explanation of the last version: assuming $conn is a valid PDO object. $conn->prepare($sql) returns a PDOStatement object if successful, false on failure OR an exception based on your error handling. So, assuming success we would want to actually get the data from the object. We can use $query->fetch() in a loop or $query->fetchAll() to get the data dependent upon your app. Passing in the class constant PDO::FETCH_ASSOC will return, you guessed it, an associative array of data.
Functionally, the foreach and while implementations are equivalent. Conceptually, a foreach is more appropriate, as a while loop has connotations of looping while a static condition holds, whereas foreach loops over elements of a collection. Read "Differences between a while loop and a for loop in PHP?" for part of the story.
Be sure to read the php.net reference on PDO
You should be using PDOStatement::fetch() to fetch the row. It fetches (by default) both numerically and associatively. You can change that as well.
With your code:
while($rs=$stmt->fetch()){
$rs['qty']=$_SESSION['basket'][$rs['productid']];
$rs['total'] += $rs['qty']*$rs['price'];
$total += $rs['total'];
$a[] = $rs;
}
Manual Reference.

Categories