Page 1 of 1
Designing reusable libraries.
Posted: Sat Apr 29, 2006 9:12 am
by Hurreman
I'm currenly in the progress of rewriting many of my classes/libraries from scratch, to enable maximum reusability.
However, I seem to have reached a stop, and need some brainstorming.
Here's how most of my pages have looked so far:
Code: Select all
<?php
// Start sessions
session_start();
// Start output buffering
ob_start();
// Include the loader module, which loops through all required plugins and loads them
require_once('modules/Loader.php');
// Define required plugins.
$plugins = array('MySQL','error_handler','Page');
// Include all plugins
$Loader = new Loader('modules/',$plugins);
// Initiate the custom error handler without logging (0).
$error = new error_handler(0);
// Connect to MySQL Database
$MySQL = new MySQL('host','database','user','pass') or trigger_error('MySQL Connection Failed');
// Page, handles content
$Page = new Page($MySQL,$_GET['page']);
?>
That takes care of all the loading of modules/libraries/classes and sets up the MySQL connection.
Now, to actually put some content on the page:
Code: Select all
<html>
<head>
<title>
<?php
$Page->getTitle();
?>
</title>
</head>
<body>
<div>
<?php
$Page->printBody();
?>
</div>
</body>
</html>
<?php
ob_flush();
?>
This is bit simplified from what I usually have, but gives an idea. This isnt' too bad, but when I add more modules and plugins, it gets worse...
Let's say I next add another module called News, which will handle the printing of newsposts on the front page. Then I'd replace $Page->printBody() with an switch of if/else statement. Something like:
Code: Select all
if($_GET['page'])
{
$Page->printBody();
}
else
{
$News->printNews();
}
You get the idea. After a while, it all just gets a bit out of hand. It's all one big CMS with plugins like Forum, Image Gallery, Calendar and so on. What I'm not trying to do is a total rethink of how I design my pages, perhaps separate layout from logic with a simple template engine, and use __autoload() for dynamic module loading?
Any ideas are welcome!
Posted: Sat Apr 29, 2006 9:32 am
by feyd
This is why I use template engines.

