Jump to content
Larry Ullman's Book Forums

Chapter 9 - Cms Registration Form Pursue


Recommended Posts

This will be my first attempt to really dig into oop on my own, without copying the code directly from the book.  It seems very overwhelming.

 

Using the CMS from Chapter 9, I will try to expand on the registration form in order to add an activation code.  The goal is that when a new user registers, an activation link will be sent by email.  I know this procedural method from one of Larry's previous books.

 

For the oop, I really need some help to get things started in the right direction. 

So far, I've added a column to the users table called active.  A char(32), default NULL.

Added an attribute $protected active = null to class User().

-Made a registration form with the following fields: desired username, email, password and retype password fields.

 

Any general advice on how I should proceed would be very helpful.

 

 

Link to comment
Share on other sites

That sounds like a good start. The "active" property is part of User, so it gets added to that table and that class.

 

From there, you'll need to first determine how to represent "active" and how to handle the generation of the activation link. In my mind, I see two ways of proceeding. I know in a procedural example, I used the same database column to represent three states: not active, activation code value, active. You could implement that design into OOP, and copy the corresponding logic into User.

 

That's a fine approach, but one could also argue that the activation link and code are more part of the site than the user. After the user's account has been activated, the activation link logic is never used again. To follow that approach, active is really a Boolean, so the database column could be tinyint(1), for 0/1. Then you create a Site class with a method for creating activation links and verifying activation. This would require a separate table that stores the user ID and the activation code. When the user goes to activate their account, the Site class instance (or it would likely be a static method of that class) verifies the activation code and updates the User instance.

 

So...two ways. I'd personally be inclined to go the second route, but with OOP, there's a lot of wiggle room when it comes to design. 

Link to comment
Share on other sites

Thanks for the advice Larry.  I will make an attempt at this and whatever I happen to conjure up, I'll post.  If nothing else, I'm sure it will give some of the experienced OOP developers a chuckle :).  We'll see.

Link to comment
Share on other sites

For the sake of simplicity, I think I'm leaning towards adding the active to the user table.

I'm going to make a page called registration.php.  The script will check the email against the users table using a prepared statement.  If that email is not previously registered it will insert the name, email, password and activation code on a second prepared statement.  Is this basically what needs to happen? 

 

So the first hurdle is getting the oop/pdo equivalent of this:

