Class Interaction

Not for 'how-to' coding questions but PHP theory instead, this forum is here for those of us who wish to learn about design aspects of programming with PHP.

Moderator: General Moderators

Post Reply
User avatar
TheMoose
Forum Contributor
Posts: 351
Joined: Tue May 23, 2006 10:42 am

Class Interaction

Post by TheMoose »

My latest endeavor of miniature proportions has caused a bit of confusion on some of the class interactions I have going on, so I am in need of guidance.

My "project" is just to create a web-based chat system, using OOP (because I still want to learn more). Classes involved in my problem (thus far) are: Message, User, UserList, UserFinder (singleton), DB (singleton), and DBQueryParser.

The Message class has the basic properties, body, who it's from, and a UserList of Users to send the message to. The Message class only has a UserList if the message is a Message_Broadcast (Message is the parent, Broadcast is the only child so far), otherwise the TO of the message is just a User. The UserList uses the UserFinder class to search the database (via DB class, which uses the DBQueryParser to clean the query) for a user, and returns a User object that represents the user (imagine that!).

So the interaction looks something like: Message -> UserList -> UserFinder -> DB -> DBQueryParser

The hold up I'm running into is that both DB and DBQueryParser both work off a global config ($config) to base their actions off (ie: db host/user/pass, table names/aliases, etc), and how to alleviate having to create an instance of DBQueryParser and then pass it to whatever needs to parse their queries.

Here's the code I have so far (edited to just show implementation, not actual execution methods):

Code: Select all

interface moose_IMessage { }
class moose_Message implements moose_IMessage {
	public function addRecipient($user) {
		$this->my_to = $user;
	}
}
class moose_Broadcast extends moose_Message {
	public function addRecipient($user) {
		$this->my_to = new moose_UserList();
		$this->my_to->addRecipient($user);
	}
}
class moose_User { }
class moose_UserList {
	public function __construct() {
		$this->my_user_finder = moose_UserFinder::getInstance();
	}
	public function addUserByID($userid) {
		$this->my_user_array[] = $this->my_user_finder->findById($userid);
	}
}
class moose_UserFinder {
	private function __construct() {
		$this->finder_db = MySQL::getInstance();
	}
	public function findById($userid) {
		$result = $this->finder_db->query("SELECT * FROM @users WHERE #id=$userid");  // calls the Parser that is part of MySQL class
	}
}
class MySQL implements IDatabase { }
Just typing this post has helped a lot, and I have answered most of my own question(s), but I posted anyways for other solutions/critique/others to use as reference.
User avatar
Ollie Saunders
DevNet Master
Posts: 3179
Joined: Tue May 24, 2005 6:01 pm
Location: UK

Post by Ollie Saunders »

The hold up I'm running into is that both DB and DBQueryParser both work off a global config ($config) to base their actions off (ie: db host/user/pass, table names/aliases, etc), and how to alleviate having to create an instance of DBQueryParser and then pass it to whatever needs to parse their queries.
What's the problem with those exactly?

Things like this:

Code: Select all

public function __construct() {
                $this->my_user_finder = moose_UserFinder::getInstance();
        }
reek of strong coupling. What if you want to change the way you find? I simple fix might look like this.

Code: Select all

public function __construct($finder = null) 
{
    if (!$finder) {
        $finder = moose_UserFinder::getInstance();
    }
    $this->my_user_finder = $finder;
}
or if you'd rather not have to pass the instance as construction time, generally you don't want to force that unless it's necessary:

Code: Select all

private $_finder;
public function getFinder()
{
    if (!$this->_finder) {
        $this->_finder = moose_UserFinder::getInstance();
    }
    return $this->_finder;
}
public function setFinder($finder)
{
    $this->_finder = $finder;
}
the only requirement of this technique being that you must use getFinder() to get finder object, but this also has the added advantage of only creating an object when one is required.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

I would do:

Code: Select all

