Two neat member classes

A

Anonymous

Guest
Here are two simple user registration/login classes, well at least I think they are:

First one I called: Member.php
Code:
<?php 

class Member extends DBConnect {
	
    // The member attributes containing required and optional information.
    // The attributes must correspond to the database table columns:
    
    private $userType=NULL; // Required (assigned)
    private $username=NULL; // Required 
    private $email=NULL; // Required    
    private $pass=NULL; // Required
    private $fullName=NULL;
    private $address=NULL;
    private $city=NULL;
    private $state=NULL;
    private $zipCode=NULL;
    
    // Get the private attributes:
    public function __get($property) {
	 if (property_exists($this, $property)) {		 
	   return $this->$property;
	 }
    }
    
    // Set the private attributes:
    public function __set($property, $value) {
	 if (property_exists($this, $property)) {
 
	   $this->$property = $value;
	 }
  
	 return $this;
    }
    
	// Method returns the user ID:
	public function getId() {
		return $this->id;
	}
	
	// Method returns a Boolean if the user is an administrator:
	public function isAdmin() {
		return ($this->userType == 'admin');
	}
	
	// Method returns a Boolean indicating if the user is an administrator
	// or if the user is the original author of the provided page:
	public function canEditPage(Page $page) {
		return ($this->isAdmin() || ($this->id == $page->getCreatorId()));
	}
	
	// Method returns a Boolean indicating if the user is an administrator or an author:
	public function canCreatePage() {
		return ($this->isAdmin() || ($this->userType == 'author'));
	}
      
}

The second class I called: Registration.php:
Code:
<?php
class Registration extends Member {
   
   // Make sure username isn't empty:
   public function isUsernameValid() {
	   return ($this->username == NULL);   
   }
   
   // Method checks to see if username isn't already taken and returns true if it is already taken:
   public function isUsernameAvailable() {
	   // Connect to PDO database:
	   $pdo = parent::connect();
	   
        $query = "
            SELECT
                1
            FROM users
            WHERE
                username = :username1
        ";
	   
        $query_params = array(
            ':username1' => $this->username
        );	

        // These two statements run the query against your database table.
        $stmt = $pdo->prepare($query);
        $result = $stmt->execute($query_params);

        // The fetch() method returns an array representing the "next" row from
        // the selected results, or false if there are no more rows to fetch.	   	   
        return $row = $stmt->fetch();	   
        // If a row was returned, then we know a matching username was found in
        // the database already and we should return a boolean value back.	   
	   	      	   
   }
   
   // Verify that password is not null:
   public function processPass($password) {	 
	 return ($password == NULL) ? NULL : sha1($password);   
   }
   
   public function isPassBlank() {
	   return ($this->pass == null);
   }
   
   public function isPassLongEnough($pass) {
	   return (strlen($pass) < 6); 
   }
   
   // Method returns a Boolean if the user's email is valid:
   public function hasValidEmail() {
	   return (!filter_var($this->email, FILTER_VALIDATE_EMAIL));	   
   
   }
      
   public function isEmailUsed() {
	   
	   $pdo = parent::connect();
	   
        $query = "
            SELECT
                1
            FROM users
            WHERE
                email = :email1
        ";
	   
        $query_params = array(
            ':email1' => $this->email
        );	
	   
        // These two statements run the query against your database table.
        $stmt = $pdo->prepare($query);
        $result = $stmt->execute($query_params);

        // The fetch() method returns an array representing the "next" row from
        // the selected results, or false if there are no more rows to fetch.	   	   
        return $row = $stmt->fetch();	   
        // If a row was returned, then we know a matching email was found in
        // the database already and we should return a boolean value back.	  	   	      
   }
   	
}

The reason I didn't have this in one class is that I don't want the user getting the chance to know about the member class. You will still need to validate the data that comes into the Member class somehow, but I'll leave that up to you to figure out. 8)

Then all you would have to do is something like the following:

