-1

I have recently switched over to using PDO from mySQLi for all mySQL queries and for the most part, it has been pretty straight forward. I recently completed a web app and am now implementing a PDO based sign up/sign in workflow for the first time.

After extensive research, I wrote the PHP and PDO for sign up and login. On Submit of sign up, the page refreshes, nothing appears in DB and the page does not redirect accordingly.

Below is the code to connect to the DB, register a user, and redirect them to their profile page.

registerUser.php

 <?php
error_reporting(E_ALL);
ini_set('display_errors', 1);

require 'dbconfig.php';

$url = "http://mattmcclintock.com";

$uname = null; 
$umail = null;
$upass = null;
$unem = array($uname, $umail);
$umore = array($uname, $umail, $upass);
$errors = array();

$message = null;

if (isset($_POST['btn-signup'])) { 
// good idea to trim non-password fields
$uname = trim($_POST['txt_uname']); 
$umail = trim($_POST['txt_umail']);
$upass = $_POST['txt_upass']; 

// I am assuming you want to check every field independently, so you need to 
// group your IF conditions for each field

// empty string is a falsey (i.e. '' == false => true; though '' === false => false), 
// so we'll shorten your IF conditions from  $var == ''  to  !$var
    if (!$uname) {
    $errors['txt_uname'] = "provide username !"; 
   }

   if (!$umail) {
    $errors['txt_umail'] = "provide email id !"; 
   } elseif (!filter_var($umail, FILTER_VALIDATE_EMAIL)) {
    $errors['txt_umail'] = 'Please enter a valid email address !';
   }

  if (!$upass) {
    $errors['txt_upass'] = "provide password !";
  } elseif (strlen($upass) < 6) {
    $errors['txt_upass'] = "Password must be atleast 6 characters"; 
  }


     // Assuming you only want to check for duplications when there are no errors with the username or email

     if (empty($errors['txt_umail']) && empty($errors['txt_umail'])) {
       if ($row = $user->getUserByUsernameOrEmail($uname, $umail)) {
        if ($row['user_name'] == $uname) {
            $errors['txt_uname'] = "sorry username already taken !";
        } else {
            $errors['txt_umail'] = "sorry email id already taken !";
        }
    }
}

// empty array is also a falsey
if ($errors) {
    $message = "There are errors";

// WHOA! where did $fname and $lname come from?!
// original: if($user->register($fname,$lname,$uname,$umail,$upass)) {

} elseif ($user->register($uname, $umail, $upass)) {
    $user->redirect($url);
} else {
    $message = "An unexpected error has occurred";
} 
}
?>

And the corresponding HTML for to signup with.

<form id="signupform" method="POST" action="registerUser.php" class="form-horizontal" role="form">
                    <div id="signupalert" style="display:none" class="alert alert-danger">
                       <p>Error:</p>
                       <span></span>
                    </div>
                    <div class="form-group">
                       <label for="email" class="col-md-3 control-label">Username</label>
                       <div class="col-md-9">
                          <input type="text" class="form-control" name="txt_uname" placeholder="Enter Username" value="<?php if(isset($error)){echo $uname;}?>" />
                       </div>
                    </div>
                    <div class="form-group">
                       <label for="firstname" class="col-md-3 control-label">Email</label>
                       <div class="col-md-9">
                          <input type="text" class="form-control" name="txt_umail" placeholder="Enter E-Mail ID" value="<?php if(isset($error)){echo $umail;}?>" />
                       </div>
                    </div>
                    <div class="form-group">
                       <label for="password" class="col-md-3 control-label">Password</label>
                       <div class="col-md-9">
                          <input type="password" class="form-control" name="txt_upass" placeholder="Enter Password" />
                       </div>
                    </div>
                    <div class="form-group">
                       <!-- Button -->                                        
                       <div class="col-md-offset-3 col-md-9">
                          <button type="submit" class="btn btn-block btn-primary" name="btn-signup">
                             <i class="glyphicon glyphicon-open-file"></i>&nbsp;SIGN UP
                          </button>          
                       </div>
                 </form>

Any help would be greatly appreciated. Having written exclusively in SQL switching over to PDO has been relatively difficult and I'm trying to get a feel for all of the common DB interactions and how to handle them with PDO.

Below I added code for class.user.php

