Jump to content

CMS's, patterns and the full monty


redbullmarky

Recommended Posts

Hi all

This is probably going to seem like alot of ground to cover, but just wanted to juggle some thoughts.

 

Lets say I wanted to build a CMS. Now - CMS's are very good for content, but if for example I wanted to create a part of my site as a dedicated jobs board/product catalogue, etc, a regular content page is obviously not going to work as it's more 'dynamic' than that.

 

I have static content sites, I have job board sites, as well as newspaper sites, etc and I'm wanting to merge all the code into a very simple to use system that i can use over and over for building other sites. The key areas for me, I guess, is a very simple back-end admin system that I can plug in different modules (Content, Users Management, Calendar, etc) and an equally flexible front-end system that can react to whatever content I'm trying to spit out - ie, a job listing page, a contact page, a simple article, etc.

 

Now my problem comes in terms of developing a nice, plugin-friendly structure that keeps components, etc as seperate as possible but also allows them to communicate if needs be. For example - if I write an article within the Content module, I'd probably like the Calendar to automatically put a summary on itself for that article. But if I was to remove the Content module altogether, then calendar should not break. I'm probably getting along the lines of some form of 'hooking' system as mentioned in another thread?

 

Also - if I was to have a 'Reports' module that was able to summarise stuff going on in my whole system (number of articles, number of jobs, registered users today, etc) then how could I get this to automatically react to a new module being installed so their own stats would also appear in my reports module? sure, I could make the 'Reports' module a Core (required) class so that it's always there, but it's just an example of what i'm after - independence but interactive at the same time, I guess.

 

 

On another note comes my 'Content' manager - I'm thinking of the idea of having ALL content driven by simple plugins. The idea I had for their structure was:

 

<?php
class MyPlugin extends Plugin
{
   protected $params = array();

   public function view() {
      // options here are:
      // 1, make amendments to the variables that would be passed to the view, based on $params
      // 2, return the output that gets appended to, for example, $body or $left
   }

   public function edit(GUI $gui) {
      // add extra fields, tabs, etc based on what this plugin needs, and return the newly modifed form object for Plugin's caller to render
      return $gui;
   }
}
?>

 

so when i set up the page, i add a 'content' plugin called 'title' with params that specifiy that it's a single line input field, a 'content' plugin called 'body' with params that specifiy it's a wysiwyg or textarea. one of the params would also say which variable to assign the plugin output to. so the 'body' one might output to a template variable called 'pagebody', but i might also then have a plugin called 'contact' which appends this pagebody variable with a contact form.

 

then, when actually editing the page, all i do is set up an instance of 'GUI' (basically a container for HTML generating classes - eg, new TextInput(), new HtmlForm(), etc) and pass it through each of the page plugins' edit method, and it adds the parts to the editing interface that are required.

 

the idea being that each 'page' in my site is build of plugins that hold particular parameters including one that says which variable in my template to prepend/append/overwrite to (eg, title, body, left, etc) - and once the 'page' has been loaded, it's passed through the view method of each plugin in order that it's been set up on the page, each of which 'manipulates' the page before the final variables are sent to the template.

 

now - maybe i'm getting WAY too complex here with my thoughts and methods, and maybe i'm being too obsessed about plugins and stuff, but if anyone can share any nice, cleaner ways of achieving any of my above ramblings, that would be great

 

apologies if some bits of that make no sense - just ask for clarification.

 

Cheers

 

edit ps, for the more literal, serious developer out there, please forgive my possibly incorrect use of words like 'components', 'plugins', 'modules', etc - all i mean by all of them is "chunk of system that can be thrown into the pot".

Link to comment
Share on other sites

Fortunately (or actually not), terms like "module", "plugin", "component" are very loose terms, subjective even.

 

There is however the "Plugin" pattern (one of PoEAA "base" patterns), which does lend a hand in coming to a concrete solution for the problem most people refer to when desiring a "pluggable" or "modular" system. And it's not exactly rocket science either. It's intent: to link classes during "configuration time".

 

A simple example:

 

Define a "Separated Interface" for the plugins. That's a fancy term (pattern) for a simple thing: say you have a client called 'Foo'. The Separated Interface could be Foo_Plugin_Interface, and some concrete plugins could be Bar_Render_Z and Meh_Assemble_X. Notice that the concrete plugins are not (necessarily) in the same package. They do share the same interface, or at least the part Foo relies on, as defined in Foo_Plugin_Interface.

 

Next, you need a Factory (or Broker) to instantiate/pass the plugins to the client. Of course, this factory needs a configuration source to find the available plugins (whether you do this based on file system location or configuration source is open, though would recommend the latter). Now, the client will simply query the Factory/Broker for plugins and through the unified interface, use them for whatever purpose they are intended. If the client should be able to function without any plugins at all, you should handle that as well.

 

