Page 1 of 1

Small framework for an 'MVC' + registry pattern

Posted: Tue Sep 18, 2007 3:50 pm
by drcphd
I want to write my own CMS or framework (with basic features I always need) for my future projects.

I've tried two times before but had to stop due to lack of planning.

Lately I've been reading about MVC (Model-View-Controller) designs and registry patterns.
I've come up with a solution that I think might work.

I have a registry class to store all objects and a module handler which fetches output from the different modules (like a router).
But in this example you can load several modules simultaneously. That means you can assign module-output anywhere on the page (as main content, block content, etc).
The 'main module' (requested by user, think $_GET['mod']) will always be responsible for the main content (the body of the page). But every other module can also be loaded and create output for other parts of the page (like a poll in a block).

My approach is best explained by looking at a simplified framework I wrote.

Code: Select all

<?php

function __autoload($class_name)
{
	require_once($class_name . '.php');
}

class Registry
{
	// Registry to hold all objects
	private static $instance;
	private $vars = array();

	public static function getInstance()
	{
		if (!isset(self::$instance))
		{
			echo "Creating instance of registry<br />";
			$c = __CLASS__;
			self::$instance = new $c;
		}
		else
		{
			echo "Registry instance already exists.<br />";
		}
		return self::$instance;
	}

	public function __set($key, $var)
	{
		echo "set $key to registry class<br />";
		if (!isset($this->vars[$key]))
		{
			$this->vars[$key] = $var;
		}

		return true;
	}

	public function __get($key)
	{
		echo "get $key<br />";
		if (!isset($this->vars[$key]))
		{
			return null;
		}

		return $this->vars[$key];
	}

	public function __unset($var)
	{
		echo "<br />unset";
		unset($this->vars[$key]);
	}
}

class Database
{
	// Database class. Nothing here yet
	function __construct()
	{
		echo "Constructing DB<br />";
	}
	public function query($sql)
	{
		echo $sql . "<br />";
	}
}

class Template
{
	// Class to handle views. Includes set/get and output functions
	private $partials = array();

	function set($key, $var)
	{
		$this->partials[$key] = $var;
	}

}

class Modhandler
{
	// Register modules we want to use.
	// Use modules to assign content

	private $registry; // registry obj

	// main mod (requested by user)
	private $mainmod = '';

	// Holds module info, eventually fetched from database.
	private $modules = array();

	// Holds the object of each module class
	private $modObjects = array();

	function __construct($mainmod)
	{
		$this->registry = Registry::getInstance();

		$this->mainmod = $mainmod; // mainmod is the module requested by user. E.g. index.php?mainmod=articles
	}
	
	function delegate()
	{
		// Load $mainmod and let it assign some content.
		// If $mainmod lets us (maybe it wants to print and article and doesn't want header/footer?),
		//    we assign some content from other modules
		$mainmod = $this->mainmod;
		$this->loadModule($mainmod);
		
		$this->registry->template->set('content', $this->execute($mainmod)); // $mainmod decides our main content

		// Check if $mainmod let's is assign other stuff to the template (not added yet)
                // $mainmod also determines the title of the page.
	
		// What else do we want on our site? Maybe put some links in a block?
		// Then load the 'links' module.
		$this->loadModule('links');
		$this->registry->template->set('leftblock', $this->execute('links', 'viewcat', 6));
		
	}

	function loadModule($mod)
	{
		// Load a module's class
		echo "loaded module $mod <br/>";
		include('./modules/' . $mod . '/index.php');

		$this->modObjects[$mod] = new $mod;
	}

	function execute($mod, $action='index', $params=array())
	{
		// return output from requested module.
		return call_user_func_array(array($this->modObjects[$mod], $action), array($params));
	}

}

abstract class Module
{
	// Abstract class for all modules to inherit.
	// $mainmod (the module requested by user ($_GET['mod']) sets the config variables to whatever it wants
	abstract protected function index();

	private $config = array(
			'pagetitle' => '',
			'showheader' => true
		);
}

// Let's start
$db 			= new Database;
$modhandler		= new Modhandler($_GET['mainmod']);
$template		= new Template;
$registry 		= Registry::getInstance();

// Register objects to registry. Am I doing this correctly?
$registry->template	= $template;
$registry->db 		= $db;
$registry->mod		= $modhandler;


$modhandler->delegate();

echo "<br /><br />" . str_replace('  ', ' &nbsp; ', nl2br(print_r($GLOBALS, true)));
?>
Comments? Is this a stupid idea?
Also, if anyone has something to say about the object oriented programming in this example, that would be great. I'm still learning, and it would be nice to know if I've grasped the basics, e.g. how the registry object should be treated.

Thanks in advance.

