Service Locator

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
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

Here's the list again:

- Be able to register a list of parameters to pass to the constructor (we may need eval() for that) DONE

- Be able to specify that a parameter is another "registered" object that we get() an instance of to pass as a parameter (think of a Model object that need a DB connection object passed to its constructor)

- Allow get() to get objects as singletons or multiple instances

- Allow instances to be accessed by a name that is not the class name. DONE

- Have the class name to file name mapping be configurable when you create the Service Locator (not everyone likes your naming style ) DONE

- Keeping Lazy Loading so that if we never get() the class it is never instansiated (say an error redirect occurs before the get()). DONE

- Make it so it is easy to use as __autoload() or spl_autoload().

- How does PHP5 make our job easier or the interface nicer?

- Do we want to add a settable list of paths that get() will search -- like Todd_Z does as an option?
(#10850)
User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Post by Luke »

arborint wrote:- Be able to specify that a parameter is another "registered" object that we get() an instance of to pass as a parameter (think of a Model object that need a DB connection object passed to its constructor)
It can do this
arborint wrote:- Allow get() to get objects as singletons or multiple instances
I am working on this... if anybody has any suggestions, please let me know because I am struggling a little
arborint wrote:- Make it so it is easy to use as __autoload() or spl_autoload().
how would I know if it is easy as those?
arborint wrote:- How does PHP5 make our job easier or the interface nicer?
Uhh....
arborint wrote:- Do we want to add a settable list of paths that get() will search -- like Todd_Z does as an option?
Where is this example?
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

Well where is Todd_Z to say adding this feature is worth doing. I had the comments about it here. I think that resolving PEAR style class names would be a better addition.
(#10850)
User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Post by Luke »

For some reason, if you get() from the registry more than once, it throws this error...
Fatal error: Cannot use object of type MysqlConnect as array in c:\wamp\www\module_admin\classes\Registry.inc.php on line 80

Code: Select all

<?php
class Registry{
	
	private $objects;
    private $path;
    private $prefix;
    private $suffix;

    function __construct($path='/', $suffix='.inc.php', $prefix='') {
	    
	    // Path to classes
        $this->path = $path;
        
        // Allows for classes to be searched out by a prefix such as ClassSession.inc.php (Where Class is the prefix)
        $this->prefix = $prefix;
        
        // Allows for classes to be suffixed and have any allowed extension
        $this->suffix = $suffix;
        
    } 	
	
    private function instantiate($class, $name) {

	    if(!is_object($name)){
		    $arglist = array();
	        if (isset($this->objects[$name]['params'])) {      // there are args
	                $n = count($this->objects[$name]['params']);
	                for ($i=0; $i<$n; ++$i) {
	                        $arglist[$i] = "\$this->objects['$name']['params'][$i]";
	                }
	        } 
	        //ob_start(); // This doesn't prevent jack, does it?
	        eval ("\$object = new " . "\$class(" . implode(', ', $arglist) . ');');
			//ob_end_clean();
			
			// Unset temporary params and class name as they are no longer needed
			unset($this->objects[$name]['class']);
			unset($this->objects[$name]['params']);
			
			// Store object in registry
			$this->objects[$name] = $object;
		}
		
        return $object;

    }
    
    public function register($class, $name=null /*, $arg1, $arg2... */){
			
		// If object name is not set, object is named the same as it's class
		if(is_null($name)) $name = $class;		
		
		// Store object's classname and params temporarily (if class hasn't already been registered)
    	if(!isset($this->objects[$name])){
	    	
            $params = func_get_args();
			array_shift($params); // Remove $name from param list
			array_shift($params); // Remove $class from param list
			
            // Register params
            $this->objects[$name]['params'] = $params;
            $this->objects[$name]['class']  = $class;
            
    	}
    	
    }
    
    // TODO: Make child classes autoload their parent before instantiation	
	private function load($class){
		
        if (! class_exists($class)) {
            include($this->path . $this->prefix . $class . $this->suffix);
        }
        
	}
		
	public function get($name){
		
		// Load the class file
		$this->load($this->objects[$name]['class']); // LINE 80
		
		// Instantiate class
		$object = $this->instantiate($this->objects[$name]['class'], $name);
		
    	// Return object id
    	return $object;
    	
	}
}
?>
User avatar
sweatje
Forum Contributor
Posts: 277
Joined: Wed Jun 29, 2005 10:04 pm
Location: Iowa, USA

Post by sweatje »

It seems odd to me that you use $this->objects[$name] in multiple ways.

Sometimes you are storing $this->objects[$name]['class'] and $this->objects[$name]['params'], but then you later override this and just store an object. Your error is probably from going back later and expecting the 'class' and 'params' stuff to be there.

It seems like you should create $this->meta as an array for the metadata about the objects you are storing, and keep $this->objects purely for the objects themselves.b
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

Jason is right. Perhaps it would be clearer if we created a little class just to hold these bits of information about a class/file/object. An object might also allows us to add other features more easily. Something as simple as:

Code: Select all

class Registry_Entry {
var $class;
var $name;
var $path;
var $params;
}
(#10850)
User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Post by Luke »

I went ahead and just used a seperate array for now... I am considering using a seperate class for the entries though... I just can't think of why I would need to. What other features are there? Here it is...

Code: Select all

<?php
class Registry{
	
	
	private $objects;
	private $entries;
    private $path;
    private $prefix;
    private $suffix;

    function __construct($path='/', $suffix='.inc.php', $prefix='') {
	    
	    // Path to classes
        $this->path = $path;
        
        // Allows for classes to be searched out by a prefix such as ClassSession.inc.php (Where Class is the prefix)
        $this->prefix = $prefix;
        
        // Allows for classes to be suffixed and have any allowed extension
        $this->suffix = $suffix;
        
    } 	

    private function instantiate($class, $name) {

	    if(!is_object($name)){
		    $arglist = array();
	        if (isset($this->entries[$name]['params'])) {      // there are args
	                $n = count($this->entries[$name]['params']);
	                for ($i=0; $i<$n; ++$i) {
	                        $arglist[$i] = "\$this->entries['$name']['params'][$i]";
	                }
	        } 
	        //ob_start(); // This doesn't prevent jack, does it?
	        eval ("\$object = new " . "\$class(" . implode(', ', $arglist) . ');');
			//ob_end_clean();
			
			// Unset registry entry as it is no longer needed
			unset($this->entries[$name]);
			
			// Store object in registry
			$this->objects[$name] = $object;
		}
		
        return $object;

    }

    public function register($class, $name=null /*, $arg1, $arg2... */){
			
		// If object name is not set, object is named the same as it's class
		if(is_null($name)) $name = $class;		
		
		// Store object's classname and params temporarily (if class hasn't already been registered)
    	if(!isset($this->entries[$name])){
	    	
            $params = func_get_args();
			array_shift($params); // Remove $name from param list
			array_shift($params); // Remove $class from param list
			
            // Register params
            $this->entries[$name]['params'] = $params;
            $this->entries[$name]['class']  = $class;
            
    	}
    	
    }

    // TODO: Make child classes autoload their parent before instantiation	
	private function load($class){
		
        if (! class_exists($class)) {
            include($this->path . $this->prefix . $class . $this->suffix);
        }
        
	}

	public function get($name){
		
		// If object has already been stored
		if(isset($this->objects[$name]) && is_object($this->objects[$name])){
			
			// Set return value to stored object
			$object = $this->objects[$name];
			
		}
		else{
			
			// Load the class file
			$this->load($this->entries[$name]['class']);
			
			// Instantiate class
			$object = $this->instantiate($this->entries[$name]['class'], $name);
			
		}
		
    	// Return object id
    	return $object;
    	
	}
}
?>
User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Post by Luke »

This isn't meant to be a bump, just posting my newest version of this: I used an array for entries and I added the setParams method so you don't have to specify the parameters in the construct if you don't want to. I also set a $path parameter in the get method so you can specify what directory to get the class from (if not the one set in the construct)

Code: Select all

<?php
class Registry{
	
	private $objects;
	private $entries;
    private $path;
    private $prefix;
    private $suffix;

    function __construct($path='/', $suffix='.inc.php', $prefix='') {
	    
	    // Path to classes
        $this->path = $path;
        
        // Allows for classes to be searched out by a prefix such as ClassSession.inc.php (Where Class is the prefix)
        $this->prefix = $prefix;
        
        // Allows for classes to be suffixed and have any allowed extension
        $this->suffix = $suffix;
        
    } 	

    private function instantiate($class, $name) {

	    if(!is_object($name)){
		    $arglist = array();
	        if (isset($this->entries[$name]['params'])) {      // there are args
	                $n = count($this->entries[$name]['params']);
	                for ($i=0; $i<$n; ++$i) {
	                        $arglist[$i] = "\$this->entries['$name']['params'][$i]";
	                }
	        } 
	        //ob_start(); // This doesn't prevent jack, does it?
	        eval ("\$object = new " . "\$class(" . implode(', ', $arglist) . ');');
			//ob_end_clean();
			
			// Unset registry entry as it is no longer needed
			unset($this->entries[$name]);
			
			// Store object in registry
			$this->objects[$name] = $object;
		}
		
        return $object;

    }

    public function register($class, $name=null /*, $arg1, $arg2... */){
			
		// If object name is not set, object is named the same as it's class
		if(is_null($name)) $name = $class;		
		
		// Store object's classname and params temporarily (if class hasn't already been registered)
    	if(!isset($this->entries[$name])){
	    	
            $params = func_get_args();
			array_shift($params); // Remove $name from param list
			array_shift($params); // Remove $class from param list
			
            // Register params
            $this->entries[$name]['params'] = $params;
            $this->entries[$name]['class']  = $class;
            
    	}
    	
    }

    // TODO: Make child classes autoload their parent before instantiation	
	private function load($class, $path, $suffix, $prefix){
		
		$path = $path ? $path : $this->path;
		$suffix = $suffix ? $suffix : $this->suffix;
		$prefix = $prefix ? $prefix : $this->prefix;
		
        if (! class_exists($class)) {
            require_once($path . $prefix . $class . $suffix);
        }
        
	}
	
	public function setParams($name, $params){
		if(isset($this->entries[$name]) && !is_object($this->objects[$name])) $this->entries[$name]['params'] = $params;
	}

	public function get($name, $path=false, $suffix=false, $prefix=false){
		
		// If object has already been stored
		if(isset($this->objects[$name]) && is_object($this->objects[$name])){
			
			// Set return value to stored object
			$object = $this->objects[$name];
			
		}
		else{
			if(!isset($this->entries[$name])){
				$this->register($name);
			}
			// Load the class file
			$this->load($this->entries[$name]['class'], $path, $suffix, $prefix);
			
			// Instantiate class
			$object = $this->instantiate($this->entries[$name]['class'], $name);
		}
		
    	// Return object id
    	return $object;
    	
	}
}
?>
User avatar
Hurreman
Forum Commoner
Posts: 61
Joined: Sat Apr 29, 2006 8:42 am

Post by Hurreman »

Just started re-writing a lot of my code the other day, and this reminded me of the Service Locator I was struggling with a while back.
I remember bashing my head to the desk a lot when figuring out a way to pass eventual parameters to the requested classes, and never really got too happy with the result. I just go stuck with my setArgs-method.

I know my solution is crap, but sometimes you get ideas just from looking at someone elses code. So here is my current Service 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,$args)
    { 		
		// Check if the requested object exists
		if(!isset($this->objects[$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]))
            	    {
	                    if(count($this->args[$handle])==1)
    	                {
        	                $arg = $this->args[$handle][0];
            	            $this->objects[$handle] =& new $handle($arg);
	                    }
    	                else
        	            {
            	            $this->objects[$handle] =& new $handle($this->args[$handle]);
                	    }
	                }
    	            else
        	        {
            	        $this->objects[$handle] =& new $handle();
                	}
            	}
        	}
		}
        return $this->objects[$handle];
    }
}
?>

Keep up the great work! Can't wait to see the final result :)
Post Reply