Using the Singleton Principal to Enhance Software
LiquidPHP's use of well-established programming patterns was discussed pretty well here, when the announcement was made LiquidPHP would be following the MVC pattern very extensively. The MVC pattern addressed one of the critical questions to how LiquidPHP would accomplish the goal of being extremely extensible and modular. Now, there is another goal to accomplish and another pattern with which to accomplish it.
I have a bone to pick with any programmer who uses global variables in any programming language for almost any reason. Do a search for {insert programming language here} and "globals" and you will find hundreds of articles on the security flaws in doing so, plus a handful of other reasons why it's just plain bad. It's a powerful tool wielded horribly wrong, akin to the abuse of "goto" statements. PHP especially has been plauged by the overuse of globals (on a related noted, PHP 5.3 introduced the "goto" keyword). Another good search to do when you're looking up why not to use globals is the history of PHP globals overwrites. Essentially, a programmer unaware or unprepared for a globals overwite is letting an attacker execute any code imaginable. This includes things like "echo $users_social_security_number" and "unlink($important_file_name)." The contributors at PHP have tried for many releases of PHP to completely plug up this hole. It seems they are now successful, but it doesn't make globals any better of a practice.
Here is one example of why it is unsafe to use globals. Let's create a trivial function foo that simply has the task to checking whether or not global bar is false. So, we write it up:
function foo() {
global $bar;
if ($bar = false) {
return false;
}
}
Experienced and novice programmers alike have noticed my intentional typo. There's the assignment operator "=" and not the comparison operator "==" in the if statement. This is not an uncommon occurance. But, this code is completely valid when it could very well destroy the rest of the program! Worse yet, this error could be nearly impossible to track down. If bar was something important, let's say it is whether or not a user is allowed to do some admin function, this could mean serious security issues for your software!
So, how does LiquidPHP work around this? Clearly, data must be shared between functions. It's easy to pass bar as a parameter to function foo, but then if we also want to work on variable baz, then we have to update every use of function foo. A class with static properties can be created to handle passing data. This solves the problem with parameters because static properties are available in every scope and retain their value. But, this is prone to the same exact issues as globals, we've simply just painted it a different color. So, we can instead create a static method to get or set our static property. Now, we have to create a function for every property in every class, which can be a lot of redundant coding and yet again another poor choice. PHP has many tools available to classes, such as the ArrayAccess interface and the __get and __set magic methods, but these are only available in non-static context, which puts us right back into parameter passing.
The singleton pattern is the solution to this problem. It enforces one static instance of any class so that non-static methods can be used. Here's an example from a class that was used in an older developmental version of LiquidPHP:
class foo implements ArrayAccess {
private static $instance;
private $bar = array();
public static function get_instance() {
if (!isset(self::$instance)) {
self::$instance = new foo();
}
return self::$instance;
}
// snipped out offsetExists, offsetGet, and offsetUnset
public function offsetSet($offset, $value) {
if ($this->offsetExists($offset)) {
if (get_type($value) != get_type($this->bar[$offset])) {
echo 'You can\'t overwrite that value with one of the wrong type!';
}
// snip
}
}
This is a trivial example when not seen in its full context, but the potential can be seen. We can't overwrite a user object with a value of NULL. With a little bit more development, logical values for each property could be checked before they are assigned and not just type safety. You can't protect every variable from every possible problem value but this way values can get a sanity check, which is more than we get from (mis)use of globals.
~Garrett