Use collection of objects or single object collector

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

User avatar
s.dot
Tranquility In Moderation
Posts: 5001
Joined: Sun Feb 06, 2005 7:18 pm
Location: Indiana

Post by s.dot »

this has also been a confusing aspect to me while learning and utilizing object oriented practices

doing this....

Code: Select all

require 'one.class.php';
require 'two.class.php';
require 'three.class.php';

$one = new one();
$two = new two();
$three = new three();
just feels too "dirty" and I don't like doing it. creates overhead.
Set Search Time - A google chrome extension. When you search only results from the past year (or set time period) are displayed. Helps tremendously when using new technologies to avoid outdated results.
User avatar
RobertGonzalez
Site Administrator
Posts: 14293
Joined: Tue Sep 09, 2003 6:04 pm
Location: Fremont, CA, USA

Post by RobertGonzalez »

I agree. As I started to put the project together I started to realize that I had a bunch of objects floating around. I thought that it would be nice to be able to have one main object that binds all the others together, so instead of:

Code: Select all

<?php
$error = new error();
$mysql = new MySQL();
$config = new Config();
?>
and using a bunch of different objects all over, I could instantiate them with a 'main' object and use them from that:

Code: Select all

<?php
$myapp = new MyApp;
$myapp->initialize();
// That method would set up all the objects
if (!$myapp->mysql->connected) {
    $myapp->error->show_error($myapp->mysql->last_error_message);
}
?>
User avatar
Maugrim_The_Reaper
DevNet Master
Posts: 2704
Joined: Tue Nov 02, 2004 5:43 am
Location: Ireland

Post by Maugrim_The_Reaper »

Your initialisation is basically a bootstrap function where the classes are included and instantiated and stored for future use. I'm aware it's a limited and private implementation - but a few comments on the theory side...

1. It's not reuseable
2. It's aware of the class names
3. It instantiates everything - even if everything is not needed in all situations
4. It adds complex method usage - the extra ->mysql bit to all uses
5. It requires manual editing for external changes
6. It's heading for a Pattern you may not see yet...see below.
7. Add complex instantiation logic and it will turn into spaghetti ;)

You could for example fit your classes into a PEAR like naming convention where the class name bears a relationship to the file's location. So you would have a Namespace_Error class - say Everah_Error stored in ./Everah/Error.php. Then have a Registry/ServiceLocator...

I have not tested the below - take with a pinch of salt and skepticism until it is...;)

Code: Select all

<?php

// set include path to include the path to Everah's parent directory. Not Everah itself though...

set_include_path(get_include_path() . PATH_SEPARATOR . '/path/to/Everah/parent/directory');

// Use static methods - i.e. globally accessible from all classes

class Everah_Locator
{
	private static $registry = array();

	// ...

	public static function get($class)
	{
                // re: Class Name is the relative Path replacing / with _ (PEAR convention)
		$path_components = explode('_', $class);
		$class_path = implode(DIRECTORY_SEPARATOR, $path_components) . '.php';
		$registry_key = end($path_components);
		if(array_key_exists($registry_key ,self::$registry))
		{
			return self::$registry[$registry_key];
		}
		// else
		require_once $class_path;
		$object = new $class;
		self::$registry[$registry_key] = $object;
		return $object;
	}

	// ...
}

Code: Select all

// usage:

require '/path/to/Everah/Locator.php';

// Everah_Mysql is stored in the Mysql.php file at ./Everah/Mysql.php
$mysql = Everah_Locator::get('Everah_Mysql');

// you now have the Everah_Mysql object, which was required, instantiated and cached
// Further get('Everah_Mysql') calls will return the cached object reference

$mysql2 = Everah_Locator::get('Everah_Mysql');

// mysql and mysql2 contain a reference to the same object as cached in Locator
No guarantees this works, it's just illustrating a possible implementation of a static ServiceLocator/Registry where inclusion/initialisation is handled in addition to object caching on a lazy loading basis - i.e. only initialise when/if required. If the initialisation logic were more complex, one might include the ability to call a Factory method which handles such logic separately.

Notably, a ServiceLocator is a once off class, reuseable with the same PEAR convention elsewhere, and fairly simple to Unit Test. So we currently have:

- Singleton
- Registry
- Static ServiceLocator (doesn't have to be static - pass by parameter)

Other possibility is Dependency Injection - something I'm not very familiar with in PHP though implementations exist...
Last edited by Maugrim_The_Reaper on Sun Oct 22, 2006 9:06 am, edited 1 time in total.
User avatar
Maugrim_The_Reaper
DevNet Master
Posts: 2704
Joined: Tue Nov 02, 2004 5:43 am
Location: Ireland

Post by Maugrim_The_Reaper »

viewtopic.php?p=274267

This topic seems related - I posted a summary of the related problems being raised here which looking back seems oddly concise for a rambling Irishman... Must have been unusually sober at the time ;).
User avatar
RobertGonzalez
Site Administrator
Posts: 14293
Joined: Tue Sep 09, 2003 6:04 pm
Location: Fremont, CA, USA

Post by RobertGonzalez »

WOW! Thanks Maugrim. That gives me a ton to think about tomorrow when I go through my structure. I truly appreciate the time you put into the explanation. Thanks again.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

scottayy wrote:doing this....

Code: Select all

require 'one.class.php';
require 'two.class.php';
require 'three.class.php';

