UPDATE 12/2/2005: It would help if I actually included the css file, eh? Code examples now contain outlines and scrolling.
11.19.2005
Still fleshing out the syntax highlighting
11.18.2005
Configuration variables via Singleton object in PHP4
/** * Web Config object using Singleton pattern * * @author Rich Zygler, Jr. * @datecreated 03/08/2005 * @access public * @usage $config =& Config::getInstance(); * @usage $config->setConfig('admin_email', 'test@test.com'); * @usage $email = $config->getConfig('admin_email'); * @TODO */ class Config { var $configVars = array(); /* * Create a new config object */ function &getInstance() { static $instance; if (!$instance) { $instance = array(new Config); } return $instance[0]; } /* * Get config data * * @param $key string - array key of variable whose data we are retrieving * @return $this->configVars string */ function getConfig($key) { return $this->configVars[$key]; } /* * Set config data * * @param $key string -variable to retrieve data stored * @param $value string -variable that contains data to store * @return true * Can use arrays as well as in $config->setConfig('email_list', array(//... )); * retrieve array by foreach($config->getConfig('email_list') as $email) { //... } * */ function setConfig($key, $val) { $this->configVars[$key] = $val; return true; } }
If you think the getInstance method looks a little borked, you're right. What we're doing there is attempting to return the first and only the first implementation of this object. So everytime you make a call to this config object, you should be getting the same one. That's why we return the [0]th item in the array. This is a little easier in PHP5 but for PHP4, this is what we're stuck with.
And here's how to use this bad boy:
/** * config include * * @author Rich Zygler, Jr. * @datecreated 03/08/2005 * @TODO */ // Bring in the Config class file require_once ('Config.php'); // Instantiate our config object // Remember in PHP4, we use the =& // to pass by reference $config =& Config::getInstance(); // Set up some config vars // will change per application // The usage here is: setConfig(varName, varValue) $config->setConfig('appVersion', '1.o'); $config->setConfig('appName','Tester'); $config->setConfig('debug', true); // Maybe set the DB connection vars $config->setConfig('dbconn', array( 'username' => 'bunsen', 'password' => '!honeydew%', 'host' => 'localhost', 'db' => 'test', 'type' => 'mysql' ) ); // Now to get those values back from the $config object // first let's initialize our vars $appVersion = ''; $appName = ''; $debug = false; $dbConnArray = array(); // used for getting back db values $dbUser = ''; $dbPass = ''; $dbHost = ''; $dbType = ''; $dbName = ''; // Now let's get the values from the config object and populate our variables // of course, for a fully functioning app, you may want to check that these values are set $appVersion = $config->getConfig('appVersion'); $appName = $config->getConfig('appName'); $debug = $config->getConfig('debug'); // Here, we slurp the whole array from the config into a new array $dbConnArray = $config->getConfig('dbconn'); $dbUser = $dbConnArray['username']; $dbPass = $dbConnArray['password']; $dbHost = $dbConnArray['host']; $dbType = $dbConnArray['type']; $dbName = $dbConnArray['db']; // Now let's echo everything out to make sure the values are // coming back correctly echo "The version is: $appVersion"; echo "The name is: $appName"; echo "The debugger is on: $debug (1 = true) "; echo "The db username is: $dbUser "; echo "The db password is: $dbPass ";
What's great about this method of doing things is that you virtually eliminate the need for global variables in functions and objects. This is a very good thing. If you need some configuration variables available to a function or object, you can pass it this config object like this:
// call the function this way doSomethingWithConfig($config); // define the function this way // Remember to accept the object by reference (add the &) // otherwise a copy of the config data is created -- not good function doSomethingWithConfig(&$config) { // get app version $appVersion = ''; $appVersion = $config->getConfig('appVersion'); return $appVersion; }
If you like this way of doing things, wait until you try to recreate this in PHP5, it's even easier.
11.11.2005
Form data validation in PHP and JavaScript
There are 3 different types of data validation checks one can do:
- Syntactic validation
- Semantic validation
- Domain or model validation
Syntactic validation is when you need to make sure that the syntax of incoming data is correct. For instance, if it's a "first name" field, you wouldn't expect numbers, ampersands or other strange characters in there. You'll have to strip out the garbage before you can put that name into the database, so you'd better make the user fix their data before you have to. Another common one I find is when trying to get telephone numbers, social security numbers, etc. You don't want any chars in there.
Syntactic validation also takes care of checking minimum and maximum lengths for fields. If a new user must have a username that's 5 or more characters, you would check that thru syntactic validation. Same thing for maxlengths. Syntactic validation is what's going on too with checking for required fields (ie, is the username field blank).
Another common use of syntactic validation on the web is for checking valid email addresses and urls. You need to make sure those email addresses folks are giving you are at least of the proper form so you can email them good marketing stuff later. You could also check to make sure that email address actually exists (this would fall under domain validation though).
Syntactic validation is the simplest form of validation. The beauty part of it is that you can do most syntactic checks in both JavaScript on the client-side and then again in PHP on the server side. This aspect of "double-checking" stumped my associates too until I mocked up a quick form on my own machine that posted junk values to their test app, causing all kinds of mysql errors. I've seen programmers that don't do validation on both the client-side and server-side, but those programmers are no longer working steadily.
Another form of data validation is Semantic validation. In semantic validation checks, you are checking to make sure that one piece of information makes sense in regards to other incoming information.
For example, when registering a new user, most web apps will place the password box twice to make sure the user enters their desired password correctly. Checking these two passwords to make sure they are the same would be an example of semantic validation. Another example of semantic validation is checking to make sure that a required field based off another field is actually filled in. So, if the user specifies the country as US, then they also have to specify a state in the US... otherwise, they don't have to specify a state.
Semantic validation rules can get very complex, very quickly. While it's easy to build up a library of reusable code of syntax validation rules, semantic validation rules often require some custom work on every form. Again, semantic validation rules are most effective when used on both the client-side and server-side.
The third kind of form data validation is Domain or model validation. Domain validation requires checking the incoming info against another source of acceptable values. This could be existing database records, a config file, or simply PHP code. This kind of validation is checking the "domain" or "model" to make sure this incoming info makes sense.
A great example of domain validation is when registering as a new user on a site. After passing all the syntactic and semantic checks for registering a new username and password on the site, the app has to check to make sure the username you are requesting isn't already taken. So, it queries the users table in the db to see if your requested username is there. If it is, then the app needs to alert you to what went wrong and give you another chance to suggest a username. So, our web app is checking your requested username against the domain of existing usernames in our system.
Other examples of domain validation include checking to make sure an article_id in a CMS exists before we edit/delete it. Domain validation is also used in many permission schemes in web apps (ie, you can perform this action if your user level is > 5 or something).
Domain validation will almost always need to be done on the server-side as that's where the domain or model usually resides. So, you can't do any domain validation in JavaScript (well, you could, but it would be the most insecure app in the world).
Now the trick with form data validation is that most of the checks you're doing on the data will be done over and over again in every web app created. So, you're best bet is to create some type of class to take care of all this for you. (more on this in future article).
11.09.2005
The SqlXML class -- PHP sql query results in XML via object
The object does a few things. There are two methods in the object, query and queryPaged. The query method just does a straight query to the db and returns the results as XML. The queryPaged method also does a query to the db and returns the results as XML. But this method takes on a few extra parameters so that it can return page navigation information in the XML also. This way, you can set up paged data tables with navigation (page 1, page 2, etc.) instead of just glomming out hundreds of rows of data.
First things first – The query method
First off, we need to include our db connection stuff and our SqlXML object file. Then we can create our sql. Then instantiate the SqlXML object and pass it our sql. Here's that code:
// start db connection require_once('db.php'); // left to the reader to do require_once('SqlXML.php'); // full code to this program below $sql = "SELECT * FROM customers LIMIT 10"; $sqlxml =& new SqlXML(); $xml = $sqlxml->query($sql);
Pretty simple right? If you want to see the results, you can always just echo out a textarea:
echo '';
Your results will obviously depend on your query, but an excerpt of mine look something like this:
<?xml version="1.0"?>|
1 Harry harry@test.com |
2 Tom tom@test.com
So the method creates an XML “row” element for each row returned. Each field in the row returned gets its own “column” element. Each “column” element has a “name” attribute which tells you which field that data is for.
Keep in mind here that $xml is a string and not an xml object at this point. All the domxml stuff is handled inside the SqlXML object so we don’t have to fuss with it outside the object.
So how does this thing work? Well, first, it executes the query. If something didn’t work in the query, it just returns this as a string, not as XML.
function query($query) { $xml = ''; $result = mysql_query($query); if (!$result) { return 'Query failed: ' . mysql_error(); }
Then it records the number of rows in the data.
$totalRows = mysql_num_rows($result);
The method then starts to build the xml tree by setting a result-set element at the root with an attribute of total-rows. The method then goes on to build the rest of the xml tree, row by row. I should note here that you do need the domxml extensions for PHP installed. All the XML creation in the object is done with domxml methods, no lame XML-as-string solutions here. Here’s the code snip of that area of the code:
$rowCount = 1; while ($line = mysql_fetch_array($result, MYSQL_ASSOC)) { $row = $root->append_child($dom->create_element("row")); $row->set_attribute("number", $rowCount); foreach ($line as $colKey => $colValue) { $col = $row->append_child($dom->create_element("column")); $col->set_attribute("name", strtoupper($colKey)); $col->append_child($dom->create_text_node($colValue)); } $rowCount++; }
Here, the while loop creates the row elements in our resulting xml and the foreach loop creates the column elements.
Once you have your xml in that $xml variable, you can bring it in to your XSL transform (article coming soon...). Here is the full code for using the query method of the object.
// start db connection require_once('db.php'); // left to the reader to do require_once('SqlXML.php'); // full code to this program below $sql = "SELECT * FROM customers LIMIT 10"; $sqlxml =& new SqlXML(); $xml = $sqlxml->query($sql); echo ''; // then take that $xml string and transform with the appropriate XSL
Now what if you wind up with a lot of data from your query? For me, if I'm displaying any more than about 20 rows of data, I'm going to paginate it (place 20 rows on each page with some page to page naviation). We can use the queryPaged method of the SqlXML object for this. But I'll save that for another article.
Here's the complete SqlXML.php class.
class SqlXML { var $resultsPerPage = 0; var $curPage = 0; var $sort_by; var $sort_dir; function SqlXML() { } function query($query) { $xml = ''; $result = mysql_query($query); if (!$result) { return 'Query failed: ' . mysql_error(); } $totalRows = mysql_num_rows($result); $dom = domxml_new_doc("1.0"); $root = $dom->append_child($dom->create_element('result-set')); $root->set_attribute('total-rows',$totalRows); $rowCount = 1; while ($line = mysql_fetch_array($result, MYSQL_ASSOC)) { $row = $root->append_child($dom->create_element("row")); $row->set_attribute("number", $rowCount); foreach ($line as $colKey => $colValue) { $col = $row->append_child($dom->create_element("column")); $col->set_attribute("name", strtoupper($colKey)); $col->append_child($dom->create_text_node($colValue)); } $rowCount++; } $xml = $dom->dump_mem(); // Free resultset mysql_free_result($result); return $xml; } function queryPaged($queryFields, $queryTail, $scriptPath = '') { $recCount = 0; $totalPages = 0; $xml = ''; // get the total count without getting field data $queryCt = "SELECT COUNT(*) " . $queryTail; $resultCt = mysql_query($queryCt); if (!$resultCt) { return 'Query failed: ' . mysql_error(); } // calc total pages $row = mysql_fetch_row($resultCt); $recCount = $row[0]; if ($recCount > 0) { $query = "SELECT " . $queryFields . $queryTail; if ($this->resultsPerPage != 0) { $recStart = ($this->curPage * $this->resultsPerPage) - $this->resultsPerPage; // add limit to query $query .= " LIMIT $recStart, $this->resultsPerPage"; } mysql_free_result($resultCt); $result = mysql_query($query); if (!$result) { return 'Query failed: ' . mysql_error(); } // calc total pages $recCount2 = mysql_num_rows($result); if (!$recCount2) { return 'Query failed : ' . mysql_error(); } $totalPages = floor($recCount / $this->resultsPerPage); if ($recCount % $this->resultsPerPage > 0 ) { $totalPages++; } } // create xml $dom = domxml_new_doc("1.0"); $rs = $dom->append_child($dom->create_element('result-set')); $rs->set_attribute('total-rows',$recCount); $rs->set_attribute('results-per-page', $this->resultsPerPage); $rs->set_attribute('total-pages', $totalPages); $rs->set_attribute('current-page', $this->curPage); $rs->set_attribute('script-path', $scriptPath); if (isset($this->sort_by) && isset($this->sort_dir) && $this->sort_by != '' && $this->sort_dir != '') { $rs->set_attribute('sort-by', $this->sort_by); $rs->set_attribute('sort-dir', $this->sort_dir); } $nav = $rs->append_child($dom->create_element('nav')); // create XML to set up paging for ($i = 1; $i < = $totalPages; $i++) { $page = $nav->append_child($dom->create_element('page')); $page->set_attribute('num', $i); if ($i == $this->curPage) { $page->set_attribute('current-page', 'true'); } } $rows = $rs->append_child($dom->create_element('rows')); if ($recCount > 0) { $rowCount = 1; while ($line = mysql_fetch_array($result, MYSQL_ASSOC)) { $row = $rows->append_child($dom->create_element("row")); $row->set_attribute("number", $rowCount); foreach ($line as $colKey => $colValue) { $col = $row->append_child($dom->create_element("column")); $col->set_attribute("name", strtoupper($colKey)); $col->append_child($dom->create_text_node($colValue)); } $rowCount++; } } if (is_resource($result)) { mysql_free_result($result); } $xml = $dom->dump_mem(); return $xml; } }
10.07.2005
Adobe Creative Suite, First impressions, Where are the Keyboard Shortcuts?
I can't remember any of the keyboard shortcuts though and it's driving me mad. For instance, in photoshop or illustrator, how do I fill with foreground or background color via keyboard shortcut? It's ctrl-something. No clue, can't find it in the help anywhere.
Also started working with Version Cue, the new version control system within Adobe. It's a memory hog, using about 128mb of memory while it's just sitting in the tray. I don't like that. But it does seem much more in tune to graphics work then using something like subversion or cvs for version control. The actual working of Cue is sort of similar to a normal version control system. With Cue, you can opt to "Save as Version" instead of "check in" a file. You have to make sure that Version Cue is running in the tray and that each Adobe program is set up in the preferences for working with Cue.
What is super annoying is that there doesn't seem to be any keyboard shortcut for "Save as Version" at all. There's a shortcut for every single option in the File menu except for "Save as Version." Anyone got any clues?
5.07.2005
Perl tutorials for HTML output?
Why anyone would develop a web application in a language whose default output is not HTML remains a mystery to me. What year is this anyway?
Another recent find... Proof Positive Perl is Pushing up Petunias At least this fellow has it right. Someone with Perl as their primary language probably gets to watch a lot of daytime soaps (because they aren't working). Don't get me wrong, I love Perl, it was probably my first "modern" programming language. But let's leave 1998 in the past, shall we?
2.07.2005
It’s the business model, stupid. How terrible programming can lead to big money.
The last few projects I’ve been working on have involved paging through lines of poorly programmed and utterly atrocious looking procedural code in PHP. I’ve mostly been mopping up for other consultants, some outsourced, some not. I’ve questioned on several occasions whether or not a 5 year old was responsible for the terrible programming decisions made.
And I’m not talking about the differences between using a switch/case statement instead of an if/else structure. I’m talking about creating 10,000 global variables and carrying them from page to page instead of populating arrays and objects as needed. Database normalizing? What’s that? We’ll just keep repeating the customer name and address for every line item of every order. This stuff should be Programming 101. Apparently these programmers were absent that semester.
Now, are you ready for the kicker? These terrible apps are all making boatloads of money for their respective companies. I’m talking millions… sometimes tens of millions of dollars a year… being sucked into the company coffers by a web application held together with the programming equivalents of toothpicks and dental floss. Shocked? I was. It’s taken me awhile to shake off the disbelief and get down to the factors at play.
The key here is that each of these projects has a really great business model. Each business model is sound and complete, providing for multiple revenue streams through a single core product. All the projects were pretty cool ideas too, coming from the “Why didn’t I think of that?” department. They aren’t web-based services for programmers, tech folks or companies. They are services for real everyday people. People that think aol IS the internet.
The common elements with these projects:
- Great business idea.
- Terrible system implementation.
- Big money coming in, from common web surfers, regardless of the terrible system.
Sound like anything else you may have heard of? For me, it’s really revolutionized my thinking on a lot of items. People will come and pay for services they want, regardless of how difficult it may be to purchase them. And they will put up with a crappy business experience if the service provided is valuable enough to them. So you can code all you want, make all those objects perfectly encapsulated, make the database fly with tight queries, stamp out every bug you find -- that isn’t going to make you any money. Because it’s the business model, stupid.
1.29.2005
Yahoo customer service is amazing
I use my.yahoo.com/ all the time lately. Obviously, I use the email and the weather feature is nice. I have both of these on my front page. But some of the other tools of my.yahoo.com have become indispensable to me, particularly, the bookmarks, notes and briefcase. I use these to store files and information of a temporary nature that I can use from both home and the office.
Yesterday morning, my yahoo briefcase broke. I’m not sure what happened but when I was moving a fairly large database file from one folder to another in the briefcase, it came back with an error. Then, no matter what I clicked on in the briefcase, it wouldn’t go anywhere or do anything. Gasping at straws I filled out the help form at my.yahoo. Lo and behold, yahoo customer service actually wrote me back within 12 hours AND fixed the problem.
I couldn’t believe it. I was dumbfounded -- a free web-based service that actually takes the time to support their users. I’m not even a “customer” so I don’t even know if that’s the correct name to use, but yahoo customer service is amazing. Enough said.
1.24.2005
Creating software products versus Supporting software products
For the first time in a long time, I find myself working on applications in a revenue generating department of a company. It’s pretty refreshing. I’m working in online product delivery so the web applications I work on are actually sold to customers via subscriptions – they gain access to a particular application on the web for the length of time that they have paid for. The business focus is to create products and the financial focus is obviously to make money by creating products that customers want to buy. This obviously has a good side and a bad side.
The good side is that it looks like I’ll be working on “new” things a lot. Most programmers love “new” stuff and I’m no different. The bad side is that “old” things aren’t given quite so much concern. Refactoring existing code to run more efficiently is not a priority. It’s almost looked down upon. If a bug arises in an existing application, the focus seems to be to fix the bug in the smallest way possible and move on with the “new” stuff. Still, there’s quite a bit of adulation to go around when something “new” is launched. That’s because it can make money.
Most of my career has been spent working in various IT departments. The business focus in the IT department is one of support. You have to support the other users and the applications they use.
The financial focus of most IT departments is one of saving money or holding onto revenue. You improve programming processes so that applications are quicker and more powerful. This enables people to do more work in less time. But in IT, no matter what you build, you are still only “supporting” the business. You aren’t creating business. No matter how good you are, you aren’t making the company money. You are costing them. You may be helping them to save more money than previously but make no mistake -- You are costing the company money. And you’ll be first on the chopping block when job cuts need to be made.