Jump to content


Photo

Structure suggestions


  • Please log in to reply
8 replies to this topic

#1 448191

448191
  • Staff Alumni
  • Advanced Member
  • 3,545 posts
  • LocationNetherlands

Posted 12 September 2006 - 05:45 PM

I could use some suggestions as to how best to structure my application. I must have changed it at least a dozen times, and am getting VERY tired of it, I want to do it right this time.

Let me first explain how the application works in general, right now.

  • index.php instantiates a class called Backbone. This is a global instance.
  • Backbone handles a variety of tasks upon construction, one wich is creating an instance of a Config object, used to read/store configuration options. It is made global so it doesn't have to read from the configuration file every time it needs an option.
  • The instance of Backbone is now fully constructed, index.php calls $backbone->startApplication(), wich starts preprocessing, no output is generated yet, it just checks and validates user input, and generates error messages upon validation failure.
  • Backbone creates an instance of Output, made global, so all following classes can use the same instance of the output.
  • Output, like the name suggests is used to assemble the XHTML output, and is reponsable for compression, caching and http headers.
  • From index.php, I add elements to the document held by the global instance of Output. These are default elements for this website.
  • I also set a 'default state' wich tells the Navigation class what to execute, unless told otherwise by either $_GET['state'], $_SESSION['state'] or the configured 'handler class' for a submitted form.
  • index.php calls $backbone->process(), that's when the action really takes place.
  • The Navigation class, in a single instance local to Backbone::process(), loads navigational items from an XML file, wich indicate what class, what method with what parameters to execute for a state, unless like I said, a form was submitted, in which case that data is read from the configuration file, which also hold the definition for all forms.
  • The classes that are executed by Navigation append their output directly to the global $output.
  • index.php calls $backbone->endApplication(), wich finishes up and calls $output->sendOutput().

For Ajax updates this works a little different, but let's take one thing at the time.


Any suggestions on improving performance, security or general structural improvements are more than welcome.

#2 effigy

effigy
  • Staff Alumni
  • Advanced Member
  • 3,600 posts
  • LocationIL

Posted 12 September 2006 - 06:46 PM

Are we allowed to see any code? :) Is there any way to reduce all of these bullet points into a small example?
Regexp | Unicode Article | Letter Database
/\A(e)?((1)?ff(?:(?:ig)?y)?|f(?:ig)?)\z/

#3 448191

448191
  • Staff Alumni
  • Advanced Member
  • 3,545 posts
  • LocationNetherlands

Posted 12 September 2006 - 07:22 PM

Are we allowed to see any code? :) Is there any way to reduce all of these bullet points into a small example?


Sure, it's not been classified. Yet.  :P

As for 'small' example: I'll try. Here's the main path (some things left out) for a traditional (non ajax) page generation:

