Hello list,
I want to bring up a topic that has bothered me whenever I use traits.
I don't have a solution proposal for it, yet, unfortunately.
I was going to comment in the other thread about traits, but it seems
better suited for a new discussion.
Traits allow to share code between classes of different inheritance chains.
They can be used instead of composition, or they can be used to help
with composition - e.g. a trait can expose functionality from a
specific injected dependency.
When using base classes, we can follow a convention to always call the
parent constructor.
We can even make the properties in the base class private, to fully
encapsulate them.
But:
When using properties in traits, how can I make sure that they are
properly initialized in the class constructor?
Also, what if I want to provide an init method with specific logic to
set that property? How can I make sure that method will be called in
the constructor?
I found that Psalm can detect "PropertyNotSetInConstructor", which is
also applied to properties from traits.
But this is not as straightforward as calling a parent constructor.
Can and should we provide a language-level solution for this?
Or should this be left to static analysis tools and IDEs?
Cheers,
Andreas
Hello list,
I want to bring up a topic that has bothered me whenever I use traits.
I don't have a solution proposal for it, yet, unfortunately.
I was going to comment in the other thread about traits, but it seems
better suited for a new discussion.
Traits allow to share code between classes of different inheritance chains.
They can be used instead of composition, or they can be used to help
with composition - e.g. a trait can expose functionality from a
specific injected dependency.
When using base classes, we can follow a convention to always call the
parent constructor.
We can even make the properties in the base class private, to fully
encapsulate them.But:
When using properties in traits, how can I make sure that they are
properly initialized in the class constructor?
I think I have a solution:
abstract properties in traits!
The idea would be:
- traits can have abstract properties that are private, protected or public.
- non-abstract classes cannot have abstract properties.
- class properties override trait properties, with some compatibility
requirements. - non-abstract protected or public class properties from the parent
class also override trait properties. - (optional) abstract classes can have abstract properties that are
protected or public. - (optional) abstract class properties from a parent class are
overridden by the trait property, but with compatibility checks.
This implies that abstract trait properties must be redeclared in
the class that uses the trait,
interface X {}
interface XHaving {
public function getX(): X;
}
trait T {
abstract private X $x;
public function getX(): X {return $this->x;}
}
class C implements XHaving {
use T;
public function __construct(
private X $x,
) {}
}
class D implements XHaving {
use T; // Error, must redeclare abstract property T::$x.
}
The benefit:
Properties are initialized in the same file where they are declared.
I don't know if we need aliasing for properties, perhaps we should
first go without that.
I did find a discussion about abstract properties in externals.io, but
this was for interfaces and classes, not for traits.
https://externals.io/message/64126#66682
Also, what if I want to provide an init method with specific logic to
set that property? How can I make sure that method will be called in
the constructor?
This part would not be solved by the abstract properties.
But I think that's ok.
I found that Psalm can detect "PropertyNotSetInConstructor", which is
also applied to properties from traits.
But this is not as straightforward as calling a parent constructor.Can and should we provide a language-level solution for this?
Or should this be left to static analysis tools and IDEs?Cheers,
Andreas