解决 PHP Trait 不能定义常量及成员变量冲突的问题

PHP Trait 只能定义成员变量和方法,不能定义常量。如下代码会报错:

PHP// test.php

trait UsageTrait
{
    const THE_CONST_NAME = 1;
}

错误消息为:

Fatal error: Traits cannot have constants in test.php

想要在不同的类中共用一组常量,可以通过接口来解决:

PHPinterface UsageConstantsInterface
{
    const THE_CONST_NAME = __CLASS__;
}

trait UsageTrait
{
    public function usageForBar()
    {
        echo UsageConstantsInterface::THE_CONST_NAME, PHP_EOL;
    }
    
    public function usageForFoo()
    {
        // 如果使用 UsageTrait 的类实现了 UsageConstantsInterface 接口,
        // 也可以使用 self::THE_CONST_NAME 来访问常量
        echo self::THE_CONST_NAME, PHP_EOL;
    }
}

class Foo implements UsageConstantsInterface
{
    use UsageTrait;
}

class Bar
{
    use UsageTrait;
}

(new Foo)->usageForFoo();
(new Bar)->usageForBar();

另外,PHP Trait 的 insteadofas 语句只能处理方法名的冲突,而无法处理成员变量的冲突。如果在 Trait 和类中都定义了同名的成员变量,当它们的初始值不一致时,编译器会报错。如下代码可以正常执行:

PHPtrait UsageTrait
{
    protected $property1;

    protected $property2 = 'some value';

    protected $property3 = __CLASS__;
}

class Foo
{
    use UsageTrait;
    
    protected $property1;

    protected $property2 = 'some value';
    
    // 非常有意思的是,$property3 这样初始化不会引发冲突
    // 虽然两处的 __CLASS__ 的实际值并不相同
    protected $property3 = __CLASS__;
}

new Foo;

如下代码则会报错:

PHP// test.php

trait UsageTrait
{
    protected $property = 'default value';
}

class Foo
{
    use UsageTrait;
    
    protected $property = 'new value';
}

new Foo;

错误消息为:

Fatal error: Foo and UsageTrait define the same property ($property) in the composition of Foo. However, the definition differs and is considered incompatible. Class was composed in test.php on line 8

在某些场景下,我们希望在 Trait 里定义一个缺省的成员变量,而在类中可以覆盖这个成员变量的值。一种方案是在子类中覆盖这个成员变量,代码如下:

PHPtrait UsageTrait
{
    protected $property = 'default value';
}

abstract class Foo
{
    use UsageTrait;
}

class Bar extends Foo
{
    protected $property = 'new value';
}

但实际上,我们之所以使用 Trait 这种 Mixin 方式就是为了更加灵活地、低耦合地组合代码。而上面的解决方案要求使用 UsageTrait 的类必须继承自同一个抽象类,显然违背了 Trait 的初衷。在没有更好的解决方案情况下,只能自己多写几行代码了:

PHP/**
 * @property string $property
 */
trait UsageTrait
{
    private $_property = 'default value';
    
    public function __get($name)
    {
        if ($name == 'property') {
            return $this->_property;
        }
    }
    
    public function usage()
    {
        echo $this->property, PHP_EOL;
        return $this;
    }
}

class Foo
{
    use UsageTrait;
    
    protected $property = 'new value';
}

class Bar
{
    use UsageTrait;

}

(new Foo)->usage();	// 打印 "new value"
(new Bar)->usage();	// 打印 "default value"