Trying to iterate over a mongodb cursor twice - failing - php

I am executing a query against my mongodb database... and then in two separate spots in my front end logic, I need to iterate through the results to extract / show different information.
Problem
The second time I try to iterate through, I get the following error message:
Fatal error: Uncaught MongoDB\Driver\Exception\LogicException: Cursors
cannot yield multiple iterators in
/var/www/localhost/htdocs/widgets/exception_details.php:46
Stack trace: #0
Backend Code
Here's the logic to query the database in my model:
try{
$id = new \MongoDB\BSON\ObjectId($docid);
$filter = ['_id' => $id];
$options = [];
$query = new \MongoDB\Driver\Query($filter, $options);
$rows = $m->executeQuery('widgets.play_summary', $query);
return $rows;
} catch (Exception $e) {
echo 'Exception:'. $e->getMessage();
return false;
}
Front End Logic
Here's the logic that attempts the iteration.
First time:
foreach ($rows as $id=>$value) {
if ( count($value->unreachable_servers > 0) ) {
foreach($value->unreachable_servers as $key=>$value) {
echo "<tr>";
echo "<td>$value</td>";
echo "</tr>";
}
}
}
And then later, I try to do this:
$it = new IteratorIterator($rows); //line 46
$it->rewind();
foreach ($rows as $id=>$value) {
if ( count($value->failed_plays > 0) ) {
foreach($value->failed_plays as $key=>$value) {
echo "<tr>";
echo "<td>$value</td>";
echo "</tr>";
}
}
}
Additional Information
Commenting out the following lines gives the exact same error, but just on line 48 instead of line 46:
$it = new IteratorIterator($rows);
$it->rewind();
Any tips would be appreciated.
Thanks.
EDIT 1
As a test, I changed the front end code to use rewind and next like this:
$it = new \IteratorIterator($rows);
$it->rewind();
//foreach ($rows as $id=>$value) {
while($value = $it->current()) {
if ( count($value->unreachable_servers > 0) ) {
foreach($value->unreachable_servers as $key=>$value) {
echo "<tr>";
echo "<td>$value</td>";
echo "</tr>";
}
}
$it->next();
}
And for the second loop:
$it = new IteratorIterator($rows);
$it->rewind();
//foreach ($rows as $id=>$value) {
while($value = $it->current()) {
if ( count($value->failed_plays > 0) ) {
foreach($value->failed_plays as $key=>$value) {
echo "<tr>";
echo "<td>$value</td>";
echo "</tr>";
}
}
$it->next();
}
I get the same results. It works the first time but the second time it bombs with the same error message.

Cursor cannot be iterated twice. Fetch it into array, if it fits into memory:
$rows = $m->executeQuery('widgets.play_summary', $query);
return $rows->toArray();
UPDATE
Since the answer still attracts attention 5 years later, there was https://jira.mongodb.org/browse/PHPC-1691 to implement full Iterator interface, not only Traversable.
The implementation does not resolve OPs issue tho.
Technically https://www.php.net/manual/en/class.mongodb-driver-cursor.php does have the rewind method, but it throws an exception Cursors cannot rewind after starting iteration when called after first document was received: https://github.com/mongodb/mongo-php-driver/blob/0960a204b3b60f8a80375abb6390735b95c821f1/src/MongoDB/Cursor.c#L333

Related

Filtering an array with foreach and for loop

