Symfony Form Validation Constraint Expression - validation

I have form, and need to create inline validation:
$builder
->add('Count1', 'integer', [
'data' => 1,
'constraints' => [
new NotBlank(),
new NotNull(),
],
])
->add('Count2', 'integer', [
'constraints' => [
new NotBlank(),
new NotNull(),
],
])
->add('Count3', 'integer', [
'data' => 0,
'constraints' => [
new NotBlank(),
new NotNull(),
],
])
How white inline validation Expression for rules
Count2 >=Count1
Count3 <=Count2
Count2 >= $someVariable

Other solution by using Expression Constraint for cases 1 and 2.
use Symfony\Component\Validator\Constraints as Assert;
// ...
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'constraints' => [
new Assert\Expression([
'expression' => 'value["Count2"] >= value["Count1"]',
'message' => 'count2 must be greater than or equal to count1'
]),
new Assert\Expression([
'expression' => 'value["Count3"] <= value["Count2"]',
'message' => 'count3 must be less than or equal to count2'
]),
],
]);
}
For case 3 you can use Assert\GreaterThanOrEqual constraint directly on Count2 field.
I guess your form doesn't have a binding object model, otherwise to read the documentation referred is enough and better because you could use these expression on your properties directly.

You can utilize CallbackValidator (docs):
In your case, in order to validate one field againt another, you need to add constraint to a form type, not the field:
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'constraints' => array(
new Assert\Callback(function($data){
// $data is instance of object (or array) with all properties
// you can compare Count1, Count2 and Count 3
// and raise validation errors
});
)
));
}
You can also pass constraints option while creating a form if you don't want to set it in setDefaultOptions.

Starting from easiest
3) Count2 >= $someVariable
->add('Count3', 'integer', [
'data' => 0,
'constraints' => [
new NotBlank(),
new NotNull(),
new GreaterThanOrEqual($someVariable),
],
])
1) As for two first, you must implement constraint for a class scope, rather than property scope. And assign these constraints for a whole form
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('Count1', 'integer', [
'data' => 1,
'constraints' => [
new NotBlank(),
new NotNull(),
],
])
->add('Count2', 'integer', [
'constraints' => [
new NotBlank(),
new NotNull(),
],
])
->add('Count3', 'integer', [
'data' => 0,
'constraints' => [
new NotBlank(),
new NotNull(),
],
])
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(['constraints' => [
new YourCustomConstraint(),
]]);
}
How to implement validator, see in the documentation.
But in your YourCustomConstraintValidator you will have something like
public function validate($value, Constraint $constraint)
{
if ($value->getCount1() > $value->getCount2() {
$this->context->addViolation(...);
}
}

I had some problem comparing two dates using Symfony's Expressions.
This is the code that works:
$builder->add(
'end',
DateTimeType::class,
[
'label' => 'Campaign Ends At',
'data' => $entity->getEnd(),
'required' => true,
'disabled' => $disabled,
'widget' => 'single_text',
'constraints' => [
new Assert\GreaterThan(['value' => 'today']),
new Assert\Expression(
[
//here end and start are the name of two fields
'expression' => 'value > this.getParent()["start"].getData()',
'message' => 'my.form.error.end.date.bigger.than.start'
]
),
]
]
);

Related

Symfony validation/comparison between two dates in a form

So I want to create a form with two dates. I want those dates to have constraints, that check if one of each is bigger than the other. This way I can get a valid time span.
I build a custom FormType like this:
class MainFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add( 'date_from', DateType::class, [
'constraints' => [
new Assert\NotBlank()
]
])
->add( 'date_to', DateType::class, [
'constraints' => [
new Assert\NotBlank(),
new Assert\GreaterThanOrEqual([
'value' => $builder->get('date_from'),
'message' => 'datepickers.to_less_than_from'
])
]
]);
}
}
At this point symfony does not throw an error, but the validation wont work either.
Is there any way to compare two dates, that are located in the same form, via Symfonys validator?
You can do this with this code
->add('dateToPublish', DateTimeType::class, array(
'label' => 'notifications.date_to_publish',
'required' => true,
'widget' => 'single_text',
'constraints' => [
new NotBlank(),
new DateTime(),
]))
->add('dateToEnd', DateTimeType::class, array(
'label' => 'notifications.date_to_end',
'required' => true,
'widget' => 'single_text',
'constraints' => [
new NotBlank(),
new DateTime(),
new GreaterThan([
'propertyPath' => 'parent.all[dateToPublish].data'
]),
]));
As you can see I'm making a constraint on dateToEnd to be greater than dateToPublish
Hopes this helps you

Yii2 cant sort in calculated values