$q = "SELECT user_id FROM users WHERE email='$e'";
	$r = mysqli_query ($dbc, $q) or trigger_error("Query: $q\n<br />MySQL Error: " . mysqli_error($dbc));
	
	if (mysqli_num_rows($r) == 0) { //Available.
		//Create the activation code.
		$a = md5(uniqid(rand(), true));
Link to comment
Share on other sites

So I'm trying to make a simplified version of checking the email against the existing users, see code snippet: (For registration purposes).

if($_SERVER['REQUEST_METHOD']=='POST'){
		// Validate the form data:
    if ($form->validate()) {
		$results = $pdo->query('SELECT id FROM users WHERE email=:email'); //Review page 262
		if ($results->rowCount() == 1){
		echo 'Sorry, that username or email is previously registered!';
		include('includes/header.inc.php');
		include('views/register.html');
		include('includes/footer.inc.php');
		exit();

I keep getting the error, "Call to a member function rowCount() on a non-object..."  I'm following or at least think I'm following the book, referring to  Chapter 8, page 262 in my book.  $results->rowCount() should be equivalent to mysqli_num_rows, but I'm not getting it to work. 

What am I doing wrong?

Jason

Link to comment
Share on other sites

There's a couple of problems here. First, you're kind of using prepared statements--:email--without actually preparing and executing or even providing an email value. Second, if you see here:

 

http://php.net/manual/en/pdostatement.rowcount.php

 

It says "For most databases, PDOStatement::rowCount() does not return the number of rows affected by a SELECT statement. Instead, use PDO::query() to issue a SELECT COUNT(*) statement with the same predicates as your intended SELECT statement, then use PDOStatement::fetchColumn() to retrieve the number of rows that will be returned. Your application can then perform the correct action."

Link to comment
Share on other sites

Thanks for the help so far.

 

Why does A not work with $r->rowCount and B work? 

 

A is my attempt at making the registration form verify email addresses previously registered, email=:email comes from user input using QuickForms2 

B is from Chapter 9, index.php.

 if ($form->validate()) {
		//A) This does not work with $r->rowCount($q): $q = 'SELECT COUNT(*) FROM users WHERE email=:email';
		//B) This works with $r->rowCount($q):  $q = 'SELECT id, title, content, DATE_FORMAT(dateAdded, "%e %M %Y") AS dateAdded FROM pages ORDER BY dateAdded DESC LIMIT 1';
		$r = $pdo->query($q);
		//check that some rows were returned.
		if($r && $r->rowCount() > 0){

Here is the form code:

require('HTML/QuickForm2.php');
$form = new HTML_QuickForm2('RegistrationForm');
//Add the desired username field.
$username = $form->addElement('text', 'username');
$username ->setLabel('Desired Username');
$username ->addFilter('trim');
$username ->addRule('required','Please choose your desired username.');
//Add the email address field.
$email = $form->addElement('text','email');
$email->setLabel('Email');
$email->addFilter('trim');
$email->addRule('required','Please enter your email address.');
//Add the password field.
$password =$form->addElement('password','pass');
$password->setLabel('Password');
$password->addFilter('trim');
$password->addRule('Required','Please create a password.');
Link to comment
Share on other sites

Right, but that doesn't get the value of the $email variable into the query. Right now you're literally running a query that is:

SELECT COUNT(*) FROM users WHERE email=:email

This undoubtedly causes a syntax error when it's run. 

Link to comment
Share on other sites

So,  I have tried to use the getValue() like this, however, it produces two errors:

  • Warning: PDOStatement::execute(): SQLSTATE[HY093]: Invalid parameter number: number of bound variables does not match number of tokens and
  • Call to a member function fetchColumn() on a non-object''

 

Why?
 

$q = 'SELECT COUNT(email) FROM users WHERE email=:email';
$stmt = $pdo->prepare($q);
$r = $stmt->execute(array(':username'=>$username->getValue(), ':email'=>$email->getValue()));
if ($r->fetchColumn() > 0){

I know you said in your previous post to use $pdo->query($q), but I'm struggling to figure out how to do this and at the same time get the input values from the form...So, here I am stuck!

Link to comment
Share on other sites

I'm using var_dump($r) to try and troubleshoot this.

Why does this query return boolean false? 

(regardless of what values I put in the form)

 if ($form->validate()) {
		$q = "SELECT COUNT(email) FROM users WHERE username=$username OR email=$email";
		$r = $pdo->query($q);

And this query return an object?

(This works fine, there is no form input)

//This is from Chapter 9, index.php)
try{
	$q = 'SELECT id, title, content, DATE_FORMAT(dateAdded, "%e %M %Y") AS dateAdded FROM pages ORDER BY dateAdded DESC LIMIT 1';
	$r = $pdo->query($q);

And this query return only boolean true?

    if ($form->validate()) {
		
		$q = 'SELECT COUNT(email) FROM users WHERE username=:username OR email=:email';
		$stmt = $pdo->prepare($q);
		$r = $stmt->execute(array(':username'=>$username->getValue(), ':email'=>$email->getValue()));

I'm just not getting this.   For the first and the last examples, I've tried loads of different combinations of if($r && $r->rowCount() == 0){,

 but nothing seems to work.   I've also tried if($r ->fetchColumn() == 0){  ...

 

On a positive note, I did manage to learn a bit more about quickforms2, regex and compare, very nice.

 

I need assistance to get this registration form email and username check through my thick head!  

Link to comment
Share on other sites

Glad you're making some progress! I expect the first query returns a Boolean false because the query is failing. The second query is returning an object because PDO query "Executes an SQL statement, returning a result set as a PDOStatement object" (

 

As a bit of advice, you may be better served by thinking about what, exactly, you need and pursuing that solution and code, rather than trying to slap together different tidbits of things. A lack of deliberation seems to be adding confusion to your process (e.g., ending up passing two variables to a statement with only one bound parameter or using and not using prepared statements here). Just a thought...

Link to comment
Share on other sites

Continuing on, the part of the registration form that checks the username and email now works, using the first prepared statement.   The next part is to insert the data which is, usertype, *username, *email, *pass, active, dateAdded.   Those marked with * come from quickform2.  usertype, active (random activation code) are giving me trouble.  Below is the code I have tried:

$q = 'INSERT INTO users (usertype, username, email, pass, active, dateAdded) VALUES (:usertype, :username, :email, pass=SHA1(:pass), :active, NOW()';
		$usertype = 'publicUser';
		$active = md5(uniqid(rand(), true));
		$stmt = $pdo->prepare($q);
		//$pdo->bindParam(':ustertype',$usertype);
		$stmt->bindValue(':active',$active);
		$stmt->bindValue(':usertype',$usertype);
		$r = $stmt->execute(array(':usertype'=>$usertype->getValue(),':username' =>$username->getValue(), ':email' =>$email->getValue(), ':pass' =>$password->getValue(),':active'=>$active->getValue())); 

The error I receive is, Call to a member function getValue() on a non-object .  Despite the fact that there are many tutoriols out there on the net I can't find anything that seems to work in my case.   How do I get this error to go away to make the insert?

Link to comment
Share on other sites

Glad you're making progress! As for this new problem, let's think this through...If "active" and "usertype" don't come from the form, then are there $active and $usertype QuickForm2 variables? No, per your code. There are variables, yes, but not QuickForm2 variables. So if $active and $usertype are not QuickForm2 variables, will they have getValue() methods? Answer: no. These are just variables, and you get their values by only referencing the variable directly.

 

You'd probably be well served to do a little research on "rubber duck debugging". If you were to walk through your code and explain it to someone else--even an imaginary rubber duck, you'd hopefully realize the problem when you get to $usertype->getValue() and explain "this gets the value from the $usertype form element". Rubber duck debugging is one of my favorite debugging techniques, and is particular good for situations like this where there's not a common, systemic problem, but rather an implementation/logic problem in your code (searches for which will not give useful results). 

Link to comment
Share on other sites

Step 1) Beg, borrow, steal, buy, fabricate or otherwise obtain a rubber duck (bathtub variety)

Step 2) Place rubber duck on desk and inform it you are just going to go over some code with it, if that's all right.

Step 3) Explain to the duck what your code is supposed to do, and then go into detail and explain your code line by line

Step 4) At some point you will tell the duck what you are doing next and then realise that that is not in fact what you are actually doing. The duck will sit there serenely, happy in the knowledge that it has helped you on your way.

Note: In a pinch a coworker might be able to substitute for the duck, however, it is often prefered to confide mistakes to the duck instead of your coworker.

 

 

When I remove the getValue() then the error is number of bound variables does not match number of tokens.

The duck just told me I was crazy.

Link to comment
Share on other sites

This forum claims it will "translate geek into English", but I have to say in this case it has gone from geek to just another version of geek. 

 

Unfortunately, on this specific oop forum (for this book), I have not read any success stories of someone implementing a registration form using oop techniques.  The forum user senso attempted it, but never finished or never re-posted.  I assume that user gave up due to frustration.  I don't plan on doing that, I haven't spent hundreds of hours over the years and purchase four Ullman books to quit.

 

The procedural way from PHP and MySQL for Dynamic Web Sites is what I have used for all my websites up until now.  That book had a great example of user registration.  I bought PHP Advanced and OOP in order to take it to the next level, but this is not happening.   The book does not contain a registration form example.  I'm trying to pursue, not just posting and expecting to be spoon fed the answers.   I thought I had made progress earlier in this post by checking for existing email addresses and username, but I was wrong. 

 

I appreciate all the help I have received on this forum, it's a great forum to compliment a lot of excellent books.  In this case, Larry or one of the other experts here, can you please post an example of a registration form code?   I need someone to show a good example of a registration form using the oop techniques from Chapter 9.   I've been working on this for one month, I'm learning a lot about oop, but still cannot get the  registration form to work.   Mainly because I can't get the ->rowCount() to work in order to check for existing users.

Link to comment
Share on other sites

This forum claims it will "translate geek into English", but I have to say in this case it has gone from geek to just another version of geek.

Well, to be clear, this forum does not do that. "Translating Geek to English" is a marketing phrase I use. Complaining that the forums didn't do that for you is akin to complaining that your new Nikes didn't make you able to "just do it". ;)

 

The forum user senso attempted it, but never finished or never re-posted.  I assume that user gave up due to frustration.

That is an assumption. In my experience running this forum for more than a dozen years, very few people say when they got their problem resolved--they just don't take the time to do so--and even fewer post their successful result. There's no evidence to suggest this user gave up due to frustration.

 

I don't plan on doing that, I haven't spent hundreds of hours over the years and purchase four Ullman books to quit.

Awesome! I'm glad you're seeing this through.

 

In this case, Larry or one of the other experts here, can you please post an example of a registration form code?

I don't mind just providing the answer for you, but it'll take a while before I have that kind of time. I'll try to get to it this week, if I can. I imagine I'll approach this by starting with the login form, copying that, and then expanding the form part to cover registration. Also, to make the example I create most useful to everyone, I'll do it using the database defined in the book (not factoring in any changes you may have made to it).

 

One thing to note here is that OOP is being used in 3 ways in this example, which adds to the complexity. There are classes defined for each database table--Page and User, if I recall correctly. Then OOP is being used by tapping into PDO for the database interactions. Then OOP is used with HTML_QuickForm2 to create and validate the forms. You could simplify what you're trying to do by only using the classes for the database tables, and not using HTML_QuickForm2 and PDO. Or maybe use the database classes and PDO but not HTML_QuickForm2. Or maybe use the database classes and HTML_QuickForm2 but not PDO. Then, when that's working, you could add the other OOP components back in if you wanted.

 

In any case, I'll mock something up when I get a spare 30 minutes in the next week or so and I'll post that here.

Link to comment
Share on other sites

 

Well, to be clear, this forum does not do that. "Translating Geek to English" is a marketing phrase I use. Complaining that the forums didn't do that for you is akin to complaining that your new Nikes didn't make you able to "just do it".

 

You got me there,  I had that one coming :).  I thought you went sort of easy on me, but you added a little salt in the wound at the end there with:

 

I'll mock something up when I get a spare 30 minutes in the next week or so and I'll post that here.

 

It must be great to just whip up something like this in 30 minutes.  A lifetime of practice I suppose.

 

 

One thing to note here is that OOP is being used in 3 ways in this example, which adds to the complexity. There are classes defined for each database table--Page and User, if I recall correctly. Then OOP is being used by tapping into PDO for the database interactions. Then OOP is used with HTML_QuickForm2 to create and validate the forms.

 

In my case, I spent a fair amount of time installing and getting the hang of the quick_form2, and really like it.  Hopefully your example will sort of align with the code that the book uses.    I'm really looking forward to your example, but take your time there is no big rush.  I mean if you have to use 45 minutes....by all means.  All joking aside, thanks so far.

Link to comment
Share on other sites

Yeah, it'll definitely be in sync with the book's example. (And you're most welcome. Happy to help!) As for the 30 minutes, in truth, it'll probably be 10-20 minutes making sure I have everything setup--the database, HTML_QuickForm2, etc.--and 10-20 minutes coding. But I've been coding on and off for 30 years and programming actively in PHP for 15 years, so you'd hope all that time spent making mistakes would pay off!

Link to comment
Share on other sites

  • 3 weeks later...

Sorry for the delay! I had to install PEAR on my Mac and MySQL and HTML_QuickForm2 and create the database etc. etc. (new Mac). Here's the working code, and then I'll mention a couple of caveats:

 

# views/register.html

<!-- # add_page.html - Script 9.16 -->
<section class="threeColumns">
    <article>
        <p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.</p>
    </article>
    <article class="twoThirds">
        <h1>Register</h1>
        <?php 
        if ($form->isSubmitted() && $form->validate()) {
            echo '<p>You are now registered!</p>';
        } else {
            echo $form;
        }
        ?>
    </article>
</section>

The only thing of note in that is if the form was successfully submitted, it's not shown again (the add page file always shows the form).

 

# register.php

<?php # register.php 
// This page both displays and handles the registration form.


// Need the utilities file:
require('includes/utilities.inc.php');
    
// Create a new form:
set_include_path(get_include_path() . PATH_SEPARATOR . '/usr/share/pear');
require('HTML/QuickForm2.php');
$form = new HTML_QuickForm2('registerForm');

// Add the username field:
$username = $form->addElement('text', 'username');
$username->setLabel('Username');
$username->addFilter('trim');
$username->addFilter('strip_tags');
$username->addRule('required', 'Please enter a username.');

// Add the email address field:
$email = $form->addElement('text', 'email');
$email->setLabel('Email Address');
$email->addFilter('trim');
$email->addRule('email', 'Please enter your email address.');
$email->addRule('required', 'Please enter your email address.');

// Add the password field:
$pass = $form->addElement('password', 'pass');
$pass->setLabel('Password');
$pass->addFilter('trim');
$pass->addRule('required', 'Please enter your password.');

// Add the submit button:
$submit = $form->addElement('submit', 'submit', array('value'=>'Register'));

// Check for a form submission:
if ($_SERVER['REQUEST_METHOD'] == 'POST') { // Handle the form submission
    
    // Validate the form data:
    if ($form->validate()) {

        // Check for the email address:
        $q = 'SELECT email FROM users WHERE email=:email';
        $stmt = $pdo->prepare($q);
        $r = $stmt->execute(array(':email' => $email->getValue()));
        if ($stmt->fetch(PDO::FETCH_NUM) > 0) {
            $email->setError('That email address has already been registered.');
        } else {

            // Insert into the database:
            $q = 'INSERT INTO users (userType, username, email, pass) VALUES ("public", :username, :email, SHA1(:pass))';
            $stmt = $pdo->prepare($q);
            $r = $stmt->execute(array(':username' => $username->getValue(), ':email' => $email->getValue(), ':pass' => $pass->getValue()));

            // Freeze the form upon success:
            if ($r) {
                $form->toggleFrozen(true);
                $form->removeChild($submit);
            }

        }
                
    } // End of form validation IF.
    
} // End of form submission IF.

// Show the page:
$pageTitle = 'Register';
include('includes/header.inc.php');
include('views/register.html');
include('includes/footer.inc.php');
?>
It's cobbled together in a rush, but I think it's sound. Two things I would change before real-world use: 1. I'd also check that the username hasn't been registered (by mimicking the email check code). 2. I'd change the entire site to use the password hashing library that's newer to PHP.
 
Let me know if you have any questions or problems with this!
Link to comment
Share on other sites

 Share

×
×
  • Create New...