This is the WHOLE content of my index.php (small isn't it):
<?php
function __autoload($className) {
   require_once 'lib/'.strtolower($className). '.class.php';
}
$backbone = new Backbone();
$backbone->startApplication();
global $output;
$output->addHeader('title',array('id'=>'title'),'domcontroltest');
$output->addCss(array('defaults','level1','level2','menu','subnav','login','textbox'));
$output->addJavascript(array('XmlHttpRequest','menu'));
$output->appendChunk($output->body,file_get_contents('indexHTMLFrame.html'));
$backbone->defaultState = 'online_offerte';
$backbone->process();
?>

Backbone::startApplication()
<?php
	public function startApplication(){
		//Start a new page.;
		global $config;
		global $output;
		$this->dtdType = $config->get('xhtmldtd');
		$this->charset = $config->get('charset');
		$output = new Output($this->dtdType,$this->charset);
		//If reading the WHOLE PAGE from cache, no further configration is nessesary.
		if($output->outputFromCache) {
			self::endApplication();
		}		
	}
?>

Oh yeah, I changed the name of Navigation to ApplicationState.
A new 'Navigation' is assigned tasks more specific to the actual navigation.
Backbone extends ApplicationState. ApplicationState extends Navigation.
Anyway, moving on to Backbone::process():
<?php
	public function process($menuDisabled=false){
		ApplicationState::process($menuDisabled);
		self::endApplication();
	}
?>
ApplicationState::process():
<?php
	public function process($menuDisabled=false){
		$this->menuDisabled = $menuDisabled;
		if(isset($_GET['update'])){
			self::getUpdates();			
		}
			else {
			if(isset($_SESSION['applicationState'])&& $GLOBALS['client']->ajax){ // 
				$_SERVER['REQUEST_URI'] = preg_replace('/([\/\w\d#\.,-]+)\??/','$1?state='.$_SESSION['applicationState'],$_SERVER['REQUEST_URI']);
			}
			self::processUri($_SERVER['REQUEST_URI']);
			Navigation::setTitle($GLOBALS['backbone']);				
		}
	}
?>
ApplicationState::processUri():
<?php
	public function processUri($uri){
		//Check if a state has been set by the uri, otherwise use default state.
		if(strrpos($uri,'state=') !== false){
			self::processItem(substr($uri,strrpos($uri,'state=')+6));
		}
			//No state set, get default.
			else {
			if($GLOBALS['backbone']->defaultState !== null){
				self::processItem($GLOBALS['backbone']->defaultState);				
			}
		}
	}
?>
ApplicationState::processItem() (still very much in development):
<?php
	private function processItem($item){
		global $backbone;
		$backbone->addDebug('Processing item: "'.$item.'".');
		$element = self::loadNavItem($item);
		global $backbone;
		extract($element);	
		//If the item was a main item:
		if(!isset($exec)){
			$subMenuItem = Navigation::getFirstChildItem($item);
			$backbone->addDebug('"'.$item.'" is a main item, recursing with child: "'.$subMenuItem.'"');
			self::processItem($subMenuItem);
		}
			else {
			//Sub item.
			if(!$this->menuDisabled){
				Navigation::appendMenu($GLOBALS['backbone'],$item);				
			}
			if(isset($handler)){ 
				$handler = new $handler;
				if(isset($params)){
					eval($handler->$exec."($params);");
				}
				else {	
					$handler->$exec($item);						
					//$backbone->endApplication();
				}
			}						
		}		
	}
	private function loadNavItem($item){
		if(!$element = Navigation::loadElement($item)){
			throw new Exception('Unregistered navigational item!');
		}
		return $element;		
	}
?>


#4 448191

448191
  • Staff Alumni
  • Advanced Member
  • 3,545 posts
  • LocationNetherlands

Posted 12 September 2006 - 07:25 PM

I'm trying to post the rest of my reply, but SMF is giving me issues.

Edit: Found the bug in the forum, and adjusted the code accordingly..

Backbone::endApplication():
<?php
	public function endApplication()
	{
		global $output;
		if(!$output->outputFromCache) {
                        //...
			if(isset($_SESSION)){
				session_write_close();	
			}	
		}
		//Attach form validation errors, if present:
		if(count($this->formValidationErrors)){
			if(!$errCont = $output->getElementById('errorcontainer')){
					throw new Exception('No form validation error container!');
			}
				else {
				//Assemble HTML:
				$html = implode("\n",$this->formValidationErrors);
				$errCont->nodeValue = $html;
			}
		}
		//Attach runtime to footer:
		$errLevel = $GLOBALS['config']->get('error_level');
		if($errLevel == 'debug'){
			$time = self::getRunTime();
			if($output->outputFromCache){
				//Whole page was read from cache, so we need a new page if we are to modify it's content.
				$newDom = new DomXhtml($this->dtdType,$this->charset);
				$newDom->loadXML($output->output);
				$foot = $newDom->getElementById('footercontainer');
				$foot->nodeValue = $time;
				$output->output = $newDom->saveXML();
			}
				else {
				$foot =$output->getElementById('footercontainer');
				$foot->nodeValue = $time;					
			}
			//Append debug info:
			$debugLayer = $output->appendElement($this->body,'div',array('id'=>'debugcontainer'));
			self::addDebug('GLOBALS');
			$output->appendChunk($debugLayer,self::getDebug());
		}
		//Send output to client.
		$output->sendOutput();
		//Just checking we didn't exceed the maximum execution time:
		self::getRunTime();
		//Maybe this is a premature end, so kill any further execution.
		die();
	}
?>

All objects that need to be available to classes (or 'modules' if you will) are globals, ensuring everyone is using the same instance.

These currently are: $backbone, $output, $config and $client.

#5 Daniel0

Daniel0
  • Staff Alumni
  • Advanced Member
  • 11,956 posts

Posted 13 September 2006 - 04:03 PM

Here is an example of how I/we have done it in IceBB: http://svn.sourcefor...EAD&view=markup

#6 448191

448191
  • Staff Alumni
  • Advanced Member
  • 3,545 posts
  • LocationNetherlands

Posted 13 September 2006 - 05:12 PM

Here is an example of how I/we have done it in IceBB: http://svn.sourcefor...EAD&view=markup


Pretty nice, but I'm going for a more 'OOPish' solution.

I posted a lot of resources in the 'resources' sticky, many of wich I haven't implemented myself yet. Right now I'm collecting knowlegde, after which I'll implement some of the concepts I learned.

#7 Daniel0

Daniel0
  • Staff Alumni
  • Advanced Member
  • 11,956 posts

Posted 14 September 2006 - 08:14 AM

I believe it is "OOP-ish"... the only things in the entire application that is written outside a class is those things in that file and the initiation file (index.php).

#8 448191

448191
  • Staff Alumni
  • Advanced Member
  • 3,545 posts
  • LocationNetherlands

Posted 14 September 2006 - 08:37 AM

I believe it is "OOP-ish"... the only things in the entire application that is written outside a class is those things in that file and the initiation file (index.php).


Ok, I'll be more specific, I'm going to implement Design patterns, dropping the global array as a means to store objects and creating a MVC type system.

#9 448191

448191
  • Staff Alumni
  • Advanced Member
  • 3,545 posts
  • LocationNetherlands

Posted 15 September 2006 - 06:24 AM

I'm applying a singleton pattern on all classes that can only have one instance per request, and those are also the classes one would put in a Registry. It might seem a bit much of the same (both are means to reuse a single class instance) but I'm sticking with it: a Registry provides a sinlge entrypoint you won't have with just Singletons, and Singletons prevent multiple instances for accidental instantiations of classes that aren't put in the registry.

That makes me wonder, if I get the Singleton's instance from outside the Registry, would I be using the same reference or do I need to indicate reference somewhere??

Enough with the rambling, here's some code: a new __autoload(er) I though up that automagicly adds singletons to the registry:
<?php
function __autoload($className) {
	if(stristr($className,'_')){
		$file = 'lib/'.substr($className,strpos('_',$className)).'/'.strtolower($className). '.class.php';
	}
		else {
		$file = 'lib/'.strtolower($className). '.class.php';
	}
	if(!file_exists($file)){
		return false;
	}	
	include_once ($file);
	//Check if the class has a 'getInstance' method, if so assume Singleton pattern and add instance to registry.
	if($className != 'Registry' && in_array('getInstance',get_class_methods($className))){
		$reg = Registry::getInstance();
		$reg[$className] = call_user_func(array($className, 'getInstance'));
	}
}
?>

Good or bad idea?

Edit:
Me thinks that I should pass the object by reference in methods that objects to the Registry. The object array is static, but the objects being added to it don't maintain reference untill they are actually added to that array. So I'm thinkin this will do the trick:
<?php
	function set($key, &$obj) {
		if (isset($this->registeredObjects[$key]) == true) {
			throw new Exception('Unable to register `' . $key . '`. Key already used.');
		}
		$this->registeredObjects[$key] = $obj;
		return true;
	}
	function offsetSet($offset, &$obj) {
		$this->set($offset, $obj);
	}
?>





0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users