PHP 5.4 – Traits, Closures, and Prototype-based Programming

According to Wikipedia, prototype-based programming is “a style of object-oriented programming in which classes are not present, and behavior reuse (known as inheritance in class-based languages) is performed via a process of cloning existing objects that serve as prototypes”With magic methods, traits, and anonymous functions – this is now possible in PHP.

    class SimpleXML
    {
        use \Prototype;
    }

    $SimpleXML = new SimpleXML();
    $SimpleXML->load = function( $path )
    {
        if( file_exists( $path ))
            return simplexml_load_file( $path );

        return null;
    };

    $SimpleXML->load = function( $data )
    {
        return simplexml_load_string( $data );
    };

The previous code uses SimpleXML and contains a class declaration by that name with a single trait that is shown later. The class is then instantiated and 2 anonymous functions are added to the prototype. With the above code and the Prototype trait, the following statements are both possible.

    $doc = $SimpleXML->load( 'test.xml' );
    $doc = $SimpleXML->load( 'test' );

The first method triggers the Closure that uses simplexml_load_file and the second method triggers simplexml_load_string respectively. You may notice that both functions are assigned to the same property. Using the prototype trait, it is possible to define 2 functions with the same name and number of arguments. This is a lot like traditional method overloading which was previously not possible with PHP. Overloading in this way is primitive and requires that overloaded methods meet certain criteria, namely that a given method return null if the input is not valid.

trait Prototype
{
    private
            $_props = array(),
            $_methods = array();

    function &__get( $name )
    {
        if( array_key_exists( $name, $this->_props ))
        {
            return $this->_props[$name];
        }
        elseif( array_key_exists( $name, $this->_methods ))
        {
            return $this->_methods[$name];
        }

        return null;
    }

    function __set( $name, $value )
    {
        if( is_object( $value ) && is_callable( $value ))
        {
            if( !array_key_exists( $name, $this->_methods ))
            {
                $this->_methods[ $name ] = array();
            }

            if( $value instanceof \Closure )
            {
                $value = $value->bindTo( $this );
            }

            $this->_methods[ $name ][] = $value;
        }
        else
        {
            $this->_props[ $name ] = $value;
        }
    }

    function __call( $name, $args = array() )
    {
        if( array_key_exists( $name, $this->_methods ))
        {
            $methods = $this->_methods[ $name ];

            if( is_array( $methods ))
            {
                foreach( $methods as $method )
                {
                    if( !is_null( $result = call_user_func_array( $method, $args ) ) )
                    {
                        return $result;
                    }
                }
            }
        }

        return null;
    }
}

The __get method This one is fairly self-explanatory as it simply returns a property value or a Closure stored in _props and _methods respectively. The __set method This method first checks to see if the value is in-fact callable. An example of a callable value would be an anonymous function or a class that implements the __invoke method. If the value is a Closure, the BindTo method is called. BindTo simply allows the use of the $this keyword in the closure as you would use it in a normal method. Finally, if the value is not callable, the prototype assumes it is a property and assigns it to the _props array. The __call method This method iterates through all of the matching methods until one returns something other than null. Now the prototype above is well, a prototype. As per the original definition of prototype-based programming, it must also be possible to easily extend your prototype by cloning all of it’s methods and properties.

Leave a Reply

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

*