Dynamically Add Functions to PHP Classes

Make your classes extensible and dynamic
Dynamically Add Functions to your PHP Classes

I’m in the process of working on the new version of my PHP Thumbnailer class, and came across a couple of interesting challenges. I’ve gotten a lot of great suggestions for features, and have wanted to add them, but at the same time don’t as I would prefer not to bloat the class with all sorts of functionality. So I started thinking about how I could provide certain functionality for people that want it, without either simply making it a part of the class (and making it more bloated as a result), or coming up with all sorts of extended classes to maintain and distribute. The other thing I don’t want to do is end up creating an app… I want this thing to be a small library. Finally, I want people to be able to integrate the library / classes into their existing apps as easily as possible. The solution (or paradigm I suppose) that jumped out was plugins. Ah, but how does one create plugins for classes? Rather, how do you dynamically add functions to PHP classes? Well, after some googling and tinkering, I think I’ve come up with the solution. Read on to see how it’s done…

Before we get started, let’s set some baseline assumptions. First, we’re not going to have one class… it’s just not possible. Second, all plugins will need to be classes themselves, and these classes will not have any member variables (you’ll see why in a bit). Outside of that we’re pretty much free to do things however we want. Let’s also assume that we’re creating a user object that other people will be able to augment with whatever functionality they’d like.

So, let’s create our base class. This will contain all the magic functionality that makes this work. What we’re essentially going to do is create a method for importing the new functions, and then one that will allow these functions to be called. Here’s what the code looks like:

<?php

class BaseUser
{
	private $imported;
	private $imported_functions;

	public function __construct()
	{
		$this->imported	= array();
		$this->imported_functions = array();
	}

	protected function imports($object)
	{
		// the new object to import
		$new_import 		= new $object();
		// the name of the new object (class name)
		$import_name		= get_class($new_import);
		// the new functions to import
		$import_functions 	= get_class_methods($new_import);

		// add the object to the registry
		array_push($this->imported, array($import_name, $new_import));

		// add teh methods to the registry
		foreach($import_functions as $key => $function_name)
		{
			$this->imported_functions[$function_name] = &$new_import;
		}
	}

	public function __call($method, $args)
	{
		// make sure the function exists
		if(array_key_exists($method, $this->imported_functions))
		{
			$args[] = $this;
			// invoke the function
			return call_user_func_array(array($this->imported_functions[$method], $method), $args);
		}

		throw new Exception ('Call to undefined method/class function: ' . $method);
	}
}

?>

So, what have we done? Well, we’ve created a function “imports”, that will allow new functionality to be imported and added to the class. Essentially, all we’re doing is looking at a provided object, storing its name in an array, and storing all of its functions in another array. The other half of the magic is the “__call” function. This is a magic method provided by PHP, that will get called any time a function that isn’t defined is invoked on the class. This is what actually invokes the so-called imported functions. We’ve also provided a nice way to pass “$this” along to any of these imported functions automatically, as they won’t technically be a part of this class.

Now that we’ve got the base class, we can create a sample user class that extends it:

<?php

class User extends BaseUser
{
	private	$first_name;
	private $last_name;

	public function __construct()
	{
		parent::__construct();

		$this->first_name = '';
		$this->last_name = '';
	}

	// getters and setters....

	public function getFullName()
	{
		return $first_name . ' ' . $last_name;
	}
}

?>

I’ve omitted a lot of functionality that you’d need / want (like the getters and setters) as they’re irrelevant to what we’re doing here. Anyway, let’s say these two classes are our user library, and this is all the functionality we’d like to provide. However, several users of the library want a function that will return the user’s name formatted as “last, first”. Well, we’ve provided a nice way for them to add their own functionality without making major changes to the class, or requiring any extra work on our part.

To take advantage of this, one would first need to create a class containing the functions we’d like to add the the User class. Let’s do that now, providing the desired functionality previously described:

<?php

class UserFunctions
{
	public function lastThenFirst(&$that)
	{
		return $that->last_name . ', ' . $that->first_name;
	}
}

?>

One thing to note here… since this function is not technically merged into the base class, we need a way to refer to the base class. So, we’ve go the parameter “$that” in our function. This variable will basically be a reference to the parent / base class, allowing us to access all the members and manipulate them in the context of our new “imported” function. If this doesn’t quite make sense yet, don’t worry. It should become clearer when you see everything together.

Now, all we need to do is glue everything together. The easiest way to do this is to simply modify the User class constructor to now read:

public function __construct()
{
	parent::__construct();

	$this->first_name = '';
	$this->last_name = '';

	$this->imports('UserFunctions');
}

That’s it! Now, any time a user object is created, it will also have the new “lastThenFirst()” function. Here’s all the code together, with a little test at the end of it (note, I’ve added some default values to the User class, and changed private vars to public since I skipped getters / setters):

