10

I'm converting to PDO and Im using prepared statements, I want to bind my result as so $stmt->bind_result($email_count); so i am able to put this into an if statement to see if the e-mail exists however I am getting the error Fatal error: Call to undefined method PDOStatement::bind_result() in /Applications/XAMPP/xamppfiles/htdocs/imanage/insert.php on line 51 which relates to the previous example.

I'm guessing bind_result is not a PDO defined method, so is there an equivalent I could use?

My code is below in case it helps:

insert.php

<?php

 include("connect/class.Database.php");

 class Users extends Database {

     public function insert() {

            $stmt = $this->pdo->prepare("SELECT COUNT(*) FROM users WHERE email=:email");
            $stmt->bindParam(":email", $_POST['email']);
            $stmt->bind_result($email_count);
            $stmt->execute();
            $stmt->fetch(PDO::FETCH_ASSOC);

                    if ($email_count > 0) {
                        echo "email exisits! click here to try <a href='register'>again</a>";
                        } else {
                            //escape the POST data for added protection
                            $username = isset($_POST['username']) ? $_POST['username'] : null;
                            $cryptedPassword = crypt($_POST['password']);
                            $password = $cryptedPassword;
                            $name = isset($_POST['name']) ? $_POST['name'] : null;
                            $email = isset($_POST['email']) ? $_POST['email'] : null;

                            $data = array($username, $password, $name, $email); 
                            $stmta = $this->pdo->prepare("INSERT INTO users (username, password, name, email) VALUES (?, ?, ?, ?)");
                            $stmta->execute($data);

                                printf("%d Row inserted.\n", $stmta->row_count);
                                /* close statement and connection */
                                $stmta->close();
                } // end email_count and insert to table
            } // end function

      }
?>

connect/class.Database.php

<?php

// Database connection PDO

class Database {

    public function __construct() {
        // Connection information
        $host   = 'localhost';
        $dbname = 'imanage';
        $user   = 'root';
        $pass   = '';

        // Attempt DB connection
        try
        {
            $this->pdo = new PDO("mysql:host=$host;dbname=$dbname", $user, $pass);
            $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            echo 'Successfully connected to the database!';
        }
        catch(PDOException $e)
        {
            echo $e->getMessage();
        }

    }

     public function __destruct()
    {
        // Disconnect from DB
        $this->pdo = null;
        echo 'Successfully disconnected from the database!';
    }


}

