Problem Diagnosis: You Will Never Think As Stupid As Some Users

September 29th, 2010

One of my clients recently came back to me with a problem. I had built an invoice system that let their users generate an invoice and go to PayPal to pay it. However, it was discovered that some users had invoice numbers that they had made payments for, but that had no corresponding entries in the database.

So, I immediately dug into the problem with my client, and we worked together to idiot-proof the system. Over the course of two days, we changed the invoice system so that you could not go back and modify an invoice after you had been sent to PayPal. I added an index to track the invoice status, to facilitate this, and we fixed a few holes in the process if a user just closed their browser at any point. Overall, I think we had fixed almost 10 different small and large problems with the process that we thought might have led to this issue with disappearing invoices.

Satisfied with the work we did, my client went happily on his way with this upgraded system. It took about two days for them to have a problem with invoices again, and for me to be contacted about this. So, we reviewed the work done, and concluded that there was nothing we could think of that we had not already fixed. I added a simple piece of code to log the contents of every single invoice every single time it was generated into the database, and we contacted the web host to get MySQL query logging turned on, in the hopes that we would be able to figure out something by doing this. Frustrated but hopeful, we left this in place, and went on our way.

The next day, we had another incident with a missing invoice. I started digging into the background of the invoice number, and pulled the records on it from the log. It was created, but as far as the system was concerned, it was empty. Digging a little more, we verified that was indeed the fact. The MySQL queries for that invoice number turned up several inserts, and sure enough, there were no inserts for actual invoice items. We pondered back and forth for a minute, but were stumped as to why there was a paid amount for that invoice number, but no value in our systems. Finally, my client figured it out. He went through the invoice process, and went on to PayPal with a zero dollar invoice.

Turns out, you can enter your own amount for any invoice number that goes to PayPal with a submitted value of $0.

The clients customers had gone through the process, and when presented with a screen of items to select from, chose nothing. These users then went on their way, clicking through the invoice verification screen, which showed them that they had selected no items, and a Grand Total amount of $0. Happy with this invoice, they continued on to PayPal, where they were presented with a box to enter their own cost, which they entered with no question as to why they had to. They all managed to enter the correct costs for what they wanted, and paid. Come time to verify these payment emails that the client was receiving from PayPal, the client went looking for a matching number in this system, only to be baffled that there was none.

I quickly added a check to disable the payment button if their invoice was empty, so that the customers have to select something to pay for before they can go to pay. And now, the client is happy.

You will never be able to think as stupidly as some users.

Version Control – A Visual Guide

June 14th, 2010

If you are new to version control, this article is a fantastic outline:
http://betterexplained.com/articles/a-visual-guide-to-version-control/

Lessons Learned From framework – A Dissection

June 4th, 2010

For the last two and a half years or so, I have spent time off and on working on a framework for my websites. There were a lot of reasons that I was doing this, but the main two were I didn’t like the frameworks that were out at the time, and I wanted to learn some cool new code.

So, here we are, a long time later, and I’ve decided to start from near scratch on a new version. Before I embark on that challenge though, I’m dissecting what I have learned through the process, so hopefully someone can learn at least one thing.

1. URL Rewriting
How you write URL’s is the subject of so many articles on the internet about SEO and other things that I won’t really get into it. Needless to say, it’s important to have useful and clear URL’s.

Processing those URL’s
This is one of the cool tricks I started working with. mod_rewrite rules are really cryptic (regex is cryptic), so I made it simple. Assuming you are using Apache, use this rule in your .htaccess, then never have to screw around with it again.

[code]RewriteEngine On
RewriteRule !\.(js|ico|gif|jpg|png|css|pdf|swf)$ @yourawesomescript.php[/code]

This points all visited URL’s to your main script. Once there, dissect the URL with the following code, and process however you want.
[code lang=”php”]
$route = $_SERVER[‘REQUEST_URI’];
$base_script = CONFIG_BASE_SCRIPT_NAME; // This should be your base script URL aka /framework/@yourawesomescript.php

for($x = 0, $c = strlen($base_script); $x < $c; $x++){
if(strtolower(substr($base_script, 0, 1)) == strtolower(substr($route, 0, 1))){
$route = substr($route, 1);
$base_script = substr($base_script, 1);
}else{
break;
}
}

$route = explode(‘/’, $route);
$appControl = (isset($route[0]) && !empty($route[0])) ? array_shift($route) : ‘index’;
$appAction = (isset($route[0]) && !empty($route[0])) ? array_shift($route) : ”;
$appVars = (!empty($route)) ? $route : array();
[/code]

2. Zend Framework Is Awesome!
Zend has started a project that saves so much time. The ZF is not just a framework though. Because the components are loosely coupled, you can pick and choose what you want to use as part of your project, and toss the rest. This is a huge time saver simply because it is code that you do not have to maintain.

