Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Best practice: multiple voters for different items #146

Open
vworldat opened this issue Apr 19, 2014 · 2 comments
Open

Best practice: multiple voters for different items #146

vworldat opened this issue Apr 19, 2014 · 2 comments

Comments

@vworldat
Copy link

I have a general question regarding the new matcher/voter system: what if I need a more complex voting mechanism for some items, but not for all of them?

Imagine having some items that need both a route check (as done by the current RouteVoter) and then look up something in the database and thus decide if the item is current or not. Since the RouteVoter would return true or false for any item that is tested, there would be no chance for other voters to check anything at all.
Voters also seem not to provide any specific order as the extensions do, so if I don't add them all at the same time I won't know which one comes first.

It seems to me that at the moment there is no other way than to check these things while creating the menu items in the first place. I would love to give the items or the menu system in general some more "intelligence", because this would allow for faster and simpler menu definitions (e.g. in a YAML file) with specific runtime details figured out later.

Is there anything I have missed?

@fyrye
Copy link

fyrye commented Jun 29, 2016

Voters are iterated over in the order they are supplied to Matcher::addVoter, though I agree should have a priority assignment.

Additionally the default Matcher stops checking on the first entry it finds and caches the result for subsequent checks.

    foreach ($this->voters as $voter) {
            $current = $voter->matchItem($item);
            if (null !== $current) {
                break;
            }
        }

The default Voters are just for the most simplistic use cases, and handles the bulk of how menus are generally handled.

For my purposes I created my own Matcher that allows the assignment of the Voters with priorities and cascades the Voters results instead of stopping at the first result.
I also created my own Voters that allowed for more complex rule checking of multiple route and uri values, along with validation against Database result sets.

The end result of my specific needs was to match, Sidebar, Footer and Boilerplate current menu links, while displaying the MenuItems that match the user's permissions.

Since Guest users do not have permissions, the User Voter checked to see if the user permissions was empty and simply returned null to allow for the next Voter to execute.

Example

class UserVoter implements VoterInterface
{
    private $permissions;

    public function __construct($permissions = null)
    {
        $this->permissions = $permissions;
    }

    public function matchItem(ItemInterface $item)
    {
       if (null !== $this->permissions && null !== $item->getExtra('permission')) {
          $item->setDisplay(isset($this->permissions[$item->getExtra('permission')]));
       }
       return null;
    }
}

$matcher->addVoter(new UserVoter($userPermissions)); //executes first
$matcher->addVoter(new UriVoter($_SERVER['REQUEST_URI'])); //executes second

@stof
Copy link
Collaborator

stof commented Apr 10, 2018

The RouteVoter will only perform positive matching, never negative matching (it abstains instead, letting next voters decide). And btw, it already handles checking multiple routes.

Regarding user permissions, this is not really a job for the voters. It should belong to the builder (or to a factory extension), as you want to hide the item entirely from the menu, not just mark it as not being the current item.

Regarding the priority, the library assumes that the code registering voters can control the order in which it registers them, rather than implementing a sorting system internally. When using the Symfony bundle, it uses a priority mechanism (during the compile-time of the container, so there is no performance impact at runtime) to build this order.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants