PHP 5.4 Traits, Closures, and Prototype-based Programming
I am really excited about the upcoming release and new features of PHP 5.4. I will not be covering the basics of traits or anonymous functions as they are both fairly well documented already. This article is intended for advanced PHP users, but if you have any questions please feel free to contact me or leave a comment.
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. How, you ask? Consider the following code example..
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><attr>test</attr></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.Now for the trait..
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 methodThis 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. I haven’t done that for the sake of this example but such a thing would not be difficult by simply copying _props and _methods into a new prototype and triggering the aforementioned BindTo method on the copied anonymous functions.
Happy coding!