Reputation: 2304
A trait work incorrectly if is used in abstract class.
Consider the following code:
abstract class C {
static $class;
use T;
}
trait T {
static $mockClass;
public function __construct() {
static::$mockClass = static::$class;
}
}
class A extends C{
static $class = 'A1';
}
class B extends C{
static $class = 'B1';
}
$a = new A();
$b = new B();
dump($a::$mockClass); // B1, should be A1
dump($b::$mockClass); // B1, should be B1
You se that when dumping mockClass
we have B1
in both cases. Why? When I do not extend from C
and use the trait directly I have expected result.
I can not find any information about the specific usage of traits in abstract classes. Can anyone explain me why my code does not work as I expect?
Upvotes: 1
Views: 1944
Reputation: 2265
The reason why your code does not work as you expect is this:
During instantiation of A: $a = new A();
,
this part of code is executed: static::$mockClass = static::$class;
where static::$class
has value of 'A1'
and obviously comes from class A
, BUT
until $mockClass
is not redeclared in child class, it resides in class C
.
Even when you use the keyword static::
because this specific part of code static::$mockClass = static::$class;
which is responsible for instantiation is executed in class C
.
The most important part which you seem to not understand is: in PHP (I'm not sure about other languages) when some part of code is defined in class A and class B inherits from class A and has additional part of code - if you execute any part of code on B, which was not redefined in B but comes for A - you can imagine that the code is in fact executed in parent class A where it comes from.
So when next time during instantiation of B the code static::$mockClass = static::$class;
executes again - static::$class
has now value of 'B1'
but overrides value of static::$class
which still resides in C
.
This slightly changed your code example should make you understand what is happening (and it works as expected):
abstract class C {
static $class;
use T;
}
trait T {
static $mockClass;
public function __construct() {
static::$mockClass = static::$class;
}
}
class A extends C{
static $class = 'A1';
static $mockClass;
}
class B extends C{
static $class = 'B1';
static $mockClass;
}
$a = new A();
$b = new B();
var_dump($a::$mockClass); // A1, should be A1
var_dump($b::$mockClass); // B1, should be B1
Upvotes: 1
Reputation: 57121
As mentioned in the comment, this is more to do with static
properties. Static properties are shared between all of the instances of a class (which is why you can't use $this
to refer to them.) As all of your classes extend C, they all will share the same value of $mockClass
. You would find that all instances will have the last value assigned to $mockClass
, if you swapped the creation of the two objects $a
and $b
round, both will display A1
.
A working version, using normal properties...
trait T {
public $mockClass;
public function __construct() {
$this->mockClass = static::$class;
}
}
abstract class C {
static $class;
use T;
}
class A extends C{
static $class = 'A1';
}
class B extends C{
static $class = 'B1';
}
$a = new A();
$b = new B();
print_r($a->mockClass); // A1, should be A1
print_r($b->mockClass); // B1, should be B1
I would like to add that having a constructor in a trait is bound to cause problems at some point, so not the best thing to start using.
Update:
As already mentioned, all of your classes extend C which uses the trait T, if you moved the use T;
to the derived classes A & B...
abstract class C {
static $class;
}
trait T {
static $mockClass;
public function __construct() {
static::$mockClass = static::$class;
}
}
class A extends C{
static $class = 'A1';
use T;
}
class B extends C{
static $class = 'B1';
use T;
}
$a = new A();
$b = new B();
print_r($a::$mockClass); // A1, should be A1
print_r($b::$mockClass); // B1, should be B1
Still don't think this is a good use of traits, but I will leave that up to you.
Upvotes: 1