Passing Arguments to Callback Functions

Let’s say you want to use add_action() to attach a function to an existing action, but you want to pass your function a few arguments. This seems to be a common problem with no good solution. Yes, you could use global variables, but that’s rather messy, especially if you want to attach the same function to a number of different actions but with different arguments each time. After running into this issue again a few weeks ago, I set out to see if there wasn’t a better way to do this. Here’s what I came up with…

Please keep in mind that this is simply an example to explain how this method works. If you’ve run into this problem, you can probably already see a use case without having one presented to you. All this example will do is attach a simple function to a few different actions and output a different string each time. Let’s start by creating an array to hold the message strings and the hooks they should be attached to. If you want to see this in action, you can place all the code in this post into your functions.php file.

$strings[] = array(
	'message' => 'This message goes in the header. ',
	'hook' => 'wp_head',
);

$strings[] = array(
	'message' => 'This message goes in the footer. ',
	'hook' => 'wp_footer',
);

$strings[] = array(
	'message' => 'This message also goes in the footer. ',
	'hook' => 'wp_footer',
);

$strings[] = array(
	'message' => 'This is the second header message. ',
	'hook' => 'wp_head',
);

As you can see, each item in the $strings array is another array that holds both the message to be displayed, and the action hook it should be attached to.

Original Class

This is the original class that I wrote. The echo_strings() function within the class has been improved by kucrut. Scroll down to see the improvements.

Let’s create a class that will accept the strings array and output the messages at the specified action hook locations.

/**
 * Example class
 *
 * This class demonstrates how to pass data to a callback function
 * without using global variables.
 */
class Example_Class{

	public function __construct( $strings ){
		$this->strings = $strings;
		foreach( $this->strings as $string ) {
			add_action( $string[ 'hook' ], array( $this, 'echo_strings' ) );
		}
	} // End function __construct()

	public function echo_strings() {

		$count = 0;

		foreach ( $this->strings as $string ) {

			// If the hook has been called, echo the script.
			if( did_action( $string['hook'] ) > 0 ) {

				echo $string['message'];

				// Removes this element from the strings array to prevent it from being echoed again.
				unset( $this->strings[ $count ] );

			} // End if

			$count++;

		} // End foreach $this->strings
	} // End function echo_strings()
} // End class Example_Class

This class has two functions. The first is the __construct() function that is called every time the class is instantiated. This function accepts the strings array as an argument and loops through it, attaching the $this->add_strings() function to the proper action hook for each item in the array. We still haven’t passed any arguments to the add_strings() function though. Instead, the add_strings() function is built to retrieve its own arguments.

Taking a closer look at the add_strings() function, you’ll notice it loops through the same array as the __construct() function did. Here is the original solution: for each item in the array, it uses the did_action() function to determine if the action hook has been fired at least once. If it has, then the message is echoed to the screen and then the item is removed from the array. If the action hook has not been fired yet, nothing is echoed to the screen and the item is left in the array.

Improved Class

Let’s take another look at the echo_strings() function. My original function only worked with action hooks (not filters) and required a count to be maintained in order to remove items from the $strings array. In the comments, kucrut suggested replacing did_action() with current_filter(). This removes the need to modify the array and also opens up the possibility of working with filter hooks instead of just action hooks. Here is the improved echo_strings() function:

public function echo_strings() {
	$hook = current_filter();
	foreach ( $this->strings as $string ) {
		if ( $string['hook'] == $hook ) {
			echo $string['message'];
		}
	}
}

Instead of checking if an action has been run, we check which action or filter is currently running. If it matches the hook property defined in our array, it echoes the message.

Using the Class

Finally, to test this out, we need to instantiate the class:

$example = new Example_Class( $strings );

As you can see, the first and last message in the array are echoed to the header, while the second and third are echoed to the footer as intended. Now what? Is that all?

I don’t really know! I would love some feedback regarding this method of passing arguments to a callback function. Can you find any reasons that this is a terrible idea? Can you improve upon it? Is there something that I didn’t account for or address? If you have any questions, comments, or suggestions, please let me know. I would really appreciate it. Thanks!

Alternate Method

After a quick email discussion with Brady Vercher of Blazer Six, he suggested another method. This method allows the original array to be modified via a filter at any point during the execution of the script. Impressive!

function prefix_get_strings() {
	$strings['wp_head'][] = 'This message goes in the header. ';
	$strings['wp_footer'][] = 'This message goes in the footer. ';
	$strings['wp_footer'][] = 'This message also goes in the footer. ';
	$strings['wp_head'][] = 'This is the second header message. ';

	return apply_filters( 'prefix_get_strings', $strings );
}

function prefix_print_strings() {
	$hook = current_filter();
	$strings = prefix_get_strings();

	if ( ! empty( $strings[ $hook ] ) ) {
		foreach ( $strings[ $hook ] as $string ) {
			echo $string;
		}
	}
}

foreach ( array( 'wp_head', 'wp_footer' ) as $hook ) {
	add_action( $hook, 'prefix_print_strings' );
}

Posted March 7, 2013 by Alex Mansfield

4 responses to “Passing Arguments to Callback Functions”

  1. kucrut says:

    Maybe something like this?

    • alexmansfield says:

      Thanks! That definitely improves on my solution. I’ve updated the post to give you credit and document the improvement.

Leave a Reply

Your email address will not be published. Required fields are marked *