This article assumes LAMP or other PHP based web layout, and basic proficiency with Object Oriented PHP scripting.
Have you ever found yourself wanting to use OOP methods in PHP but found it frustrating due to the rather bolted on nature of object support? In particular, dealing with outside data arrays like $_POST and $_GET?
Since the advent of OOP support in PHP, it doesn’t take long to get spoiled by the type hinting and name space advantages OOP offers (I’ll leave the other benefits to be debated by the ever on going Procedural vs OOP soldiers still at war out there).
The problems come when you have to start processing uncontrolled outside data, and as mentioned above, one of the biggest offenders is request data from the client. The moment this occurs – pretty much the moment you build any website, your nice clean OOP concept goes right out the window. Type hinting and automated error checking quickly follow suit. How do we fix this? Let’s start with our goals:
- Type hinting. Without type hinting, powerful IDEs like Dreamweaver are little more than very expensive text editors.
- Deal with missing variables. Even with validation, there are times, perhaps by design when some incoming variables might not have a value assigned.
- Avoiding repetition. Re typing array keys over and over isn’t just inconvenient. You’re practically begging to insert hard to locate typo bugs in the code.
Now with those objectives in mind, it’s easy to see the first two could be solved by grouping our variables into an object. The real question is how to get them there and avoid repetitious code.
For a working example let’s assume we have an incoming form post with the following fields:
- First Name
- Last Name
- Phone
We’ll use the HTML5 required attribute and JavaScript validation to make sure most of the fields are filled out, but we want “Phone” to be optional. This is all well and good, but as any experienced developer is aware, you can never, EVER trust client side validation. So even without an optionally blank field, we’re going to have to ensure missing variables are initialized on post.
Let’s try a couple of solutions and evaluate them vs. our criteria. For this article, we’re going to use $_REQUEST. If you aren’t familiar, $_REQUEST is a handy single command that acquires all values you’d normally get from $_GET, $_POST and $_COOKIE.
Standard Object
// Initialize standard object with request array. $request = object($_REQUEST); // Do stuff...
On the surface this is a beautifully elegant solution. The PHP Standard Object initializes with $_REQUEST in a single line, and any $_REQUEST vars are now available as data members.
Unfortunately, this method has two serious drawbacks.
- Firstly, Standard Object has no methods for our members. All we’ve really accomplished is shifting a bunch of data from one global space to another with no validation or protection. Might be quick and easy, but it’s nothing close to an object oriented solution and will inevitably bite us in the butt at some point.
- Any missing $_REQUEST vars stay missing. The Standard Object can’t include what isn’t there to begin with. To solve that issue you might try this:
// If any post vars are missing, populate them with NULL. if(!isset($_REQUEST['name_f'])) $_REQUEST['name_f'] = NULL; if(!isset($_REQUEST['name_l'])) $_REQUEST['name_l'] = NULL; if(!isset($_REQUEST['mail'])) $_REQUEST['mail'] = NULL; if(!isset($_REQUEST['phone'])) $_REQUEST['phone'] = NULL; // Initialize standard object with post array. $post = object(<a title="$_REQUEST" href="http://php.net/manual/en/reserved.variables.request.php" target="_blank"><em>$_REQUEST</em></a>); // Do stuff...
With this technique we insert blank values into $_REQUEST before initializing Standard Object, and solve the missing variable issue. Unfortunately we’ve exchanged one problem for another: Now we have to repeat every key twice. Lastly, we’ve also done nothing about type hinting. Conclusion? Not with the hassle.
Custom Class, Populate Members Individually In Constructor
class request { private $name_f = NULL, // First name. $name_l = NULL, // Last name. $mail = NULL, // Email. $phone = NULL; // Phone number. // Class constructor public function __construct() { $this->populate_members(); } private function populate_members() { if(isset($_REQUEST['name_f'])) $this->name_f = $_REQUEST['name_f']; if(isset($_REQUEST['name_l'])) $this->name_l = $_REQUEST['name_l']; if(isset($_REQUEST['mail'])) $this->mail = $_REQUEST['mail']; if(isset($_REQUEST['phone'])) $this->phone = $_REQUEST['phone']; } // Accessors public function get_name_f() { $return $this->name_f; } ... // Mutators public function set_name_f($value) { $this->name_f = $value; } ... } // Initialize class object. $post = new post(); // Do stuff...
This is a little better. By writing our own class and running a function that populates piecemeal in the constructor, we get type hinting from our IDE and all of our variable instantiate work is packaged up. One problem remains – we still have to type every identifier three times at minimum. That’s a bug just waiting to happen. It’s a good start though, so let’s build on it a bit with the method below:
Custom Class, Dynamic Population
class request { private $name_f = NULL, // First name. $name_l = NULL, // Last name. $mail = NULL, // Email. $phone = NULL; // Phone number. // Class constructor public function __construct() { $this->populate_members(); } private function populate_members() { // Iterate through each class variable. foreach($this as $key => $value) { // If we can find a matching a post var with key matching // key of current object var, set object var to the post value. if(isset($_POST[$key])) { $this->$key = $_POST[$key]; } } } // Accessors public function get_name_f() { $return $this->name_f; } ... // Mutators public function set_name_f($value) { $this->name_f = $value; } ... } // Initialize object. $post = new post(); // Do stuff...
Now we’re in business! This might look a bit complex at first glance, but it’s really the simplest of all attempts thus far. More importantly it provides everything we asked for and then some.
Let’s look closer at the populate_members() method and see what’s going on:
foreach($this as $key => $value)
PHP allows us to loop through all the data members of an object just as if it were an array (this has other uses elsewhere, more on that in another article). We can even key the data member’s name as a string just as if it were an array key, and that’s what we’re leveraging for a dynamic data member population. FYI, we really don’t care about $value here, it’s just a necessary filler syntax.
if(isset($_REQUEST[$key])) { $this->$key = $_POST[$key]; }
Now in each loop we use the value of $key as an string array key for $_POST and evaluate it with isset(). If isset() returns TRUE, we know the value exists. All that’s needed is to populate the corresponding data member with that $_POST value. Again, PHP helps us out; it is perfectly legal to pass string variables as a data member id at run time.
Once the loop is completed, all $_REQUEST keys that have a matching data member will have been located and the data members populated. Any missing $_REQUEST values are simply ignored; we already established a default value for their corresponding data members.
At this point all conditions have been met. Your IDE will provide type hinting and error checking. Variables are only entered once as data member names. Missing $_REQUEST values are dealt with elegantly. No helper code outside of the class is needed. Best of all, this concept isn’t limited to $_REQUEST at all. Any array array is also fair game.
We could stop right here, but unfortunately there’s just one little flaw remaining. Elegant as it is, this solution fully bypasses the object’s mutator methods, and in the process throws best practice data validation right out the window. There’s nothing stopping you from putting some data validation into the loop (or ignoring best practice), but with just a little more front end work, you can leverage your mutators and keep all of the data validation encapsulated.
Custom Class, Dynamic Mutation
class request { private $name_f = NULL, // First name. $name_l = NULL, // Last name. $mail = NULL, // Email. $phone = NULL; // Phone number. // Class constructor public function __construct() { $this->populate_members(); } private function populate_members() { // Interate through each class variable. foreach($this as $key => $value) { // If we can find a matching a post var with key matching // key of current object var, set object var to the post value. if(isset($_REQUEST[$key])) { // Add 'set_' prefix so member name is now a mutator method name. $method = 'set_'.$key; // If a mutator method by the current name exists, run it and // pass current request value. if(method_exists($this, $method)=== TRUE) { $this->$method($_REQUEST[$key]); } } } } // Accessors public function get_name_f() { $return $this->name_f; } ... // Mutators public function set_name_f($value) { $this->name_f = $value; } ... }
This looks and works very similar to the above method, but instead of populating members directly, values are routed through the object’s mutator methods. This does double duty of encapsulating validation and providing a secondary gateway to control just which members you want populated at all.
The secret is that PHP allows you to reference methods by string at run time just it it does members. First, we use a bit of string manipulation to get a member name from our $_REQUEST name. I use a prefix of “set_” for my mutators, so it’s a simple matter of tacking that on to a placeholder variable.
// Add 'set_' prefix so member name is now a mutator method name. $method = 'set_'.$key;
Next, we will need to make sure the method exists. PHP will immediately halt the script and throw a fatal error if an attempt is made to call non-existent methods, so this check is vital. If the check passes, we use the string to call out mutate method and pass it our $_REQUEST value. Notice how the member’s signature is NOT part of the string – treat it exactly as you would any hard coded member’s signature.
// If a mutator method by the current name exists, run it and // pass current request value. if(method_exists($this, $method)=== TRUE) { $this->$method($_REQUEST[$key]); }
Now we truly are ready. All of our $_REQUEST data is packaged into a fully encapsulated object with elegant evaluation and gateways. Our IDE will now provide us with type hinting and error checking, plus all of the code is reusable and double keying has been kept to the bare minimum. No more hidden typos, mystery variables, and annoying PHP notice errors!
Give it a try, season to taste and enjoy.
Until next time!
DC
1 Comment