Symfony2 validation value inside a form event listener? - forms

I have the following snippet of code in a Symfony2 form:
$builder->add('AccountID');
$builder->get('AccountID')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $Event) {
//Do something but only if AccountID passed validation
}
);
Right now POST_SUBMIT gets triggered whether it passes validation or not.
How can I tell if the field was properly validated inside the event listener?
I'd rather not have an if to check for the same validation I specified inside the validation.yml on the field.
Is this possible?

How about using $event->getForm()->isValid()?
This should be reliable if your event listener gets called after the validation step took place.
Note that the validation step is to be found within a form subscriber itself and is listening for POST_SUBMIT - same event you are trying to attach to.
For reference, check Symfony\Component\Form\Extension\Validator\EventListener\ValidationListener.

in symfony 4 i do like this for validating form input base on another form element :
->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
$cardData=$event->getData();
$form=$event->getForm();
if(Your Valdiation Condition){
}
else{
$form->addError(new FormError("form error message"));
}
});

Related

Laravel backpack form request getting empty input

Laravel backpack auto generated form request for crud. How should i get the input from the form request to use it in unique validation eg: email?
I've tried dd($this), but all the value is empty or null. i think the request is not passing in at all ?
public function rules()
{
return [
'email' => 'required|unique:users,email', $this->id
];
}
I expect to get the id from the request and using it in my unique form validation
We can get the id with the below simple way , i have tried it and it works for me.
public function rules()
{
$id = $this->get('id') ?? request()->route('id');
return [
'email' => 'required|unique:users,email', $id
];
}
Referenced from the below mentioned url
https://github.com/Laravel-Backpack/PermissionManager/blob/master/src/app/Http/Requests/UserUpdateCrudRequest.php
If you dd($this) you will get nothing because the method rules() is called on page load and not only on form submission. If you want to check the submitted values, you can use something like:
if (!$this->id) dd($this);
Then, under request->parameters, you can see the value of all the fields included in the request.

Symfony2 (>= 2.3): How to listen to parent form event from child?

