Ingenio Home  | Blog Policies  | Help
Welcome to Community Sign in | Join | Help

Wow...

It's pretty hard to believe that I've been a expert on Keen/Ingenio for 6 1/2 years now. It's a bit easier to believe that I haven't in-fact posted on this blog in just over 2 years. To be honest, I kinda forgot I even had a blog on Keen but hopefully I'll start posting a bit more.

I like posting things that come up during phone conversations. I had a phone call earlier today from someone doing a class project: the task was to create a pizza ordering system. I've mentioned before that when you code you must always expect the unexpected...and today's advice furthers that topic.

My caller had almost a fully-functioning system. Obviously since this was just a class project it didn't have all the bells and whistles, but it met the requirements for the project. However, almost immediately I noticed a problem.

The orders were being stored in $_SESSION as the user chose each item to add to the cart. The first thing I added was a Small Onion Rings...so the $_SESSION variable looked something like this:

Array
(
[cart] => Array
(
[Onion Rings] => Array
(
[name] => Onion Rings
[size] => Small
[price] => 2.5
[quantity] => 1
[total] => 2.5
)
)
)
See the problem? When items were added to the cart, the name of the item was used as the key for the array. But what problems could that cause?

Expecting the unexpected doesn't just mean "Make sure the information they put in the form is valid"...it also means "Make sure my code produces the expected result no matter what information they put in the form."

So, given that my cart is as above, what would happen if I added 1 Large Onion Rings to my order? Short answer: the Large Onion Rings would replace the Small Onion Rings. Why?

Onion Rings is the name of the product...and that's being used as the key. Whether I choose large or small is neither here nor there...size is a parameter of the product in this case.

So, we redesign our array. Let's modify it so it uses Size as a key as well...and while we're at it, let's get rid of the duplicate information. ONE VERSION OF THE TRUTH! So, the code to add items to the $_SESSION variable looks something like this:

$_SESSION['cart'][$itemName][$itemSize] = array(
'price' => $itemPrice ,
'quantity' => $itemQuantity ,
'total' => $itemTotal
);
Now I can add items of various sizes and they'll all be stored neatly in my array.

Next Question: What happens if I add 1 Small Onion Rings, and then decide to add 2 Small Onion Rings? A better question would be: what will the user EXPECT to happen? In this case, there's three options:

1. Give the user an error message saying that there are already Small Onion Rings in the order and if they'd like to change the quantity they should first remove the Small Onion Rings.

2. Assume that the user made a mistake the first time and REPLACE the 1 Small Onion Rings with the 2 Small Onion Rings.

3. Assume that the user wants MORE Onion Rings so ADD the 2 Small Onion Rings so there's a total of 3.

At this point, you need to throw out what YOU want the system to do and focus on what your user will EXPECT to happen. Whatever the user EXPECTS is by definition the proper behavior. In this case, people expect option #3...that in the end they'd have 3 Onion Rings. Why? Because the button says "Add to Cart". (ADD)

So, I need to add some code like this:

function addItem( $itemName , $itemSize , $itemPrice , $itemQuantity ) {
if ( isset( $_SESSION['cart'][$itemName][$itemSize] ) ) {
$_SESSION['cart'][$itemName][$itemSize]['quantity'] += $itemQuantity;
$_SESSION['cart'][$itemName][$itemSize]['total'] += ( $itemQuantity * $itemPrice );
} else {
$_SESSION['cart'][$itemName][$itemSize] = array(
'price' => $itemPrice ,
'quantity' => $itemQuantity ,
'total' => ( $itemPrice * $itemQuantity )
);
}
}

Now I'm handling it correctly and have ensured that the behavior of my script matches what the user expects to happen. Obviously from here we make sure our "View Cart" screen has the option to remove items and change quantities so users can make changes as they see fit.

Expecting the unexpected might seem tedious...and sometimes it can be. But it's necessary. It might also seem impossible - especially with larger complex projects. You might ask, "How am I supposed to anticipate every possible thing my users might do?"

Well, sometimes you can't! Your job is to anticipate as much as you can...but that's where people like me come in! A fresh pair of eyes can make all the difference in the world. I might think of things that hadn't occurred to you before. So, if you find yourself wanting someone to check over your code and look for gaps, give me a call!

Use Functions

Creating functions for re-use will not only make your code shorter, but it makes your code much more legible.

Let's say we've got a string that we want to process to remove all non-numeric characters. We would start with something like this:

<?php
$InString = "The Quick Brown Fox Jumped Over all 1500 of the Lazy Dogs";
$OutString = "";
$OKChars = "1234567890";
for( $x = 0 ; $x += 1 ; $x < strlen( $InString ) ) {
    if( strstr( $OKChars , $InString[$x] ) ) $OutString .= $InString[$x];
}
echo $OutString; // Outputs "1500"
?>

This code is perfectly fine...but let's say we now need to process two different strings. There are many different ways we could accomplish this, but here is the best:

