-
Notifications
You must be signed in to change notification settings - Fork 31
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
Allow to specify validation group #140
Conversation
What would be ideal would be to being able to do something like this directly in the input filter: class PostInputFilter
{
public function __construct()
{
// ...
$this->addValidationGroup('update', ['foo', bar']);
}
} Then the controller specification would simply be "put => 'update'". But this not possible so we need to stick with this for now :(. |
WTH? You don't like? |
@bakura10 no, this seems limiting to me, as well as very shady/hidden. What happens if I want some kind of filtering on one http method, and a different filtering on a different one ( |
Well, we HAVE to use validation group (this is made for that). The issue is that we cannot move this logic into the input filter because it has no idea of the context. So the only place where we actually know the context is the handlers and the controllers. Regarding your thing, the name of the method (post, put...) is used as name. So: class PostController extends AbstractRestfulController implements ValidationGroupProviderInterface
{
public function getValidationGroups()
{
return [
'post' => ['username', 'password'],
'put' => ['username']
]
}
} The PostHandler automatically set the validation group name to "post", while PutHandler to "put". If you have a custom handler you can pass "myMethod", and having a validation group "myMethod" in your specification. This is quite flexible because, as you are in the controller, you can also have access to the authorization service, to change the validation group according to the roles the user has; If you have any idea to make this clearer/less magic, don't hesitate. But we don't have A LOT of choices, actually. |
The idea is that wherever the input filter is built, we then set the validation group on it. Ideally in a factory here, or otherwise in the controller action itself (not sure if we handle validation before the action tho) |
That's what we do.
Not possible, because if you do that, you cannot have the awesome feature of having the complete validated and hydrated entity in the action. So this has to happen before :'(. regarding factory, of course it would be nice BUT I'm against forcing people to write even more factories for setting this, especially as this is pretty common to need validation groups. Input filters can, most of the time, be written without having to alter the input filter manager (thanks to the autoAddInvokable), and I'd like to keep this feature to avoid a factory explosion. Maybe @danizord has a good idea =). |
We can't set it in metadata?
|
You loose a lot of flexibility by doing this. For instance what if I want only admin to edit the "role" property? You can't do that in annotations, while ou can do that dynamically in the getValidationGroupSpecification by getting any authorization service, for instance. |
@bakura10 I don't see much of a difference between stuffing all the metadata in metadata or in a controller. An admin controller could still do whatever it wants by just ignoring that mapping in particular. |
Well, the validation group task is to actually "strip" any unwanted data from the incoming data. In ZfrRest all the data does input filter => hydrator. |
@bakura10 yes, that with the default controller though. We had a setting to disable automatic validation/hydration iirc |
Yep. You had a setting to disable auto hydration and autovalidation. But once they disable those sane settings, they are up to instantiating the input filter themselves, and doing what they want in the controller. It's not much our problem then. |
I think what you are trying to do is |
Note that filtering and authorization rules quickly escalate in much worse/complex things, that are mostly impossible to map via static config: So I don't think that trying to solve the problem in a controller's method will do the trick. Instead, I'd suggest having a dependency deal with it, and in this case it is tied to the filter itself. |
* @param InputFilterInterface $inputFilter | ||
* @return InputFilterInterface | ||
*/ | ||
public function configureInputFilter(InputFilterInterface $inputFilter) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
protected
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How is the handler supposed to call it then? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interface plz! 😃
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bakura10 isn't the handler trait applied to the controller itself?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. a handler is a different object than the controller. The traits are applied to the handler.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure an interface is needed here @danizord .
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -25,12 +25,13 @@ class DataValidationObject | |||
{ | |||
use DataValidationTrait; | |||
|
|||
protected $controller; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused?
@@ -19,7 +19,10 @@ | |||
namespace ZfrRest\Mvc\Controller\MethodHandler; | |||
|
|||
use Zend\InputFilter\InputFilterPluginManager; | |||
use Zend\Mvc\Controller\AbstractController; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Useless
@@ -133,6 +134,17 @@ public function getMethodHandlerManager() | |||
} | |||
|
|||
/** | |||
* Hook to configure an input filter depending on the method |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should mention that the passed in input filter is the one produced/fetched by zfrrest
Allow to specify validation group
```php | ||
use Zend\InputFilter\InputFilterPluginManager; | ||
|
||
class UserController |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
extends AbstractRestfulController
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I'll update this.
After a bit of usage, this feature is really really nice. Most of the time, it's sufficient to only play with validation group, so this thing is really a big win. However, at usage, I don't like the I really preferred the The reasons for having getInputFilter were:
I'd say that for that kind of thing, usage must have priority over an edge case. And actually, this: public function configureInputFilter(InputFilterInterface $inputFilter)
{
// Configure it
} is a more nicer than: public function getInputFilter(InputFilterPluginManager $mnager, $inputFillterName)
{
$inputFilter = parent::getInputFilter($maanger, $inputFilterName);
// Configure it
} |
tl;dr: performance is NOT a problem (here).
If you override a method, you know you need to respect the LSP - nothing wrong with this.
It was much less flexible tbh. What would you have done if the validation logic for persistence and update differed by more than setting validation groups? For example, updating an email address of a user (assuming it's the identity) requires different validation while persisting or updating.
I don't see the performance concern - I approved @danizord's approach for the flexibility - I couldn't care less for the small performance footprint. public function getInputFilter(InputFilterPluginManager $manager, $inputFillterName)
{
$inputFilter = parent::getInputFilter($manager, $inputFilterName);
// Configure it
} This code block is simple and less magic, and it is really using simple principles. And most importantly, it allows me to build other input filters on demand. |
Well, you still can with the configure approach: public function configureInputFilter(InputFilterInterface $inputFilter)
{
// Wow, I've reached an edge case, I don't like this one. Let's create another one:
$inputFilterManager = $this->serviceLocator->get(InputFilterPluginManager::class);
$input = $inputFilterManager->get('foo');
return $input;
} What I don't like with this approach is that it introduces some incoherencies in the API. The HydrationTrait that is used in handlers actually create himself the hydrator, but for input filter, it's actually a constructor that construct it. I preferred to keep the construction in the handle, and let the controller just configuring it. |
Yes, but then you have an unused parameter, and that's indeed a code smell indicating some sort of waste. The smell indicates indeed a too specific implementation that cuts out customization |
Hi,
I run into a problem this morning, because of lack of validation group. With this PR, your controllers now can implement the new ValidationGroupProviderInterface (ideally, input filters should support named validation groups out of the box, this is something I'll think about):
The data validation trait will automatically detect if the controller implement this interface, and get the appropriate input filter.
Thoughts? ping @danizord