<?php

class BaseUser
{
	private $imported;
	private $imported_functions;

	public function __construct()
	{
		$this->imported	= array();
		$this->imported_functions = array();
	}

	protected function imports($object)
	{
		// the new object to import
		$new_import 		= new $object();
		// the name of the new object (class name)
		$import_name		= get_class($new_import);
		// the new functions to import
		$import_functions 	= get_class_methods($new_import);

		// add the object to the registry
		array_push($this->imported, array($import_name, $new_import));

		// add teh methods to the registry
		foreach($import_functions as $key => $function_name)
		{
			$this->imported_functions[$function_name] = &$new_import;
		}
	}

	public function __call($method, $args)
	{
		// make sure the function exists
		if(array_key_exists($method, $this->imported_functions))
		{
			$args[] = $this;

			// invoke the function
			return call_user_func_array(array($this->imported_functions[$method], $method), $args);
		}

		throw new Exception ('Call to undefined method/class function: ' . $method);
	}
}

class User extends BaseUser
{
	public	$first_name;
	public $last_name;

	public function __construct()
	{
		parent::__construct();

		$this->first_name = 'Ian';
		$this->last_name = 'Selby';

		$this->imports('UserFunctions');
	}

	// getters and setters....

	public function getFullName()
	{
		return $this->first_name . ' ' . $this->last_name;
	}
}

class UserFunctions
{
	public function lastThenFirst(&$that)
	{
		return $that->last_name . ', ' . $that->first_name;
	}
}

$user = new User();

echo $user->getFullName() . '<br />';
echo $user->lastThenFirst() . '<br />';

?>

Which happily outputs:

Ian Selby
Selby, Ian

Before I close, one other note… you would probably want to take this one step further by adding some functionality to the base class to automatically load plugins, thus preventing the need to manually add $this->imports() to your class. My approach to this notion will be available when I release the next version of my Thumbnailer class in the near future, so look there for some ideas… or, drop me a comment!

Enjoy!