Re: Designing reusable libraries.
Posted: Sat Apr 29, 2006 10:28 am
by timvw
Hurreman wrote:Code: Select all
// Define required plugins.
$plugins = array('MySQL','error_handler','Page');
// Include all plugins
$Loader = new Loader('modules/',$plugins);
Why do you assign the new Loader instance to a variable if you're not going to use it anymore?
(Which makes me wonder why you have to initialize it in the first place)
Since you tell the loader which plugins he has to load, i would expect that this loader can actually return instances of your plugins... Notice that in this case all your initialization is centralized in the loader.. Possible advantage: if you change your mysql credentials you only have to change them in the loader instead of in all your files..)
Code: Select all
$loader = new Loader('modules/');
$mysql = $loader->GetMySQL();
$page = $loader->GetPage($mysql, $_GET['foo']);
Can't really say much about the html generation itself since i'm in a constant flux between template systems and classes that generate html...
Posted: Sat Apr 29, 2006 11:24 am
by Christopher
What you need is a Front Controller. You are headed that direction with your Loader script. A Front Controller is like your Loader but it loads a one of many Page classes based on the $_GET['page'] parameter. Then you can have different templates for each page rather than having to put logic in the template to sort out which page you are on.
You can see examples I have done
hereand
here. Most frameworks these days implement more complex Front Controllers but they all do the same thing.
Posted: Sat Apr 29, 2006 12:36 pm
by Hurreman
I looked through your skeleton code, and it was a bit over my head, even though I think I understand the concept.
I feel some ideas in the back of my head, I just need some time before they find their way through the door, so to speak.
Think I'll sit down and create some diagrams/mindmaps of what I'm actually trying to achieve.
Posted: Sat Apr 29, 2006 4:27 pm
by Christopher
If you copy and paste the code that matthijs posted (in the thread I linked to) into the filenames he gives, you can get the simple Front Controller working. Then if you look at matthijs' "contacts" class you will see that it is similar to your script above. Maybe try to adapt that. It is actually easier than it looks and you will see the benefits as your code grows.
Posted: Sun Apr 30, 2006 6:13 am
by Hurreman
I did as you suggested
arborint, and actually got it running with a few modifications.
Earlier I created one MySQL-object that was reused over each page by passing it by reference.
So I'm thinking of how I could create a dynamic way of passing arguments into the loaded "actions" or classes without having an switch-case like earlier. Perhaps creating a local variable at the top of each file which specifies additional dependencies.
Take my Page-class for example. A bit simplified, it looks something like this:
Code: Select all
class Page
{
private $db = null;
private $page = null;
public function __construct(&$db,$page)
{
$this->db = $db;
$this->page = $page;
}
public function printBody()
{
$query = 'SELECT pageBody FROM pages WHERE pageID=? LIMIT 1';
$st = $this->db->createQuery($query);
$st->setParam(1,$this->page);
$rs = $st->execute();
while($rs->next())
{
echo '<p>' . $rs->getField('pageBody') . '</p>';
}
}
}
And many of my other classes that require database connection works in the same way, by using the already open MySQL object.
Variables like which page to view etc can easily be passed through the querystring instead, but required objects...
Perhaps something like this could work:
Inside page.php:
changed commandFactory ni FrontController.php:
Code: Select all
function &commandFactory($action)
{
$obj = null;
$filename = $this->action_dir . $action . '.php';
if (file_exists($filename))
{
include($filename);
if (class_exists($action))
{
// Try to find required objects..
if($args)
{
$obj =& new $action($this->$args);
}
else
{
$obj =& new $action();
}
}
}
else
{
trigger_error('File ' . $this->action_dir . $action . '.php could not be found!');
}
return $obj;
}
However, this would also require that the actual object in question (here MySQL) is already loaded and stored inside the FrontController.
Posted: Sun Apr 30, 2006 2:43 pm
by Christopher
You have run into a classic problem, your action classes are dependent on some base objects that the action classes need to do their work. The solution is to "inject" those base objects into your action. This pattern is amazingly called
Dependency Injection. There are many very fancy ways to do this, and I still trying to figure out many of the more complex ones. The simplest way is what is called a Service Locator. That can be created as a generic Registry or as a Context object that contains specific methods to retrieve objects. There is an article about this (of which I understand about half)
here.
This might look like:
Code: Select all
// index.php
$locator = new ServiceLocator();
$locator->set('DB', new MySQLConnection());
$locator->set('Page', new Page());
$frontcontroller->execute($locator);
Code: Select all
// and your Action class would be:
class Page
{
private $db = null;
private $page = null;
public function __construct(&$locator)
{
$this->db = $locator->get('DB');
$this->page = $locator->get('Page');
}
...
The interesting thing going on here is that you now have a dependency on an interface instead of specific classes. From what I have read, that is actually a bigger deal than is seems. I know that it allows me to change the "Page" object type with zero code changes to the actions. It also is easier to write tests that mock an interface like the locator than static class names.
The second link I gave you
here has links to a slightly more complex version of the Front Controller that passes a Service Locator to the Actions. That code also shows using Request and Response objects, which in the other obvious thing you can do to make your code cleaner. These are usually injected into the Actions as well.
Posted: Mon May 01, 2006 8:05 am
by Hurreman
A service locator did the trick, even a very simple one!
Here's the code so far. No error checking to see if the dependency isn't loaded, but figured that's not the most important at this step.
Locator.php
Code: Select all
<?php
class Locator
{
private $objects = array();
// Empty construct
public function __construct()
{
}
// Add module to array
public function set($handle,&$class)
{
$this->objects[$handle] =& $class;
}
// Fetch module from array
public function get($handle)
{
return $this->objects[$handle];
}
}
?>
Then I made one small change to the
FrontControllers construct:
Code: Select all
public function __construct(&$locator,$action_dir='actions', $default_action='home', $error_action='error', $action_param='action')
{
$this->locator = $locator;
$this->action_dir = $action_dir;
$this->default_action = $default_action;
$this->error_action = $error_action;
$this->action_param = $action_param;
}
And the
command factory:
Code: Select all
private function &commandFactory($action)
{
$obj = null;
$filename = $this->action_dir . $action . '.php';
if (file_exists($filename))
{
include($filename);
if (class_exists($action))
{
$obj =& new $action($this->locator);
}
}
else
{
trigger_error('File ' . $this->action_dir . $action . '.php could not be found!');
}
return $obj;
}
index.php
Code: Select all
<?php
error_reporting(E_ALL);
include 'config.php';
include LIB_DIR . 'MySQL.php';
include LIB_DIR . 'Locator.php';
include LIB_DIR . 'FrontController.php';
$locator = new Locator();
$locator->set('MySQL', new MySQL('host','database','user','password'));
$fc = new FrontController($locator,'actions/','home','error');
$fc->execute();
?>
And last,
page.php
Code: Select all
<?php
class page
{
private $db = null;
// Construct
public function __construct(&$locator)
{
$this->db = $locator->get('MySQL');
$this->page = $_GET['id'];
}
// Print the header
private function printHeader()
{
echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">';
echo '<html xmlns="http://www.w3.org/1999/xhtml">';
echo '<head>';
echo '<title></title>';
echo '<link rel="stylesheet" href=""/>';
echo '</head>';
echo '<body>';
}
// Print the body
private function printBody()
{
$stmt = $this->db->createQuery('SELECT pageBody FROM pages WHERE pageID=?');
$stmt->setParam(1,$this->page);
$rs = $stmt->execute();
while($rs->next())
{
echo '<p>' . $rs->getField('pageBody') . '</p>';
}
}
// Print footer
private function printFooter()
{
echo '</body>';
echo '</html>';
}
public function execute()
{
$this->printHeader();
$this->printBody();
$this->printFooter()
}
}
?>
So by visiting index.php?action=page&id=1, I should get some output like so:
Code: Select all
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<link rel="stylesheet" href=""/>
</head>
<body>
<p>You are viewing a page body which is being dragged from a MySQL database, cool huh?</p>
</body>
</html>
I have to say, I like! Still a lot of mixed PHP/HTML, so a template class might be next since the MV are missing from MVC

Posted: Mon May 01, 2006 10:31 am
by Hurreman
After doing some thinking, I realised that if the Locator can't include and load modules on-the-fly, I'd end up doing:
Code: Select all
$locator->set('Class',new $class);
For every module, just in case they'll be needed by some for any of the actions.
I had a look at your Locator class
arborint, but thought I'd try to solve it myself. So here's what I was thinking:
Locator.php
Code: Select all
public function get($handle)
{
if(!isset($this->objects[$handle]))
{
if(!class_exists($handle))
{
@include_once($handle . '.php');
}
if(class_exists($handle))
{
// Optional array of additional arguments
$args = array();
// If there are more than one argument, pass them to the module
if(func_num_args()>1)
{
// Pass all arguments but the first one to the module
// (First argument is the module name)
for($i=1;$i<func_num_args();$i++)
{
$args[($i-1)] = func_get_arg($i);
}
$this->objects[$handle] =& new $handle($args);
}
else
{
$this->objects[$handle] =& new $handle();
}
}
}
return $this->objects[$handle];
}
Then, inside the action
page.php:
Code: Select all
public function __construct(&$locator)
{
$this->db = $locator->get('MySQL','hostname','database','username','password');
$this->page = $_GET['id'];
}
In case the MySQL-module isn't already loaded, page.php will also pass on the required arguments.
But if the index page looks like this:
Code: Select all
error_reporting(E_ALL);
include 'config.php';
include LIB_DIR . 'MySQL.php';
include LIB_DIR . 'Locator.php';
include LIB_DIR . 'FrontController.php';
$locator = new Locator();
$locator->set('MySQL', new MySQL(array('hostname','database','username','password')));
$fc = new FrontController($locator,'actions/','home','error');
$fc->execute();
There's no need for the extra arguments within page.php. The MySQL-module is a bad example, but that's all I have right now.
Any thoughts on this? Perhaps it's a very bad idea, so feel free to tell me if it is.
Posted: Mon May 01, 2006 12:32 pm
by silicate
Hey there,
I think you have taken one step forward and two steps back with your solution.
Although it is great that you have handled the fact that you might not need all of the junk that you can throw into the locator, now you have to specify the configuration of the database in more than one place. This is not so bad, but as you said in your first post, what happens if there are more modules and plugins that require different resources than page? What happens if you change the credentials for the MySQL database, are you going to chase down all of the places that you have the config?
If you added the ability to pass into the locator a factory for the MySQL object then you could still ask for the MySQL object using the get method. If the MySQL object wasn't instantiated then then locator could run the factory to build one before passing it back to you.
Later,
Matthew Purdon.
Posted: Mon May 01, 2006 12:48 pm
by Hurreman
Thanks
silicate, you're right.
I'm not quite sure what you mean by
factory, but this idea popped into my head as I read your post.
In the index file, instead of calling the locator and having it create an instance of MySQL, I could do as so:
Code: Select all
$locator->setArgs('MySQL','username','database','user','pwd');
And inside
Locator.php
Code: Select all
public function setArgs($handle)
{
$args = array();
if(func_num_args()>1)
{
// Separate $handle argument from the rest
for($i=1;$i<func_num_args();$i++)
{
$args[$i-1] = func_get_arg($i);
}
}
$this->objects[$handle]['args'] = $args;
}
And when $locator->get() is called:
Code: Select all
public function get($handle)
{
if(!isset($this->objects[$handle]))
{
if(!class_exists($handle))
{
@include_once($handle . '.php');
}
if(class_exists($handle))
{
// If the module has any arguments, pass them as well
if(isset($this->objects[$handle]['args']))
{
$this->objects[$handle] = &new $handle($this->objects[$handle]['args']);
}
else
{
$this->objects[$handle] =& new $handle();
}
}
}
return $this->objects[$handle];
}
Was it something like that what you meant by factory?
Posted: Mon May 01, 2006 1:17 pm
by Hurreman
I tried the code I posted, and well, it was a bit flawed and clumsy. So here's the new Locator:
Code: Select all
<?php
class Locator
{
private $objects = array();
private $args = array();
// Empty construct
public function __construct()
{
}
// Add module to array
public function set($handle,&$class)
{
$this->objects[$handle] =& $class;
}
// Set arguments
public function setArgs($handle)
{
$args = array();
if(func_num_args()>1)
{
for($i=1;$i<func_num_args();$i++)
{
$args[$i-1] = func_get_arg($i);
}
}
$this->args[$handle] = $args;
}
// Fetch arguments
public function getArgs($handle)
{
return $this->args[$handle];
}
// Fetch module from array
public function get($handle)
{
if(!isset($this->objects[$handle]))
{
if(!class_exists($handle))
{
@include_once($handle . '.php');
}
if(class_exists($handle))
{
// If the module has any arguments, pass them as well
if(isset($this->args[$handle]))
{
$this->objects[$handle] =& new $handle($this->args[$handle]);
}
else
{
$this->objects[$handle] =& new $handle();
}
}
}
return $this->objects[$handle];
}
}
?>
Posted: Mon May 01, 2006 11:32 pm
by EricS
I was in your shoes 3 years ago when I decided to learn OO programming and really trying to figure out how to write more re-usable code. I will tell you that I learned the most by studying code others had written and figuring out why they did things a certain way.
I would suggest two things if you want to get to where you are going as quickly as possible.
1. Read "Design Patterns" by the Gang of Four. Excellent book on solving complex problems with simple design techniques.
2. Look at several of the Rapid Application Web Frameworks that are already written and available for you to download and scrutizine freely. I'm betting a search for Rapid application frameworks on this board will give a good size list of frameworks to learn from. WASP seems to be a good one.
I personally, did not restrict my research to PHP. I also looked at Java and several other languages when I was researching OO design patterns and rapid application frameworks. I eventually modeled my own RAD on WebObjects. While I do not recommend you diving head first into WebObjects unless you want to experience a DEEP learning curve, my point is that getting a well rounded look at what's out there, including outside PHP, will pay you back large dividends in your programming experience. Take advantage of what some very smart people have already done for you.
Lastly, have an open mind and take advice with a grain of salt. Remember that what is right for the goose is not necessarily right for the gander. Only when you've had a LOT of programming experience (preferably some in different languages) will you really understand what is right for you and what is not. Rarely are thing black and white. Static typing vs Dynamic typing? Procedural vs OO? What makes you a good programmer is knowing when to use one thing vs another and why.
- Eric
Posted: Mon May 01, 2006 11:54 pm
by Christopher
You have other options as well. You may want your Page to create the database connection and simply have the Locator inject a Configuration object that holds the connection information (plus other values). Another idea is to turn you Page class into a base class that specific pages extend. This is often a Controller or View class depending on its duties. You could even create different base Page classes for the different types of pages in your system -- some needing a database others not.