Save time, reduce maintenance pains, simplify your code, and do it all while feeling like a freakin' genius! In this tutorial, learn how to use variable variables, lookup arrays, and a bit of clever programming to simplify form handling in a big way.
Variable Variables, Methods, and Properties
Before we can get too deep into using a lookup array, it's important to first understand the concept behind variable variables.
What Are Variable Variables?
Variable variable
is a term, which describes the use of a variable to declare another variable.
In the simplest form, a variable variable might look like:
$foo = 'A value!'; // Declare an initial value $bar = 'foo'; // Wait, what's happening? echo $$bar; // Holy crap! That output 'A value!'
Why Should you Care?
When you look at a proof of concept like the previous example, using variable variables probably looks pretty silly and overcomplicated. However, there really are solid, practical reasons to use them in some cases.
Practical Examples
The responsible use of variable variables can drastically reduce the amount of code that needs to be repeated by, say, converting an associative array to an object with sanitized values.
Example without variable variables
$comment = new stdClass(); // Create an object $comment->name = sanitize_value($array['name']); $comment->email = sanitize_values($array['email']); $comment->url = sanitize_values($array['url']); $comment->comment_text = sanitize_values($array['comment_text']);
Example with variable variables
$comment = new stdClass(); // Create a new object foreach( $array as $key=>$val ) { $comment->$key = sanitize_values($val); }
See how much simpler that was? And you can imagine what the example without variable variables would look like if the array had something like 50 or 100 values.
NOTE: I'm aware that you could also use array_map() and explicitly cast the array as an object to accomplish the same thing. That's not the point. We're illustrating a concept, here. Play along.
The Problem with Form Processing
Make form processing a breeze.
Now that you know how to use variable variables, we can move on to the meat and potatoes of this article, which is the idea that incorporating a lookup array instead of multiple controller files or a switch statement can save you a lot of extra maintenance, repeated code, and headache in general.
To illustrate our concept, we're going to use the idea of form processing. This is an essential aspect of web programming, and can also be one of the most tedious areas of any project.
However, after reevaluating your coding habits, you can potentially make form processing a breeze.
Frequently, either an individual file is created for each form to be processed, or a switch statement is used. In this section, we'll go over how both solutions might be implemented, and then we'll examine a solution using variable variables, and how it can improve your projects.
A Working Example with Multiple Form Processing Files
An often used method for handling form submissions is to create a whole new file to handle each form's data separately.
Take, for example, these three forms that update a user account with a new name, new email, or both:
<form action="assets/inc/ex1-form1.php" method="post" id="ex1-form1"> <div> <h4>Form 1</h4> <label>Name <input type="text" name="name" class="input-text" /> </label> <input type="submit" class="input-submit" value="Submit" /> <input type="hidden" name="token" value="secret token goes here" /> </div> </form> <form action="assets/inc/ex1-form2.php" method="post" id="ex1-form2"> <div> <h4>Form 2</h4> <label>Email <input type="text" name="email" class="input-text" /> </label> <input type="submit" class="input-submit" value="Submit" /> <input type="hidden" name="token" value="secret token goes here" /> </div> </form> <form action="assets/inc/ex1-form3.php" method="post" id="ex1-form3"> <div> <h4>Form 3</h4> <label>Name <input type="text" name="name" class="input-text" /> </label> <label>Email <input type="text" name="email" class="input-text" /> </label> <input type="submit" class="input-submit" value="Submit" /> <input type="hidden" name="token" value="secret token goes here" /> </div> </form>
Each of these forms points to a different processing file. So what does each of these files look like?
Processing form 1 (assets/inc/ex1-form1.php)
<?php // Turn on error reporting so we can see if anything is going wrong error_reporting(E_ALL); ini_set('display_errors', 1); // Make sure our faux-token was supplied if( isset($_POST['token']) && $_POST['token']==='secret token goes here' ) { // Require the necessary class require_once 'class.copterlabs_account.inc.php'; // Create a new instance of the class $account_obj = new CopterLabs_Account(); // Handle the form submission $output = $account_obj->save_name(); // For this example, just output some data about the form submission echo "<pre>Processing File: ", $_SERVER['PHP_SELF'], "\n\n<strong>Method Output:</strong>\n", $output, "</pre>\n", '<p><a href="../../">Go back</a></p>'; } else { die( 'Invalid form submission' ); }
Processing form 2 (assets/inc/ex1-form2.php)
<?php // Turn on error reporting so we can see if anything is going wrong error_reporting(E_ALL); ini_set('display_errors', 1); // Make sure our faux-token was supplied if( isset($_POST['token']) && $_POST['token']==='secret token goes here' ) { // Require the necessary class require_once 'class.copterlabs_account.inc.php'; // Create a new instance of the class $account_obj = new CopterLabs_Account(); // Handle the form submission $output = $account_obj->save_email(); // For this example, just output some data about the form submission echo "<pre>Processing File: ", $_SERVER['PHP_SELF'], "\n\n<strong>Method Output:</strong>\n", $output, "</pre>\n", '<p><a href="../../">Go back</a></p>'; } else { die( 'Invalid form submission' ); }
Processing form 3 (assets/inc/ex1-form3.php)
<?php // Turn on error reporting so we can see if anything is going wrong error_reporting(E_ALL); ini_set('display_errors', 1); // Make sure our faux-token was supplied if( isset($_POST['token']) && $_POST['token']==='secret token goes here' ) { // Require the necessary class require_once 'class.copterlabs_account.inc.php'; // Create a new instance of the class $account_obj = new CopterLabs_Account(); // Handle the form submission $output = $account_obj->save_both(); // For this example, just output some data about the form submission echo "<pre>Processing File: ", $_SERVER['PHP_SELF'], "\n\n<strong>Method Output:</strong>\n", $output, "</pre>\n", '<p><a href="../../">Go back</a></p>'; } else { die( 'Invalid form submission' ); }
As you can plainly see, the example files above are duplicating a ton of code. Expand this to 15 forms on a site, and you'll quickly find that maintenance could become a nightmare.
The Account Class
As you can see, the processing files are creating an instance of the class CopterLabs_Account. This will be a very simple class that outputs information about a form submission.
Here's the code for the class (assets/inc/class.coperlabs_account.inc.php):
<?php /** * A simple class to test form submissions * * PHP version 5 * * LICENSE: Dual licensed under the MIT or GPL licenses. * * @author Jason Lengstorf <[email protected]> * @copyright 2011 Copter Labs * @license http://www.opensource.org/licenses/mit-license.html * @license http://www.gnu.org/licenses/gpl-3.0.txt */ class CopterLabs_Account { public $name = NULL, $email = NULL; public function save_name() { $this->name = htmlentities($_POST['name'], ENT_QUOTES); return "Method: " . __METHOD__ . "\nName: " . $this->name . "\n"; } public function save_email() { $this->email = htmlentities($_POST['email'], ENT_QUOTES); return "Method: " . __METHOD__ . "\nEmail: " . $this->email . "\n"; } public function save_both() { $this->name = htmlentities($_POST['name'], ENT_QUOTES); $this->email = htmlentities($_POST['email'], ENT_QUOTES); return "Method: " . __METHOD__ . "\nName: " . $this->name . "\nEmail: " . $this->email . "\n"; } }
You can try out this code at Example 1 on the demo page.
A Working Example with a Single Processing File and a Switch Statement
Another popular solution for form processing is to consolidate all the processing scripts into one file and determine what to do with the data using a switch statement.
The switch approach commonly employs a trick in which a hidden input is added to the form containing an action to be taken upon submission. This
action is then used to determine what to do with the form.
Here are the same three forms, from above, with actions added, all pointing to a single processing file:
<form action="assets/inc/ex2-switch.php" method="post" id="ex2-form1"> <div> <h4>Form 1</h4> <label>Name <input type="text" name="name" class="input-text" /> </label> <input type="submit" class="input-submit" value="Submit" /> <input type="hidden" name="action" value="update-name" /> <input type="hidden" name="token" value="secret token goes here" /> </div> </form> <form action="assets/inc/ex2-switch.php" method="post" id="ex2-form2"> <div> <h4>Form 2</h4> <label>Email <input type="text" name="email" class="input-text" /> </label> <input type="submit" class="input-submit" value="Submit" /> <input type="hidden" name="action" value="update-email" /> <input type="hidden" name="token" value="secret token goes here" /> </div> </form> <form action="assets/inc/ex2-switch.php" method="post" id="ex2-form3"> <div> <h4>Form 3</h4> <label>Name <input type="text" name="name" class="input-text" /> </label> <label>Email <input type="text" name="email" class="input-text" /> </label> <input type="submit" class="input-submit" value="Submit" /> <input type="hidden" name="action" value="update-both" /> <input type="hidden" name="token" value="secret token goes here" /> </div> </form>
And the new processing file looks like: (assets/inc/ex2-switch.php)
<?php // Turn on error reporting so we can see if anything is going wrong error_reporting(E_ALL); ini_set('display_errors', 1); // Make sure our faux-token was supplied if( isset($_POST['token']) && $_POST['token']==='secret token goes here' ) { // Require the necessary class require_once 'class.copterlabs_account.inc.php'; // Create a new instance of the class $account_obj = new CopterLabs_Account(); // Sanitize the action $action = htmlentities($_POST['action'], ENT_QUOTES); // Use the new 'action' hidden input to determine what action to call switch( $action ) { // Form 1 handling case 'update-name': $output = $account_obj->save_name(); break; // Form 2 handling case 'update-email': $output = $account_obj->save_email(); break; // Form 3 handling case 'update-both': $output = $account_obj->save_both(); break; // If no valid action is found, something isn't right default: die( 'Unsupported action.' ); } // For this example, just output some data about the form submission echo "<pre>Processing File: ", $_SERVER['PHP_SELF'], "\nAction: ", htmlentities($_POST['action'], ENT_QUOTES), "\n\n<strong>Method Output:</strong>\n", $output, "</pre>\n", '<p><a href="../../#ex2">Go back to example 2</a></p>'; } else { die( 'Invalid form submission' ); }
You can see this in action by visiting Example 2 on the demo page. This is a marked improvement over using multiple forms, but you can see that we're still duplicating some code.
On top of that, it's a personal preference of mine to avoid switch statements whenever I can. This is due to the fact that switch uses loose comparisons ('a string' will trigger case 0 because a string evaluates to 0 if you convert it to an integer) and is extremely easy to turn into spaghetti code.
Fixing the Problem: Lookup Arrays and Variable Variables
As we've seen so far, both of the above solutions have their drawbacks, and require duplicate code. Imagine if there were a dozen or more forms on the site — not pretty.
To address this issue, we can use a concept called a lookup array, which maps the actions passed from the form to a method called on the object.
Yes, you could set the action as the method name, but that allows a bogus form submission to call any public method. Making the array a key-value pair is a small step to add a little more control without much extra work.
A Working Example with a Single Processing File and a Lookup Array
Using our knowledge of variable variables from the beginning of this tutorial, let's modify our demo to use a lookup array.
Modify the three forms to point to a new controller file:
<form action="assets/inc/ex3-lookup-array.php" method="post" id="ex3-form1"> <div> <h4>Form 1</h4> <label>Name <input type="text" name="name" class="input-text" /> </label> <input type="submit" class="input-submit" value="Submit" /> <input type="hidden" name="action" value="update-name" /> <input type="hidden" name="token" value="secret token goes here" /> </div> </form> <form action="assets/inc/ex3-lookup-array.php" method="post" id="ex3-form2"> <div> <h4>Form 2</h4> <label>Email <input type="text" name="email" class="input-text" /> </label> <input type="submit" class="input-submit" value="Submit" /> <input type="hidden" name="action" value="update-email" /> <input type="hidden" name="token" value="secret token goes here" /> </div> </form> <form action="assets/inc/ex3-lookup-array.php" method="post" id="ex3-form3"> <div> <h4>Form 3</h4> <label>Name <input type="text" name="name" class="input-text" /> </label> <label>Email <input type="text" name="email" class="input-text" /> </label> <input type="submit" class="input-submit" value="Submit" /> <input type="hidden" name="action" value="update-both" /> <input type="hidden" name="token" value="secret token goes here" /> </div> </form>
Next, put together the processing file that will handle form submissions (assets/inc/ex3-lookup-array.php):
<?php // Turn on error reporting so we can see if anything is going wrong error_reporting(E_ALL); ini_set('display_errors', 1); // Make sure our faux-token was supplied if( isset($_POST['token']) && $_POST['token']==='secret token goes here' ) { // Require the necessary class require_once 'class.copterlabs_account.inc.php'; // Create a new instance of the class $account_obj = new CopterLabs_Account(); // Sanitize the action $action = htmlentities($_POST['action'], ENT_QUOTES); // Set up a lookup array to match actions to method names $lookup_array = array( 'update-name' => 'save_name', 'update-email' => 'save_email', 'update-both' => 'save_both' ); // Make sure the array key exists if( array_key_exists($action, $lookup_array) ) { // Using variable variables, call the proper method and store the output $output = $account_obj->$lookup_array[$action](); } else { die( 'Unsupported action.' ); } // For this example, just output some data about the form submission echo "<pre>Processing File: ", $_SERVER['PHP_SELF'], "\nAction: ", htmlentities($_POST['action'], ENT_QUOTES), "\n\n<strong>Method Output:</strong>\n", $output, "</pre>\n", '<p><a href="../../#ex3">Go back to example 3</a></p>'; } else { die( 'Invalid form submission' ); }
Check this out on the demo page by trying out the forms on Example 3.
Since we set the action as the key in the array, we use array_key_exists() to ensure that the action is valid. Then, we use the value that corresponds to the action as the method name. Notice that we added the parentheses after the value to make sure it's executed as a method.
The addition of the lookup array keeps the code concise, simple, and clear (once you get the hang of variable variables).
Summary
Used responsibly, lookup arrays can make your scripts far easier to update and maintain when you combine them with variable variables.
How do you think you can integrate lookup arrays and variable variables into your projects to make maintenance easier? Let me know in the comments!
Comments