Chronicles of a build - the theme framework 08

Having played a little with the framework I’ve decided to sacrifice some comfort, when it came to the Main class, in favor of flexibility.
Right now the Main class uses static methods alone to work its magic and, while requiring little overhead initialization code, is really becoming too stateful to remain static.

Planning from the end

If I have to go from the comfort of using the Main class this way

// file index.php
<?php
Theme::show('head');
Theme::show('header');
Theme::show('sidebar');
Theme::show('content');
Theme::show('footer');

to something else.
I’m planning my transition from the end. The need for a proper stateful Main class is born out of usage and an idea that came to my mind while browsing Stackoverflow: I like to work on separate files and already laid out the structure to have separate script and style files for each controller; this would mean flooding a page with small script and styles requests and it’s very bad practice.
To solve the problem I’d like to fiddle with the idea of concatenating and minifying scripts and styles in PHP. I will go into more detail in a next post when I myself will have clearer ideas. Right now the need arises for the Main class to keep track of Controllers shown on a page and their optional scripts and styles. Furthermore the actual implementation of the Main class will load into memory any Controller it can find whether it’s used or not: not efficient.

The new end

After some thought my new dream framework would allow me to write the same index.php file like

// file index.php
<?php
$theme = Theme::getInstance();
$theme->showHead();
$theme->showHeader();
$theme->showSidebar();
$theme->showContent();
$theme->showFooter();
$theme->end();
?>

Where Theme is still a class extending the Main one, Main is implemented using the Singleton pattern and the showSomething methods are calls to the magic __call method. Right now they seem more legible than calls like

$theme->show('header');

The end method is needed to, I’m guessing here, trigger the enqueing/minifying/concatenation of scripts and styles. Again: I’ll be much wiser in a week or so I guess.

New tests for a new implementation

First things first I will use the Singleton interface that I’ve already implemented in TAD libraries and will set-up some tests for it. Since I’m implementing the interface in an abstract class that’s meant to be extended the tests will be made on extending classes and have their main ratio in making sure that the instance returned is proper in its uniqueness and class.

public function testGetInstanceReturnsInstanceOfExtendingClass()
{
    $i = TestMain::getInstance();
    $this->assertNotNull($i);
    $this->assertInstanceOf('\TAD\Test\TestMain', $i);
}
public function testGetInstanceReturnsInstanceOfProperClass()
{
    $testMainInstance = TestMain::getInstance();
    $this->assertNotNull($testMainInstance);
    $this->assertInstanceOf('\TAD\Test\TestMain', $testMainInstance);
    $testMain2Instance = \TAD\Test\TestMain2::getInstance();
    $this->assertNotNull($testMain2Instance);
    $this->assertInstanceOf('\TAD\Test\TestMain2', $testMain2Instance);
    $testMain3Instance = \TAD\Test\TestMain3::getInstance();
    $this->assertNotNull($testMain3Instance);
    $this->assertInstanceOf('\TAD\Test\TestMain3', $testMain3Instance);
}
public function testSetInstanceSetsInstance()
{
    $o = (object)4;
    TestMain::setInstance($o);
    $i = TestMain::getInstance();
    $this->assertEquals($o, $i);
}
public function testSetInstanceWillThrowForNonObjects()
{
    $nonObjects = array(
        'string',
        4,
        array() ,
        array(
            'foo',
            'baz',
            'bar'
        )
    );

    foreach ($nonObjects as $nonObject) {
        $this->setExpectedException('InvalidArgumentException', null, 1);
        TestMain::setInstance($nonObject);
    }
}

Some additional tests are made to ensure that only objects are accepted as legit setInstance arguments. The final code is below

// file Main.php

class Main implements \TAD\Interfaces\Singleton {

    protected static $instances = array();

    /**
     * Returns an instance of the Main-extending class
     * @return object An instance of the Main-extending class
     */
    final public static function getInstance()
    {
        $class = get_called_class();
        if (!isset(self::$instances[$class])) {
            self::$instances[$class] = new $class();
        }

        return self::$instances[$class];
    }
    /**
     * Sets the instance of the Main-extending class
     * @param mixed $instance The new value for the Main-extending class instance
     */
    final public static function setInstance($instance = null)
    {
        if (!is_null($instance) and !is_object($instance)) {

            throw new \InvalidArgumentException("New instance value must either be an object or null", 1);
        }
        $class = get_called_class();
        self::$instances[$class] = $instance;
    }

}

The caveat here is that the setInstance method cannot be used to unset the actual instance due to the fact that unsetting and getting it, via subsequent calls of setInstance and getInstance, will trigger the construction of a new instance.