I'm pulling data from mssql database into an array called
$results2
I need to echo out each 'Item' only one time, so this example should only echo out:
"52PTC84C25" and "0118SGUANN-R"
I can do this easily with:
$uniqueItems = array_unique(array_map(function ($i) { return $i['ITEM']; }, $results2));
The issue is when i try to echo out the other items associated with those values. I'm not sure how to even begin on echoing this data. I've tried:
foreach($uniquePids as $items)
{
echo $items."<br />";
foreach($results2 as $row)
{
echo $row['STK_ROOM']."-".$row['BIN']."<br />";
}
}
This returns close to what I need, but not exactly:
This is what I need:
Assuming your resultset is ordered by ITEM...
$item = null; // set non-matching default value
foreach ($results2 as $row) {
if($row['ITEM'] != $item) {
echo "{$row['ITEM']}<br>"; // only echo first occurrence
}
echo "{$row['STK_ROOM']}-{$row['BIN']}<br>";
$item = $row['ITEM']; // update temp variable
}
The if condition in the code will check if the ITEM has already been printed or not.
$ary = array();
foreach($results2 as $row)
{
if(!in_array($row['ITEM'], $ary))
{
echo $row['STK_ROOM']."-".$row['BIN']."<br />";
$ary[] = $row['ITEM'];
}
}

PHP how to handle when there is no foreach loop?

