roopurt18 Posted July 14, 2007 Share Posted July 14, 2007 I set out to develop a method of implementing singletons in PHP with a simple initial critera: To not use any non-class helper functions and have as much of the behavior inherited as possible. Here's what I came up with: class singleton <?php abstract class singleton { // associative array; "className" => "instance" private static $s_singletons = Array(); /** * Retrieve the instance of the class if it exists, otherwise instantiate * and return the new instance. Return null on failure. * @var string Class name * @var string Path to class definition file */ public static function getInstance($class = __CLASS__, $path = null){ $instance = null; // No instance initially if($class == __CLASS__){ // No override check echo "derived class didn't override singleton::getInstance<br>"; return $instance; } if(isset(singleton::$s_singletons[$class])){ echo "already instantiated<br>"; $instance = singleton::$s_singletons[$class]; }else{ echo "not instantiated<br>"; // Here we would do a require_once using the $path, $class, etc. $instance = new $class(); if(is_object($instance)){ echo "created new instance<br>"; singleton::$s_singletons[$class] = $instance; // Save instance }else{ echo "couldn't create new instance<br>"; } } return $instance; // null or a class instance } public static function dumpInfo(){ if(!count(singleton::$s_singletons)){ echo "No singletons created.<br>"; return; } echo "<pre>" . print_r(singleton::$s_singletons, true) . "</pre>"; } } ?> I then created three derived classes. In the final derived class, c, I didn't override the getInstance() method to make sure my override check was working correctly. <?php class a extends singleton { public static function getInstance(){ parent::getInstance(__CLASS__, dirname(__FILE__)); } } class b extends singleton { public static function getInstance(){ parent::getInstance(__CLASS__, dirname(__FILE__)); } } class c extends singleton { } ?> I then ran the following: <?php a::getInstance(); b::getInstance(); c::getInstance(); singleton::dumpinfo(); a::getInstance(); b::getInstance(); singleton::dumpinfo(); $a = new a(); echo "<pre>" . print_r($a, true) . "</pre>"; ?> Here is the output: not instantiated created new instance not instantiated created new instance derived class didn't override singleton::getInstance Array ( [a] => a Object ( ) [b] => b Object ( ) ) already instantiated already instantiated Array ( [a] => a Object ( ) [b] => b Object ( ) ) a Object ( ) 99% of the time in an application I'm only ever using a single instance of a Database object. So the code will end up doing something like $db->select($sql); However, sometimes it would be nice to access database functions using the currently instantiated Database object but without having a reference to it. Here is one way of implementing that: <?php class database extends singleton { /** * Run a SELECT query */ public function select($sql){ } /** * Run a SELECT query statically */ public static function st_select($sql){ $obj = __CLASS__::getInstance(); return is_object($obj) ? $obj->select($sql) : null; } } ?> This enables me elsewhere in the code to do the following: // If I have a reference to the $db object, I can do: $db->select($sql); // But if I don't have a reference to the $db object, I can still run the following: // (It's slightly more typing, but it may allow me to be lazy in other regards, such // as finding a nifty way to pass an object somewhere it was never intended to // go.) database::st_select($sql); Overall, I like this approach because it uses inheritance and locates the actual getInstance() method in a single location, should I need to modify it later. Also there are no non-class functions which also makes me happy since I try to avoid polluting the global name space. One reservation that I have about it though is that I can create an additional instance of any of the derived classes. I'm sure I can disable that ability somehow, but I'm not sure I want to. This would seem to defeat the purpose of creating singletons in the first place, but here is a possible scenario. Let's say for arguments sake that 99% of my application is using a single Logging object. This object is writing to a specific file in a specific directory; this means the majority of my program would be using calls like $log->msg($msg); or logger::msg($msg); However, now let's say I need to debug a portion of the program and logging is the best way to do so, but I don't want to add extra information into the current logging process. I could still create an extra instance of the logger object, set it to log to a different file, and use it temporarily for debugging. Curious what you folks think about all this. Quote Link to comment https://forums.phpfreaks.com/topic/60003-critique-this-singleton-design/ Share on other sites More sharing options...
Buyocat Posted July 15, 2007 Share Posted July 15, 2007 I'll just leave a quick response now, and I'll come back later most likely. Taking a look at what you've done I wonder why you don't implement a factory class instead of spreading your factories across each class? You could create a factory class which generates singletons or new instances, and through that you would not have to write code in each class setting up the factory method. Of course the one drawback is that your IDE wouldn't be able to help you with autocompletes based on the return value of the factory (assuming that the factory returns disparate types, though you could make factories for each hierarchy). As an example: /** * Returns a single instance of a given class * * @throws ReflectionException // custom exception for the Reflection Facade * * @param String $classname class name following the PEAR convention * @param array $arguments arguments for the constructor * @return unknown */ public function getSingleton($classname, array $arguments) { static $existing_classes; if (!isset($existing_classes[$classname])) { using($existing_classes); // function which includes files based on PEAR convention class names $class = ReflectionFacade::getInstance($classname, $arguments); // facade around the Reflection API $existing_classes[$classname] = $class; } return $existing_classes[$classname]; } I think it's pretty clear how you could fill in the blanks I've left, and how to adapt things to fit your own conventions. With something like the above you could generate single instances of classes whenever you wanted without ever writing new code to do so. Also I think it's easy to see how you could use the reflection API (or a wrapper such as my facade) to call methods dynamically and as if they were static. For instance you could have this second method rely on the first to get its hands on an instance of the object in question. Thus the call to the reflection wrapper would be static even though it would be executing an ordinary method call. Those are my thoughts, I'll come back later and take a closer look. Quote Link to comment https://forums.phpfreaks.com/topic/60003-critique-this-singleton-design/#findComment-298523 Share on other sites More sharing options...
steelmanronald06 Posted July 15, 2007 Share Posted July 15, 2007 Stupid question, but what is a singleton? Quote Link to comment https://forums.phpfreaks.com/topic/60003-critique-this-singleton-design/#findComment-298619 Share on other sites More sharing options...
Buyocat Posted July 15, 2007 Share Posted July 15, 2007 A singleton is a single instance of a given object. The goal of the pattern is to restrict the number of instances of a given object to one. For example, if you have an expensive DB object which is costly to instantiate you might just pass around a single instance of it via a factory or method instead of just recreating it every time it is needed. Quote Link to comment https://forums.phpfreaks.com/topic/60003-critique-this-singleton-design/#findComment-298624 Share on other sites More sharing options...
KevinM1 Posted July 17, 2007 Share Posted July 17, 2007 For us novices following along, would you mind explaining the __CLASS__ bit? I sort of see how it works, but the default value in the getInstance() function signature is throwing me off. Also, am I right in guessing you merely left out the bit that deals with the class' path? Or am I missing something obvious? Thanks! Quote Link to comment https://forums.phpfreaks.com/topic/60003-critique-this-singleton-design/#findComment-300367 Share on other sites More sharing options...
roopurt18 Posted July 17, 2007 Author Share Posted July 17, 2007 I'll answer your questions in reverse. 1) I did leave off the bit about the class path, in it's place is a comment: // Here we would do a require_once using the $path, $class, etc. Although honestly I don't think that part would be necessary. If the function is accessed through the class statically then you would have to assume the file had already been included. 2) __CLASS__ is the same as __LINE__ or __FILE__. They are replaced with the actual value at run-time. In this case, __CLASS__ evaluates to the name of the class it is used within. The default value of the singleton::getInstance will evaluate to "singleton". This gives the singleton a way of knowing if the function is being called through the singleton class itself or a derived class; an instance should be created only if being called through a derived class. Therefore, there is a check at the top of getInstance to see if $class is equal to the default value ("singleton"), if it is, the function returns early. I still don't know how I feel about this design. It has the disadvantages of requiring extra code in each derived class, most of which is duplicated but can't be derived. It also has the disadvantage of the class file needing to be included before being used. The alternative is a factory method, such as that suggested by Buyocat. It has the obvious advantage of not requiring extra code in each class, additionally class files don't need to be included by the calling code as the factory method can take care of that step. Decisions, decisions! Quote Link to comment https://forums.phpfreaks.com/topic/60003-critique-this-singleton-design/#findComment-300399 Share on other sites More sharing options...
KevinM1 Posted July 17, 2007 Share Posted July 17, 2007 I'll answer your questions in reverse. 1) I did leave off the bit about the class path, in it's place is a comment: // Here we would do a require_once using the $path, $class, etc. Although honestly I don't think that part would be necessary. If the function is accessed through the class statically then you would have to assume the file had already been included. 2) __CLASS__ is the same as __LINE__ or __FILE__. They are replaced with the actual value at run-time. In this case, __CLASS__ evaluates to the name of the class it is used within. The default value of the singleton::getInstance will evaluate to "singleton". This gives the singleton a way of knowing if the function is being called through the singleton class itself or a derived class; an instance should be created only if being called through a derived class. Therefore, there is a check at the top of getInstance to see if $class is equal to the default value ("singleton"), if it is, the function returns early. I still don't know how I feel about this design. It has the disadvantages of requiring extra code in each derived class, most of which is duplicated but can't be derived. It also has the disadvantage of the class file needing to be included before being used. The alternative is a factory method, such as that suggested by Buyocat. It has the obvious advantage of not requiring extra code in each class, additionally class files don't need to be included by the calling code as the factory method can take care of that step. Decisions, decisions! Ah, okay, cool. Makes total sense. And yeah, I didn't see that comment about __FILE__ until you just mentioned it. Thanks! Quote Link to comment https://forums.phpfreaks.com/topic/60003-critique-this-singleton-design/#findComment-300420 Share on other sites More sharing options...
448191 Posted July 18, 2007 Share Posted July 18, 2007 Stupid question, but what is a singleton? http://www.phpfreaks.com/forums/index.php/topic,117952.30.html Quote Link to comment https://forums.phpfreaks.com/topic/60003-critique-this-singleton-design/#findComment-301559 Share on other sites More sharing options...
roopurt18 Posted July 18, 2007 Author Share Posted July 18, 2007 Are you repeating the question? Or did you forget to type a body in your post? If you're repeating the question: A singleton is an object which is intended to be instantiated only once during a programs execution. When the program requests a singleton object, let's say the DB, it first checks if the object is instantiated. If it is, it returns the existing instance. If it is not, it instantiates the object (via new) and returns the instance; it also has to save a reference to that instance in case the program requests the singleton again later. Quote Link to comment https://forums.phpfreaks.com/topic/60003-critique-this-singleton-design/#findComment-301571 Share on other sites More sharing options...
448191 Posted July 18, 2007 Share Posted July 18, 2007 LOL, no I'm not repeating the question, I know what a singleton is. I did edit the post because I found out one could access PHP Application Design part 3 (Design Patterns), which is far from finished, so I replaced it with a link to a thread in the Recommended lounge which Ronald should be able to access, but non mods/admins/recommended shouldn't. Quote Link to comment https://forums.phpfreaks.com/topic/60003-critique-this-singleton-design/#findComment-301581 Share on other sites More sharing options...
roopurt18 Posted July 18, 2007 Author Share Posted July 18, 2007 Boo! I want to see special content too. Quote Link to comment https://forums.phpfreaks.com/topic/60003-critique-this-singleton-design/#findComment-301583 Share on other sites More sharing options...
448191 Posted July 18, 2007 Share Posted July 18, 2007 Boo! I want to see special content too. Don't worry, if it's up up to me you will. Quote Link to comment https://forums.phpfreaks.com/topic/60003-critique-this-singleton-design/#findComment-301593 Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.