Jump to content

Structure suggestions


448191

Recommended Posts

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.

[list]
[*]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().
[/list]

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.
Link to comment
Share on other sites

[quote author=effigy link=topic=107807.msg432949#msg432949 date=1158086818]
Are we allowed to see any code? :) Is there any way to reduce all of these bullet points into a small example?
[/quote]

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):
[code]<?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();
?>[/code]

Backbone::startApplication()
[code]<?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();
}
}
?>[/code]

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():
[code]<?php
public function process($menuDisabled=false){
ApplicationState::process($menuDisabled);
self::endApplication();
}
?>[/code]
ApplicationState::process():
[code]<?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']);
}
}
?>[/code]
ApplicationState::processUri():
[code]<?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);
}
}
}
?>[/code]
ApplicationState::processItem() (still very much in development):
[code]<?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;
}
?>[/code]
Link to comment
Share on other sites

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():
[code]<?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();
}
?>[/code]

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.
Link to comment
Share on other sites

[quote author=Daniel0 link=topic=107807.msg433540#msg433540 date=1158163402]
Here is an example of how I/we have done it in [url=http://icebb.net]IceBB[/url]: http://svn.sourceforge.net/viewvc/icebb/trunk/icebb.php?revision=HEAD&view=markup
[/quote]

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.
Link to comment
Share on other sites

[quote author=Daniel0 link=topic=107807.msg434017#msg434017 date=1158221668]
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).
[/quote]

Ok, I'll be more specific, I'm going to implement Design patterns, dropping the global array as a means to store objects and [url=http://www.phpit.net/article/simple-mvc-php5/]creating a MVC type[/url] system.
Link to comment
Share on other sites

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:
[code]<?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'));
}
}
?>[/code]

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:
[code]<?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);
}
?>[/code]
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.