This principle is applicable at all levels of your application.

 

That should handle your 'Content' and 'Reports' examples. The 'GUI' example is perhaps better handled in a different way, though that assessment is not much more than intuition at this point. I'll have to get with you on that one later.

 

Sorry for the late response, I've been stressed for time lately.

 

Link to comment
Share on other sites

hmmm i'll have to do some more reading of that one. I have come across factories a fair bit recently but just probably need to read up a bit first so i can build a solid implementation. i suppose in many ways i don't want to jump the gun too much - I have a handful of sites i personally need to build (and will need to easily maintain long term) so pretty much need a solid foundation for my CMS that's unlikely to change much as it grows.

 

On a side note - I actually had a look at the way EyeOS (the webtop) do it, and it's pretty slick IMO - tho again, not sure if it's the best way or not.

 

Thanks for your help - anything else you come up with tho would be much appreciated.

 

*off to do some reading*

Link to comment
Share on other sites

Here's simplified example I threw together using a plugin Factory:

 

<?php
class Foo_StringManipulator {

public function __construct(Foo_StringManipulator_PluginFactory $pluginFactory){
	$this->pluginFactory = $pluginFactory;
}

public function manipulateInput($string){
	$string = $this->scramble($string);
	foreach($this->pluginFactory->getAll() as $plugin){
		$string = $plugin->input($string);
	}
	return $string;
}
private function scramble($string){
	//do string scramble
}

}
interface Foo_StringManipulator_Interface {

public function input($string);
public function output($string);
}
class Bar_Codec_Utf8 implements Foo_StringManipulator_Interface {

public function input($string){
	return utf8_encode($string);
}
public function output($string){
	return utf8_decode($string);
}
public function doSomethingTerriblyInterestingThatDoesntConcernFoo_StringManipulator($string){

}
}
?>

 

Using a plugin "broker" is not that much different. Except instead of just a Factory, it uses the Message Broker pattern. It is perticularly usefull if you have "pluggable" components throughout your system, as it employs a central point of Plugin distribution.

 

http://www.enterpriseintegrationpatterns.com/MessageBroker.html

Link to comment
Share on other sites

As for keeping you different modules separate, I think as long as you're smart about it, it should not be a problem. If ModuleA uses Calendar module, then it's a dependency. You just have to have a way to understand ModuleA's dependencies before allowing yourself(or users of your CMS) to install.

Link to comment
Share on other sites

is there any way/techniques i can use to de-couple the modules though? i mean, a Calendar is pretty much personal taste, so may not be required - but say there was a way for ModuleA to use the calendar (or any other plugin for that matter) IF it's there? i'm guessing some form of hooking system would help, but implementing that ontop of all the other stuff i'm thinking of is prooving to be a mindjob.

 

John - thanks for the example :)

Link to comment
Share on other sites

That's silly.

 

If ModuleA requries Calendar it requires Calendar. If it's not there, do you expect ModuleA to have it's own Calendar? If so, why use Calendar in the first place?

 

 

Or are you saying, you would like to do something like the following:

<?php
try {
   $cal = $pluginFactory::getPlugin("Calendar"); # throws exeption if plugin not installed
   $cal->init();
   $cal->draw();
} catch (PluginNotFoundException $e) {
   // nothing
}

 

As long as your contract for plugins is strict and is met by the implementation, using 'optional' plugins shouldn't be too difficult.

Link to comment
Share on other sites

That's silly.

 

I'm not so sure.

There are plenty of 'fancy' bits that could go on a site that ultimately aren't essential to the running of the site, but could be nice.

 

For example - lets say ModuleA is part of a News section for the site. Now - when I'm displaying my site, it's not really much more than eye candy to have a nice pretty calendar with all my news items plotted on it next to my articles, based on the news article's date. So I decide to install the Calendar. Now somehow - the News module is now aware that there is something installed that it can manipulate. It doesnt know or care what it is, it just has some form of common interface that it can talk through.

 

Then I decide to remove ModuleA and install ModuleB, which also uses the calendar. The calendar does not care that ModuleA is no longer there, as it's not dependent on it. But ModuleB now uses the calendar. Automagically.

 

Ok - maybe i'm waffling a bit - but does that make sense?

Link to comment
Share on other sites

Yes, it makes sense.

 

The problem with designing a system like this is, we're being very vague and saying: you can write any set of modules which can have optional dependencies of other modules which may or may not exist and it all has to work. Automagically :) Not only that, but this magical calendar has to guess :)

 