Posted: Tue Sep 18, 2007 5:43 pm
by superdezign
Well, I'm certainly not the expert on MVC, but the way you handle modules is different than bthe way that I do. For me, the modules perform operations and then become a collection of content that is later injected into the view(s). I may have read your code wrong, but it looks like your modules are actually files, correct? I used to use a registry, but moved away from it in the interest of singleton objects. The registry could have worked fine, but I got lazy. :P

I'm not sure about this 'modhandler' thing that you have going. I have it so that my controller handles the module data. I may just be missing something.

Posted: Tue Sep 18, 2007 6:51 pm
by Christopher
Your Modhandler is a simple Front Controller and your modules are Actions (though not controllers that I can see). One of the great things about the Front Controller pattern is that it supports injection of Registry.

Since you are already going to the trouble of dispatching, it would be cleaner to inject the Registry into the actions (via their constructor or dispatched method). So add any object you always want available to the Registry. Pass the Registry to the Front Controller which in turn will pass it to the Action.

Posted: Fri Sep 21, 2007 10:04 am
by drcphd
arborint,
The modules have their own files (e.g. modules/poll/index.php) which contains a class (class Poll).

The module class can have several 'actions' (methods), but they must at least have an 'index' action.
These are called by Modhandler (which, as you say, acts like a Front Controller).

You suggested to pass the Registry to the Front Controller which in turn will pass it to the modules.
Like this?

Code: Select all

class Modhandler {
        function __construct($registry, $mainmod) {
                $this->registry = $registry;
        }

        // (.....)

        function loadModule($mod) {
                // include module class
                $this->modObjects[$mod] = new $mod($this->registry); // pass the registry to the module
        }



        function execute($registry, $mod, $action='index', $params=array()) {
                // return output from requested module.
                return call_user_func_array(array($this->modObjects[$mod], $action), array($params));
        }
}

And then, an example module:

Code: Select all

class articles extends Module
{
        private $registry;

        function __construct($registry)
        {
                $this->registry = $registry;
                echo "constructing articles.<br/>";
        }

        function index()
        {
                $this->registry->db->query('select'.....); // Now I can access the objects in the registry
                return "Hi, I'm article index";
        }
}

If that's correct, I have a question:

What about other objects that need the Registry? In my first example I used Registry::getInstance(), but are you suggesting that I pass the Registry to every class I instantiate? (database, input, config, template etc).

Thanks a lot for replying. It really helps.

And superdezign, thanks for your reply as well. I'm aware that my approach might not be like tra

Posted: Fri Sep 21, 2007 12:12 pm
by Christopher
drcphd wrote:You suggested to pass the Registry to the Front Controller which in turn will pass it to the modules.
Like this?
Yes, though I would pass it directly to the dispatched method as well -- so you don't need to save it in a property. (You might also want to look into allowing the method called to be specified with a parameter as well. That is the current practice)
drcphd wrote:What about other objects that need the Registry? In my first example I used Registry::getInstance(), but are you suggesting that I pass the Registry to every class I instantiate? (database, input, config, template etc).
This is a common concern, but I don't think it is actually much of a problem in actual implementations. I don't tend to pass the Registry to other objects that are not also Controllers -- because the don't need it. Usually the Controller will get an object from the Registry and pass that to other objects. And, passing objects down the hierarchy is actually a pretty good practice in the long run. It creates clear dependencies on interefaces rather than class names. And, it allows for polymorphism.

Posted: Sat Sep 22, 2007 12:02 pm
by drcphd
arborint wrote: Yes, though I would pass it directly to the dispatched method as well -- so you don't need to save it in a property.
Then all the dispatched methods must have take an additional argument - I was hoping I wouldn't have to force module writers (myself) to write that much :P
arborint wrote: (You might also want to look into allowing the method called to be specified with a parameter as well. That is the current practice)
If I understand you correctly, I'm already doing that.
return call_user_func_array(array($this->modObjects[$mod], $action), array($params));
arborint wrote: This is a common concern, but I don't think it is actually much of a problem in actual implementations. I don't tend to pass the Registry to other objects that are not also Controllers -- because the don't need it. Usually the Controller will get an object from the Registry and pass that to other objects. And, passing objects down the hierarchy is actually a pretty good practice in the long run. It creates clear dependencies on interefaces rather than class names. And, it allows for polymorphism.
I see. But I think I might have some classes that needs the registry: log, config, template... or maybe I won't? I'll have to look into it. But if I do, it's ok to pass the Registry to these objects as well?

Thanks again for replying. You're really helpful.

Posted: Sat Sep 22, 2007 5:13 pm
by Christopher
You action methods won't take an additional argument -- they will take a single argument. You can use it or not but because it is a Registry, that one argument is like an OO version of passing an array for variable arguments.

Yes to specifying the method name.

It is fine to pass the Registry object if necessary. In general, you only want to have as few and as specific dependencies as necessary.