?>
user0129e021939232
  • 6,205
  • 24
  • 87
  • 140
  • bind_result looks like a MySQLi function: http://php.net/manual/en/mysqli-stmt.bind-result.php which means you can't probably mix and match pdo functions with MySQLi and vice versa. – Maximus2012 Sep 09 '13 at 20:49
  • 2
    `$stmt->fetch(PDO::FETCH_ASSOC)` returns an associative array, unlike `fetch()` in a mysqli call, which expects variable references. You can just take the array returned by a call like `$row = $stmt->fetch(PDO::FETCH_ASSOC)` To see what you got, `print_r($row);` – Michael Berkowski Sep 09 '13 at 20:49
  • This is how bind works in PDO: http://php.net/manual/en/pdostatement.bindparam.php (also look at other bind functions on that page) – Maximus2012 Sep 09 '13 at 20:51
  • Review the [`PDOStatement::fetch()` docs for examples](http://www.php.net/manual/en/pdostatement.fetch.php)... it's generally easier to use than MySQLi. – Michael Berkowski Sep 09 '13 at 20:51
  • ok thanks but then how do i store the count of the email? so that I can alert the user that this email already exists? @MichaelBerkowski – user0129e021939232 Sep 09 '13 at 21:02
  • @gutigrewal With your current query, it would be in `$row['COUNT(*)']` if done as in my earlier example. However, it is recommended to alias that column `SELECT COUNT(*) AS the_count....` in your query, then retrieve `$row['the_count']` – Michael Berkowski Sep 09 '13 at 21:12
  • @MichaelBerkowski sorry im fairly new to PHP so forgive my ignorance, how would i go about creating an alias for that column? – user0129e021939232 Sep 09 '13 at 21:23
  • Just as in my comment above. AS the_count – Michael Berkowski Sep 09 '13 at 21:56
  • @gutigrewal in PDO you have $stm->count(); which will give the number of rows affected/returned by the last query – Cito Sep 09 '13 at 21:57
  • @AbrahamSustaita I think you mean `rowCount()` and for a `SELECT COUNT()` query (and only for MySQL), this would only return 1 – Phil Sep 10 '13 at 00:37
  • It's just so refreshing seeing somebody use a `COUNT` query rather than the mostly incorrect `PDOStatement::rowCount` method. Nicely done – Phil Sep 10 '13 at 01:07

4 Answers4

9

You do not need an ugly bind_result with PDO at all.

Yet you don't need to count either. Please, avoid unnecessary actions - they only bloat and obfuscate your code for no reason.

Think first, what you need from the query? Do you really need to count? No. What you actually need is just a flag - if user exists or no. So, make a query to return such a flag.

$stmt = $this->pdo->prepare("SELECT 1 FROM users WHERE email=?");
$stmt->execute(array($_POST['email']));
$exists = $stmt->fetchColumn();

Same goes for all the other parts of code

//escape the POST data for added protection

You don't actually "escape" any data in this code block and add no protection. Yet I see absolutely no point in inserting NULL as email. Are you sure you really want it?

Community
  • 1
  • 1
Your Common Sense
  • 156,878
  • 40
  • 214
  • 345
  • @YourCommonSense is this line now redundant `$stmta->close();`? or does the query automatically close? – user0129e021939232 Sep 10 '13 at 15:48
  • @YourCommonSense, also I have null on the line $email = isset($_POST['email']) ? $_POST['email'] : null; as without this on mysqli prepared statement it was not inserting the data correctly, it was inserting a numerical value instead of the actual value – user0129e021939232 Sep 10 '13 at 15:55
  • @gutigrewal A statement that only returns at most, one row, will be closed automatically after the first fetch operation so yes, using `$stmt->closeCursor()` is redundant in this case – Phil Sep 11 '13 at 01:29
  • @gutigrewal Most of time you don't have to bother. When you have a **dedicated method** that runs your query and returns the result - it will be a good practice to close. But as long as you are using raw PDO - don't bother. – Your Common Sense Sep 11 '13 at 05:59
  • @gutigrewal speaking od these NULLs - you are confusing something for sure. Speaking od DB connection - the main idea is to have only **ONE** connection per application. Which way you achieve it is not that important. But you have to make sure you are connecting only once – Your Common Sense Sep 11 '13 at 06:02
5

For quickly retrieving a value from something like a SELECT COUNT() query, have a look at PDOStatement::fetchColumn, eg

$stmt = $pdo->prepare('SELECT COUNT(1) FROM users WHERE email = :email');
$stmt->bindParam(':email', $email);
$stmt->execute();
$email_count = $stmt->fetchColumn();

I'd also like to offer some further advice. You shouldn't be creating a PDO connection in your class constructor. This means that every time you instantiate a class extending Database, you create a new connection. Instead, pass the PDO instance as a dependency, eg

abstract class Database {
    /**
     * @var PDO
     */
    protected $pdo;

    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
    }
}

The same goes for your User::insert method. Try to pass any required parameters via method arguments. If you ever want to start writing unit tests for your classes, this will be invaluable

public function insert($email) {
    $stmt = $this->pdo->prepare('SELECT COUNT(1) FROM users WHERE email = :email');
    $stmt->bindParam(':email', $email);
    $stmt->execute();
    $email_count = $stmt->fetchColumn();

    // and so on

And finally, for PHP only files, omit the closing PHP tag ?>. This will save you from accidentally including whitespace at the end of your files that may be sent to the browser.

Phil
  • 157,677
  • 23
  • 242
  • 245
  • thanks, in regards to the database class should I my db connection be in a separate function to the construct? basically like above but not in the construct? – user0129e021939232 Sep 10 '13 at 15:37
2

For people who come here seeking a literal answer.

Use bindColumn

PDOStatement::bindColumn — Bind a column to a PHP variable

using the example from the same source :

function readData($dbh) {
  $sql = 'SELECT name, colour, calories FROM fruit';
  try {
    $stmt = $dbh->prepare($sql);
    $stmt->execute();

    /* Bind by column number */
    $stmt->bindColumn(1, $name);
    $stmt->bindColumn(2, $colour);

    /* Bind by column name */
    $stmt->bindColumn('calories', $cals);

    while ($row = $stmt->fetch(PDO::FETCH_BOUND)) {
      $data = $name . "\t" . $colour . "\t" . $cals . "\n";
      print $data;
    }
  }
  catch (PDOException $e) {
    print $e->getMessage();
  }
}
readData($dbh);
user10089632
  • 5,216
  • 1
  • 26
  • 34
0

Here's my custom function:

function bindColumns($stmt, $columns){
    if(!is_array($columns)) $columns=array($columns);
    $count=count($columns);
    for($i=0;$i<$count;$i++) $stmt->bindColumn($i+1, $columns[$i]);
}

And usage (variables by reference):

bindColumns($stmt, array(&$worker_id, &$password, &$salt));
Manunich
  • 26
  • 3