22nd January, 2007

Part 3: My First Function

Monday, 11:13 am in Build-a-Blog v2.0

We-ee-ee-e’re ba-aa-aa-aa-ack!  Didjamissus?

Welcome to part three of the BaB v2.0.  Hopefully you’ll all remember that in the previous two parts we set up a database, a basic file structure and started working on our first form; the new posts form.  Awesome.  When we left off, we’d just written the placeholder for the ‘do post’ function, which we’re going to work on today.

Are you sitting comfortably?


3.1 Form & Functionality

The first thing for us to decide is where, exactly, we want to put our doNewPost() function.  We could put it on the same page as the new posts form itself, but there’s a slim possibility that we might want to use it in more than one location.  Besides, as ‘doing’ code rather than ‘showing’ code it’s probably a good idea to file it away in our function library…  which we haven’t written yet.  Right, let’s get on that shall we?

As usual, open up a new blank file in your text editor and stick the following inside:

<?php
/***********************************************************************
  FILE:     ba.blog/functions.inc.php
  BEGUN:    __TODAY__
  AUTHOR:   __YOURNAME__
  PURPOSE:  Our very first function library.
  NOTES:    From the Build-a-Blog v2.0 tutorial.
            <http://void-star.net/archive.php/bab>
***********************************************************************/

?>

Remembering to fill in the header details as appropriate.  Now, about that function; stick the following between the PHP tags:

function doNewPost( $d ){
  print_r( $d );
  
  return 0;
}

It doesn’t do much, but it works, and it demonstrates the basic structure of custom functions.  First off, of course, is the function keyword.  This is the flag to tell PHP that what’s coming up is a custom-defined function.  The next part, doNewPost, is the name we want to assign our function.  There are a few rules; function names are be alphanumeric, must start with a letter, may contain underscores and are case-sensitive.  They also should not have the same names as existing functions within PHP (ie. so don’t call your function print, for example).  The next part, within the brackets, contains the arguments the function expects.  A function can take zero or more arguments.  For the moment, we’re going to talk like all arguments passed to functions are required when that function is called.  This isn’t technically the case, but for now we’ll pretend that it is.  We can see here that doNewPost takes one argument, helpfully called $d.  This variable holds the post data we submitted from our form, and extracted out of $_POST.  The next lines, everything in between the curly-braces, is the body of the function, and should contain all the lines of code that you want your function to do.  Finally, we’ve got a return statement; this will pass back a value to wherever we’ve called our function from.  At the moment, it’s just returning 0; eventually we’re going to use this value as a return code to tell us if our function has executed correctly or not.  And that’s the basic anatomy of a function.

Now we just need to tell our new posts page the location of our function library.  Again, we’re faced with a design choice; where do we put the include?  The first obvious place would be in the new post page itself.  However, it’s highly likely that we’re going to be including our function library on pretty much every page we write (it’s just that damn useful).  There’s one other file that we’re going to include on every page we write, too, so maybe we should stick the include in there?  That sounds like a good idea to me.

So, open up values.inc.php and, at the end of the file, under the database connection (but above the closing PHP tag, of course), put the following line:

require_once( BAB_PATH .'/ba.blog/functions.inc.php' );

Back in your browser, fill in some data into your new post form and submit it; if all has gone well, you should see the values you entered echoed across the top of the page.  Okay, that’s the proof of concept, now let’s actually make it do something.  Back in your function library, change the doNewPost() function to look like the following:

function doNewPost( $d ){
  if( empty( trim( $d['text'] ) ) )
    return 1;

  if( !preg_match( '/^(19|20)dd-(0{0,1}[1-9]|1[012])-(0{0,1}[1-9]|[12][0-9]|3[0-1]) (0{0,1}[1-9]|1[0-9]|2[0123]):(0[1-9]|[12345][0-9]):(0[1-9]|[12345][0-9])$/', $d['time'] ) )
    $d['time'] = date( 'Y-m-d H:i:s', time() );

  $d['title'] = htmlspecialchars( strip_tags( $d['title'] ) );
  
  $sql = "INSERT INTO `blogentries` VALUES ( '', '$d[time]', '$d[title]', '$d[text]' );";
  if( !mysql_query( $sql ) )
    return 2;
  
  return 0;
}

