Conditional field validation that depends on another field - forms

I need to change the validation of some field in a form. The validator is configured via a quite large yml file. I wonder if there is any way to do validation on two fields at once.
In my case I have two fields that cannot be both empty. At least one has to be filled.
Unfortunately till now I just could see that the validation are defined on a per-field basis, not on multiple fields together.
The question is: is it possible in the standard yml configurations to perform the aforementioned validation?
thanks!

I suggest you to look at Custom validator, especially Class Constraint Validator.
I won't copy paste the whole code, just the parts which you will have to change.
Extends the Constraint class.
src/Acme/DemoBundle/Validator/Constraints/CheckTwoFields.php
<?php
namespace Acme\DemoBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class CheckTwoFields extends Constraint
{
public $message = 'You must fill the foo or bar field.';
public function validatedBy()
{
return 'CheckTwoFieldsValidator';
}
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
}
Define the validator by extending the ConstraintValidator class, foo and bar are the 2 fields you want to check:
src/Acme/DemoBundle/Validator/Constraints/CheckTwoFieldsValidator.php
namespace Acme\DemoBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class CheckTwoFieldsValidator extends ConstraintValidator
{
public function validate($protocol, Constraint $constraint)
{
if ((empty($protocol->getFoo())) && (empty($protocol->getBar()))) {
$this->context->addViolationAt('foo', $constraint->message, array(), null);
}
}
}
Use the validator:
src/Acme/DemoBundle/Resources/config/validation.yml
Acme\DemoBundle\Entity\AcmeEntity:
constraints:
- Acme\DemoBundle\Validator\Constraints\CheckTwoFields: ~

Related

How to require javascript after construction but before template render in a SilveStripe form field

I currently have a custom form field with a bunch of optional parameters in the constructor. I want to change this and have the field use setter functions but I can't find any way to include my templated JavaScript except during construction
class CustomField extends FormField {
protected $myField;
public function __construct($name, $title = null, $myField = null)
{
parent::__construct($name, $title);
$this->setMyField($myField);
Requirements::javascriptTemplate('path/to/script.js', ['Field' => $this->myField]);
}
/**
* I can update the value of myField but the value is already baked into the JavaScript and wont be updated
*/
public function setMyField($value) {
$this->myField = $value;
return $this;
}
I found a solution but it does feel a little hacky. I added a RequireJs() function to the form field as such:
<?php
function RequireJs() {
Requirements::javascriptTemplate('path/to/script.js', ['Field' => $this->myField]);
}
The added $RequireJs to the top of my template file so it would be called when the template is being rendered.

PostSharp C# - How to Implement All Fields Required

PostSharp contracts make it easy to label individual fields as Required. But I want a class attribute that makes all of the class fields required. I'm guessing I would have to implement a custom aspect to support this.
It seems like it would be a common need for anyone passing around data containers. Can anyone direct me to some code that implements a custom "AllFieldsRequired" aspect in PostSharp?
You can implement PostSharp.Aspects.IAspectProvider:
public class AllFieldsRequiredAttribute : TypeLevelAspect, IAspectProvider
{
IEnumerable<AspectInstance> IAspectProvider.ProvideAspects(object targetElement)
{
Type type = (Type)targetElement;
return type.GetFields().Select(
m => new AspectInstance(m, new ObjectConstruction(typeof(RequiredAttribute))));
}
}
[AllFieldsRequired]
public class Foo
{
public string Bar;
public object Baz;
}

Symfony 2.8+, Doctrine Inheritance and Forms

Before i start, Note that I'm learning symfony so keep that in mind ! I just want to understand how it works.
Here's what i am trying to achieve :
I would like to make a working crud example of entities inheritance using doctrine. So this is how my example looks like :
Abstract Parent class : character
Child class 1 : Magician
Child class 2 : Warrior
Child class 3 : Archer
So after reading some documentation i decided to use the STI (Single Table Inheritance) of Doctrine.
Parent class :
/**
* Character
*
* #ORM\Table(name="character")
* #ORM\Entity(repositoryClass="AppBundle\Repository\CharacterRepository")
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="discr", type="string")
* #ORM\DiscriminatorMap({"magician_db" = "Magician", "warrior_db" = "Warrior", "archer_db" = "Archer"})
*/
abstract class Character{
protected id;
protected name;
public function getId();
public function getName();
public function setName();
}
Child Class 1 :
class Warrior extends Character{
protected armor;
public function battleShout();
}
Child Class 2:
class Magician extends Character{
protected silk;
public function spellAnnounce();
}
Child Class 3:
class Archer extends Character{
protected leather;
public function arrows();
}
I managed to create the table in my db, and i successfully loaded my fixtures for tests purposes. I also made my main view work (listing all characters).
My Problem :
Now i want to be able to create, edit & delete a specific character in the list with a single form. So for example i would have a 'type' select field where i can select 'warrior' , 'magician' or 'archer' and then i would be able to fill in the specific fields of the chosen entity. So let's say i choose 'warrior' in the form, then i would like to be able to set the armor property (along with the parents one of course) and persist it in the database.
I don't know how to do it since my parent class is abstract so i can't create a form based on that object.
Thx in advance for your help, i really need it !
PS: If there is a better solution / implementation don't hesitate !
The easiest way is to provide all fields and to remove them according to the 'type' value.
To do that you have to implement the logic on the client side (for displaying purpose) and server side (so that the removed fields cannot be changed in your entity).
On the client side :
Use javascript to hide the types which can't be set for each 'type' change (you can use JQuery and the .hide() function).
On the server side:
Add a PRE_BIND event to your form type, to remove the fields from the form :
http://symfony.com/doc/current/components/form/form_events.html#a-the-formevents-pre-submit-event
Your Form should look like :
// ...
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
$form = $formFactory->createBuilder()
->add('type', ChoiceType::class)
->add('armor')
->add('silk')
->add('leather')
->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$submittedData = $event->getData();
$form = $event->getForm();
switch($submittedData['type'])
{
case 'warrior':
$form->remove('silk');
$form->remove('leather');
break;
case 'magician':
$form->remove('armor');
$form->remove('leather');
break;
case 'archer':
$form->remove('armor');
$form->remove('silk');
break;
default:
throw new ...;
}
})
->getForm();
// ...
EDIT
To deal with Single Table Inheritance, you can't use an abstract class, the base class must be a normal entity.
In your form, just set the class as AppBundle\Character.
In your controller action which creates the character, you must initiate your entity with something like this :
if($request->isMethod('POST')){
// form has been submitted
switch($request->get('type'))
{
case 'warrior':
$entity = new Warrior();
...
}
}
else{
// form has not been submitted, default : Warrior
$entity = new Warrior();
}
By editing and removing the character, you can directly deal with the Character Entity.
I recommand to not let the user change the type by edit, see Doctrine: Update discriminator for SINGLE_TABLE Inheritance

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.'));
}
}

