For those who have been doing a lot of Javascript programming, you know what prototype-based programming is all about. The basic idea is that functions can be added to classes dynamically. In Javascript functions can be added to a static class (using prototype) and it will be added to all instances of the class, or they can be added to a specific instance and only be added to that instance.
Anyway, lets get to the point. I decided to try adding this functionality to PHP. I'm not sure why its a good idea, or if it even is, but I'll let you be the judge of that.
So here is the class I came up with:
/** * @copyright (C) 2006, Schmalls / Joshua Thompson, All Rights Reserved * @license http://www.opensource.org/licenses/bsd-license.php New BSD * @author Joshua Thompson <spam.goes.in.here@gmail.com> * @version 1.0.0 * @link http://www.countercubed.com */ /** * This class holds the prototype capabilities * * Extending this class makes it prototype capable */ class Prototype { /** * Holds prototype functions * * @var array */ private $_functions = array(); /** * Default constructor * * This is here so that php doesn't complain about the prototype function */ public function __construct() { } /** * Sets the prototype functions or variables * * @param string $name * @param mixed $value */ public function __set( $name, $value ) { if ( function_exists( $value ) ) : $this->_functions[$name] = $value; else : $this->$name = $value; endif; } /** * Gets static prototype variables if they exist * * @param string $name * @return mixed */ public function __get( $name ) { if ( isset( $this->prototype()->$name ) ) : return $this->prototype()->$name; else : trigger_error( 'Undefined property: ' . __CLASS__ . '::' . $name, E_USER_NOTICE ); endif; } /** * Calls a static prototype function * * @param string $name * @param array $arguments * @return mixed */ public function __call( $name, $arguments ) { if ( isset( $this->_functions[$name] ) ) : return call_user_func_array( $this->_functions[$name], $arguments ); elseif ( $this->prototype()->isCallable( $name ) ) : return call_user_func_array( array( $this->prototype(), $name ), $arguments ); else : trigger_error( 'Call to undefined method ' . __CLASS__ . '::' . $name . '()', E_USER_ERROR ); endif; } /** * Returns the static prototype holder * * @return Prototype */ public static function prototype() { static $prototype = null; if ( $prototype === null ) : $prototype = new Prototype(); endif; return $prototype; } /** * Needed for the static calling functionality * * @return boolean */ public function isCallable( $name ) { return ( isset( $this->_functions[$name] ) ); } }
Now all a class needs to do is extend the Prototype class. A sample of its use follows:
// make some prototype classes class Test1 extends Prototype { } class Test2 extends Prototype { } class Test3 extends Test1 { } // lets create some test static functions Test1::prototype()->fun1 = create_function( '$arg1', ' echo \'Static Test1::fun1 \' . $arg1 . "\n"; '); Test2::prototype()->fun2 = create_function( '$arg1', ' echo \'Static Test2::fun2 \' . $arg1 . "\n"; '); // now instantiate the objects $test1 = new Test1(); $test2 = new Test2(); // and make some more functions $test1->fun3 = create_function( '$arg2', ' echo \'Test1::fun3 \' . $arg2 . "\n"; '); $test2->fun4 = create_function( '$arg2', ' echo \'Test2::fun4 \' . $arg2 . "\n"; '); // output: Static Test1::fun1 bob $test1->fun1( 'bob' ); // create another function Test1::prototype()->fun2 = create_function( '$arg1', ' echo \'Static Test1::fun2 \' . $arg1 . "\n"; '); // output: Static Test1::fun2 bobby $test1->fun2( 'bobby' ); // output: Static Test2::fun2 robert $test2->fun2( 'robert' ); // output: Test1::fun3 robby $test1->fun3( 'robby' ); // output: Test2::fun4 rob $test2->fun4( 'rob' ); // another instance still has the static functions $test1_2 = new Test1(); // output: Static Test1::fun1 bob $test1_2->fun1( 'bob' ); $test3 = new Test3(); // this will give an error because prototype functions do not extend down to a child class $test3->fun1( 'roberto' );
Once again, I don't know how useful it is, but let me know what you think.
Discussion
This Class looks very interesting, and I can already think of some uses for it, I have an idea of how to extend it in a way that the prototype functions could be carried down but it would require some work. but overall I do like this class, its very good work.
My biggest problem with extending it downward was the “self” keyword. As seen in a bug report, but there may be a fix in PHP 6.
How about using $this in runtime created functions?
Arikon: I'm not exactly sure what you mean by that, could you give an example?
I think what he means is using
and not
but that has nothing to do with the problem
I think I figured out what he was talking about. I think it can be rephrased like, can you use the $this keyword in prototype functions? The answer seems to be no. If anyone can come up with a good solution to allow $this to be used, let me know. I tried some changes, but I could only get public functions and variables to work and I had to use a different keyword (I used $self). So any ideas?
I'm doing something a little bit similar with a Decorator class: http://nornix.svn.sourceforge.net/viewvc/nornix/cms/trunk/class/Decorator.class.php?revision=206&view=markup To add members/methods ta a class, you create a class that extends the Decorator class. Then you have to supply the “parent object” to the constructor. When a member/method isn't found in the extending class, the Decorator functions (call, get, __set) will try to find them in the parent instance object. So you write the “subclass”, as if it was a real subclass, and the Decorator class will manage the difference in most cases. (But you cant use isset() on a managed member, so then you have to reference it from the instance object directly.) In most cases you can use the $this keyword as usual.
I actually something similar to this, you can have a look on my article here → http://activedeveloper.info/prototype-based-object-oriented-programming-in-php
Regards