Okay, let’s get one thing out of the way first:

“Holy mother of God what the fuck is that preg_match() thing!”

I’m glad you asked; it’s what’s known as a regular expression.  Essentially, that one line is checking whether we’ve gotten the date in an ‘expected’ format, that is: YYYY-MM-DD HH:MM:SS.  Regexps are probably the most evil (and, unfortunatley, the most useful) thing you’re likely to encounter in programming, so don’t worry if it just looks like gobbledegook at the moment.  Honestly, I just couldn’t think of a better way of double-checking against the date field.

With that out of the way, let’s go through this line-by-line.

The first thing we’re doing is examining the contents of $d['text'] to see whether or not we’ve actually got some data in there.  I’m making the assumption that we don’t want to submit a post that has no text in it (ie. we’ve hit the button accidentally) here, so if this is not the case (for whatever reason), feel free to remove these two lines.  Anyway, we’ve got two functions embedded in an if-statement here.  When you see multiple functions stacked on top of one another like this, it’s best to start on the inside first.  In this case, we’ve got a call to trim(); this function examines a string to look for whitespace characters (tabs, spaces, newlines and so forth) at the beginning and end of the string and cuts them off.  This should prevent us from submitting a bunch of return characters and no text, for example.  The string returned by trim() is then immediatley picked up by empty().  This function checks whether a variable is, well, empty.  ‘Empty’ in this sense is a bit of a gotcha; it picks up variables that are not set at all, but it also picks up variables that are set but have a numeric value of 0, or a string value of nothing.  Note that in PHP, there’s a difference between a variable that is not set (which we can test via isset()), and one which is set but is empty.  In this case, we’re testing for the latter.  If $d['text'] is found to be empty, we are going to send a return value of 1.  We’ll do something with these numbers in a moment, but right now all you need to know is that if a function encounters a return statement, it stops executing and returns control to wherever it was called (in this case the new post page).  If you think of a function like a corridor with a door at the end (the terminating curly brace), a return statement is another exit located before the end of the corridor.

Note that, because our if-statement only has one line of code ‘inside’ it, we can omit the curly braces.

The next if-statement is our friend the regexp.  Essentially, it’s checking the date time value passed to it in $d['time'].  If the date is not in the format YYYY-MM-DD HH:MM:SS (or is not there at all), it scraps the input entierley and replaces it with the current datetime.  The question to ask here is why are we doing it this way?  Why not have neato little date entry fields on our post page?  Well, mostly because I think they’re fluff; feel free to put them in if you’re feeling adventurous.  The other reason, of course, is that I don’t want to have to enter in a date when I make a new blog post; 99% of the time I just want the date to magically appear for me.  And because I tend to write really long posts, or start a post at one time and come back to it at a later date, I want this date value to get filled in for me when I submit the form, as opposed to when I load the post page.  Again, this is a design decision.

The next line down we encounter two more functions, this time being applied to the post subject.  strip_tags() will strip out all HTML tags, since we don’t really want these in our subject, and then htmlspecialchars() will do things like convert < and > to < and >.

All of this is basic input validation.  Could we do more?  Hell yes.  Are we going to?  Not right now.  Why?  Because I’m making an assumption here, and that is that you are the one who will be using your new post form, and similarly that you aren’t really interested in attempting to hack your own site via bad input.  If this was a public form (eg. a comments form), we’d probably put some more checking in, but right now it’s not, so this is a nice introduction.

Anyway, once we’re past all this stuff we get down to the ‘meat’ of the SQL query that will actually insert this data into our database.  It’s fairly simple.  Note that we haven’t specified which values are going into which fields in the table; when it sees this, MySQL just assumes that it is filling in table information in order.  This is why we have the blank ‘placeholder’ value for the postID; because that field is an auto-increment, MySQL will see the blank value and automatically insert a number (which, in this case, is highly likely to be 1).

