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.

Also, if trait A and trait B both have a function X(), and I use both of them in the same class, why not just use the X() from whichever trait is named last?

Show thread

Fatal error: Trait method FieldCollective has not been applied, because there are collisions with other trait methods on greenmine\workferret\cCommonTable in /home/htnet/site/git/greenmine/dropins/WorkFerret/lib/base.php on line 25

Ya know, , it would be reeeally helpful if you could tell me what methods are colliding, rather than just mentioning one that isn't.

KDevelop does a decent job of mapping out classes in a project, but does nothing at all for traits or interfaces.

Single-step debugging (SSD) with

a brief review

✅ SSD support in PHP (Xdebug module)
✅ SSD support in KDevelop (native)
🚫 instructions on how to activate SSD in KDevelop when working with an interpreted language
🚫 any information at all about how to use KDevelop with PHP, other than "there's a plug-in!" (which I have installed)

meh.

Mood: trying to use traits to self-impose coding conventions

Not sure if it's a good mood.

It's annoying that in I can't declare an interface with very general terms (e.g. a class that handles some kind of variable, type unspecified, in various ways) and then implement it with more specific terms (e.g. this class handles strings in the same ways, this other class handles objects of a certain class family...).

PHP issue 

Apparently you can do

declare(ticks=1) {
[ ...code... ]
}

instead of

declare(ticks=1);
[ ...code... ]

but you can't do

declare(strict_types=1) {
[ ...code... ]
}

instead of

declare(strict_types=1);
[ ...code... ]

...or at least not in PHP 7.4.

This isn't mentioned or explained in the documentation, as far as I can tell.

It also leaves open the question of how to turn on strict typing globally.

It's annoying the way inheriting traits in doesn't confer parentage.

Like... if trait B uses trait A which has function X() which is re-implemented in B, B can't call parent::X(); you have to alias it in the use statement and call the alias... or else split off the functionality you want them both to have in common into a separate function, and call that from each version of X().

(The latter technique is kind of good, in a way, since it forces you to be more descriptive and compartmental in your functions... but then why not make class inheritance work the same way?)

(albeit minor)

Apparently this is only actually supported for return values? I'm getting a warning trying to override an argument type with a class that descends from the original type...

I wish had an equivalent to dumping the memory address of an object -- some kind of unique identifier. It does have the === operator which lets you determine if two variables point to the same data-space, but sometimes that's not practical to use when debugging.

This doesn't quite qualify as a software gripe... maybe : it would be nice if would let you declare that a function isn't supposed to return anything at all (e.g. NULL as a return type).

Benefits:

  • minimizes confusion with older functions where I just haven't gotten around to giving them a return type
  • catches mistaken attempts to return a value, when you forget that the output is supposed to be conveyed via some other means.

thought: If you use a trait in a class, are you a class-traiter?*

Bonus joke: if the parent classes don't like it, they may make a "final" declaration and cut off your inheritance.

(*This joke is probably obvious and and only 1% of PHP devs working with traits haven't already thought of it -- which is why 1% of PHP devs own 99% of the bad PHP jokes.)

If I were to implement an SMTP server in , would that be chaotic neutral?

How about if I stored the data in MySQL instead of mbox? Or... JSON? 🌩️ ☠️ :kestraglow:

Another Actual Gripe (minor): I wish traits could require an interface from the host class. (That is, a trait could specify that any class which uses it must implement one or more specified interfaces, because it needs that interface's methods in order to function.)

Related gripe mentioned earlier: I wish traits could specify that they satisfy a given interface.

So the syntax to incorporate both of these would be something like this:

trait MyTrait
implements SatisfiedInterface
extends NeededInterface {
[...]
}

(I'm using "extends" mainly to avoid adding another keyword like "requires", but I think it's logically consistent: if a class extends a class that has abstract methods, then it must either be abstract or implement those methods -- and an interface is pretty much identical to an abstract class with only abstract methods, all public.)

(P.S. This is my first toot!)

Another actual (minor) gripe:

A class uses a trait, but wants to override one of the trait's methods by modifying its output, which requires calling the trait's version of the method to get the original output.

If the trait were used by a parent class, you could use parent::method() to call it -- but if the trait is used in the current class, you can't.

I get around this by declaring a dummy parent class which uses the trait, then overriding it in the real class (which descends from the dummy) -- but honestly, I shouldn't have to do that.

While typing this, I remembered there was a syntax for modifying how trait methods are accessed when they're used. I tried that just now, and it worked. This is better than my old solution, but still seems like it should be unnecessary.

Actual complaint about : you can't typecast classes.

That is, you can't treat an object of Class1 as being an object of Class2 (so as to have access to additional/different functions (methods) for handling its data)...

...even if Class2 is a descendant of Class1.

...unless that has changed since the last time I tried it.

Woozle Hypertwin's choices:

Toot.Cat

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