0

Im building a small MVC system and i want my controllers to be Singletons. I made the base controller "Controller" a singleton and every other controller extends from that. My Router processes the request from the URL and grabs the controller string name.

This is where im getting the private constructor error because i am trying to do this:

class IndexController extends Controller {
     //the "Index" part comes from the url
}

    class Controller {

        private $instance;

        /**
        *   Initializes a new Singleton Controller
        */
        private function __construct() {
        }

        /**
        *   Get the instance of the Controller
        */
        public static function getInstance(){
            if (null === self::$instance) {
                self::$instance = new self();
            }
            return self::$instance;
        }
   }

$className = "Controller";
$inst = new $className; //here is where i get the error
$inst = $className::getInstance() //also fails

I have done my research and i stumbled upon this (http://www.php.net/manual/en/reflectionclass.newinstancewithoutconstructor.php), however I am not sure if that will work or is the best method if it does.

hakre
  • 193,403
  • 52
  • 435
  • 836
1337holiday
  • 1,924
  • 1
  • 24
  • 42
  • [Who needs singletons?](http://stackoverflow.com/questions/4595964/who-needs-singletons) – hakre Jul 14 '12 at 09:34

4 Answers4

4

$inst = new $className; //here is where i get the error This error is right, as the constructor is private.

$inst = $className::getInstance() //also fails This error is also right, as this syntax is not allowed.

If you want your conrollers to be singletons, you can, but you need to "twist" the rules for that.

b.t.w

  1. Why on earth would you want to do this?
  2. Why not just use an existing MVC (you do not need the entire FW).
  3. replace the self with static as this will reference the actual controller you try to instantiate and not the Controller class, as static references only the class it is written in.

Twisting the rules: You will need to instantiate your controllers through a factory (it is a pattern). Usually the front controller is used for that.

Itay Moav -Malimovka
  • 52,579
  • 61
  • 190
  • 278
  • Nice catch on using `static` as compared to `self`. Although agree with you on wanting to do it in the first place. – Charles Sprayberry Jan 29 '12 at 05:12
  • Thats a good idea, i will probably resort to that. This means the FrontController will essentially have to keep track of whats already been instantiated right? – 1337holiday Jan 29 '12 at 05:16
  • That is one way. A rule of thumb, software, Design Patterns, FW are supposed to make your work easier and shorter, if it starts to be complicated, you are on the wrong track – Itay Moav -Malimovka Jan 29 '12 at 05:19
  • Well i have used many different FW before but the problem i constantly have is everything i write sits on their FW, some are heavy weight and worst of all i have to spend hours in their documentation. I am building it because i want to have a solid understanding of its capabilities, no documentation and know exactly how to extend it. – 1337holiday Jan 29 '12 at 05:26
  • Why would you need the controllers to be singletons then? It can be implicit, say your FC (front controller) has only one method that instantiate a controller, and that methos is called only once in the FW (which is actually how every other FW does). – Itay Moav -Malimovka Jan 29 '12 at 05:27
0

A couple things really in regards to errors in using the "pattern"

class Controller {

    private static $instance;

    /**
    *   Initializes a new Singleton Controller
    */
    private function __construct() {
    }

    /**
    *   Get the instance of the Controller
    */
    public static function getInstance(){
        if (null === self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }

}

Note that the property is now static and the constructor is private as necessary for this anti-pattern.

Second your calling code should look like

$instance = Controller::getInstance();

You should never have to instantiate 'Controller' from the outside; the point of the pattern is to only allow access through Controller::getInstance()


That being said Singleton is little better than global state. Misko Hevery talks about it in this Google Clean Code Talk: Global State and Singleton. It is a really good video and explains far better than I can why Singletons are bad news. sourcemaking.com also has a good write-up on Singletons where they say that Singletons are unnecessary most of the time

Charles Sprayberry
  • 7,741
  • 3
  • 41
  • 50
  • Exactly, i do not want to instantiate it, i simply want access to the getInstance() method. However since loading of the controller is dynamic i.e. i cannot hard code Controller::getInstance() but rather i need to do "SomeExtendedController"::getInstance(). So the question is, how do i call the getInstance() when i only have the string name of the Controller object? – 1337holiday Jan 29 '12 at 05:02
  • @1337holiday You should be able to `$ControllerName::getInstance()` with the `$ControllerName` being the string variable holding the controller name. Just wrote up some code, this does work. – Charles Sprayberry Jan 29 '12 at 05:03
  • Yea that produces this error "unexpected T_PAAMAYIM_NEKUDOTAYIM" which is what Itay said would happen. – 1337holiday Jan 29 '12 at 05:09
  • keep in mind this is on PHP5.2. I just tested this too in another file and its still giving that error "unexpected T_PAAMAYIM_NEKUDOTAYIM" – 1337holiday Jan 29 '12 at 05:13
  • @1337holiday Ah, yes. PHP didn't introduce [Late Static Binding](http://php.net/lsb) until PHP 5.3 – Charles Sprayberry Jan 29 '12 at 05:14
  • On a side note, I am familiar with the clean code talks, so here is what is actually said, Databases are BAD (What is a DB if not a GLOBAL state :-D ) Anyone who uses a DB has the same EXACT method returning different results over time... – Itay Moav -Malimovka Jan 29 '12 at 05:16
  • @ItayMoav How is a database global state? Why in the world should all the things have access to the database? That just doesn't make sense to me. Only the very limited, very specific classes should ever need to access the DB. – Charles Sprayberry Jan 29 '12 at 05:18
  • @Charles Sprayberry - I come from the web world, almost 100% of what you write there (server side, i.e.) is to read, write and process data that comes from DB. You read data in method R::M(), data might change, you run R::M() again, you get different results. I am not really against DB, just pointing out the elephant – Itay Moav -Malimovka Jan 29 '12 at 05:22
  • @ItayMoav I don't see what any of that has to do with accessing the database through Singleton/global state. – Charles Sprayberry Jan 29 '12 at 05:23
  • @Charles Sprayberry Global state is a piece of data that is being shared throughout you application. Isn't this definition includes a DB? (And session too). I wrote this as a side note. – Itay Moav -Malimovka Jan 29 '12 at 05:25
  • @ItayMoav If you would like to discuss this further we can do so in chat but this has already gotten a little out of hand for a comments thread :) – Charles Sprayberry Jan 29 '12 at 05:26
0
class Controller {

        /**
         *   Let the __construct method be private to prevent new instance though new.
         */
        private function __construct() {}

        /**
         *   Get the instance of the Controller
         *   Here use the lazy loading. (need php >= 5.3)
         */
        public static function getInstance(){
            if (null === static::$instance) {
                static::$instance = new static();
            }
            return static::$instance;
        }
   }

class ControllerA extends Controller {
        //You need an static property to hold the instance.
        protected static $instance;
}

var_dump($a = ControllerA::getInstance());
var_dump($b = ControllerA::getInstance());
var_dump($a === $b);

If your Controller Name is a string, You can do like this:

$classname = "ControllerA";
call_user_func(array($classname, 'getInstance'));
call_user_func($classname .'::getInstance'); // As of 5.2.3
xdazz
  • 158,678
  • 38
  • 247
  • 274
  • Try doing this "ControllerA"::getInstance(); The Controller name is a string. – 1337holiday Jan 29 '12 at 05:17
  • @1337holiday See my edit, give the example if you want to call getInstance where your Controller name is a string. – xdazz Jan 29 '12 at 05:24
  • Yea i know that this method works, infact i would not have this issue if my host would support PHP 5.2.3+ but unfortunately they do not. I had to resort to removing the singleton but thanks for your suggestion. – 1337holiday Feb 03 '12 at 21:41
-1

On this line:

$inst = $className::getInstance() //also fails

You are using the scope resolution operator. You should be using this '->' for access to object data members/methods.

$inst = $className->getInstance() //also fails

Otherwise, you need to use the scope resolution operator on the class rather than the object:

$inst = Controller::getInstance() //also fails

I do not know where you are getting the value off $className as a class. There is no class called IndexController.

Shane
  • 2,007
  • 18
  • 33
  • The scope resolution operator is not the problem here. In fact since he should be calling the method statically `::` is the correct operator to use. Where are you getting 'IndexController'? I don't see any reference to this in the question. – Charles Sprayberry Jan 29 '12 at 05:06
  • Charles, since my posting, the OP has edited the question. Possibly numerous times. Initially, $className was being set to "IndexController". Possibly since your comment, he has edited it again to create the class IndexController extending off Controller. I was not saying the scope resolution operator was the issue, I was saying he should not be using it on an object. – Shane Jan 29 '12 at 17:15