2

This is the function supportsClass in class Voter

http://symfony.com/doc/2.5/cookbook/security/voters_data_permission.html

 public function supportsClass($class)
{
    $supportedClass = 'AppBundle\Entity\Post';

    return $supportedClass === $class || is_subclass_of($class, $supportedClass);
}

I'd like to know if it is possible to use one class voter for many entities in the same bundle or I have to create a Voter for each entity ?

EDIT I have found this solution:

 public function supportsClass($class)
{
    $classes = array(
        'Project\AgenceBundle\Entity\Agence',
        'Project\AgenceBundle\Entity\SubAgence',
        'Project\AgenceBundle\Entity\Subscription'
    );
    $ok = false;
    foreach($classes as $supportedClass)
    $ok = $ok || $supportedClass === $class || is_subclass_of($class, $supportedClass);

    return $ok;
}
Wouter J
  • 41,455
  • 15
  • 107
  • 112
hous
  • 2,577
  • 2
  • 27
  • 66
  • Yes, as you see in @Jindra answer, but do it only for classes which have something in common and express this via an interface. Don't create one voter class for a bunch of different entities and mess with a huge amount of ifs and switches. – Emii Khaos Jan 27 '15 at 23:23
  • Hi, what about delete all lignes in this function and just replace it by `return true;` . – hous Jan 28 '15 at 14:09
  • That pretty much makes whole point of voter system useless. Voter is there to allow you write reusable logic for specific objects. You should have as many voters as your application logic needs. Maybe you could tell us what you are trying to do. There might be something better for your usecase. Like ACL. – Jindra Jan 28 '15 at 14:23
  • Hi , @Jindra : I have some entities related with entity User, I'd like juste to secure them , user can delete and edit only his own entity. I have edited my question – hous Jan 28 '15 at 19:30
  • The idea of what you find is pretty much same as what I wrote as my answer. I have edited my answer to give more information. – Jindra Jan 28 '15 at 20:33

1 Answers1

2

In short yes, you can reuse your Voter as much as you want. For example your voter can work against interface.

However you shouldn't use voter to judge too many things just to save few lines of code. Probably if voter can judge some set of objects that are not derivate class but they have something in common. Which in turn is good place for interface and trait. And so voter should work against that interface. Thats what interfaces are there for, to give you contract.

If you have array of classes in supportsClass and than in future you change something in one of them. You might break Voter for that class, but since it is not bound by interface, no static analysis or PHP interpretr will catch it. And that is quite a problem.

As you can see Voter is build from 3 parts.

  • supportsClass which tells Symfony whether this Voter can decide about objects of certain class.
  • supportsAttribute which tells Symfony whether this Voter can decide about this action.
  • vote Based on passed object it decides if it yes/no/dunno

This is not exactly how it works. But it should give you idea what it is voter is there for.

You:

//You in controller
 if (!$this->get('security.context')->isGranted('edit', $object)) {
     throw new AuthenticationException('Not a step furher chap!');
 }

Framework:

//security.context
//again it is rough idea what it does for real implementation check Symfoy github
public function isGranted($action, $object) {
  //There it goes trough all voters from all bundles!
  foreach ($this->voters as $voter) {
      if (!$voter->supportsClass(get_class($object))) {
          //this voter doesn't care about this object
          continue;
      }

      if (!$voter->supportsAttribute($action)) {
          //this voter does care about this object but not about this action on it
          continue;
      }

      //This voter is there to handle this object and action, so lest se what it has to say about it
      $answer = $voter->vote(..);
      ...some more logic
  }
}

Weird example from top of my head:

interface Owneable {
    public function getOwnerId();
}

trait Owned {

    /**
     * @ORM....
     */ 
    protected $ownerId;

    public function getOwnerId() {
        return $this->ownerId;
    }

    public function setOwnerId($id) {
        $this->ownerId = $id;
    }
}

class Post implements Owneable {
   use Owned;
}

class Comment implements Owneable {
   use Owned;
}

class OwnedVoter implements VoterInterface
{

    public function supportsAttribute($attribute)
    {
        return $attribute === 'edit';
    }

    public function supportsClass($class)
    {
        //same as return is_subclass_of($class, 'Owneable');
        $interfaces = class_implements($class);
        return in_array('Owneable' , $interfaces);
    }

    public function vote(TokenInterface $token, $ownedObject, array $attributes)
    {
        if (!$this->supportsClass(get_class($ownedObject))) {
            return VoterInterface::ACCESS_ABSTAIN;
        }


        if (!$this->supportsAttribute($attributes[0])) {
            return VoterInterface::ACCESS_ABSTAIN;
        }


        $user = $token->getUser();
        if (!$user instanceof UserInterface) {
            return VoterInterface::ACCESS_DENIED;
        }




        $userOwnsObject = $user->getId() === $ownedObject->getOwnerId();
        if ($userOwnsObject) {
            return VoterInterface::ACCESS_GRANTED;
        }


        return VoterInterface::ACCESS_DENIED;
    }
}

TIP: Voter is just class as any other, things like inheritance and abstract class work here too!

TIP2: Voter is registered as service you can pass security.context or any other service to it. So you can reuse your code pretty well

Jindra
  • 780
  • 13
  • 39