Other's Thoughts   (26 so far...)


  • Adam Culp
    Jan 2 '09 at 7:53 pm

    Very nice, thanks. (My mind is reeling to find a use in my projects.)


  • Kris Jordan
    Jan 3 '09 at 10:32 am

    A technique very similar to this is used in the base class of the newly released Recess Framework: http://www.recessframework.org/ Models with relationships that introduce new methods dynamically use this extensively. The base class ‘RecessObject’ is much like Base User class but intended to be more generic. The ability to add methods to a class dynamically is indeed quite powerful! Cheers, Kris


  • Ian
    Jan 3 '09 at 10:59 am

    @Kris,

    I’ll definitely take a look at the framework and see what’s going on there. Thanks for the comment!


  • Tonio
    Jan 4 '09 at 1:49 am

    Nice work !
    Have you take a look at symfony’s way to do it with events ?
    Your way is perhaps more simple to deal with a single class but to share this behavior with differents class, symfony’s way is really powerfull.


  • Marc Jakubowski
    Jan 5 '09 at 9:21 am

    Have a look at the Traits concept in general and the proposal for PHP http://wiki.php.net/rfc/traits
    It’s pretty much the same as what you implemented :)
    Regards


  • Ian
    Jan 5 '09 at 11:08 am

    @Marc,
    Would love to see traits become a part of PHP core.

    @Tonio,
    Still haven’t had a chance to check out symfony, I’ll make sure and take a look.

  • [...] Selby has posted a new tutorial today looking at something that can be very handy in the right situations – dynamically adding new [...]

  • [...] Selby has posted a new tutorial today looking at something that can be very handy in the right situations – dynamically adding new [...]


  • Jani Hartikainen
    Jan 5 '09 at 7:21 pm

    This is quite similar to mixins, and how you can add functionality into object prototypes in languages like JavaScript. Personally I can’t think of anything where I would have needed this, but it’s a nice idea nevertheless =)


  • Wesley Mason
    Jan 6 '09 at 4:20 am

    I’ve done a few internal demo’s of something similar but using the new lambda functions functionality in 5.3 to just pass dynamic methods directly to a parent class without needing any delegation objects.
    So basically a hack to implement traits.


  • Ian
    Jan 6 '09 at 9:02 am

    Yeah, I thought about doing something like that, but since this will be distributed to the masses, I didn’t want to tie it to 5.3. I’d love to see how you did it tho, using that new functionality

  • [...] Dynamically Add Functions to PHP Classes « Gen X Design | Ian Selby [...]


  • crem0r
    Jan 8 '09 at 3:00 am

    Thanks for that great idea!

    I Refactored it to an more dynamic and powerfull version.
    The code is cleaned up and unnecessary lines were removed.

    As a bonus imported methods can have parameters now.
    http://pastebin.com/f2cdff60c


  • Ronny
    Jan 8 '09 at 5:11 am

    The idea is nice, but i cannot chum up with making private variables public.
    > (note, I’ve added some default values to the User class, and changed private vars to public since I skipped getters / setters). < Your imported functions can modify them i a way, you don’t have the control about it.

    And what about the decorator pattern? Doesn’t it fit to your needs? http://en.wikipedia.org/wiki/Decorator_pattern


  • Ian
    Jan 8 '09 at 8:50 am

    @Ronny

    I agree with you that making the variables public is generally a bad idea, I just didn’t want to make the sample code longer by including getters/setters in there. If this were a real-world implementation, the class would have properly defined private member vars with the appropriate getter and setter functions.

    As far as the decorator pattern goes, it’s certainly a good alternative in many situations. I’d suggest others take a look into it as well… here’s a decent article I found after some quick googling:
    http://www.fluffycat.com/PHP-Design-Patterns/Decorator/


  • Sven
    Jan 8 '09 at 1:45 pm

    Awesome stuff. That’s all I needed to know ;) Now i can make things like act_as_tagable or act_as_revisionable happen in ZendFramework-Models prior to PHP_5.3..


  • Martin Pietschmann
    Jan 9 '09 at 4:18 am

    Hello, I had just the same idea a few days ago :-)
    So here is my version: http://pastebin.com/fd1812de

    Now attributes of imported classes are also accessible in the base class and the imported classes have access to the public AND protected vars of the base class.
    Within imported classes your have also access to functions and vars of other imported classes.
    You can now simply use $this instead of $this->that inside imported classes to access functions and vars of the base class or other imported classes.

    I hope you like it.


  • Ian
    Jan 9 '09 at 9:21 am

    @Martin,
    I do like it, but it looks like a combination of my example and the example posted by @crem0r. However, the spin you’ve put on it is quite nice. I personally wouldn’t use it because I don’t like using magic functions any more than I absolutely have to (as they’re usually very slow and unoptomized). However, all that being said, you still did a fantastic nice job!


  • Martin Pietschmann
    Jan 10 '09 at 9:23 am

    @Ian
    Yes, the magic functions might be very slow, but they are necessary to implement this kind of multiple inheritance :-)

    Here is my new version: http://pastebin.com/f29f852ad
    Demo: http://tinyurl.com/8ml54k
    Demo code: http://pastebin.com/f27b70275

    Now you have only one base class, so you can build a chain of imports.
    But now you can have the diamond problem:
    ‘boat’ imports ‘car’
    ‘automobile’ imports ‘car’
    ‘amphibian vehicle’ imports ‘automobile’ and ‘boat’

    Therefor I expanded the import function as well as the __get and __set function to redirect all access to the lastly imported ‘car’ object. This is like virtual inheritance in c++.

    Unfortunately there are still problems with dublicated functionnames.


  • Phil
    Jan 13 '09 at 6:29 am

    I was thinking it was similar to an Observer pattern, but Decorator might be closer. I should look them both up again and refresh myself…

    If you look into some of the recent PHP5 specific frameworks like Kohana, they’re all using magic functions now, even for getters and setters in their Object relational stuff.

    Anyway, congratulations, my young friend. I bought you a cup of coffee.


  • Phil
    Jan 13 '09 at 8:33 am

    Isn’t call_user_func_array deprecated?

    I took a crack at the idea… I ended up going with a parent class for the Extend-able stuff and the Extensions, using magic functions a lot, so it’s probably slow, but I worked around the need to have the Extend-able class add it’s own extensions. Anyway – read it and weep:

    http://pastebin.com/f14aeced8


  • Phil
    Jan 13 '09 at 8:39 am

    Ooops, the array_unshift line in my example is cruft.


  • ap
    Jan 22 '09 at 10:37 am

    Here’s an interesting thread that eventually leads to this. Perhaps it is of interest to you.


  • Les
    May 15 '09 at 6:46 am

    Nice technique I must say however not wanting to dampen everyone’s spirit on the subject, I am left with the feeling the same pretty much could be accomplished, more easily I might add, using a Decorator.

    In this case, there is no desire to alter the public API so Decorator would be my first choice.


  • Ian
    May 15 '09 at 8:21 am

    I wouldn’t say you’re dampening the spirit… if the decorator pattern works better, it’s certainly a more elegant solution to the problem than this one.

    I think I’ve actually looked at it before, and can’t quite remember if it suited the needs of the problem I was working on that inspired me to write the article tho. Here’s an article I found on the subject if anyone’s interested: http://tinyurl.com/5etz97


  • Katsuke
    Jul 21 '09 at 7:42 am

    Excellent article, thanks a lot ^&^

  • Share Your Thoughts...

    Some HTML is ok. If this is your first comment on my site, it will be reviewed before being posted publicly. Your comment may be edited or marked as spam if it appears intended for SEO purposes.