Page 1 of 1

How to return data back from plugins?

Posted: Wed Mar 14, 2007 3:00 pm
by AlexC
Morning,

I'm creating a plugin/pluggable system for my already modular content management system to make it even more flexible, and customizable when released. It's working fine so far, loads the plugins correctly when called based upon events in the 'main' code. However, I'm having trouble wrapping my head around how to return the data from several plugins.

The way my plugin system works is it will call a single method to many different plugins that have mapped an event to a function/method of theres. The problem I have is that I store each plugin return data into an array, this is fine however if a plugin returns an array, it screws the final return up ... here's the code:

Code: Select all

//---
		// call_event() is the main function used to call an event to each plugin
		// that has mapped it's self to an event via it's plugin map. 
		// @param string $event
		// [@param mixed $param]
		// @return mixed
		//---
		public function call_event( $event, $param='' ) {
			if ( empty( $this->plugins ) ) {
				return '';
			} else {
				// Find all plugins for this event and store their data
				$plugins = $this->find_plugins( $event );
				$plugin_data = array();
				foreach( $plugins as $key=>$val ) {
					$plugin_class = $this->load_plugin( $val['name'] );
					if ( is_object( $plugin_class ) ) {
						$method_name = $val['map'][ $event ];
						if ( method_exists( $plugin_class, $method_name ) ) {
							$plugin_data[] = $plugin_class->$method_name();
						} else {
							Log::message( 'Plugin "'.$val['name'].'" event could not be loaded. Requested event method "'.$method_name.'" does not exist in the class.', Log::L_WARNING );
						}
					}
				}			
				return implode( '', $plugin_data );
			}
		}
As you can see on the final return call I implode() the $plugin_data to 'flattern' all of the returned data from the different plugins that have been called. But, if one of the plugins returns an array, like I said above it messes the implode() function up and call_event() ends up just returning an empty array. So, what is the best way to deal with plugins that return an array? Am I doing this whole plugin bit wrong?

Posted: Wed Mar 14, 2007 3:20 pm
by Chris Corbyn
This is basically the observer pattern and is how I handle plugins. IMHO, plugins (listeners) should not return any data, but they may decorate the pluggable (event-dispatcher) in some way. What sort of things would they return? How does the pluggable know what to expect?

Posted: Wed Mar 14, 2007 3:38 pm
by AlexC
It's kind of an observer pattern I guess, but nothing is really being 'observed'.
What sort of things would they return?
Take this little bit of code from my View/Template class, it just sets up an array of default tags to replace within the view/template file.

Code: Select all

//---
		// get_default_tags() provides some common, built-in tags that can be 
		// used throughout views, such as Time/Date, IP address etc etc
		// @return array
		//---
		private function get_default_tags() {
			$router = &Registry::get( 'router' );
			$default_tags = array(								
									'REL_THEME_DIR'        => trim( $router->get_relative_base_dir(), ' /').'/'.Zula::get_dir( 'themes' ),
	
									'TCM_VERSION' => 'yay version',
									
									);
			
			return $default_tags;
			
		}
Those are just some quick default tags I threw in quick to test if it worked. Now I could just keep hand-coding the default tags in but I would like to be able to do, is offer a plugin system (not just for this bit of code) so that plugins can react to an event called in the get_default_tags() function and then return an array of more default tags to used. So, the code above would look something like this:

Code: Select all

//---
		// get_default_tags() provides some common, built-in tags that can be 
		// used throughout views, such as Time/Date, IP address etc etc
		// @return array
		//---
		private function get_default_tags() {
			$router = &Registry::get( 'router' );
			$default_tags = array(								
									'REL_THEME_DIR'        => trim( $router->get_relative_base_dir(), ' /').'/'.Zula::get_dir( 'themes' ),
	
									'TCM_VERSION' => 'yay version',
									
									);

			// Load plugins to provide extra default tags
			$plugin = &Registry::get( 'plugin' );
			$extra_tags = $plugin->call_event( 'view_default_tags' );			
			return array_merge( $default_tags, $extra_tags );			
		}
What this would do is call every plugin that has mapped a function/method to the event 'view_default_tags' and so each of the plugins would then return an array containing extra tags to use.

Posted: Wed Mar 14, 2007 7:06 pm
by Christopher
Just don't implode $plugin_data.

Posted: Thu Mar 22, 2007 7:40 am
by dreamscape
On a simple plugin system, I would probably make the API return the last value from the last plugin that returned something not null, except if the returns from plugins were arrays, and then merge the arrays.

Off the top of my head maybe something like:

Code: Select all

public function call_event($event)
{
	if (empty($this->plugins))
	{
		return;
	}


	// get args
	$args = array();
	if (func_num_args() > 1)
	{
		$args = func_get_args();
		array_shift($args);
	}


	$return = null;

	// call all plugins for this event
	foreach ($this->find_plugins($event) as $plugin)
	{
		$plugin_instance = $this->load_plugin($plugin['name']);

		if (!is_object($plugin_instance))
		{
			continue;
		}

		$method = $plugin['map'][$event];

		if (!method_exists($plugin_instance, $method))
		{
			Log::message( 'Plugin "' . $plugin['name'] . '" event could not be loaded. Requested event method "' .
				$method . '" does not exist in the class.', Log::L_WARNING );
			continue;
		}

		$tmp_return = call_user_func_array(array($plugin_instance, $method), $args);
		if ($tmp_return !== null)
		{
			if (is_array($tmp_return))
			{
				if (!is_array($return))
				{
					$return = array();
				}

				$return = array_merge($return, $tmp_return);
			}
			else
			{
				$return = $tmp_return;
			}
		}
	}

	return $return;
}
There are also a few 3rd party plugin APIs floating around that might make life easier.