Code:
<?php #register.php
// Need the utilities file:
require('includes/utilities.inc.php');
// Process form once user clicks on Register:
if (isset($_POST['action']) && $_POST['action'] == 'register') {
	
	 $guest = new Registration();	// Registration Class extends Members Class.	   
      $errorMsg = NULL;
	 
	 $data['userType']  = 'public'; // User has no rights to add or edit pages.
	 $data['username']  = htmlspecialchars($_POST['username']); // Required
	 $data['email']     = htmlspecialchars($_POST['email']); // Required
	 $password['pass']  = htmlspecialchars($_POST['pass']); // Do Not Store
	 $data['pass']      = $guest->processPass($password['pass']); // Required
	 $data['fullName']  = htmlspecialchars($_POST['fullName']);
	 $data['address']   = htmlspecialchars($_POST['address']);
	 $data['city']      = htmlspecialchars($_POST['city']);
	 $data['state']     = htmlspecialchars($_POST['state']);
	 $data['zipCode']   = htmlspecialchars($_POST['zipCode']);
 	 
	 // Validate length of password:
	 if ($guest->isPassLongEnough($password['pass'])) {
		$errorMsg .= '<li class="stop-bullet">Password is too short, please re-enter</li>';	 
	 }
	 
	 // Unset the user's unecrpyted password:	 
	 unset($password['pass']);
	 
	 // Set the attributes (Member Class):	 
	 foreach($data as $property => $value) {
		$guest->$property = $value;	 
	 }
	 	 
	 // Validate username, email and pass attriutes:
	 if ($guest->isUsernameValid()) {
		 $errorMsg .= '<li class="stop-bullet">Username is empty, please enter.</li>';	
	 } 
	 
	 if ($guest->isUsernameAvailable()) {
		 $errorMsg .= '<li class="stop-bullet">Username: ' . $guest->username . ' is already taken</li>';
	 }

	 if ($guest->isPassBlank()) {
		$errorMsg .= '<li class="stop-bullet">Password is empty, please enter.</li>';	 
	 }
	 	 
	 if ($guest->hasValidEmail()) {
		 $errorMsg .= '<li class="stop-bullet">' .$guest->email . ' is an invalid email address.</li>';
	 }	 
	 
	 if ($guest->isEmailUsed()) {
		 $errorMsg .= '<li class="stop-bullet">' .$guest->email . ' is already registered.</li>';	
	 }
	 
	 // Store user's credentials if form data is validated:
	 if(!$errorMsg) {		
		$query = 'INSERT INTO users (userType, username, email, pass, fullName, address, city, state, zipCode, dateAdded) VALUES (:userType, :username, :email, :pass, :fullName, :address, :city, :state, :zipCode, NOW())';
		$stmt = $pdo->prepare($query);
		$result = $stmt->execute(array(':userType' => $guest->userType, ':username' => $guest->username, ':pass' => $guest->pass, ':email' => $guest->email, ':fullName' => $guest->fullName, ':address' => $guest->address, ':city' => $guest->city, ':state' => $guest->state, ':zipCode' => $guest->zipCode));			 
	 }
}
?>

This is just a basic registration class, I plan to enhance by tighten the password security code, but seeing this I think one can see the benefits of Object-Oriented Programming.
 
I revised my classes and made for better validation:
This class is strictly for Registering a user, I figured it would be better and safer to contain it to just this one class, plus I have added better validation:
Code:
<?php
class Registration extends DBConnect {
   
     // The member attributes containing required and optional information.
    // The attributes must correspond to the database table columns:
    
    private $id = NULL;
    private $userType=NULL; // Required (assigned)
    private $username=NULL; // Required 
    private $email=NULL; // Required   
    private $pass=NULL; // Required
    private $fullName=NULL;
    private $address=NULL;
    private $city=NULL;
    private $state=NULL;
    private $zipCode=NULL;
    
    // Get the private attributes:
    public function __get($property) {
	 if (property_exists($this, $property)) {		 
	   return $this->$property;
	 }
    }
    
