Symfony 4: how to get voters who voted for granted?

610 views Asked by At

How can I get voters who voted for "granted", inside event subscriber? Is there any service which I can fetch and use it?

My use case. My app has two voters: main voter and admin voter. I want to show additional flash message when access is disallowed by main voter but allowed by admin voter. I can solve this problem by two different solutions: by dispatching event inside admin voter or by setting flash msg inside voter — but I want to avoid it because I need to be sure that ONLY that one voter voted for access.

1

There are 1 answers

6
Jakumi On BEST ANSWER

So, I'll start with solutions, that answer the question you asked. I'll conclude with a solution to the (IMHO) underlying problem.

Extremely trivial solution:

Merge both voters into one, replacing them both. Setting the flash message if your condition is met is the responsibility of that merged voter, but it's trivial, since you have the "results" of both voters ...

Slightly more eloquent:

Create a CombinedVoter, replacing both. The CombinedVoter would receive both voters as dependencies and forward vote requests to them and return / cast the appropriate combination of them (so essentially a AccessDecisionManager disguised as a voter). The voter obviously would set the flash message, when your condition is met.

A little more useless:

Create a CombinedVoter, not replacing anything, but still receiving both voters as dependencies, forward vote requests to them and return ABSTAIN in all cases. The voter obviously would set the flash message, when your condition is met.

A little more over-engineered:

Add an EventDispatcher to both Voters and send out the vote events. Let a listener listen to those events and either trigger when it receives the second one (dirty af) or triggers again on a kernel event to set the flash message when your condition is met.

Slightly less over-engineered:

Add a service that is also a EventListener, that listens to the same kernel event to set the flash message, but doesn't listen to vote events, instead, inject that service into both voters and set the result of the vote directly on that service via setAdminVote()/setMainVote() or whatever.

The underlying problem:

Semantically, your voters are not meant to care about flash messages and what other voters have voted. It's neither their purpose nor their responsibility. To add this to their function is IMHO misguided, it's the wrong place and time! (Semantics!)

Instead, since this is probably a special message anyway, why not add this to your base template (there are certainly ways to check if someone is an admin but not allowed by "main voter", for example using is_granted in the template or app.user.roles directly (if appropriate). you could also write a small twig extension that adds that function and uses both voters somehow.

Why do I propose putting this logic in the template: because it's a display issue. You want something to be displayed, so it should be placed in the display part of your application. Also, there are many edge cases which you probably ignore, are unaware of, or put effort into avoiding: Submitting a form on a page that would trigger the flash message will probably get the flash message added on the POST (which, if done cleanly triggers a redirect to a page with GET) AND on the following GET, essentially adding two flash messages. Now you could not add the flash message on POST requests, but then the invalid POSTED forms would be without the flash message (because there'd be no GET). It's a mind f***. I really would advise against it.

Display is the frontend's job, add some helpers in services/extensions. Let the voters keep their single responsibility (which certainly is NOT adding flash messages). possibly extend your base template to function as a "pages using the extended base template should display the message", and then use that on the pages you want to show the message.

Really, do this cleanly ;o)

Addendum:

Flash messages are usually to provide feedback to the user to an action (s)he has done recently. For example, the user deleted a blog post. Then the flash message should read "blog post was deleted" (it might contain more information ^^).

And the utility of the sessions flash bag is the concept, that the flash messages will be kept there, until they are retrieved. This is convenient for feedback, because it is usually rendered the next time the page is actually rendered.

There are many many cases where this would be hard to manage otherwise. What if you only load fragments of your page (maybe ajax refreshing stuff), don't want your flash message in there, also not removed. What if you POSTed a form and are successfully redirected to some completely unrelated page. Should that page know anything about where the user comes from? Preferably not (separation of concerns). Flash messages don't care (single responsibility). The developer decides where flash messages can be displayed (usually on "full page render" or if accessed specifically).

They aren't a silver bullet either. If you have multiple browser tabs open, they can be confusing as hell (because flash messages might be shown, that were produced from another tab). However, if your web page requires multiple tabs in the browser, you have some other problems as well. However, I feel acceptance for the occasional "stray flash message" is good.