I've been catching up with the discussion of grafts and traits. Here at P'unk Avenue we do a lot of Symfony development, which frequently involves kludges to achieve horizontal reuse - ugly, clever, imaginative, combinations of the three but the first of the three is pretty much always present. PHP needs "real" horizontal reuse, no question.
My first reaction to traits was positive - it's simple and simple is good. However I'm very concerned about the implications of not allowing state. In the real world people are going to want to do horizontal reuse that involves state. So they will work around it. It's true that if they are extending a class they have control over, they can do it fairly elegantly... In the parent class: public $traitData = null; public function __construct() { $traitData = array(); } Now, in a trait method, they can do: public function mymethod() { $this->traitData['age'] = 38; } The PHP 5.3 cycle breaker can presumably clean up any references made in traitData when all noncyclical references to the object itself are gone. However this is not without problems. There will be collisions between key names in traitData when mixing traits, and there will be no way to detect those conflicts and automatically resolve them. It's presumably less efficient than an implementation of state in the core. And most importantly, it only works if the parent class can be modified by the programmer trying to extend it. One of the stated goals of horizontal reuse is to accommodate adding multiple non-mutually-exclusive extensions, written by you and not written by you, to parent classes you didn't write (of course, without the "written by you and not written by you... multiple non-mutually-exclusive" part, you could just use a subclass). So what happens when the programmer wants to write a trait to extend classes they didn't write, and they need to save state? Well, their first workaround attempt might be this: $foo->bar = "I'm an object!"; $hash[$foo]['age'] = 5; echo($hash[$foo]['age']); That is, use the object as a key in a hash. Fortunately, PHP doesn't allow objects as keys in hashes. That's good, because this otherwise clever technique would leave stray references in the hash which would never get cleaned up, ever. Cycle breaking or no cycle breaking. We're right back in "why can't I manipulate a lot of objects in a 'for' loop without hating life" land. Okay, so what's our hypothetical user's next workaround going to be? In some cases, they're just stuck. In others, the parent class does offer some sort of unique identifier. Common examples being Propel and Doctrine objects, which have getId() methods. So the programmer does this (simplest possible implementation to show the idea): public function myTraitMethod() { global $hash; // * $hash[$this->getId()]['age'] = 38; } This works. But it leaks memory forever. When the object goes away, there is no way to break the connection to the data in $hash. Which might not be as trivial as 'age'. More likely it'll be crammed with objects with references to objects with... you get the idea. To clean these references up you'd need a destructor. Except the traits RFC makes no mention of destructors. So you'd need a parent class destructor that explicitly invoked trait destructors. Except you went this road in the first place because you didn't have control over the parent class to make changes like that. Etc. Since PHP is moving in the direction of better garbage collection (and thank heaven for that), it's a bad idea to make a design decision that will lead developers in the direction of breaking that garbage collection system. The natural thought at this point is to endorse grafts. But the grafts proposal has more complex implementation requirements and there doesn't seen to be an existing patch to implement it. The big stumbling point is that in a naive implementation, $this would refer to the graft object, not its parent. But the programmer expects to be able to pass $this around to other code which will expect the parent object, etc. My feeling is that traits should permit state and allow the renaming of member variables just as they allow the renaming of methods. If that's an implementation problem, it might be better to transparently move the trait member variables to a hash and interpret references to those variable names accordingly within the trait methods. The member variable hash becomes $this->__traitName_0, $this->__traitName_1, etc. for each consecutive use of the same trait in the same class (if we really need that feature). (*) Yes, $hash shouldn't be a global, it should be a static member of a globalDataManager class or something. Makes no difference for purposes of this discussion. -- Tom Boutell P'unk Avenue 215 755 1330 punkave.com window.punkave.com -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: http://www.php.net/unsub.php