class.User.php

  <?php
 class USER {
 private $db;

 public function __construct($DB_con)
 {
    $this->db = $DB_con;
 }

public function getUserByUsernameOrEmail($uname, $umail)
{
    try {   
        $stmt = $this->db->prepare("
            SELECT user_name, user_email FROM users WHERE user_name= ? OR user_email = ?
        ");
        $stmt->execute();
        return $stmt->fetch(PDO::FETCH_ASSOC); 
    } catch(PDOException $e) {
        echo $e->getMessage();
    }
    return null;
}

// WHOA! where did $fname and $lname come from?!
// original: public function register($fname, $lname, $uname, $umail, $upass)

public function register($uname, $umail, $upass)
{
    try {   
        $stmt = $this->db->prepare("
            INSERT INTO users (user_name, user_email, user_pass) VALUES(?, ?, ?)
        ");
        // keep things short and simple
        $stmt->execute($uname, $umail, $upass);            
        // better to return the number of affected rows when dealing with insert/update/delete queries
        return $stmt->rowCount(); 
    } catch(PDOException $e) {
        echo $e->getMessage();
    }
    return false;
}

public function login($uname, $umail, $upass)
{
    try {
        $stmt = $this->db->prepare("SELECT * FROM users WHERE user_name = ? OR user_email= ?");
        $stmt->execute();
        if ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
            if (password_verify($upass, $row['user_pass'])) {
                $_SESSION['user_session'] = $row['user_id'];
                return true;
            }
        }
    } catch (PDOException $e) {
        echo $e->getMessage();
    }
    return false;
}

public function is_loggedin()
{
    return isset($_SESSION['user_session']); // shortened
}

// your code is expecting the URL to be passed into the method
// original: public function redirect()

public function redirect($url) 
{
    header("Location: $url");
    // it's good practice to exit 
    exit;
}

public function logout()
{
    session_destroy();
    unset($_SESSION['user_session']);
    return true;
  }
 }
 ?>

dbconfig.php

<?php

session_start();

$DB_host = "bbb";
$DB_user = "bbb";
$DB_pass = "bbb";
$DB_name = "bbb";

try
{
$DB_con = new PDO("mysql:host={$DB_host};dbname=    {$DB_name}",$DB_user,$DB_pass);
 $DB_con->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
 }
 catch(PDOException $e)
 {
 echo $e->getMessage();
 }


include_once 'class.User.php';
$user = new USER($DB_con);
Emily
  • 1
  • 4
  • Can you provide the User class? – Jonnix Jun 18 '16 at 17:59
  • Editing post to include user class. Thank you. – Emily Jun 18 '16 at 18:33
  • 1
    Turn on errors: $DB_con->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); – WhoIsRich Jun 18 '16 at 19:20
  • Updated code with suggestions above. Still can't get PDO error reporting to tell me why my form value submissions aren't do into my database. – Emily Jun 19 '16 at 21:17
  • First, as of PHP 5.4 you can use `[]` as a short array for `array()`. You are probably running an earlier version of PHP if it is failing. So, replace `$errors = [];` to `$errors = array();` Second, [execute()](http://php.net/manual/en/pdostatement.execute.php) expects nothing or an array. `$unem = $uname && $umail;` is a boolean. Replace `$unem = $uname && $umail;` with `$unem = array($uname, $umail);`. Do the same for all your other `execute()` calls. The variables should be within an array. – Mikey Jun 19 '16 at 21:37
  • Same result. With mySQLi this took five minutes. – Emily Jun 19 '16 at 22:05
  • Post your code for `dbconfig.php`. **BTW**: if you can't use `[]` (PHP 5.4) then you can't use [password_verify](http://php.net/manual/en/function.password-verify.php) (PHP 5.5) ? – Mikey Jun 19 '16 at 22:37
  • Added dbconfig code. Thanks for helping I think I'm close. Probably a small syntax error at this point. – Emily Jun 19 '16 at 22:55

2 Answers2

1

Okay, so even if this was working as you've written it, you should still be seeing warnings / errors, so I'd suggest the first thing you do is find your error logs, or enable error reporting.

Most of the errors would be picked up by basic error handling so I'm going to go through a few points. This isn't strictly an "answer" (so I suppose I should expect some downvotes) but it is far too long to be a comment.

So, let's get started.

First:

$stmt = $DB_con->prepare("SELECT user_name,user_email FROM users WHERE user_name=:uname OR user_email=:umail");
$stmt->execute(array(':uname'=>$uname, ':umail'=>$umail));
$row=$stmt->fetch(PDO::FETCH_ASSOC);

if($row['user_name']==$uname) {
    $error[] = "sorry username already taken !";
}
else if($row['user_email']==$umail) {
   $error[] = "sorry email id already taken !";
}
else
{
   if($user->register($fname,$lname,$uname,$umail,$upass)) 
   {
       $user->redirect('me.php');
   }
}

Let's assume that the e-mail address / username combo hasn't been taken. In the case, $row is null, so your $row[whatever] will be throwing warnings since $row is not an array. So, start sorting out your if blocks e.g.

$stmt = $DB_con->prepare("SELECT user_name,user_email FROM users WHERE user_name=:uname OR user_email=:umail");
$stmt->execute(array(':uname'=>$uname, ':umail'=>$umail));
$row=$stmt->fetch(PDO::FETCH_ASSOC);

if($row) {
    if($row['user_name']==$uname) {
        $error[] = "sorry username already taken !";
    }
    else 
    {
        $error[] = "sorry email id already taken !";
    }
}
else
{
   if($user->register($fname,$lname,$uname,$umail,$upass)) 
   {
       $user->redirect('me.php');
   }
}

Next, inside your register method, you again don't actually check whether what is going on actually worked. Since error reporting appears to be turned off, you need to test what the value of $stmt is after the prepare (if can return false on error), and test the result of the execute which can also return false. When you find out the values, then you can start drilling down into what's going on.

Finally, a little side note. You should not be modifying the password as input by the user as you do with trim($_POST['txt_upass']) as it will hash to a different value since you don't to the same on verify.

Hope that helps a little.

Jonnix
  • 4,101
  • 1
  • 29
  • 31
  • I got rid of the trim, separated the code to its own file called on action and successfully created one user but at some point I made a change to break the code. Also, my redirecting after registering is not working. Have updated code above. – Emily Jun 19 '16 at 14:28
  • Updated code with suggestions above. Still can't get PDO error reporting to tell me why my form value submissions aren't do into my database. – Emily Jun 19 '16 at 21:16
0

Your problem could be many things, so I will review your code line by line with some comments.

class.user.php

<?php
class USER 
{
    private $db;

    public function __construct($DB_con)
    {
        $this->db = $DB_con;
    }

    public function getUserByUsernameOrEmail($uname, $umail)
    {
        try {   
            $stmt = $this->db->prepare("
                SELECT user_name, user_email FROM users WHERE user_name= ? OR user_email = ?
            ");
            $stmt->execute(array($uname, $umail));
            return $stmt->fetch(); 
        } catch (PDOException $e) {
            echo $e->getMessage();
        }
        return null;
    }

    public function register($uname, $umail, $upass)
    {
        try {   
            $stmt = $this->db->prepare("
                INSERT INTO users (user_name, user_email, user_pass) VALUES(?, ?, ?)
            ");
            $stmt->execute(array($uname, $umail, $upass));
            return $stmt->rowCount(); 
        } catch (PDOException $e) {
            echo $e->getMessage();
        }
        return false;
    }

    public function login($uname, $umail, $upass)
    {
        try {
            $stmt = $this->db->prepare("SELECT * FROM users WHERE user_name = ? OR user_email= ?");
            $stmt->execute(array($uname, $umail));
            $row = $stmt->fetch();
            // this will only work PHP >= 5.5.0
            //if (password_verify($upass, $row['user_pass'])) {
            if ($row && $row['upass'] === $upass) {
                $_SESSION['user_session'] = $row['user_id'];
                return true;
            }
        } catch (PDOException $e) {
            echo $e->getMessage();
        }
        return false;
    }

    public function is_loggedin()
    {
        return isset($_SESSION['user_session']); // shortened
    }

    public function redirect($url) 
    {
        header("Location: $url");
        exit;
    }

    public function logout()
    {
        session_destroy();
        unset($_SESSION['user_session']);
        return true;
    }
}
?>

dbconfig.php

<?php
session_start();

$DB_host = "bbb";
$DB_user = "bbb";
$DB_pass = "bbb";
$DB_name = "bbb";

try {
    $DB_con = new PDO("mysql:host={$DB_host};dbname={$DB_name}",$DB_user,$DB_pass);
    $DB_con->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $DB_con->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
} catch (PDOException $e) {
    echo $e->getMessage();
}

include_once 'class.User.php';

$user = new USER($DB_con);
?>

registerUser.php

<?php
// good idea to turn on the errors during development
error_reporting(E_ALL);
ini_set('display_errors', 1);

require_once 'dbconfig.php';

$url = "http://mattmcclintock.com";

$uname = null; 
$umail = null;
$upass = null;

// always declare the variables that you will be using

// this will be an associative array where the keys will be the name of the fields and the values will be their associated error message
$errors = array();

// new: this will hold the main error message
$message = null;

if (isset($_POST['btn-signup'])) { 
    // good idea to trim non-password fields
    $uname = trim($_POST['txt_uname']); 
    $umail = trim($_POST['txt_umail']);
    $upass = $_POST['txt_upass']; 

    // validate fields

    if (!$uname) {
        $errors['txt_uname'] = "provide username !"; 
    }

    if (!$umail) {
        $errors['txt_umail'] = "provide email id !"; 
    } elseif (!filter_var($umail, FILTER_VALIDATE_EMAIL)) {
        $errors['txt_umail'] = 'Please enter a valid email address !';
    }

    if (!$upass) {
        $errors['txt_upass'] = "provide password !";
    } elseif (strlen($upass) < 6) {
        $errors['txt_upass'] = "Password must be atleast 6 characters"; 
    }

    if (empty($errors['txt_uname']) && empty($errors['txt_umail'])) {
        if ($row = $user->getUserByUsernameOrEmail($uname, $umail)) {
            if ($row['user_name'] == $uname) {
                $errors['txt_uname'] = "sorry username already taken !";
            } else {
                $errors['txt_umail'] = "sorry email id already taken !";
            }
        }
    }

    if ($errors) {
        $message = "There are errors";
    } elseif ($user->register($uname, $umail, $upass)) {
        $user->redirect($url);
    } else {
        $message = "An unexpected error has occurred";
    } 
}
?>

<form id="signupform" method="POST" action="registerUser.php" class="form-horizontal" role="form">
    <div id="signupalert" style="display:none" class="alert alert-danger">
        <p>Error: <?= $message ?></p>
        <?php foreach ($errors as $name => $message) : ?>
          <span><?= $message ?></span><br>
        <?php endforeach ?>
    </div>
    <div class="form-group">
       <label for="email" class="col-md-3 control-label">Username</label>
       <div class="col-md-9">
          <input type="text" class="form-control" name="txt_uname" placeholder="Enter Username" value="<?= $uname ?>">
       </div>
    </div>
    <div class="form-group">
       <label for="firstname" class="col-md-3 control-label">Email</label>
       <div class="col-md-9">
          <input type="text" class="form-control" name="txt_umail" placeholder="Enter E-Mail ID" value="<?= $umail ?>">
       </div>
    </div>
    <div class="form-group">
       <label for="password" class="col-md-3 control-label">Password</label>
       <div class="col-md-9">
          <input type="password" class="form-control" name="txt_upass" placeholder="Enter Password">
       </div>
    </div>
    <div class="form-group">
       <!-- Button -->                                        
       <div class="col-md-offset-3 col-md-9">
          <button type="submit" class="btn btn-block btn-primary" name="btn-signup">
             <i class="glyphicon glyphicon-open-file"></i>&nbsp;SIGN UP
          </button>          
       </div>
    </div>
</form>

IMO I think your USER class is doing too much and that you are violating the Single Responsibility Principle.

For more information on booleans, read this link.

For more information on redirecting, read the answers on this post.

Community
  • 1
  • 1
Mikey
  • 6,620
  • 4
  • 21
  • 40
  • Thank you so much. By far the most helpful and efficient way someone has ever helped learn a new method, PDO Register/Sign In flow in this case, on stackoverflow. Greatly appreciated. – Emily Jun 19 '16 at 19:28
  • I'm having some issues with PDO error messages. Updating the two scripts above. – Emily Jun 19 '16 at 21:07
  • Updated code. No users are being created and I can't access any pages from too many redirects. – Emily Jun 19 '16 at 23:57
  • Your revised code did not follow my comment above. Please look and understand my revised code above. Take a look at all changes I made in **each** file. If the problem still persist, start debugging by placing `echo`s and `print_r`s of your variables at different points in your code. Comment out different sections and find where it breaks. For now, I would comment out `$user->redirect($url);`. You have to find exactly where the problem is and not keep guessing. Also check that [PDO is enabled](http://stackoverflow.com/a/6113496/1022914). – Mikey Jun 20 '16 at 00:03