I have seen an example on Stackexchange (please note the trait accessing the class property):
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");
}
}
}
And read one of the repliers comments:
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.
Why is the author claiming that being a bad use case, if that might be what someone needs? Like if someone has few classes that all have the required dependency and the same logic reccures in all of them, should they just keep the code duplicated?
Indeed trait used in this way - a bad idea. If someone decide to use this trait in your code - he must ensure the existence of "container" attribute. "container" should be the correct type (include the methods used) - otherwise it will get an error. In fact this code can not be reused, and this potentially bug. In addition, it violates a rule DIP (dependency inversion principle) of the rules of SOLID.
It is possible to get around this:
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;
}
or like this (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;
}
}
I agree with that, I don't think traits should use any methods or attributes of the class, but when I read the laravel framework source code, I see many abuse of this kind of traits, for example, the InteractWithInput trait is deeply coupled with the request class, it's very confused
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;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With