Here’s a sample database query. Nice and clean, and Zend_DB handles both building the query (including escaping the data) and running it.
[code language=”php”]
$userconf = $db->fetchAll(
$db->select()
->from($config->dbschema->table_users, array(‘uid’))
->where(’email = ?’, $in[’email’])
->where(‘actkey = ?’, $in[‘cid’])
);
[/code]

3. minify Is Also Awesome
If you are not using minify, you should be.

It combines multiple CSS or Javascript files, removes unnecessary whitespace and comments, and serves them with gzip encoding and optimal client-side cache headers.

The best reason for using minify is that you can take the 5 or 6 Javascript files you might be serving, and group them into one nice little package. You will never have to upload pre-compressed scripts to your server, and you can still serve them all compressed (gzip and packed) and optimized with one URL (1 HTTP request instead of several):

[code]<script type="text/javascript" src="min/g=js&1"></script>[/code]

And, to save you more time, here is a sample groupsConfig.php file (reference file system layout at the bottom of this article):
[code language=”php”]
<?php

$mingc_base_url = ‘/framework’;
$mingc_base_layout = $mingc_base_url . ‘layout/’;

return array(
‘js’ => array(
$mingc_base_layout . ‘js/jquery.lightbox.js’,
$mingc_base_layout . ‘js/DD_roundies.js’,
$mingc_base_layout . ‘js/base.js’,
),
‘css’ => array(
$mingc_base_layout . ‘css/main.css’,
$mingc_base_layout . ‘css/jquery.lightbox.css’,
),
);
[/code]

4. Output Buffering For Amazing Awesomeness
Output buffering will save you a lot of hassle. You can pass headers at any point in your script simply by turning this on. This means that you can do things like redirecting users to another page without having to show them any content (which saves you a little hassle as a dev, and them a little time). And speaking of redirecting, that leads me into:

5. Shortcircuit The Problem (redirect_hard)
Not every page you load needs to show content. Some just need to process what you post from a form, or check if someone is logged in, or just bounce you somewhere else in a site. For these times, I have a function that I use that sends a Location: header directly to the browser, telling it to go away to somewhere else.

[code language=”php”]
function redirect_hard($url = null, $internal = 1){
if($internal){
// CONFIG_HREF_SITE is defined as http://www.site.com/framework/
header(‘Location: ‘ . CONFIG_HREF_SITE . $url);
}else{
header(‘Location: ‘ . $url);
}
}
[/code]

6. Exceptions Are Better Errors
I have seem (and written) a lot of code that handles errors in a strange way.
[code language=”php”]
$err = 0;

if($x != 4){$err = 1;}
if($y != 2){$err = 1;}

if($err){echo ‘Error’; break;}else{echo ‘Happy ‘ . ($x + $y);}
[/code]

There is a better, and way more awesome way to do this. Exceptions are much easier and cleaner to work with, because you can just wrap a big piece of dependent code with one big exception handler, and go to town on whatever you want to do. Here is the same functionality as above, with exceptions:

[code language=”php”]
try{
if($x != 4){throw new Exception(‘x is not set!’);}
if($y != 2){throw new Exception(‘y is not set!’);}
// If either condition is not met, and an exception is thrown
// then the code below here is not executed.
echo ‘Happy ‘ . ($x + $y);
}catch(Exception $e){
echo ‘Error: ‘, $e->getMessage(), "\n";
}
[/code]

Now, of course, it looks a little more complicated in this small example, but imagine if you had a half-dozen events that you were executing that could throw an error. With exceptions, your execution of that particular try{}catch{} block would stop as soon as one of these events occurred, and you would know precisely where. Learn and love exceptions and exception handling, because it will save you a lot of mess.

7. Components Are So Generic
You have a form that you use for both adding and editing a food, for example. Well, you could build the form once, then copy-paste it into the two places where you will use it. Of course, then you need to make sure that when you make changes, they stay in sync. Easy enough if you only have a form in two places. Imagine though, that you have a form that you use in a half-dozen places.

Make it into a component, because less code is always easier to maintain. Below is a wrapper component that I use around every form I create. All I have to do is make sure that I set the form URL and content, then include this piece of code. It doesn’t seem like much, but now, I have removed 6 lines of HTML from every form I create, and I have a centralized control over the form.

[code language=”php”]
$page_content .= ‘
<div class="center_div">
<form action="’ . $form[‘url’] . ‘" method="post">
<div>
‘ . $form[‘content’] . ‘
</div>
</form>
</div>
‘;
[/code]

If you have a piece of complex code in more than two places, do this instead.

8. Have A Config File
This is a simple lesson. Never put any variable that relates to any configuration anywhere but in this file. Database settings, the constant for PI, the name of your website, they all go here. Everything is so much easier when the only change between your production and development environment is your config file.