    // Set the private attributes:
    public function __set($property, $value) {
	 if (property_exists($this, $property)) {
 
	   $this->$property = $value;
	 }
	 return $this;
    }
      
   
   // Method checks to see if username isn't already taken and returns true if it is already taken:
   public function isUsernameAvailable() {
	   // Connect to PDO database:
	   $pdo = parent::connect();
	   
        $query = "
            SELECT
                1
            FROM users
            WHERE
                username = :username1
        ";
	   
        $query_params = array(
            ':username1' => $this->username
        );	

        // These two statements run the query against your database table.
        $stmt = $pdo->prepare($query);
        $result = $stmt->execute($query_params);

        // The fetch() method returns an array representing the "next" row from
        // the selected results, or false if there are no more rows to fetch.	   	   
        return $row = $stmt->fetch();	   
        // If a row was returned, then we know a matching username was found in
        // the database already and we should return a boolean value back.	   
	   	      	   
   }
         
   // Method returns a Boolean if the user's email is valid:
   public function hasValidEmail() {
	   return (!filter_var($this->email, FILTER_VALIDATE_EMAIL));	      
   }
   
   // Method returns to check if password is strong enough:
   public function isPassWordStrong($pass=Null) {
	   return (preg_match("/^.*(?=.{8,})(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z]).*$/", $pass) === 0);
   }
   
   // Check for valid user name:
   public function isUsername() {
	   if (preg_match("/^[0-9a-zA-Z_]{5,}$/", $this->username) === 0) {
		   return true;
	   }
   }
   
      
   public function isEmailUsed() {
	   
	   $pdo = parent::connect();
	   
        $query = "
            SELECT
                1
            FROM users
            WHERE
                email = :email1
        ";
	   
        $query_params = array(
            ':email1' => $this->email
        );	
	   
        // These two statements run the query against your database table.
        $stmt = $pdo->prepare($query);
        $result = $stmt->execute($query_params);

        // The fetch() method returns an array representing the "next" row from
        // the selected results, or false if there are no more rows to fetch.	   	   
        return $row = $stmt->fetch();	   
        // If a row was returned, then we know a matching email was found in
        // the database already and we should return a boolean value back.	  	   	      
   }
   	
}

This is my limited MVC file:
Code:
<?php #register.php
// Need the utilities file:
require('includes/utilities.inc.php');
require('includes/password.inc.php');

if ($user) {
	header('Location:index.php');
	exit;	
}

