我在 Stackexchange 上看到过一个示例(请注意访问类属性的特征):
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");
}
}
}
并阅读其中一条回复评论:
你的特质不是一个有效的用例:它的所有用户都是 根据定义,需要将 $this->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;
}
}
我同意这一点,我认为特征不应该使用类的任何方法或属性,但是当我阅读 Laravel 框架源代码时,我看到很多对这种特征的滥用,例如,InteractWithInput 特征深深地再加上request类,就很混乱了
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;
}
}
或者您可以使用方法注入并且不违反任何内容:
trait CheckPermissionTrait
{
protected function checkPermission($object_id, $container)
{
$judge = $container->get('acme_judge');
$user = $container->get('security.context')->getToken()->getUser();
if( !$judge->isPermitted($user, $object_id) ) {
throw $this->createAccessDeniedException("Brabbel");
}
}
}
显然,当有人想要使用这个特征时,他必须在方法调用中提供容器作为参数。现在一切都已保存。