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

Reply via email to