class moose_UserList {
        public function __construct($user_finder) {
                $this->my_user_finder = $user_finder;
        }

class moose_UserFinder {
        private function __construct($db) {
                $this->finder_db = $db;
        }
I doubt that when the user list is created it would be any inconvenience to instantiate a finder at the same time, but it makes it clean, clear and makes the moose_UserList dependent on an interface rather than a specific class.

The database connection class is usually considered to be in a different tier (datasource) of your domain model so I like there to be explicit dependencies for all to see in the code between tiers.
(#10850)
User avatar
Ollie Saunders
DevNet Master
Posts: 3179
Joined: Tue May 24, 2005 6:01 pm
Location: UK

Post by Ollie Saunders »

Yeah I think you are right arborint, those dependencies should be embraced.
User avatar
TheMoose
Forum Contributor
Posts: 351
Joined: Tue May 23, 2006 10:42 am

Post by TheMoose »

ole wrote:
The hold up I'm running into is that both DB and DBQueryParser both work off a global config ($config) to base their actions off (ie: db host/user/pass, table names/aliases, etc), and how to alleviate having to create an instance of DBQueryParser and then pass it to whatever needs to parse their queries.
What's the problem with those exactly?
The problem I had initially (if I can recall correctly, since I already changed it around a bit :P) was that any time a query needed to be ran, it needed a DBQueryParser with it, and that any time a DBQueryParser was created, I had to pass it the config values. What I ended up doing was allowing that to happen, but when a query is run, it creates a default Parser and uses the DB's config values.

Code: Select all

// MySQL wrapper
public function query($sql) {
	if(!isset($this->parser))
		_setup_aliases();
	$sql = $this->parser->parse($sql);
	// rest of query function
}
private function _setup_aliases() {
	$this->parser = new MySQLQueryParser("@", "#");
	$tables = $this->mconfig['db']['tables'];
	$prefix = $this->mconfig['db']['prefix'];
	foreach($tables as $alias => $name) {
		$this->parser->addTableAlias($prefix . $name, $alias);
	}
}
public function setParser($parser) {
	$this->parser = $parser;
}
private function __construct($config, $parser = null) {
}
This way you can run a query quickly and easily without having to set up extra objects, but if you want/need to set different values in the Parser, you can set a different parser with the setParser($p).

I did the same with the userfinder/userlist deal similar to what Ole had posted.
arborint wrote: The database connection class is usually considered to be in a different tier (datasource) of your domain model so I like there to be explicit dependencies for all to see in the code between tiers.
Elaborate? Is that something I am missing by linking the userfinder to a DB object (it is loose, and to an interface and not the object)?
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

TheMoose wrote:Elaborate? Is that something I am missing by linking the userfinder to a DB object (it is loose, and to an interface and not the object)?
This code has a dependency on the MySQL class:

Code: Select all

class moose_UserFinder {
        private function __construct() {
                $this->finder_db = MySQL::getInstance();
        }
Whereas this code has a dependency on a generic database connection interface (could be MySQL, Postgres, SQLite, mock...)

Code: Select all

class moose_UserFinder {
        private function __construct($db) {
                $this->finder_db = $db;
        }
(#10850)
User avatar
TheMoose
Forum Contributor
Posts: 351
Joined: Tue May 23, 2006 10:42 am

Post by TheMoose »

arborint wrote:
TheMoose wrote:Elaborate? Is that something I am missing by linking the userfinder to a DB object (it is loose, and to an interface and not the object)?
This code has a dependency on the MySQL class:

Code: Select all

class moose_UserFinder {
        private function __construct() {
                $this->finder_db = MySQL::getInstance();
        }
Whereas this code has a dependency on a generic database connection interface (could be MySQL, Postgres, SQLite, mock...)

Code: Select all

class moose_UserFinder {
        private function __construct($db) {
                $this->finder_db = $db;
        }
Yeah that's what I thought you were aiming at. I did change it to the second interfaced version so it will be DB independent, so long as the class implements the basic wrapper interface I have setup
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

I tend to find two cases. First, like your UserFinder, where it is pretty easy to instantiate objects all in one place and pass them to constructors as needed. It may be a line or two more code, but the general flexiblity is more important to me.

The second case is where it is not that easy or convenient. In those cases I tend to lean on the framework to do the injection for me, either directly or via a Registry or Service Locator. It may be an extra step, but it provides a consistent method to access these more global objects.
(#10850)
Post Reply