FreeLightman
FreeLightman

Reputation: 2304

Why trait does not work in abstract class?

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

Answers (2)

Jacek Dziurdzikowski
Jacek Dziurdzikowski

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

Nigel Ren
Nigel Ren

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

Related Questions