One of my favorite features of C# is Partial Classes. For the uninitiated, it is a way of defining a class in two separate locations. Very useful when you have code generation utilities such as LINQ.
Unfortunately, PHP has no such feature (though if anyone’s listening it would be a great feature to add to the PHP6 feature list), however thanks to the magic of __call($method, $args), __get($key), and __set($key, $value) overload functions as well as passing by reference (aah the good ol’ &) we can imitate partial classes.
The idea behind this partial class hack is to instance a copy of the partial class in question and have our magic functions forward any undefined requests to the partial class.
The partial class (probably with the naming convention Partial_CLASSNAME) will contain a reference to the main class and also have magic functions forwarding undefined requests to the main class. The reason why we have our magic functions in the partial class is so that any internal references can still be made (methods in Partial_CLASSNAME must have access to the methods and members in CLASSNAME).
The constructor in the main class will automatically seek out the partial class (and additional coding can be done to seek out more than 1 partial class as well as do some integrity checking) so that the programmer does not have to intervene to form the ‘full class’.
Here’s some sample code:
class MainClass
{
private $_partial;
public $a = 'a';
public function __construct()
{
if( class_exists("Partial_" . __CLASS__) )
{
$partial = "Partial_" . __CLASS__;
$this->_partial = new $partial($this);
}
}
public function __call($method, $args)
{
return call_user_func_array( array($this->_partial, $method), $args );
}
public function __get($key)
{
return $this->_partial->$key;
}
public function __set($key, $value)
{
$this->_partial->$key = $value;
}
}
class Partial_MainClass
{
private $_parent;
public $b = 'b';
public function __construct(&$parent)
{
$this->_parent = $parent;
}
public function foo()
{
$this->a = 'c';
return 'foo called';
}
public function __call($method, $args)
{
if( function_exists( array($this->_parent, $method) ) )
return call_user_func_array( array($this->_parent, $method), $args );
else
trigger_error("Call to undefined method " . get_class($this->_parent) . "::" . $method, E_USER_ERROR);
}
public function __get( $key )
{
if( isset($this->_parent->$key) )
return $this->_parent->$key;
}
public function __set( $key, $value )
{
if( isset($this->_parent->$key) )
$this->_parent->$key = $value;
}
}
$tmp = new MainClass();
echo "$tmp->a: " . $tmp->a . "";
echo "$tmp->b: " . $tmp->b . "";
echo "$tmp->foo(): " . $tmp->foo() . "";
echo "$tmp->a: " . $tmp->a . "";
echo "$tmp->b: " . $tmp->b . "";
And the output is going to look something like:
$tmp->a: a $tmp->b: b $tmp->foo(): foo called $tmp->a: c $tmp->b: b
Performing a var_dump() on $tmp gives us:
object(MainClass)[1]
protected '_partial' =>
object(Partial_MainClass)[2]
protected '_parent' =>
&object(MainClass)[1]
public 'b' => string 'b' (length=1)
public 'a' => string 'c' (length=1)
If you call a function that doesn’t exist in either class, the trigger_error() in the Partial_MainClass will throw an error message. This is, of course, to prevent having an infinite loop of having each class call each other to find the non-existant function.
The big limitation is accessing private and protected members and methods, however with a small amount of time, the PHP5 Reflection API should replace the call_user_func_array() and provide access to the private and protected members and methods.
Ignoring the limitations, the sample code above achieves what it sets out to accomplish: combines two classes so that the rest of the code sees it as a unified object.
Comments 5
just what i was looking for… i needed a way to separate my generated code from custom hand written code, but wanted both within the same class instance. thanks for posting
Posted 23 Jun 2008 at 9:41 am ¶Why wont you simply extend the class ?
“For the uninitiated, it is a way of defining a class in two separate locations.” – its not a clean reason to emulate partial classes.
Partial classes in C grill (:P) are imho invented for offering MVC support through the language(why would one do that) in .net framework.
Don’t get me wrong, i like the language, i worked with it for 6 years now, but this partial class creation is such a hard to grasp feature, i do not see any concrete OOP pattern (and by this i mean real world concepts), i do not thing they have any concrete representation for the real world, as all the other oop patterns do.
Posted 25 Jun 2008 at 1:10 pm ¶@iongion: While this code sample was just an exercise in fun, partial classes do have their uses.
Partial classes are not used to fill in or expand any OOP design pattern, and aren’t used to do MVC either. Visual Studio does a lot of code generation and partial classes allow you to alter generated code without having to edit the usually unreadable generated code nor worry about losing your changes the next time Visual Studio auto generates code.
The best example I can think of is a class pertaining to a GUI window. Visual Studio (and others like GLADE for GTK, QT Designer, etc.) gives you a visual interface to design a GUI window and generates the code for the window. If you’ve ever looked at a generated class, while cleaner in C# than other languages, it’s still long, terse, and difficult to follow changes. You cannot simply extend the class as launching the window is very dependent on the current class name and you run into issues of Privately scoped variables not being visible to it’s children (the extended class).
So partial classes came into existence to allow you to maintain separate files containing code for the same class. While Visual Studio operates on foo.cs, auto generating and regenerating code, you can make all your changes in bar.cs and they merge transparently, allowing you to not worry about losing code to an overwrite or things breaking if an action causes Visual Studio to somewhat radically change the structure of the class or remove methods you had custom code in.
Posted 25 Jun 2008 at 1:22 pm ¶Thanks for the resource. I’ll also throw in my vote for support of partial classes in the next versions of PHP. In case someone of influence comes across this post.
– Alex
Posted 07 Jul 2008 at 6:00 pm ¶A good start, but with one sneaky lurking problem. It won’t work with any methods that require reference parameters unless you pass them in by reference, which is deprecated and may throw warnings.
Perhaps extensive use of reflection could solve that, but if the $args in __call have all come in by value, then it’s too late to make them references by the time you get them.
I imagine returning references might also be a problem.
Posted 01 Jul 2009 at 4:59 pm ¶Post a Comment