Follow

I think I've figured out one of the problems I'm running into with traits in .

Background

The canonical usage of a trait is to define a set of behaviors that work together -- a feature that classes can use, basically.

That often requires initializing some stuff within the trait.

If it's just a matter of setting initial values for member-variables, you can do that when those members are declared... but if it involves picking up information from the host-object[1] , then some code needs to be executed before trying to access any of the trait's functionality.

There are a couple of ways to handle this.

A. The trait can declare a constructor, if it knows enough about the potential host classes[2] to do that gracefully (i.e. without interfering with their construction processes) to execute the initialization code.

B. The base class can define an initialization event (I usually define function OnCreate() : void) for the trait to implement.

C. The trait can check itself for initialization every time any of its methods are called. (This probably works, but hurts my optimizing-squirrel's soul.)

Minor Obstacle

It starts to get tricky if the host-class also needs to do some initialization, because if class A uses trait B and they both define function X(), A::X() can't call parent::X() to access B::X()... but there's a syntax for hosting traits which allows their functions to be aliased to another name when hosted, so you could rename B::X() to B::Trait_X(), and then call that.

...which means that whenever you host the trait, you also have to remember to do the aliasing.

The Problem

The real problem is when I have a set of traits that all get hosted together.

...and they all need initialization.

So say you have class A which needs traits B,C, and D, each of which has its own OnCreate() function that needs calling.

I guess it can be done by aliasing OnEvent() in each one (OnEventB(), OnEventC(), OnEventD()), then explicitly calling those each individually from A::OnEvent()... but that seems messy and easy to get wrong. There's too much stuff that needs to be done manually.

I guess it can be slightly tidied by declaring OnEvent() under a different name in each trait -- basically pre-aliasing them -- so the only messy part is that you have to explicitly call the aliased OnEvent()s from A::OnEvent()... but it still seems like this kind of breaks how class-inheritance is supposed to work.

You should be able to just use a trait, and not worry about initialization.

I should be able to just call parent::OnEvent() from A::OnEvent() to get D::OnEvent(), which would then call parent::OnEvent() to get C::OnEvent()... and so on, up to B::OnEvent() calling parent::OnEvent() to get A's parent class's OnEvent()[3] .

A way to kluge that desired behavior, I suppose, would be to have a series of intermediate classes:

class AB hosts trait B
class AC hosts trait C and extends AB
class AD hosts trait D and extends AC
class A extends AD

...in which case calling up the parent chain would work as needed.... but again, that's kind of messy: more code to manually include.

I'm not sure which of those last two solutions creates the least technical debt, and I keep feeling like there's got to be a better solution, even without changing how PHP handles traits.

I guess I'll try some things and see how deep a pit I end up digging myself into...

(is this a , or something else?)

Notes

  1. When I say "host class", I mean a class that has used a trait. If class A has use B;, then A is hosting B. Similarly, a "host object" is an instantiation of a class that is hosting a trait.
  2. This is one of the reasons I wish traits could "require" their host-class to be of a certain class-family, so a trait can prevent itself from being used by a class-family that it's not designed to work with.
  3. ...or it could work the other way: functions in hosted traits could take precedence over the host-class's functions -- so if class A hosts trait B, and class C extends A, and they all declare OnEvent(), then C calling parent::OnEvent() would call B::OnEvent() and B calling parent::OnEvent() would call A::OnEvent() -- that would be fine too.
· · Web · 0 · 2 · 3
Sign in to participate in the conversation
Toot.Cat

The social network of the future: No ads, no corporate surveillance, ethical design, and decentralization! Own your data with Mastodon!