Schmalls » Blog » 2006 » November 2006 » Prototype-based programming in PHP

Prototype-based programming in PHP

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

JordanJordan, 2006-11-09 14:15

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.

SchmallsSchmalls, 2006-11-09 14:43

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.

ArikonArikon, 2006-11-09 15:46

How about using $this in runtime created functions?

SchmallsSchmalls, 2006-11-09 16:23

Arikon: I'm not exactly sure what you mean by that, could you give an example?

JordanJordan, 2006-11-09 21:42

I think what he means is using

Test1::prototype()->fun2 = create_function( '$arg1', ' echo \'Static $this->fun2 \' . $arg1 . "\n"; ');

and not

Test1::prototype()->fun2 = create_function( '$arg1', ' echo \'Static Test1::fun2 \' . $arg1 . "\n"; ');

but that has nothing to do with the problem

SchmallsSchmalls, 2006-11-09 23:02

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?

AndersAnders, 2006-11-10 10:37

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&amp;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.

mhitzamhitza, 2010-12-18 15:25

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

Enter your comment
PQBVT
 
blog/2006/11/prototype-based-programming-in-php.txt · Last modified: 2009-09-20 00:02 by Josh Thompson