Finally, we have one more if-statement checking against the mysql_query() call.  The exclamation point in front of the function is a negation operator (we pronounce it ‘not’, so if not mysql query, return 2).  Like most functions, mysql_query() returns true if it ‘works’ and false if it doesn’t.  However, we want to test for failure, not success, and so we use !.  This means, that if the function fails we fall into the if-condition, which in this case is to return another non-zero value.  If the query succeeds, however, the if-condition is not triggered, and we progress down the function to the final return statement.

Now it’s time to test it out; make two test posts, the first one with a blank date field, and the second one with a date in the past (eg. 1983-11-12 18:32:21).  If all is good, it should work… but the only way we could ever tell would be by manually checking the database.  Hrm, that’s not so good; we really should provide some feedback for ourselves on whether we’ve succeeded or not…


3.2 A Brief Introduction to Error Reporting

Christ; error reporting and function feedback.  You could write a whole tutorial on this subject alone.  So you’re trying to add something to the database and it fails; what do you do?  So you forgot to fill in a form field and the submission fails; what do you do?  So your query executed successfully and you want to tell your user; what do you do?  Welcome to the deeper waters.

For the purposes of this tutorial I’m going to be using some very basic error reporting and feedback ‘tricks’, just to give you the idea.  We’ve already started, too, by coding in the return codes in our doNewPost() function.  But how do we use them?

Time to open up newpost.php again.  In the PHP tags, but underneath the call to doNewPost(), add the following lines:

$msg = "";

if( isset( $r ) ){
  switch( $r ){
    case 0:
      $msg = "Post added successfully!";
      break;
    case 1:
      $msg = "<b>Error:</b> Post text field empty. Post not added.";
      break;
    case 2:
      $msg = "<b>Error:</b> Could not connect to the database. Post not added.";
      break;
    default:
      $msg = "Unknown error!";
  }
}

Now, down in the HTML section, somewhere appropriate (I put mine just underneath the title but above the form), add the following:

<? if( !empty( $msg ) )
  print "\n<div class=\"errmsg\">$msg</div>\n"; ?>

  
What are we doing here?  You’ll notice in the line in which we call doNewPost() we are actually assigning the return value to a variable ($r for ‘return’).  We are then taking this variable and first testing if it exists with isset(), then running it through a switch() statement.  Switches essentially take one variable and examine them for a number of expected values, known as cases.  In this case, we’re checking to see if the value of $r corresponds to one of the numbers we returned from doNewPost, and if it does we’re assigning an appropriate error message string to $msg.  Note the syntax of a switch, which isn’t quite like the syntax of any other control structure.  The break statements are there because switches have a weird fallthrough ladder effect.  Try taking some out, and making some posts; which error messages are you getting?  And how do they change depending on which breaks you remove?  Finally, at the end of the switch we have the default case, which will get printed out in the offchance that no other conditions are satisfied (ie. $r is equal to, say, 3).

Finally, we’ve added in another wee little if-statement interspersed down in our HTML that will print out our error message if it contains a value.  The \n character is a newline, and is basically there to make our sourcecode look pretty if it’s viewed.  Also note how, because we’ve enclosed our string in double-quotes, when we then try and use the same character in our HTML we need to escape them with a backslash character.  This tells PHP that the quote isn’t a ‘real’ quote, and that it’s to be printed to the screen rather than used as a string delimiter.

Okay, we’re doing pretty good so far; we’ve got ourselves a working post update form.  Hooray!  Next step, I suppose, is going to be writing a page that displays our posts.

Keep an eye out, kids, we’ll be right back.

Part 3 Files: functions.inc.php, newpost.php.

Comments

Add Comment
auto insert line breaks
use log.code
use smilies
Verification
  • v-s.net v0.6 and all content (unless noted) © Dee.
  • sk.log v0.6 spat this out in 1.53 seconds.
  • 78 / 181,759
artistic-twobyfour