You could use the method I mentioned above to accomplish this, though. The init() function would take date/value pairs to be displayed?

 

 

 

Link to comment
Share on other sites

Here's a useless, but not exactly vague example:

 

<?php
interface GUI_Element_Plugin_Interface {
public function renderGUIElement(GUI_Element $element);
}
interface SomeOtherPluginInterface {
public function encodeObject(SomeClass $someObject);
}
class PluginNotFoundException extends Exception {}
class GenericPluginBroker {

private $classMap = array();
private $plugins = array();

public function __construct(array $config){
	foreach($config as $descriptor){
		if(isset($descriptor['classes'])){
			$this->plugins[$descriptor['handle']] = $descriptor['plugin'];

			if(!is_array($descriptor['classes'])){
				$descriptor['classes'] = array($descriptor['classes']);
			}
			foreach($descriptor['classes'] as $class){
				$this->classMap[$class][] = $descriptor['plugin'];
			}
		}
	}
}

public function getPlugins($object){
	$className = get_class($object);
	if(isset($this->classMap[$className])){
		$plugins = array();
		foreach($this->classMap[$className] as $pluginClass){
			$plugins[] = new $pluginClass();
		}
		return $plugins;
	}
		else {
		throw new PluginNotFoundException("No plugins found for class \"$className\"");
	}
}
public function getPlugin($handle){
	if(isset($this->plugins[$handle])){
		return new $this->plugins[$handle];
	}
		else {
		throw new PluginNotFoundException("Plugin \"$handle\" not found");
	}		
}
}
abstract class GUI_Element {

protected $childElements = array();
protected $plugins = array();
protected $output;

public function __construct(){}	

public function render(){

	if(count($this->plugins)){
		foreach($this->plugins as $plugin){
			$this->output = $plugin->renderGUIElement($this);
		}
	}
		else {
		$this->output = $this->doRender();		
	}
	return $this->output;
}
public function addElement(GUI_Element $element){
	$this->childElements[] = $element;
	return $this;
}
public function registerPlugin(GUI_Element_Plugin_Interface $plugin){
	$this->plugins[] = $plugin;
}
public function getOutput(){
	return $this->output;
}
protected abstract function doRender();
}
class FormElement extends GUI_Element {

public function __construct($submitUrl){
	$this->submitUrl = $submitUrl;
}
protected function doRender(){
	$this->output = "<form action='{$this->submitUrl}'>";
	foreach($this->childElements as $child){
		$this->output .= $child->render();
	}
	return "$this->output </form>";
}
}
class CalenderElement extends GUI_Element {

private $week;

public function __construct($week){
	$this->week = $week;
}
public function getWeek(){
	return $this->week;
}
protected function doRender(){
	return '<span>default calender for week ' . $this->getWeek() . '</span>';
}
}
class HtmlEntityEncoder implements GUI_Element_Plugin_Interface, SomeOtherPluginInterface {

public function renderGUIElement(GUI_Element $element){
	return $this->encode($element->getOutput());
}
public function encodeObject(SomeClass $someObject){
	return $this->encode($someObject->getSomething());
}
public function encode($string){
	return htmlentities($string);
}
}
class FancyCalendarRenderer implements GUI_Element_Plugin_Interface {

public function renderGUIElement(GUI_Element $calendar){
	return '<span>fancy calender for week ' . $calendar->getWeek() . '</span>';
}
}
class GUI extends GUI_Element {

private $pluginBroker;

public function __construct(GenericPluginBroker $pluginBroker){
	$this->pluginBroker = $pluginBroker;
}

protected function doRender(){
	$this->output = "<html><head></head><body>";
	foreach($this->childElements as $child){
		try {
			$plugins = $this->pluginBroker->getPlugins($child);

			foreach($plugins as $plugin){
				$child->registerPlugin($plugin);
			}
		}
		catch(PluginNotFoundException $e){
			//May want to respond to the absense of a plugin.
		}
		$this->output .= $child->render();
	}
	return "$this->output </body></html>";
}
}
$broker = new GenericPluginBroker(
array(
	array(
		'handle' 	=> 'fancy_calenders',
		'classes'	=> 'CalenderElement',
		'plugin' 	=> 'FancyCalendarRenderer'
	),
	array(
		'handle' 	=> 'html_entities',
		'classes'	=> array('CalenderElement', 'SomeOtherClass'),
		'plugin' 	=> 'HtmlEntityEncoder'
	)		
)
);

$gui = new GUI($broker);

$gui->addElement(new FormElement('/dosumbit'))
->addElement(new CalenderElement(4));

var_dump($gui->render());
?>

Link to comment
Share on other sites

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.