$one = new one();
$two = new two();
$three = new three();
just feels too "dirty" and I don't like doing it. creates overhead.
Why? If you will use those three classes it that context then that is the cleanest and most concise way to do it. I would like to know what feels 'dirty' about it?
(#10850)
User avatar
Maugrim_The_Reaper
DevNet Master
Posts: 2704
Joined: Tue Nov 02, 2004 5:43 am
Location: Ireland

Post by Maugrim_The_Reaper »

WOW! Thanks Maugrim. That gives me a ton to think about tomorrow when I go through my structure. I truly appreciate the time you put into the explanation. Thanks again.
NP ;). If it was food for thought, it was worth the time... I'd make several changes if I added a unit tested version - making the registry key's unique and not just the final term in the class name, refactoring the class loading logic into a distinct "load" method (for classes with static methods).
just feels too "dirty" and I don't like doing it. creates overhead.
What overhead? You just described the only method of instantiating an object...
User avatar
RobertGonzalez
Site Administrator
Posts: 14293
Joined: Tue Sep 09, 2003 6:04 pm
Location: Fremont, CA, USA

Post by RobertGonzalez »

Ok, lets see if my studying has paid off...

I build a registry class and essentially instantiate my objects within the registry. If I make the registry a singleton, I can actually call it from within my objects instead of passing my objects around from object to object and each object lends itself to the app overall.

Am I catching on?
User avatar
Maugrim_The_Reaper
DevNet Master
Posts: 2704
Joined: Tue Nov 02, 2004 5:43 am
Location: Ireland

Post by Maugrim_The_Reaper »

Getting there :).

I'd actually refer to the object as a "Service Locator". A Registry (having a debate in another thread on this) is largely a storage medium. In it's simplest form it's a glorified array. You can set values, retrieve values, etc. Of course a class format lends itself to Type Hinting limitations on parameters and other OOP benefits. A Service Locator is up the ladder a bit - not only does it cache, it also finds, includes and instantiates.

A Service Locator is commonly a Singleton.

A few things to watch for. A basic Service Locator has no trouble with instantiating unless the constructor of the new object require's parameters, or instantiating is only done on one Class based on some creational logic. In these complex cases the SL may need to fall back on some additional method to instantiate the "right" object - i.e. Factory, Abstract Factory, or other.

That's the additional fluff. ;) If what you have now works, and has some unit test support - then it's likely useful as it stands.
User avatar
RobertGonzalez
Site Administrator
Posts: 14293
Joined: Tue Sep 09, 2003 6:04 pm
Location: Fremont, CA, USA

Post by RobertGonzalez »

What I am doing works, but I am not sure that it is the best way to go. The challenge that I am faced with right now is determining the correct pattern to apply so that all of the objects are instantiated correctly without being instantiated more than once (where applicable) and with the correct parameters. I have a few objects that take my MySQL database object, but I am really not wanting to make the connection details available application wide, so I have it set up to where the MySQL object instantiates with just a database name, called from the constructor within an initializers script (essentially one small page that calls all the objects that I need). The same thing holds true for the Sybase object and to a lesser degree, the template object.

I'll figure all this out eventually. Thanks for taking the time to make it more clear to me.
User avatar
Jenk
DevNet Master
Posts: 3587
Joined: Mon Sep 19, 2005 6:24 am
Location: London

Post by Jenk »

That would be a Factory pattern, fo' sho'.

You'd then integrate it with a Registry, or god forbid a singleton.

With regards to dependency injection, it's now relatively simple with the introduction of the Reflection model in PHP's SPL. However, the problem lies in the implmentation of DI, not the technicalities of it.. if that makes sense.
User avatar
Maugrim_The_Reaper
DevNet Master
Posts: 2704
Joined: Tue Nov 02, 2004 5:43 am
Location: Ireland

Post by Maugrim_The_Reaper »

The Factory certainly seems applicable - also since a Factory is also a single class it can run off of a configuration value and not require a complicated setup - i.e. you could mangle the ServiceLocator to use it where appropriate, perhaps with a class constant to tell the SL that it needs to locate the factory and not the standalone class...
User avatar
RobertGonzalez
Site Administrator
Posts: 14293
Joined: Tue Sep 09, 2003 6:04 pm
Location: Fremont, CA, USA

Post by RobertGonzalez »

Ok, here are the objects and the objects that drive them. They are necessary on every page. If you wouldn't mind, what do you suppose would be the best way to call them/user them in this app?

$dbsettings (singleton, but looking now like a registry)
$mysql (requires $dbsettings)
$sybase (requires $dbsettings)
$config (requires $mysql)
$pager (page handler, requires $mysql, $template)
$sessions (requires $mysql)
$user (requires $sybase)
$error
$template

Or should I scrap this entire layout and do something else?
User avatar
Maugrim_The_Reaper
DevNet Master
Posts: 2704
Joined: Tue Nov 02, 2004 5:43 am
Location: Ireland

Post by Maugrim_The_Reaper »

If I were to quickly take MySQL. It's dependent on the dbsetting object (presumably driven from a config file).

I could have a MySQL factory class (or a more generic DB Factory class if MySQL is one option of many). This takes in the dbsettings as a Singleton or from an already established Registry. It creates the MySQL object and returns a reference to the instance.

My Locator class would be of a form with a get() method with an optional parameter Locator::Factory constant. get('MySQL', Locator::Factory) would seek the Mysql_Factory class instead of the standalone Mysql class - the Factory would handle *all* steps required to instantiate a valid MySQL object. Same could apply to Sybase (use a Factory). All other classes have dependencies from here out which should be registered on the Locator - just use a Singleton call to the Locator to grab them, or pass the Locator as a standard parameter when instantiating.
User avatar
RobertGonzalez
Site Administrator
Posts: 14293
Joined: Tue Sep 09, 2003 6:04 pm
Location: Fremont, CA, USA

Post by RobertGonzalez »

That's funny. I was already headed in that direction. Thanks for confirming what I thought might be appropriate Maugrim.
Post Reply