I have a foreach loop that runs perfectly without flaw.
foreach ($row AS $row) {
echo $row['id'];
}
There are times that there are no rows returned. I want to echo out 'there are no rows'; when there are no rows. The problem is if I try to do something like as follows:
foreach ($row AS $row) {
if (!isset($row['id'])) {
echo 'there are no rows';
} else {
echo $row['id'];
}
}
It never returns the echo "there are no rows". I assume this is because when there are no rows, the foreach loop doesn't run. The question becomes, how do I echo "there are no rows" if and only if there are no rows while not interfering with the foreach when there are rows.
I have also tried code such as:
$row1 = $stmt->fetch();
$row = $stmt->fetchAll();
if (isset($row1['id'])) {
foreach ($row AS $row) {
Still no luck
So the desired outcome would be something as follows:
When loop runs:
1
2
3
4
When loop doesn't run:
there are no rows
you should check before the loop like so
if(count($row)>0){
//foreach ...
}else{
echo 'no data';
}
Test if the array is empty:
if (empty($row)) {
echo "there are no rows";
} else {
foreach($row as $row) {
...
}
}

php second foreach doesn't write out values

I have a problem with displaying my data. I have data in the following
filter_name filter_value
Hard Drize Size 16GB
Hard Drize Size 32GB
Screen Size 7''
Screen Size 8''
And I want to present it like this.
Hard Drize Size
16GB
32GB
Screen Size
7''
8''
What I want in my php code is to check if the filter_name is in the $temp array and if it isn't then add it to $temp array so it won't cause duplicate entries. The problem I'm getting now is when I use the same data to do a second foreach inside the loop I get no data and my sql is correct. So I don't know how to out put the values. With the second foreach it only prints out the first $filter_name["filter_name"] and that all.
php:
if($stmt->rowCount() > 0)
{
$filters = "<div class=\"filters\">
<div class=\"apply-filters\">Filters</div>";
$temp = array();
$stmt->fetch(PDO::FETCH_ASSOC);
foreach($stmt as $filter_name)
{
if(!in_array($filter_name["filter_name"], $temp))
{
$temp[] = $filter_name["filter_name"];
$filters .= "<div class=\"filter-header\">".$filter_name["filter_name"]."</div>";
//second loop here doesn't work with the same data.
// test if filter_name["filter_name"] == second loop second_loop["filter_name"]
// ten write out second_loop["filter_value"] e.g.
foreach($stmt as $filter_value)
{
if($filter_name["filter_name"] == $filter_value["filter_name"])
{
$filters .= $filter_value["filter_value"] ."<br />";
}
}
}
}
$filters .= "</div>";
}
First, do a var_dump of $stmt before the first and second foreach to look at it's form. Perhaps it's not formatted the way you think?
Why don't you loop over $stmt once and in that loop print $stmt['filter_name'] and $stmt['filter_value']? Now you loop $stmt one "extra" time for each iteration of the first foreach. Instead just do a foreach($stmt as $filter) and $filter, being a associative array, should contain filter_name and filter_value for each entry.
You could then move the actual printing outside of the foreach and only construct your array in the loop that should look something like
array("Hard Drive Size" => array("16GB", "32GB"), "Screen Size" => array("7''", "8''"));
Then you could traverse that data structure using
foreach($myArray as $filter_name => $filter_values)
{
// Print filter name header
foreach($filter_values as $filter_value)
{
// print each value of the specific filter
}
}
To build the presented structure you could do something like
...
$filters = array();
foreach($stmt->fetchAll(PDO::FETCH_ASSOC) as $filter)
{
$filters[$filter['filter_name']][] = $filter['filter_value'];
}
...
Then iterate the $filters structure and print it.
Update:
From your var_dump of $stmt it's clear that $stmt is not your result set. You should assign the result of $stmt->fetch to something. Like $result = $stmt->fetch(PDO::FETCH_ASSOC) and then iterate over $result.
Check
http://php.net/manual/en/pdostatement.fetch.php
http://php.net/manual/en/pdostatement.fetchall.php
i slightly modified your code to fit your needs:
$temp = array();
foreach($stmt as $filter_name)
{
if(!in_array($filter_name["filter_name"], $temp))
{
echo '<b>' . $filter_name['filter_name'] . '</b><br>';
$temp[] = $filter_name["filter_name"];
foreach($stmt as $filter_value)
{
if($filter_name["filter_name"] == $filter_value["filter_name"])
{
echo $filter_value['filter_value'] . '<br>';
}
}
}
}
you can check a working sample here -> http://codepad.viper-7.com/aFD079
You are missing $ before filter_value.
Change
foreach($stmt as filter_value)
To
foreach($stmt as $filter_value)
$stmt->fetch(PDO::FETCH_ASSOC);
foreach($stmt as $filter_name)
may be (i am not sure)
$aaa= $stmt->fetch(PDO::FETCH_ASSOC);
foreach($aaa as $filter_name)
and try
$rows = $stmt->fetch(PDO::FETCH_ASSOC);
$tmp_name='';
$html='';
foreach($rows as $row)
{
if($row['filter_name'] != $tmp_name){
$html .=$row['filter_name'];
}
$html .=$row['filter_value'];
$tmp_name=$row['filter_name'];
}
echo $html;
if($stmt->rowCount() > 0)
{
$filters = "<div class=\"filters\">
<div class=\"apply-filters\">Filters</div>";
$temp = array();
$stmt->fetch(PDO::FETCH_ASSOC);
foreach($stmt as $filter)
$temp[$filter['filter_name']][] = $filter['filter_value'];
foreach($temp as $filter_name => $filter_values)
{
$filters .= "<div class=\"filter-header\">".$filter_name."</div>";
foreach ($filter_values as $value)
$filters .= $filter_value."<br />";
}
$filters .= "</div>";
}

If a row is not unique - ignore it - php

I'm working on the following code:
function form_Subcat_Picker() {
$mysqli = new mysqli(DB_SERVER, DB_USER, DB_PASSWORD, DB_NAME);
if (!$mysqli) {
die('There was a problem connecting to the database.');
}
$catPicker = "SELECT Subcatid, Subcatname, Parentid
FROM ProductSubCats
ORDER BY Subcatid";
if ($Result = $mysqli->query($catPicker)){
if (!$Result) {
echo 'Could not run query: ' . mysql_error();
exit;
}
while ($row = $Result->fetch_assoc()) {
echo '<div class="parentid'.$row['Parentid'].'">';
echo '<select name="Subcatid">';
echo '<option value="'.$row["Subcatid"].'">'.$row["Subcatname"]."</option>";
echo '</select>';
echo '</div>';
}
}
$mysqli->close();
}
What I want to do, is in the line:
while ($row = $Result->fetch_assoc()) {
echo '<div class="parentid'.$row['Parentid'].'">';
If the $row['Parentid'] part is the same as the previous iteration, I want to ignore that particular line (adding the div class)
So that if for example in the first run $row['Parentid'] is 1, and in the next loop it is 1 again, I want to not create a new div, just echo everything else and thus keep it in the same div.
Is this possible? Alternatively, how could I make multiple sub category id's and names appear in the one div, if they share a common parentid (there are multiple parent ids)
For the line:
echo '<option value="'.$row["Subcatid"].'">'.$row["Subcatname"]."</option>";
Maybe this would work:
$last_id = 0;
while ($row = $Result->fetch_assoc()) {
if ($last_id != $row['Parentid']) {
echo '<div class="parentid'.$row['Parentid'].'">';
echo '<select name="Subcatid">';
echo '<option value="'.$row["Subcatid"].'">'.$row["Subcatname"]."</option>";
echo '</select>';
echo '</div>';
$last_id = $row['Parentid'];
}
}
However, I think the best solution is to filter them out in the SQL statement, maybe the GROUP BY clause, but I'm not 100% sure how to do it :).
Regards,
That is just something basic for looping. Let's see your current loop:
while ($row = $Result->fetch_assoc()) {
...
}
As you want to skip with a specific condition, let's just introduce this skipping (not taking much care about the condition first):
while ($row = $Result->fetch_assoc()) {
if ($condition) continue;
...
}
Now let's formulate the condition. As we want to look into the last $row we need to keep a copy:
$last = null;
while ($row = $Result->fetch_assoc()) {
if ($condition) continue;
...
$last = $row;
}
Now we've got the data we need to have to create the condition, $last can contain the last row (if there was one) and therefore the comparison can be done:
$last = null;
while ($row = $Result->fetch_assoc()) {
$condition = $last && $row['Parentid'] === $last['Parentid'];
if ($condition) continue;
...
$last = $row;
}
And that is it basically. Depending on the logic, you might want to switch to a for loop:
for ($last = null; $row = $Result->fetch_assoc(); $last = $row) {
$condition = $last && $row['Parentid'] === $last['Parentid'];
if ($condition) continue;
...
}
This for example does ensure that for each iteration (even the skipped ones), the $last is set to the $row at the end of the loop.
Instead of continue you can naturally do different things, like not outputting the <div> or similar.
Hope this helps.
This is the way I'd write it.
// add a variable to hold the previous value
$previous_parent_id = "";
while ($row = $Result->fetch_assoc()) {
// then add an if statement to see if it's the previous statement
if ($row['parent_id'] != $previous_parent_id){
echo '<div class="parent_id'.$row['parent_id'].'">';
$previous_parent_id = $row['parent_id'];
}
}
So going in a loop on these records
ID ParentID
1 0
2 0
3 1
4 1
4 2
4 2
the output would be:
<div class="parent_id0">
<div class="parent_id1">
<div class="parent_id2">

output specific array key for each result

i have a search engine for my page. as a result i would like to ouput the array key for each result.
so i have this code:
$results = search($keywords);
$results_num = count($results); //what shows the message how many items were found
if (!empty($errors){
foreach ($results as $result){
echo "this is result: "
.$result['key']; //thought would be the solution, its not.
}
} else {
foreach ($errors as $error){
$error;
}
}
i also tried using a counter like:
$results = search($keywords);
$results_num = count($results); //what shows the message how many items were found
$counter = 0;
if (!empty($errors){
foreach ($results as $result){
$counter++;
echo "this is result: "
.$counter;
}
} else {
foreach ($errors as $error){
$error;
}
}
what doesnt work as i thought and is still not that professional.
so if there is someone who could tell me how to solve this i really would appreciate. thanks a lot.
foreach ($results as $key => $result) {
echo 'this is result: ' . $key;
}
The current key will be assigned to $key and the value for that property will be assigned to $result
http://php.net/manual/en/control-structures.foreach.php
edit
In response to your comments, I think this is what you're trying to achieve:-
$i=0;
foreach($results as $result) {
echo 'this is result: ' . ++$i;
}
foreach($arr as $key=>$val){
//do something with key and value
}
Here in code you can check first if $results array is not empty because sometime due to empty $results array foreach generates error
/*Check if $results array is empty or not*/
if(!empty($results)){
foreach ($results as $key=>$result){
/*result you want to show here according conditions*/
}
}

Categories