<?php
function stripToNumbers( $InString ) {
    $OutString = "";
    $OKChars = "1234567890";
    for( $x = 0 ; $x += 1 ; $x < strlen( $InString ) ) {
        if( strstr( $OKChars , $InString[$x] ) ) $OutString .= $InString[$x];
    }
    return $OutString;
}

$String1 = "The Quick Brown Fox Jumped Over all 1500 of the Lazy Dogs";
$String2 = "The Quick Brown Fox Jumped Over all 200 of the Lazy Dogs";
echo stripToNumbers( $String1 );
echo stripToNumbers( $String2 );
?>

Now that we have created the function, we can re-use it over and over without needing to re-write any code. But, let's take it to the extreme...let's say we've got like 100 variable that need processing, each of which need to be stripped down to a different set of characters. (I know, this is bizarre and silly, but it's a great example)...we can write a group of functions:

<?php
function stripProcess( $InString , $OKChars ) {
    $OutString = "";
    for( $x = 0 ; $x += 1 ; $x < strlen( $InString ) ) {
        if( strstr( $OKChars , $InString[$x] ) ) $OutString .= $InString[$x];
    }
    return $OutString;
}
function stripToNumbers( $InString ) {
    return stripProcess( $InString , "1234567890" );
}
function stripToLowercase( $InString ) {
    $OKChars = "abcdefghijklmnopqrstuvwxyz";
    return stripProcess( $InString , $OKChars );
}
function stripToUppercase( $InString ) {
    $OKChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    return stripProcess( $InString , $OKChars );
}
?>

Now we have a bunch of specialized functions which work great for the task at hand, but it is kind of messy to have so many functions rolling around in our code...it would be much better as a class...something I cover in an upcoming post.

Comment Your Code

For many programmers, this one is obvious. Commenting code is important for a number of reasons:
  1. It will remind you what all the code does - if you don't look at it every day, coming back to uncommented code is often a hassle.
  2. It will be a nice gift to your successor - when you develop code for a client, chances are you won't be the only one working on it during it's lifetime.
  3. It makes calls with me quicker! If I can skim over code by looking at valid comments, I can diagnose problems in a fraction of the time.
Here's an example:

<?php
$n = "1234.5678";
$n += pow( 10 , -3 );
$n = round( $n * pow( 10 , 2 ) ) / pow( 10 , 2 );
$n += pow( 10 , -3 );
$n = substr( $n , 0 , strpos( $n , '.' ) + 3 );
echo $n;
?>

So...what does that code do? Any idea at all? Wouldn't that be nicer if it was commented?

<?php
$n = "1234.5678";
// Instead of using PHP's built-in rounding function, manually round $n to 2 decimal places.
$n += pow( 10 , -3 );
$n = round( $n * pow( 10 , 2 ) ) / pow( 10 , 2 );
$n += pow( 10 , -3 );
$n = substr( $n , 0 , strpos( $n , '.' ) + 3 );
echo $n;
?>

This example gives rise to two other topics which I will cover later: Using Functions and Using Good Variable Names. When it's all said and done, here's the finished code:

<?php
function roundToTwo( $Number ) {
    // Instead of using PHP's built-in rounding function only, round $Number to two decimal places.
    $Number += pow( 10 , -3 );
    $Number = round( $Number * pow( 10 , 2 ) ) / pow( 10 , 2 );
    $Number += pow( 10 , -3 );
    $Number = substr( $Number , 0 , strpos( $Number , '.' ) + 3 );
    return $n;
}
echo roundToTwo( 1234.5678 );
?>

Expect the Unexpected

This one isn't easy...it takes a lot of practice to master, but the basic principle is simple:

When you write your code, write it to handle what it isn't designed to handle.

Here's an example:

Let's say you've got a form where you ask for a zipcode because you want to know if the user lives in Florida. Your PHP code might look something like this:

<?php
$ZipCode = $_REQUEST['zipCode'];
if ( $ZipCode >= 32000 || $ZipCode <= 34000 ) {
    echo "You live in Florida!";
} else {
    echo "You live somewhere else!";
}
?>

But, we aren't doing any error checking. We need to make sure that the field has something in it, that it's a number, and that it's 5-9 digits in length. So, we need to adjust our code:

<?php
if ( ! isset( $_REQUEST['zipCode'] ) ) {
    echo "You didn't enter a Zip Code!";
} else {
    $ZipCode = $_REQUEST['zipCode'];
    if ( is_numeric( $ZipCode ) && strlen( $ZipCode ) >= 5 && strlen( $ZipCode ) <= 9 ) {
       if ( $ZipCode >= 32000 && $ZipCode <= 34000 ) {
          echo "You live in Florida!";
       } else {
          echo "You live somewhere else!";
       }
    } else {
       echo "You didn't enter a valid zipcode!";
    }
}
?>

Always try and anticipate what your users might do and write your script accordingly - otherwise not only do you risk an undesired effect, you might send your users nasty errors or even open up your website to hacking.

If you need your scripts proof-read, let me know!