特征访问 class 依赖是个坏主意吗?

Is trait accessing class dependency a bad idea?

我在 Stackexchange 上看到了一个示例(请注意访问 class 属性 的特征):

trait CheckPermissionTrait
{
    protected function checkPermission($object_id)
    {
        $judge = $this->container->get('acme_judge');

        $user  = $this->container->get('security.context')->getToken()->getUser();

        if( !$judge->isPermitted($user, $object_id) ) {
            throw $this->createAccessDeniedException("Brabbel");
        }
    }
}

并阅读其中一位回复者的评论:

Your trait, then is not a valid use-case: all of its users are required, by definition, to add a $this->container property to its dependencies, which will of course have an impact on that class' contract and the contract of its children.

为什么作者声称这是一个糟糕的用例,如果这可能是某人需要的?就像如果某人几乎没有 class 都具有所需的依赖关系并且所有这些都重复出现相同的逻辑,他们是否应该只复制代码?

确实以这种方式使用了 trait - 一个坏主意。 如果有人决定在你的代码中使用这个特性——他必须确保 "container" 属性的存在。 "container" 应该是正确的类型(包括使用的方法)——否则会出错。实际上这段代码是不能重用的,而且这有潜在的bug。另外,它违反了SOLID规则的一个规则DIP(依赖倒置原则)。

可以解决这个问题:

interface ExampleContainerInterface{

}
trait CheckPermissionTrait
{
    protected $container;
    public function __construct(ExampleContainerInterface $container)
    {
        $this->container = $container;
    }

    protected function checkPermission($object_id)
    {
        $judge = $this->container->get('acme_judge');
        $user  = $this->container->get('security.context')->getToken()->getUser();
        if( !$judge->isPermitted($user, $object_id) ) {
            throw $this->createAccessDeniedException("Brabbel");
        }
    }
}

class ExampleClassA
{
    use CheckPermissionTrait;
}
class ExampleClassB
{
    use CheckPermissionTrait;
}

或者像这样 (php7):

interface ExampleContainerInterface{

}
trait CheckPermissionTrait
{
    abstract public function getContainer():ExampleContainerInterface;
    protected function checkPermission($object_id)
    {
        $container = $this->getContainer();
        $judge = $container->get('acme_judge');
        $user  = $container->get('security.context')->getToken()->getUser();
        if( !$judge->isPermitted($user, $object_id) ) {
            throw $this->createAccessDeniedException("Brabbel");
        }
    }
}

class ExampleClassA
{
    use CheckPermissionTrait;
    protected $container;
    public function getContainer():ExampleContainerInterface
    {
        return $this->container;
    }
}
class ExampleClassB
{
    use CheckPermissionTrait;

    protected $container;
    public function getContainer():ExampleContainerInterface
    {
        return $this->container;
    }
}

我同意这一点,我认为 traits 不应该使用 class 的任何方法或属性,但是当我阅读 laravel 框架源代码时,我看到很多滥用这个一些trait,比如InteractWithInput trait跟request是深度耦合的class,很迷惑

trait InteractsWithInput
{
    //the getInputSource method is defined in Request class
    public function input($key = null, $default = null)
    {
        return data_get(
            $this->getInputSource()->all() + $this->query->all(), $key, $default
        );
    }
}

class Request
{
    use InteractsWithInput
    protected function getInputSource()
    {
        if ($this->isJson()) {
            return $this->json();
        }

        return in_array($this->getRealMethod(), ['GET', 'HEAD']) ? $this->query : $this->request;
    }
}