// Process form once user clicks on Register:
if (isset($_POST['action']) && $_POST['action'] == 'register') {
	
	 $guest = new Registration();	// Registration Class extends DBConnect Class.	 	   
      $errorMsg = NULL;
	 
	 $data['userType']  = 'public'; // User has no rights to add or edit pages.
	 $data['username']  = trim($_POST['username']); // Required
	 $data['email']     = trim($_POST['email']); // Required
	 $password          = trim($_POST['pass']);
	 $passOK            = $guest->isPassWordStrong($password);
	 if ($passOK) {
		 $errorMsg     = '<li class="stop-bullet">Password must be at least 8 characters, </li><li class="stop-bullet">and must contain at least one lower case letter, </li><li class="stop-bullet">one upper case letter and one digit</li>';
	 }
	 $data['pass']      = password_hash($password, PASSWORD_BCRYPT, array("cost" => 15));
	 $password          = NULL;
	 $data['fullName']  = trim($_POST['fullName']);
	 $data['address']   = trim($_POST['address']);
	 $data['city']      = trim($_POST['city']);
	 $data['state']     = trim($_POST['state']);
	 $data['zipCode']   = trim($_POST['zipCode']); 	 
	 
	 // Set the attributes (Member Class):	 
	 foreach($data as $property => $value) {
		$guest->$property = $value;	 
	 }
	 	 
	 // Validate username, email and pass attriutes:	
	 if ($guest->isUsername()) {
           $errorMsg .= '<li class="stop-bullet">Username must be bigger that 5 chars and contain only digits, letters and underscore</li>';
	 }
		 
	 if ($guest->isUsernameAvailable()) {
		 $errorMsg .= '<li class="stop-bullet">Username: ' . $guest->username . ' is already taken</li>';
	 }
	 	 
	 if ($guest->hasValidEmail()) {
		 $errorMsg .= '<li class="stop-bullet">Email must is invalid, Re-Enter</li>';
	 }	 
	 
	 if ($guest->isEmailUsed()) {
		 $errorMsg .= '<li class="stop-bullet">' .$guest->email . ' is already registered.</li>';	
	 }
	 
	 // Store user's credentials if form data is validated:
	 if(!$errorMsg) {		
		$query = 'INSERT INTO users (userType, username, email, pass, fullName, address, city, state, zipCode, dateAdded) VALUES (:userType, :username, :email, :pass, :fullName, :address, :city, :state, :zipCode, NOW())';
		$stmt = $pdo->prepare($query);
		$result = $stmt->execute(array(':userType' => $guest->userType, ':username' => $guest->username, ':pass' => $guest->pass, ':email' => $guest->email, ':fullName' => $guest->fullName, ':address' => $guest->address, ':city' => $guest->city, ':state' => $guest->state, ':zipCode' => $guest->zipCode));			 
		header('Location: index.php');
		exit;		
	 }
}
require('includes/header.inc.php');
?>
<section>
  <article>
    <div class="error-styling">
        <ul>
           <?php echo (isset($errorMsg)) ? $errorMsg : '<li class="start-bullet">Registration Page</li>'; ?>
        </ul>
    </div>
    <div id="format-form">
        <form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post">
        <input type="hidden" name="action" value="register" />   
        
        <label class="label-styling" for="style-name" >Username:*</label>
        <input type="text" maxlength="40" id="style-name" name="username" value="">    
        <br> 
        <label class="label-styling" for="style-pass">Password:*</label>
        <input type="password" name="pass" id="style-pass" value="">
        <br>     
        <label class="label-styling" for="style-email">Email:*</label>
        <input type="text" name="email" id="style-email" value="">
        <br>
        <label class="label-styling" for="style-fname">Full Name:</label>        
        <input type="text" name="fullName" id="style-fname" value="">
        <br>
        <label class="label-styling" for="style-address">Address: </label>        
        <input type="text" name="address" id="style-address" value="">
        <br>
        <label class="label-styling" for="style-city">City: </label>
        <input type="text" name="city" id="style-city" value="">
        <br>
        <label class="label-styling" for="style-state">State: </label>
        <input type="text" name="state" id="style-state" value="">
        <br>
        <label class="label-styling" for="style-zipCode">Zip Code: </label>      
        <input type="text" name="zipCode" id="style-zipCode" value="">
        <br>
        <br>
        <input type="submit" value="Register" />
        <br>
        <p> * - Required </p>
    </form>
    </div>
    
  </article>    
</section>
</body>
</html>

I'm still working on security issues, but from the reading and testing code I have done so far using prepared PDO statements seems to be the way to go:

I revised my connection string to improve the security:
Code:
<?php
abstract class DBConnect {
    protected static function connect() {
	   $db_options = array(
		   PDO::ATTR_EMULATE_PREPARES => false                     // important! use actual prepared statements (default: emulate prepared statements)
		   , PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION           // throw exceptions on errors (default: stay silent)
		   , PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC      // fetch associative arrays (default: mixed arrays)
	   ); 		 
	   $pdo = new PDO('mysql:host=localhost;dbname=cms;charset=utf8', 'root', '******', $db_options);
	   return $pdo;
    }	
}

I will be definitely be updating this once I figure out the best security and if anyone can suggest a way to improve it just let me know. Note: Just saying that you should be doing it this way isn't going to cut it, back it up with something other than I read it somewhere on the internet.
 
Back
Top