9. Hold The Mail
I have seen a lot of approaches to mail. Some say fork it, some say send it and make the user wait. I have a more interesting solution. I built a mailqueue script. What happens when I want to send email is it gets placed in a database. There is a script attached to a cron job (a task on a timer), and it is called every 5 minutes. The only thing this script does is check the database and send the email sitting in the queue. The benefits of this are:

  1. I don’t have to make a user wait (hitting a database will likely always be faster than sending an email on a high load server)
  2. Email sent can now be logged
  3. All sending on the backend can be taken care of in a speedier language (perl or c)
  4. I have precise control over how much mail is processed in a day, so I can throttle it as needed

Classes Are Classier
Again, another topic that has been written about extensively. Here is the shortlist of why I find classes awesome.

  • Everything is accessible through one discreet interface ie $db->connect, $db->select()
  • Once it’s written, it’s easy to maintain and expand, and still all through that one interface
  • You can move it so much easier
  • No mucking around with variables that you need to keep track of ($age, $height, $weight can all be inside $person, so you can $person->get(‘age’))

I hope that all this information is useful to someone else, and if you have any comments, feel free to let them be heard.

Reference Filesystem Layout

http://site.com/framework/
/framework/
/framework/.htaccess
/framework/@yourawesomescript.php <--- Base script /framework/min/ <--- minify directory /framework/layout/ /framework/layout/js/ /framework/layout/css/

jQuery Modal AJAX Form

May 19th, 2010

I wanted to load forms through a jQuery dialog, but could not locate a suitable simple solution. After a bit of code hacking, I have created a decent solution. Hope you find it useful.

This solution requres jQuery (I built with v1.4.2), jQueryUI (v1.8.0+).
[code lang=”js”]<script type="text/javascript">
function formdialog(url){
$.getJSON(url+"/form", function(data){
$(‘<div><form id="formdialog">’+data["form"]+'</form></div>’).dialog({
title: data["title"],
modal: true,
buttons: {
"Save": function(){
$.post(url+"/process", $("#formdialog").serialize());
$(this).dialog("close");
// You may have problems with this destroying the form
// before it has saved.
$(this).remove();
},
"Cancel": function(){$(this).dialog("close");}
}
});
});
}
</script>[/code]

jQuery and CKEditor Popout Editor

March 12th, 2010

I had to integrate CKEditor into a site that I was building for a client. One of the requirements of this integration was to be able to edit the content as it was seen on the page. So, for example, if you are on the About Us page, you could click on an edit link on the bottom of the content column, and be able to edit it in place. Other than that requrement, the implementation was up to me to figure out.

After several hours of hacking out code, a lot of Google research and documentation reading, and some Stack Overflow questions, I was able to piece together a solution.

This solution requres jQuery (I built with v1.4.2), jQueryUI (v1.8.0+), and CKEditor (v3.2+).

[code lang=”js”]<script type="text/javascript">
var CONFIG_HREF_SITE = ‘http://websitebaseurl.com/’;

function redirect(url, outsite){
if(outsite){
location.href = url;
}else{
location.href = CONFIG_HREF_SITE + url;
}
}

function editdialog(editid){
var editwin = ‘<div><form action="formprocess/’+editid+’" method="post" class="inform"><div id="editorheader"><label for="coltitle">Column Title: </label><input type="text" name="coltitle" id="coltitle"></div><br><div id="editorcontent"><textarea id="ckeditcolcontent"></textarea></div><input type="hidden" value="edit"></form></div>’;
var $dialog = $(editwin).dialog({
autoOpen: false,
title: "Editor",
height: 520,
width: 640,
closeOnEscape: false,
open: function(event, ui){
$(this).parent().children().children(".ui-dialog-titlebar-close").hide();
},
buttons: {
"Save and Close": function(){
var editor = $("#ckeditcolcontent").ckeditorGet();
var coltitle = $("#coltitle").val();
var colcontent = $("#ckeditcolcontent").val();
$.post("formprocess/"+editid, {coltitle: coltitle, colcontent: colcontent}, function(data){
redirect(location.href, 1);
});
},
"Cancel": function(){redirect(location.href, 1);}
}
});

$.getJSON("ajax/" + editid, function(data){
$("#coltitle").attr("value", data.header);
$("#ckeditcolcontent").val(data.content).ckeditor(config);
$("<div></div>").addClass("ui-widget-overlay").appendTo(document.body).css({width:$(document).width(),height:$(document).height()});
$dialog.dialog("open");
});
}

var config = new Array();
config.height = "280px";
config.resize_enabled = false;
config.tabSpaces = 4;
config.toolbarCanCollapse = false;
config.toolbar_Full = [["Cut","Copy","Paste","-","Undo","Redo","-","Bold","Italic","Underline","Format", "-", "NumberedList","BulletedList","-","Link","Unlink","-","Image","Table"]];

$(function(){
$("a.admineditlink").click(function(){
config.width = "600px";
var editid = $(this).attr("href");
editdialog(editid);
return false;
});
});
</script>[/code]

WordPress - Entries (RSS) and Comments (RSS) - © 2011 Ben Dauphinee