Note: this is part of a series.
The first part is here: Building a Big Project, Part 1
The second part is here: Building a Big Project, Part 2
The third part is here: Building a Big Project, Part 3
The fourth part is here: Building a Big Project, Part 4
The fifth part is here: Building a Big Project, Part 5
In the last tutorial, I cheated. I showed the finished init.php without showing you the flawed version I had originally.
finished init.php:
broken init.php:Code:<?php session_start(); //define root URLs $root = "http://localhost/MyFiction/"; $sroot = "http://localhost/MyFiction/"; //open database. //This is going to store everything, so we might as well have our connection now! if (!($sourcedb = mysql_connect('localhost:3306','root',''))) { die('Database error: could not connect!'); } if (!mysql_select_db('MYFICTION',$sourcedb)) { die('Cannot use database!'); } //set flags for ValString() $vsNone=0; $vsStrict=1; $vsHTML=2; $vsSQL=4; $vsEmail=8; //Validation function //$vsStrict forces a test failure to return "", otherwise the functions will attempt to make it valid. // function ValString($instr, $flags, $len=-1) { //reintroduce flags to function scope $vsNone=0; $vsStrict=1; $vsHTML=2; $vsSQL=4; $vsEmail=8; //test length if (strlen($instr)>$len && $len!=-1) { if ($flags & $vsStrict) $instr=""; else $instr=substr($instr,1,$len); } //test valid single email address if ($flags & $vsEmail) { if(!preg_match('/^[a-zA-Z0-9._-]+@[a-zA-Z0-9-]+\.[a-zA-Z.]{2,5}$/', $instr) && ($flags & $vsStrict)) $instr=""; } //test for invalid SQL characters if ($flags & $vsSQL) { if(($flags & $vsStrict) && (pos($instr,"'") || pos($instr,"`") || pos($instr,'"'))) $instr=""; else { $instr=str_replace("'","''",$instr); $instr=str_replace("`","``",$instr); $instr=str_replace('"','""',$instr); } } return $instr; } ?>
It took me a while to figure out that global variables don't get brought into function scope. It was a little bit irritating, to say the least. What was even more annoying were the gyrations I went through to figure out what was happening. I ended up polluting join.php with debugging messages as I worked through the problem. There has to be a better way to deal with this.Code:<?php session_start(); //define root URLs $root = "http://localhost/MyFiction/"; $sroot = "http://localhost/MyFiction/"; //open database. //This is going to store everything, so we might as well have our connection now! if (!($sourcedb = mysql_connect('localhost:3306','root',''))) { die('Database error: could not connect!'); } if (!mysql_select_db('MYFICTION',$sourcedb)) { die('Cannot use database!'); } //set flags for ValString() $vsNone=0; $vsStrict=1; $vsHTML=2; $vsSQL=4; $vsEmail=8; //Validation function //$vsStrict forces a test failure to return "", otherwise the functions will attempt to make it valid. // function ValString($instr, $flags, $len=-1) { //test length if (strlen($instr)>$len && $len!=-1) { if ($flags & $vsStrict) $instr=""; else $instr=substr($instr,1,$len); } //test valid single email address if ($flags & $vsEmail) { if(!preg_match('/^[a-zA-Z0-9._-]+@[a-zA-Z0-9-]+\.[a-zA-Z.]{2,5}$/', $instr) && ($flags & $vsStrict)) $instr=""; } //test for invalid SQL characters if ($flags & $vsSQL) { if(($flags & $vsStrict) && (pos($instr,"'") || pos($instr,"`") || pos($instr,'"'))) $instr=""; else { $instr=str_replace("'","''",$instr); $instr=str_replace("`","``",$instr); $instr=str_replace('"','""',$instr); } } return $instr; } ?>
Welcome to testing! Maybe it's because I just finished reading The Pragmatic Programmer, or because I just had to fix a bug that I thought I had fixed a couple months ago at work, or because I know where this function is going, but testing is really on my mind right now.
This type of function just SCREAMS for unit testing. I'm going to create a folder inside my main folder called Tests. That is where I'll create mini-program files for doing unit testing. I don't anticipate having a lot of them, but it should be enough to get me by. Our basic test method looks like this:
tests\valstring.php:
We'll beef this basic file up with a little data, and have fun!Code:<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <meta content="text/html; charset=ISO-8859-1" http-equiv="content-type"> <title>Test ValString Function</title> </head> <body> <?php include("../init.php"); include("../close.php"); ?> </body> </html>
OK, that wasn't very fun. But it should give you an idea of where this is going. We'll just add a little logic to finish off our simple table and...Code:<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <meta content="text/html; charset=ISO-8859-1" http-equiv="content-type"> <title>Test ValString Function</title> </head> <body> <?php include("../init.php"); $rawtext = array("boring","<h>HTML</h>","don't use in SQL","email@nowhere.com","<not>e'mail<safe>@garbage.junk") %> <table> <thead> <tr> <td>raw text</td><td>length</td><td>flags</td><td>result</td> </tr> </thead> <tbody> </tbody> </table> <% include("../close.php"); ?> </body> </html>
If you're one of those people who just reads these things and goes "ooh, ahh!", stop! Run the above code with our init.php. You'll need close.php again:Code:<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <meta content="text/html; charset=ISO-8859-1" http-equiv="content-type"> <title>Test ValString Function</title> <link rel="stylesheet" type="text/css" href="Myfiction.css" /> </head> <body> <?php include("../init.php"); $rawtext = array("boring","<h>HTML</h>","don't use in SQL","email@nowhere.com","<not>e'mail<safe>@garbage.junk") ?> <table> <thead> <tr> <td>raw text</td><td>length</td><td>flags</td><td>result</td> </tr> </thead> <tbody> <?php foreach($rawtext as $string) { for($flags=0;$flags<16;$flags++){ for($length=-1;$length<40;$length+=5){ $output="<tr><td>" . htmlspecialchars($string) . "</td><td>" . $length . "</td><td>"; if ($flags==0){ $output .= " vsNone "; } if ($flags&$vsStrict){ $output .= " vsStrict "; } if ($flags&$vsHTML){ $output .= " vsHTML "; } if ($flags&$vsSQL){ $output .= " vsSQL "; } if ($flags&$vsEmail){ $output .= " vsEmail "; } $output .= "</td><td>" . htmlspecialchars(ValString($string,$flags,$length)) . "</td>"; echo "$output"; } } } ?> </tbody> </table> <?php include("../close.php"); ?> </body> </html>
What we get is a TON of the following:Code:<?php //close database if still open if ($sourcedb) { mysql_close($sourcedb); } ?>
!Code:Warning: Wrong parameter count for pos() in C:\wamp\www\MyFiction\init.php on line 53 Warning: Wrong parameter count for pos() in C:\wamp\www\MyFiction\init.php on line 53 Warning: Wrong parameter count for pos() in C:\wamp\www\MyFiction\init.php on line 53 Warning: Wrong parameter count for pos() in C:\wamp\www\MyFiction\init.php on line 53 Warning: Wrong parameter count for pos() in C:\wamp\www\MyFiction\init.php on line 53 Warning: Wrong parameter count for pos() in C:\wamp\www\MyFiction\init.php on line 53 Warning: Wrong parameter count for pos() in C:\wamp\www\MyFiction\init.php on line 53 Warning: Wrong parameter count for pos() in C:\wamp\www\MyFiction\init.php on line 53 Warning: Wrong parameter count for pos() in C:\wamp\www\MyFiction\init.php on line 53 Warning: Wrong parameter count for pos() in C:\wamp\www\MyFiction\init.php on line 53 Warning: Wrong parameter count for pos() in C:\wamp\www\MyFiction\init.php on line 53
It's the darnedest thing! I never saw that before! It seems I was using the pos() function instead of the strpos() function! How about that? Making a quick substitution and rerunning it now gives me:
as the first few lines. Notice that "orin"? That's not right! It seems that substr() in PHP is 0-based instead of 1-based (the first language I've seen that happen in)!Code:raw text length flags result boring -1 vsNone boring boring 4 vsNone orin boring 9 vsNone boring boring 14 vsNone boring boring 19 vsNone boring
Finally, if you scroll down on the results a ways, you'll see a seciont like this:
If you'll recall, we didn't do anything to deal with HTML handling. We can add that now. Our final init.php ends up being:Code:<h>HTML</h> -1 vsHTML <h>HTML</h> <h>HTML</h> 4 vsHTML <h>H <h>HTML</h> 9 vsHTML <h>HTML</ <h>HTML</h> 14 vsHTML <h>HTML</h> <h>HTML</h> 19 vsHTML <h>HTML</h> <h>HTML</h> 24 vsHTML <h>HTML</h>
This was a pretty simple test, with visual inspection instead of automatic inspection. On the good side, we were able to catch two bugs and on piece of unimplemented functionality! If we're going to use something like ValString() as a core piece of our product moving forward, it is very important to ensure it is working correctly.Code:<?php session_start(); //define root URLs $root = "http://localhost/MyFiction/"; $sroot = "http://localhost/MyFiction/"; //open database. //This is going to store everything, so we might as well have our connection now! if (!($sourcedb = mysql_connect('localhost:3306','root',''))) { die('Database error: could not connect!'); } if (!mysql_select_db('MYFICTION',$sourcedb)) { die('Cannot use database!'); } //set flags for ValString() $vsNone=0; $vsStrict=1; $vsHTML=2; $vsSQL=4; $vsEmail=8; //Validation function //$vsStrict forces a test failure to return "", otherwise the functions will attempt to make it valid. // function ValString($instr, $flags, $len=-1) { //reintroduce flags to function scope $vsNone=0; $vsStrict=1; $vsHTML=2; $vsSQL=4; $vsEmail=8; //test length if (strlen($instr)>$len && $len!=-1) { if ($flags & $vsStrict) $instr=""; else $instr=substr($instr,0,$len); } //test valid single email address if ($flags & $vsEmail) { if(!preg_match('/^[a-zA-Z0-9._-]+@[a-zA-Z0-9-]+\.[a-zA-Z.]{2,5}$/', $instr) && ($flags & $vsStrict)) $instr=""; } //test for invalid SQL characters if ($flags & $vsSQL) { if(($flags & $vsStrict) && (strpos ($instr,"'") || strpos ($instr,"`") || strpos ($instr,'"'))) $instr=""; else { $instr=str_replace("'","''",$instr); $instr=str_replace("`","``",$instr); $instr=str_replace('"','""',$instr); } } if ($flags & $vsHTML) { if(($flags & $vsStrict) && $instr !=htmlspecialchars($instr)) $instr=""; else { $instr=htmlspecialchars($instr); } } return $instr; } ?>
In addition to unit testing, you should also perform functional testing. I was able to find one tool that looks very promising: a FireFox pluging called Molybdenum. I haven't had a chance to try it out yet, but it should give us the ability to create rigorous tests that will help validate functionality works, and continues to work.
Last edited by WingedPanther; 02-23-2011 at 05:24 PM.
Thanks for breaking it down in detail.
You're the man... +rep
Awesome job!!
Nice read! You can use the global keyword to bring global functions into scope.
+rep!Code:function ValString($instr, $flags, $len=-1)
{
//reintroduce flags to function scope
global $vsNone, $vsStrict, $vsHTML, $vsSQL, $vsEmail;
Thanks for the info, Jordan! That's exactly what I needed. I should probably rework things so that there's a single array of flags. That way I can update it in the global and have it work correctly in ValString without changes.
The idea of polluting the global namespace makes me want to slit my wrists.![]()
That's one of the things that irritates me about Delphi, actually. Technically, every form is its own class, but you feel like you're dealing with a global space. I suppose I could create a validation class, and store the flags and functions in it. To be honest, I'm feeling too lazy to mess with learning PHP OOP right now. This project feels like procedural will do the job just fine.
There are currently 1 users browsing this thread. (0 members and 1 guests)
Bookmarks