In my model i have
public function getRating(){
$rating=ShopRating::getRating($this->id);
if($rating)
return $rating->rating;
else
return 0;
}
And in search model i have created a virtual column like
public $rating;
and marked it as safe
[['rating'], 'safe']
And in the controller defined my sort
private function getSort(){
$sort = new Sort([
'attributes' => [
'date-asc' => [
'asc' => ['date' => SORT_ASC],
'label' => Inflector::camel2words('Old to New'),
],
'date-desc' => [
'desc' => ['date' => SORT_DESC],
'label' => Inflector::camel2words('New to Old'),
'default' => SORT_DESC,
],
'rating-desc' => [
'desc' => ['rating' => SORT_DESC],
'default' => SORT_DESC,
],
'rating-desc' => [
'desc' => ['rating' => SORT_DESC],
'default' => SORT_DESC,
],
],
]);
return $sort;
}
like this.
$sort=$this->getSort();
And my date-wise sort is working perfectly.But the sorting on the calculated value doesn't work. Meaning sorting with the rating asc and desc is not working. How to can I make it to work

How to add fields which are not available in database?

I am not able to add field which is not available in my database
I have tried adding
$this->crud->addFields([
[
'name' => 'coupon_type',
'label' => 'Coupon For',
'type' => 'select_from_array',
'options' => [
// Options
],
'allows_null' => true,
'default' => 1,
'attributes' => [
'id' => 'coupon_type'
]
]
]);
I want to add fields in my create page.
You can do this by defining accessors for your "virtual" attributes
public function getIsAdminAttribute()
{
return $this->attributes['admin'] == 'yes';
}
and defining the appends on your model
protected $appends = ['is_admin'];
Find everything in the docs here:
https://laravel.com/docs/5.8/eloquent-serialization#appending-values-to-json

Yii2: How to filter in a custom ActiveDataProvider?

I made this ActiveDataProvider:
$dataProvider = new ActiveDataProvider([
'query' => $query->asArray(), // It is a simple SQL query.
'key' => 'item',
'sort' => [
'attributes' => [
'item',
'quantity',
],
]
]);
I need to add the filters for item and quantity because it doesn't work (it doesn't search):
You must have not set your attributes item and quantity as safe in your SearchModel rules(), method.
You have to set them as safe in SearchModel.
public function rules()
{
return [
[['item', 'quantity'], 'safe'],
];
}
In your case, your Data Provider should be simply like this:
// It is not necessary to use "$query->asArray()", because your "$query" itself is object of ActiveQuery
$dataProvider = new ActiveDataProvider([
'query' => $query,
'key' => 'item',
]);

yii2 behaviors ActiveRecord::EVENT_BEFORE_INSERT not working

My behavior function in my model is as follows
public function behaviors()
{
return [
'timestamp' => [
'class' => 'yii\behaviors\TimestampBehavior',
'attributes' => [
ActiveRecord::EVENT_BEFORE_INSERT => ['log_in_time' ],
ActiveRecord::EVENT_BEFORE_UPDATE => ['log_in_time'],
],
'value' => new Expression('NOW()'),
],
];
}
/**
* validation rules
*/
public function rules()
{
return [
['username','filter', 'filter' => 'trim'],
['username','required'],
//['username','unique'],
['username','string', 'min' => 2, 'max' => 255],
['password','required'],
];
}
/* Your model attribute labels */
public function attributeLabels()
{
return [
/* Your other attribute labels */
];
}
public function scenarios()
{
$scenarios = parent::scenarios();
$scenarios['login'] = ['username','log_in_time'];//Scenario Values Only Accepted
return $scenarios;
}
But it is not updating the log_in_time column . log_in_time is DATETIME . The value getting inserted is 0000-00-00 00:00:00 .What is the problem ?
Are you by any chance overwriting beforeSave (or beforeInsert or beforeUpdate) in that particular model? If you do then you have to call something like
public function beforeSave($insert)
{
if (parent::beforeSave($insert)) {
............
return true;
}
return false;
}
I recently did something similar and spent some good time researching this only to realize that I did not call the parent before save.
If you use AttributeBehavior instead of TimestampBehavior and do exactly what you did, I believe it will work.
public function behaviors()
{
return [
'timestamp' => [
'class' => 'yii\behaviors\AttributeBehavior',
'attributes' => [
ActiveRecord::EVENT_BEFORE_INSERT => ['log_in_time' ],
ActiveRecord::EVENT_BEFORE_UPDATE => ['log_in_time'],
],
'value' => new Expression('NOW()'),
],
];
}
Or you can try setting createdAtAttribute and $updatedAtAttribute to 'log_in_time' in the TimestampBehavior, that should also work.
public function behaviors()
{
return [
'timestamp' => [
'class' => 'yii\behaviors\TimestampBehavior',
'createdAtAttribute' => 'log_in_time',
'updatedAtAttribute' => 'log_in_time',
],
];
}
I am not sure why it does not work like you posted.
This works for me 100%
/**
* #inheritdoc
*/
public function behaviors()
{
return [
'blameable' => [
'class' => BlameableBehavior::className(),
'attributes' => [
BaseActiveRecord::EVENT_BEFORE_INSERT => ['create_by', 'update_by'],
BaseActiveRecord::EVENT_BEFORE_UPDATE => 'update_by'
],
],
'timestamp' => [
'class' => TimestampBehavior::className(),
'attributes' => [
BaseActiveRecord::EVENT_BEFORE_INSERT => ['create_time', 'update_time'],
BaseActiveRecord::EVENT_BEFORE_UPDATE => 'update_time',
],
'value' => new Expression('NOW()'),
],
];
}

Resources