I have a custom FormType, which needs to add itself to the parent Entity when the parent Form persists.
In Symfony < 2.3 this could be done by doing the following:
class FooType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options);
...
$builder->getParent()->addEventSubscriber(new FooSubscriber)
}
}
class FooSubscriber implements EventSubscriberInterface
{
static function getSubscribedEvents()
{
return array(
FormEvents::POST_SUBMIT => 'postSubmit'
);
}
}
But after upgrading to Symfony 2.6 I've discovered that $builder->getParent() has been removed. But now I can't listen to the parent being submitted.
So I added the listener to my builder and referenced the parent from within the Subscriber. But this doesn't really work, since I do a check on the parent form being valid - which it isn't, since it's not submitted yet:
function postSubmit(FormEvent $e)
{
if ($e->getForm()->getParent()->getRoot()->isValid()) {
//this gives 'false'
This false is caused by the next piece of code:
// Symfony\Component\Form\Form.php # line 744
public function isValid()
{
if (!$this->submitted) {
return false;
}
And because the parent form first loops through all the childs and submits that, before setting $this->submitted = true on itself... I'm not sure if the parent is valid.
TL;DR
How can I add an Eventlistener to my parent Form, without having to adjust my parent Form? I want my FooType be something I can add to all forms, without having to know/remember to do some logic for that FooType specific.
I needed the same functionality because I have a custom form field that needs the parent entity after all mapped fields have been updated. Unfortunately the POST_SUBMIT of child forms is called before SUBMIT on the parent is run.
I ended up passing the eventDispatcher to the child, and tying my listener there. I needed two listeners to get the job done: one to get the processed value, and one to update the main entity. passing $generatedPassword to the closure by reference allows you to share data from the child event to the parent.
#Parent::buildForm
$builder->add('generate_password', GeneratePasswordType::class, [
'event_dispatcher' => $builder->getEventDispatcher(),
]);
#Child::buildForm
// first listen to submit event to get current field value
$generateNewPassword = false;
$builder->addEventListener(FormEvents::SUBMIT, function (FormEvent $event) use (&generateNewPassword) {
$generateNewPassword = null !== $event->getData();
});
// then run updater after parent entity has been updated
$parentDispatcher = $options['event_dispatcher'];
$parentDispatcher->addListener(FormEvents::POST_SUBMIT, function (FormEvent $event) use (&$generateNewPassword) {
$user = $event->getData();
if(true === $generateNewPassword){
// update password & email user new credentials
}
}
(The custom field is a checkbox marked 'generate new password on save' for a user management module. It emails the user the generated password, which is why I need the latest email address from the main entity)

Yii2: how to use custom validation function for activeform?

In my form's model, I have a custom validation function for a field defined in this way
class SignupForm extends Model
{
public function rules()
{
return [
['birth_date', 'checkDateFormat'],
// other rules
];
}
public function checkDateFormat($attribute, $params)
{
// no real check at the moment to be sure that the error is triggered
$this->addError($attribute, Yii::t('user', 'You entered an invalid date format.'));
}
}
The error message doesn't appear under the field in the form view when I push the submit button, while other rules like the required email and password appear.
I'm working on the Signup native form, so to be sure that it is not a filed problem, I've set the rule
['username', 'checkDateFormat']
and removed all the other rules related to the username field, but the message doesn't appear either for it.
I've tried passing nothing as parameters to checkDateFormat, I've tried to explicitly pass the field's name to addError()
$this->addError('username', '....');
but nothing appears.
Which is the correct way to set a custom validation function?
Did you read documentation?
According to the above validation steps, an attribute will be
validated if and only if it is an active attribute declared in
scenarios() and is associated with one or multiple active rules
declared in rules().
So your code should looks like:
class SignupForm extends Model
{
public function rules()
{
return [
['birth_date', 'checkDateFormat'],
// other rules
];
}
public function scenarios()
{
$scenarios = [
'some_scenario' => ['birth_date'],
];
return array_merge(parent::scenarios(), $scenarios);
}
public function checkDateFormat($attribute, $params)
{
// no real check at the moment to be sure that the error is triggered
$this->addError($attribute, Yii::t('user', 'You entered an invalid date format.'));
}
}
And in controller set scenario, example:
$signupForm = new SignupForm(['scenario' => 'some_scenario']);
Try forcing the validation on empty field
['birth_date', 'checkDateFormat', 'skipOnEmpty' => false, 'skipOnError' => false],
Also, make sure you don't assign id to your birth_date field in your view.
If you do have id for your birth_date, you need to specify the selectors
<?= $form->field($model, 'birth_date', ['selectors' => ['input' => '#myBirthDate']])->textInput(['id' => 'myBirthDate']) ?>
To make custom validations in yii 2 , you can write custom function in model and assign that function in rule.
for eg. I have to apply password criteria in password field then I will write like this in model.
public function rules()
{
return [
['new_password','passwordCriteria'],
];
}
public function passwordCriteria()
{
if(!empty($this->new_password)){
if(strlen($this->new_password)<8){
$this->addError('new_password','Password must contains eight letters one digit and one character.');
}
else{
if(!preg_match('/[0-9]/',$this->new_password)){
$this->addError('new_password','Password must contain one digit.');
}
if(!preg_match('/[a-zA-Z]/', $this->new_password)){
$this->addError('new_password','Password must contain one character.');
}
}
}
}
You need to trigger $model->validate() somewhere if you are extending from class Model.
I stumbled on this when using the CRUD generator. The generated actionCreate() function doesn't include a model validation call so custom validators never get called. Also, the _form doesn't include and error summary.
So add the error summary to the _form.
<?= $form->errorSummary($model); ?>
...and add the validation call - $model->validate() - to the controller action
public function actionCreate()
{
$model = new YourModel();
if ($model->load(Yii::$app->request->post()) && $model->validate()) {...
Although it's an old post i thought I should answer.
You should create a Custom Validator Class and to create a validator that supports client-side validation, you should implement the yii\validators\Validator::clientValidateAttribute() method which returns a piece of JavaScript code that performs the validation on the client-side. Within the JavaScript code.
You may use the following predefined variables:
attribute: the name of the attribute being validated.
value: the value being validated.
messages: an array used to hold the validation error messages for
the attribute.
deferred: an array which deferred objects can be pushed into
(explained in the next subsection).
SO that means you can use messages array to push your messages to the client end on runtime within the javascript code block in this method.
I will create a class that includes dummy checks that could be replaced the way you want them to. and change the namespace according to your yii2 advanced or basic.
Custom Client-side Validator
namespace common\components;
use yii\validators\Validator;
class DateFormatValidator extends Validator{
public function init() {
parent::init ();
$this->message = 'You entered an invalid date format.';
}
public function validateAttribute( $model , $attribute ) {
if ( /*SOME CONDITION TO CHECK*/) {
$model->addError ( $attribute , $this->message );
}
}
public function clientValidateAttribute( $model , $attribute , $view ) {
$message = json_encode ( $this->message , JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
return <<<JS
if ($("#DATE-1").val()=="" || $("#DATE-2").val() =="") {
messages.push($message);
}
JS;
}
}
and then inside your model SigupForm add the rule
['birth_date', 'common\components\DateFormatValidator'],
Deferred Validation
You can even add ajax calls inside the clientValidateAttribute function and on the base of the result of that ajax call you can push message to the client end but you can use the deferred object provided by yii that is an array of Deferred objects and you push your calls inside that array or explicitly create the Deferred Object and call its resolve() method.
Default Yii's deferred Object
public function clientValidateAttribute($model, $attribute, $view)
{
return <<<JS
deferred.push($.get("/check", {value: value}).done(function(data) {
if ('' !== data) {
messages.push(data);
}
}));
JS;
}
More about Deferred Validation
You need to render the model from controller. Without initializing the model in view. And in the controller you need to call the validate function
Are you sure the first parameter of addError shouldn't be like this
$this->addError(**'attribute'**, Yii::t('user', 'You entered an invalid date format.'));
I had common problem.
In your validation function:
public function checkDateFormat($attribute, $params)
{
// no real check at the moment to be sure that the error is triggered
$this->addError($attribute, Yii::t('user', 'You entered an invalid date format.'));
}
$params doesn`t get any value at all. It actually always equals to Null. You have to check for your attribute value in function:
public function checkDateFormat($attribute, $params)
{
if($this->birth_date == False)
{
$this->addError($attribute, Yii::t('user', 'You entered an invalid date format.'));
}
}
that`s how it worked for me.
If you don't use scenarios for your model, you must mark your atribute as 'safe':
['birth_date','safe'],
['birth_date', 'checkDateFormat'],
And, on the other hand, you can use this for date validation:
['birth_date','safe'],
[['birth_date'],'date', 'format'=>'php:Y-m-d'],
You can change format as you want.
**We should set attributes to the function to work with input value **
public function rules()
{
return [
['social_id','passwordCriteria'],
];
}
public function passwordCriteria($attribute, $params)
{
if(!empty($this->$attribute)){
$input_value = $this->$attribute;
//all good
}else{
//Error empty value
$this->addError('social_id','Error - value is empty');
}
}
Are you by any chance using client side validation? If you do then you have to write a javascript function that would validate the input. You can see how they do it here:
http://www.yiiframework.com/doc-2.0/guide-input-validation.html#conditional-validation
Another solution would be to disable client validation, use ajax validation, that should bring back the error too.
Also make sure that you have not overwritten the template of the input, meaning make sure you still have the {error} in there if you did overwrite it.
Your syntax on rules should be something like this man,
[['birth_date'], 'checkDateFormat']
not this
['birth_date', 'checkDateFormat']
So in your case, it should look like below
...
class SignupForm extends Model
{
public function rules()
{
// Notice the different with your previous code here
return [
[['birth_date'], 'checkDateFormat'],
// other rules
];
}
public function checkDateFormat($attribute, $params)
{
// no real check at the moment to be sure that the error is triggered
$this->addError($attribute, Yii::t('user', 'You entered an invalid date format.'));
}
}

how to handle custom exceptions/flashes messages in a RESTful API with Symfony2

Let's say I run a clothing web shop. I have an order form that my customers can place orders. A customer fills out their order for a t-shirt and submits the form. The problem is the t-shirt is out of stock.
This isn't a validation issue, because the order was valid, so i wouldn't create a custom validator.
I'm using the FOSRestBundle and trying to support both HTML and JSON. I'm also trying to accomplish a lot through domain events.
Here's my pseudo coded controller
class OrderController extends BaseOrderController
{
public function createAssetAction(Request $request)
{
$form = $this->getOrderCreateForm();
$form->handleRequest($request);
if ($form->isValid()) {
// should i wrap this in a try/catch?
$this->dispatch(OrderEvents::PRE_CREATE, $event = new FormEvent($form, $form->getData()));
// or check if the event is stopped and pull out an error message?
if ($order = $event->getData()) {
$this->getOrderRepository()->persist($order);
$this->dispatch(OrderEvents::POST_CREATE, new ResourceEvent($order));
$view = $this->routeRedirectView('my_orders', array(), Codes::HTTP_MOVED_PERMANENTLY);
$view->setData(array('order' => $order));
return $this->handleView($view);
}
}
$view = $this->view($form)
->setTemplateVar('form')
->setTemplate('OrderBundle:Order:newOrder.html.twig');
return $this->handleView($view);
}
}
I'm thinking i would check the inventory of the order in the OrderEvents::PRE_CREATE event. I don't know whether to throw an exception in an event listener if there is insufficient stock or stop the event propagation and add a message to the event.
If i threw an exception, i'd have to catch it in the controller so i could set the exception message as a flash message for an HTML request or just return the message directly for a JSON response.
If i did that, i'd have to wrap all event dispatching in the controller in try/catches, which seems wrong.
I've seen the LiipCacheControl bundle which converts flash messages to a cookie but that introduces state into my stateless REST API.
Anybody have suggestions?

Symfony 1.4: Check if form has errors inside form class

Is there a simple way in Symfony 1.4 to know whether a submitted form had any errors inside the form class? I'm familiar with the $form['some_field']->hasErrors() for templates but in this case I'd like to run a post-validator only if the form didn't have any errors with the standard validators. I'm basically after something like:
public function configure() {
// widgets
// standard validators
if (!this->hasErrors()) {
// run post-validator
}
}
The API documentation is as cryptic as usual. Thanks in advance.
Since the validation is perfom on the bind call, I don't see other place to post validate on error than in the bind function. So, in your form class:
public function bind(array $taintedValues = null, array $taintedFiles = null)
{
parent::bind($taintedValues, $taintedFiles);
if ($this->hasErrors())
{
// do post validate
// you can access values from your form using $taintedValues
}
}
But you will have to manually call the validator instead of just define a new one (since the bind process has already been done).