God Object Singleton...

Not for 'how-to' coding questions but PHP theory instead, this forum is here for those of us who wish to learn about design aspects of programming with PHP.

Moderator: General Moderators

Post Reply
User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

God Object Singleton...

Post by Ambush Commander »

Good evening, folks. I have just implemented a God Object Singleton.

Code: Select all

<?php

/**
 * Super singleton object that does everything. Used for unit testing.
 */
class XHTMLCompiler
{
    
    /** Private instance of singleton */
    private static $_instance;
    
    /** Private constructor, prevents other people from making it */
    private function __construct() {}
    
    /** Retrieves the single instance of the object */
    static public function getInstance() {
        if(is_null(self::$_instance)) {
            self::$_instance = new self();
        }
        return self::$_instance;
    }
    
    /**
     * Overloads the instance with another one, usually a mock object
     * @param Object substitute for XHTMLCompiler
     */
    static public function setInstance($stub) {
        // unit testing only!
        self::$_instance = $stub;
    }
    
    /** Aborts script execution, equivalent to exit */
    public function quit() {exit;}
    
    /** Sends an HTTP header, equivalent to header() */
    public function header() {
        $args = func_get_args();
        call_user_func_array('header', $args);
    }
    
    /** Outputs text, equivalent to echo */
    public function paint($text) {echo $text;}
    
    /** Retrieves the relative URI with which the page was requested. */
    public function getRequestURI() {return $_SERVER['REQUEST_URI'];}
    
    /** Retrieves the relative URI which denotes the frontend PHP file */
    public function getPHPSelf() {return $_SERVER['PHP_SELF'];}
    
}

?>
Is this a good idea? The idea is that this gives me an object that's easy to mock, so I can unit test functions that normally would have a very concrete effect, such as send headers, or quit execution. There's always a bit of functionality here, and a bit there, that if I went all out object-frenzy I'd have some very airy files: this lets me keep things compact. Hmm...
User avatar
feyd
Neighborhood Spidermoddy
Posts: 31559
Joined: Mon Mar 29, 2004 3:24 pm
Location: Bothell, Washington, USA

Post by feyd »

To keep things centralized, I can't blame you. It seems fine. Extrapolated a bit, one could see it implementing a few interfaces, possibly even extending a class.
User avatar
Ollie Saunders
DevNet Master
Posts: 3179
Joined: Tue May 24, 2005 6:01 pm
Location: UK

Post by Ollie Saunders »

setInstance() seems like a cool idea. Isn't a factory class typically what you would use to solve problems like this though?
All this...

Code: Select all

class XHTMLCompiler
{
   
    /** Private instance of singleton */
    private static $_instance;
   
    /** Private constructor, prevents other people from making it */
    private function __construct() {}
   
    /** Retrieves the single instance of the object */
    static public function getInstance() {
        if(is_null(self::$_instance)) {
            self::$_instance = new self();
        }
        return self::$_instance;
    }
   
    /**
     * Overloads the instance with another one, usually a mock object
     * @param Object substitute for XHTMLCompiler
     */
    static public function setInstance($stub) {
        // unit testing only!
        self::$_instance = $stub;
    }
could quite happily be an abstract class called Singleton.
User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

Post by Ambush Commander »

To keep things centralized, I can't blame you. It seems fine. Extrapolated a bit, one could see it implementing a few interfaces, possibly even extending a class.
Yay, if Feyd says it's okay, :-D I guess I can refactor later if the class gets too big... I'll set the limit at 20 methods.
could quite happily be an abstract class called Singleton.
Yep, except that it's not used by anyone else. Therefore, I'm not so worried about the lack of inheritance. If it becomes necessary, I'll refactor.
Isn't a factory class typically what you would use to solve problems like this though?
You're right. The more appropriate term for this class is a Strategy with a Prototype factory method. But maybe I'll add some global data to it. Then it's a Singleton.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

No ... bad idea. Slapping the functionality of the request, response and view objects into a single object ... and then patting yourself on the back for making a pseudo-testable Singleton ... urgh. If you application is really that simple, then why to you need even this. If it isn't then put request, response and view objects into a registry and pass it through. Separate concerns are still separate concerns.
(#10850)
User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

Post by Ambush Commander »

If you application is really that simple, then why to you need even this.
So I can test stuff like:

Code: Select all

function normalize_index($page, $directory_index) {
    $xc = XHTMLCompiler::getInstance();
    if ($page == '') $page = $directory_index;
    if ($xc->isDir($page)) {
        if ($page[strlen($page)-1] !== '/') $page .= '/';
        $page .= $directory_index;
    }
    return $page;
}
It's simple, yes, but I have no interest in implementing a full "FileSystem" object hierarchy, so the ability to overload is_dir() with a mock is appreciated.

What would you recommend? My main concern with separating concerns is that it leads to a level of abstraction that is unnecessary for something as simple as this. There are fifteen functions in the entire application, dealing with Response, Request, Filenames, Directory Globbing, Cache Management, and HTML processing.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

Ambush Commander wrote:What would you recommend? My main concern with separating concerns is that it leads to a level of abstraction that is unnecessary for something as simple as this. There are fifteen functions in the entire application, dealing with Response, Request, Filenames, Directory Globbing, Cache Management, and HTML processing.
I would recommend creating a library of helper functions. None of those function have much to do with each other, they just wrap PHP library calls. To me, calling it XHTMLCompiler_isdir() is much more honest.
Last edited by Christopher on Sun Mar 04, 2007 3:20 pm, edited 1 time in total.
(#10850)
User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

Post by Ambush Commander »

If given the choice, I'd use the real functions, but then it's a PITA to mock them. With the god object, I can do this:

Code: Select all

function test_normalize_index_File() {
        $this->mock->expectOnce('isDir', array('main.html'));
        $this->mock->setReturnValue('isDir', false);
        $this->assertEqual(normalize_index('main.html', 'index.html'), 'main.html');
    }
I wasn't originally planning on unit testing this application, but when I tried to do a function renaming I realized how running blind I was. The unit tests, even if just for code coverage, help me cohesively test the entire app in one go.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

I think I am not clear what is being tested and what is supporting testing?
(#10850)
User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

Post by Ambush Commander »

Alright. Here's the setup.

- We have several PHP files that serve as entry points into the application. Not unlike page controllers.
- These pages use helper functions to do duplicated logic. These functions are what we want to test.
- The helper functions call lots of internal PHP functions. Safe ones are like strpos and substr, unsafe ones are like header and scandir.

I need some way to test helper functions that use unsafe internal functions. So, instead of calling unsafe functions directly, I pass them through my god class wrapper, which acts purely as a facade, and implements no logic. When I need to test, I replace the god class with a server stub, so I can carefully control the testing environment.

Ideally speaking, we would have a much finer granularity of objects to facilitate server stubs. However, I've been balking at implementing objects, because many of them would be effectively one or two methods and no data.
Post Reply