Nested Jsr 303 validation annotations

I would like to use a class level annotation constraint. However I cannot get the inner constraints to validate automatically. I'd like to help with one part, to incorporate validation groups into this technique.
#ConstraintA({
#ConstraintB(stuff),
#ConstraintB(stuff, groups=SomeGroup.class)
})
public class Form{
}
I currentily trigger the constraints like so.
if(constraint instanceof ConstraintB){
new ConstraintBValidator().isValid(target, context);
}
However this sucks obviously.I will eventually refactor to trigger the isValid methods through a call to the AnnotationInvocationHandler.invoke() method, but im a little way from that still.
My issue is that all ConstraintB instances are passed into my ConstraintA. I wish only the ones with the appropriate groups to be passed to ConstraintA. I doubt this ability exists, so how can i identify which groups need to be triggered and which dont?
I dont see in my debug, any objects which specify which groups should be triggered?
Any ideas?
I've found a pattern in the JSR 303 spec that allows this type of validation. Its a recursive pattern that wont execute the parent validation rule, only fulfill the nested validations. This is VERY handy. My nested validation rules are conditionally based on other properties values so it allows conditional validation with nested jsr303 annotations.
#Documented
#Constraint(validatedBy = ZipCodeValidator.class)
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
#Retention(RUNTIME)
public #interface ZipCode {
String countryCode();
String message() default "{com.acme.constraint.ZipCode.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
/**
* Defines several #ZipCode annotations on the same element
* #see (#link ZipCode}
*/
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
#Retention(RUNTIME)
#Documented
#interface List {
ZipCode[] value();
}
my own validation is more like this:
#RequiredIf ({
#RequiredIf(ifField="field1", matches={"true","somethingElse"}, requiredField="anotherField", andDisplay="error.code.msg"),
#RequiredIf(ifField="field2", matches={"true","somethingElse"}, requiredField="anotherField", andDisplay="error.code.msg")
})