-2

In the following code, I run two queries, one to pull the first and last names of a user from the database, and a second to write to a different field in the same entry in the database. The first query produces no results (and if I were to uncomment the $rowCount line and the line below, the script would abort there 100% of the time). The second query works perfectly.

Anyone have a theory about why?

$db = new mysqli($db_host, $db_user, $db_pass, $db_name, $db_port);

$user = get_post_var('email');
$user = preg_replace('/[^A-Za-z0-9@._-]/', '', $user);
$code = substr(str_shuffle("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), 0, 20);

($stmt = $db->prepare('select first, last from users where user = ?'));
$stmt->bind_param('s', $user);
$stmt->execute();
$stmt->bind_result($first,$last);
//$rowCount = mysqli_num_rows($stmt); 
//if($rowCount == 0) { fail("19","forgot"); } else {}
$stmt->close();

($stmt = $db->prepare('update users set reset = ? where user = ?'));
$stmt->bind_param('ss', $code, $user);
$stmt->execute();
$stmt->close();

mail( "$user", "Example.org Password Reset Code", "Dear $first $last,\n\n Please visit the following url to reset your password:\n http://www.example.org/reset.php?c=$code\n\nSincerely,\nSender", "From: noreply@example.org" );
fail('18',"forgot");

$db->close();   
Quoi
  • 35
  • 5
  • What error is being generated when you run `mysqli_num_rows`? – andrewsi Aug 31 '13 at 00:04
  • show us `$stmt->errno` and `$stmt->error` – Davit Aug 31 '13 at 00:05
  • @Prix - This does not produce an error. The code completes successfully and the e-mail is sent successfully, albeit without the aforementioned first and last names. – Quoi Aug 31 '13 at 00:22
  • @andrewsi - No overt error with mysqli_num_rows - the script completes successfully as far as I can tell. The value of $rowCount returns as blank. So, this triggers the "fail" function every time: if($rowCount == 0) { fail("19","forgot"); } else {} and this NEVER triggers the function: if($rowCount === 0) { fail("19","forgot"); } else {} – Quoi Aug 31 '13 at 00:23
  • don't replace stuff with question marks and other non-actual data that may be the cause of your problems – php_nub_qq Aug 31 '13 at 00:31
  • @JeffThomas - that means that the query is failing, and returning false. Try changing the error function to use `$db->error` – andrewsi Aug 31 '13 at 00:31
  • @php_nub_qq - he's using bound parameters. That is the correct syntax – andrewsi Aug 31 '13 at 00:32
  • OH my bad should have looked more – php_nub_qq Aug 31 '13 at 00:33
  • @andrewsi - Not 100% sure what you are going for there (I'm new to this mySQLi business) - but running it with if (!$stmt->execute()) die('Execute failed: (' . $stmt->errno . ') ' . $db->error); does not produce a result, nor does if (!$stmt->execute()) die('Execute failed: (' . $db->error . ') ' . $stmt->error); – Quoi Aug 31 '13 at 00:38
  • @JeffThomas - I'm trying to find out why the first query is failing, not the second. Could you try `$stmt->execute() or die ($db->error . " " . $stmt->error)` for the first statement? That should either run it, or stop the program with the output from both the database and the statement. – andrewsi Aug 31 '13 at 00:42
  • @andrewsi - Ran first query as: ($stmt = $db->prepare('select first, last from users where user = ?')); $stmt->bind_param('s', $user); $stmt->execute() or die ($db->error . " " . $stmt->error); $stmt->bind_result($first,$last); $rowCount = mysqli_num_rows($stmt); if($rowCount === 0) { fail("19","forgot"); } else {} $stmt->close(); Script completed successfully, no errors reported, e-mail was sent sans names. – Quoi Aug 31 '13 at 00:50
  • @andrewsi - Copy/pasted the wrong code in that initially - if it looked like I didn't do what you said, you saw the first version... – Quoi Aug 31 '13 at 00:51
  • @JeffThomas - so the first query works when you put the debugging in? What about if you uncomment the `mysqli_num_rows()` call? – andrewsi Aug 31 '13 at 00:57
  • Yes, it does. Or rather, it produces no results, but the "die" is not triggered. Uncommenting the mysqli_num_rows() call by itself does nothing. If I put the if($rowCount == 0) statement on the following line, it triggers 0% of the time, if I put it as if($rowCount === 0) it triggers 100% of the time. – Quoi Aug 31 '13 at 01:10
  • @andrewsi - I'm still pretty early in this project, and looking around, I only have one other "select" type query talking to this table in the whole darn thing - but that one works! I'm going to do a little compare and contrast to see what is different between the one that works and this one. Will report back when I know something. – Quoi Aug 31 '13 at 01:12
  • Try a `var_dump($rowCount)` - it'll show you what the value of $rowCount actually is. I suspect it'll tell you it's False rather than 0 – andrewsi Aug 31 '13 at 01:15
  • @andrewsi - Close but not quite. Throwing in the line die (var_dump($rowCount)); - it comes up with NULL. – Quoi Aug 31 '13 at 01:21
  • @JeffThomas - can you try `$rowCount = $stmt->num_rows or die ($stmt->error)`? I wonder if we're calling it wrongly? – andrewsi Aug 31 '13 at 01:25
  • @andrewsi - Okay, it looks like we have two distinct problems here, and I just solved ONE of them. What was missing in the query itself was $stmt->fetch(); - adding that, and it produces the "right" result vis-a-vis the e-mail. However, adding in your line of code triggers the "No matches found" even on what would otherwise be successful inputs. – Quoi Aug 31 '13 at 01:39
  • @andrewsi - As the answer below states, its an order of operations question - needed to have bind_result, store_result, fetch in that order, with num_rows coming basically anywhere after store_result. Thanks for talking this through with me. – Quoi Aug 31 '13 at 01:46
  • @Prix - Nothing wrong with the data. Query didn't work because I was missing fetch, num_rows did not work because I was missing store_result. – Quoi Aug 31 '13 at 01:48

1 Answers1

0

In order to use num_rows on prepared statements you need to call store_result first.

The manual also mention the above.

here is a simple example:

<?php
// Your database info
$db_host = 'localhost';
$db_user = '';
$db_pass = '';
$db_name = '';
$code = substr(str_shuffle("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), 0, 20);

if (!isset($_POST['email']))
{
    die('Please fill in the email field.');
}

if (!filter_var($_POST['email'], FILTER_VALIDATE_EMAIL))
{
    die('Invalid email address');
}

$con = new mysqli($db_host, $db_user, $db_pass, $db_name);
if ($con->connect_error)
{
    die('Connect Error (' . mysqli_connect_errno() . ') '. mysqli_connect_error());
}

$sql = "SELECT first, last FROM users WHERE user = ?";
if (!$result = $con->prepare($sql))
{
    die('Query failed: (' . $con->errno . ') ' . $con->error);
}

if (!$result->bind_param('s', $_POST['email']))
{
    die('Binding parameters failed: (' . $result->errno . ') ' . $result->error);
}

if (!$result->execute())
{
    die('Execute failed: (' . $result->errno . ') ' . $result->error);
}

$result->store_result();
if ($result->num_rows == 0)
{
    die('No username found...');
}

$result->bind_result($first, $last);
$result->fetch();

// After using fetch, we can print the data
echo $first, " => ", $last;

$result->close();

$update = 'UPDATE users SET reset = ? WHERE user = ?';
if (!$stmt = $con->prepare($update))
{
    die('Update query failed: (' . $con->errno . ') ' . $con->error);
}

if (!$stmt->bind_param('ss', $code, $_POST['email']))
{
    die('Update binding parameters failed: (' . $stmt->errno . ') ' . $stmt->error);
}

if (!$stmt->execute())
{
    die('Update execute failed: (' . $stmt->errno . ') ' . $stmt->error);
}

$stmt->close();
$con->close();

if (mail($_POST['email'], "Example.org Password Reset Code", "Dear $first $last,\n\n Please visit the following url to reset your password:\n http://www.example.org/reset.php?c=$code\n\nSincerely,\nSender", "From: noreply@example.org"))
{
    echo "Email sent";
}
else
{
    echo "Failed to send email";
}
Prix
  • 19,417
  • 15
  • 73
  • 132
  • Darn you - I literally JUST figured that out and started writing a note saying basically the same thing when you ninja'd it. Thanks for the correct answer to this part of the question! – Quoi Aug 31 '13 at 01:44
  • @JeffThomas also see how I am verifying the email, you should use that instead of replace. – Prix Aug 31 '13 at 01:46
  • How would you sanitize an e-mail address for usage within a database? I know FILTER_VALIDATE_EMAIL doesn't do an adequate job of that - would you just use mysqli_real_escape_string and regex out the \ characters if you ever needed to get the addresses back out in usable form? – Quoi Aug 31 '13 at 02:02
  • @JeffThomas the filter takes care of making sure its a valid email and the prepared statements takes care of properly sanitizing it to place on the database. [See this for reference](http://stackoverflow.com/a/5855853/342740). – Prix Aug 31 '13 at 02:08