Conditional validation of fields based on other field value in Symfony2 - php

So here is the scenario: I have a radio button group. Based on their value, I should or shouldn't validate other three fields (are they blank, do they contain numbers, etc).
Can I pass all these values to a constraint somehow, and compare them there?
Or a callback directly in the controller is a better way to solve this?
Generally, what is the best practice in this case?

I suggest you to use a callback validator.
For example, in your entity class:
<?php
use Symfony\Component\Validator\Constraints as Assert;
/**
* #Assert\Callback(methods={"myValidation"})
*/
class Setting {
public function myValidation(ExecutionContextInterface $context)
{
if (
$this->getRadioSelection() == '1' // RADIO SELECT EXAMPLE
&&
( // CHECK OTHER PARAMS
$this->getFiled1() == null
)
)
{
$context->addViolation('mandatory params');
}
// put some other validation rule here
}
}
Otherwise you can build your own custom validator as described here.
Let me know you need more info.
Hope this helps.

You need to use validation groups. This allows you to validate an object against only some constraints on that class. More information can be found in the Symfony2 documentation http://symfony.com/doc/current/book/validation.html#validation-groups and also http://symfony.com/doc/current/book/forms.html#validation-groups
In the form, you can define a method called setDefaultOptions, that should look something like this:
public function buildForm(FormBuilderInterface $builder, array $options)
{
// some other code here ...
$builder->add('SOME_FIELD', 'password', array(
'constraints' => array(
new NotBlank(array(
'message' => 'Password is required',
'groups' => array('SOME_OTHER_VALIDATION_GROUP'),
)),
)
))
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'validation_groups' => function (FormInterface $form) {
$groups = array('Default');
$data = $form->getData();
if ($data['SOME_OTHER_FIELD']) { // then we want password to be required
$groups[] = 'SOME_OTHER_VALIDATION_GROUP';
}
return $groups;
}
));
}
The following link provides a detailed example of how you can make use them http://web.archive.org/web/20161119202935/http://marcjuch.li:80/blog/2013/04/21/how-to-use-validation-groups-in-symfony/.
Hope this helps!

For anyone that may still care, whilst a callback validator is perfectly acceptable for simpler dependencies an expression validator is shorter to implement.
For example if you've got a field called "Want a drink?" then if yes (true) "How many?" (integer), you could simplify this with:
/**
* #var bool|null $wantDrink
* #ORM\Column(name="want_drink", type="boolean", nullable=true)
* #Assert\NotNull()
* #Assert\Type(type="boolean")
*/
private $wantDrink;
/**
* #var int|null $howManyDrinks
* #ORM\Column(name="how_many_drinks", type="integer", nullable=true)
* #Assert\Type(type="int")
* #Assert\Expression(
* "true !== this.getWantDrink() or (null !== this.getHowManyDrinks() and this.getHowManyDrinks() >= 1)",
* message="This value should not be null."
* )
*/
private $howManyDrinks;
You write the expression in PASS context, so the above is saying that $howManyDrinks must be a non-null integer at least 1 if $wantDrink is true, otherwise we don't care about $howManyDrinks. Make use of the expression syntax, which is sufficient for a broad range of scenarios.
Another scenario I find myself frequently using a expression validator are when I've got two fields to the effect of "date start" and "date end", so that they can each ensure that they are the right way around (so that the start date is before or equal to the end date and the end date is greater or equal to the start date).

Related

Which condition i need for laravel validation if i need to check max strlen just for numbers from a string which contains comma and dot

I have this situation:
An input which can contain max chars 11 ( e.g. 12345678.00 ) 10 numbers and 1 comma or dot.
I need to check only if max strlen of numbers is grater than 10, for example user cannot insert this number 12345678901. This input is for price.
So, i use laravel framework and in my function at the beggining i already extracted the max strlen value of numbers of that input but i don't know how to put in laravel condition.
My function:
$price = (int) filter_var($request->price, FILTER_SANITIZE_NUMBER_INT);
$max = strlen($price);
$this->validate($request,
//rules for input validation
[
'price' => ??
],
//message for input validation
[
'price.??' => 'Some text error'
]);
If exist another way to verify the condition in php and send the message please tell me. For clarifying i cannot use numeric number, or max|min condition..
Best regards,
You have to create a custom validator. For that you can follow this blog. https://laraveldaily.com/how-to-create-custom-validation-rules-laravel
Or Create a validator class by running
php artisan make:rule StrDigitCalculator
This will create a class in App\Rules folder. Open App\Rules\StrDigitCalculator.php and paste code below so your custom validator will look like this
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
class StrDigitCalculator implements Rule
{
/**
* Determine if the validation rule passes.
*
* #param string $attribute
* #param mixed $value
* #return bool
*/
public function passes($attribute, $value)
{
$int = (int) filter_var($value, FILTER_SANITIZE_NUMBER_INT);
return strlen($int) == 10 : true : false;
}
/**
* Get the validation error message.
*
* #return string
*/
public function message()
{
// Your custom message for this validation
return 'Validation Fails';
}
}

Validation NotBlank on a non nullable field

I'm trying to validate a form that includes a field that absolutely cannot be empty or null. So in my model, it is defined like this:
/**
* #var string
*/
private $end;
/**
* #param string $end
* #return Blabla
*/
public function setEnd(string $end): Blabla
{
$this->end = $end;
return $this;
}
Here is the validation of this field in my form:
$builder
->add('end', TextType::class, [
'label' => 'blabla',
'constraints' => [
new Length([
'min' => 3,
'minMessage' => 'Min {{ limit }} chars',
]),
new NotBlank([
'message' => 'not null blabla',
]),
],
])
Here is the error I receive when I send a wrong input per example "multiple spaces" :
Expected argument of type "string", "null" given at property path "end".
I can correct this error by adding the possibility to receive a null in my setter
/**
* #var string|null
*/
private $end;
/**
* #param string|null $end
* #return blabla
*/
public function setEnd(?string $end=null): blabla
{
$this->end = $end;
return $this;
}
But I don't find it very coherent to allow a field to receive null only to validate it and prevent to set the field with null value.
Can't we do otherwise?
When form is submitted, all empty values are set to null by default so you should either accept null in your setter or use empty_data to customise default value:
The model:
#[Assert\NotBlank]
private string $end = ''; // Remember to initialise the field with empty string if you use typed properties
public function setEnd(string $end): void
{
$this->end = $end;
}
Form:
$builder->add('end', TextType::class, [
'label' => 'blabla',
'empty_data' => '',
]);
Since NotBlank checks for empty string, you can still use it this way.
This works for some built-in fields like TextType but may still convert an empty string to null in other fields because DataTransformers are applied to empty_data too: docs
Form data transformers will still be applied to the empty_data value. This means that an empty string will be cast to null. Use a custom data transformer if you explicitly want to return the empty string.
If you want to costomize null handling in your form types, add a custom DataTransformer or implement DataTransformerInterface directly in your form type just like it's done in TextType: https://github.com/symfony/symfony/blob/5.4/src/Symfony/Component/Form/Extension/Core/Type/TextType.php
Please note that I used some of modern PHP features (Attributes and typed properties) to make the code a bit shorter.

Symfony validation

I'm working in a bundle where the user creates a "comision" using a form, and I'm trying to check that the user still have "credit". So I created a custom validator that queries past comisions and throws an error if credit is not enough.
My problem is that if the user submits a date with a wrong format in the "date" field (i.e. 32-13-20122 24:05) Symfony still tries to run my custom validation and I get all sort of errors (because $comision->getDate() is null and not a valid DateTime object).
I'm getting this error:
clone method called on non-object
I can also check if value of $comision->getDate() is a valid datetime in my custom validator, but it seems to me that it should be not necessary since I added this rules in the date property.
This is my entity (simplified)
/**
* #MyValidation\TotalHours()
*/
class Comision
{
/**
* #ORM\Column(type="datetime")
* #Assert\DateTime()
* #Assert\NotNull()
*/
protected $date;
/**
* #ORM\Column(type="decimal", nullable=false, scale=1)
* #Assert\NotBlank()
*/
protected $hours;
...
My form class ...
class NewComisionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('date', DateTimeType::class, array(
'widget' => 'single_text',
'label' => 'Starting date and time',
'format' => 'dd/MM/yyyy HH:mm'
))
->add('hours', ChoiceType::class, array(
'label'=> 'How many hours',
'choices' => array(
'1:00' => 1,
'1:30' => 1.5,
'2:00' => 2,
'2:30' => 2.5,
'3:00' => 3
)
))
...
And my cutom validator that checks past comisions to find if user still has "credit"
public function validate($comision, Constraint $constraint)
{
$from = clone $comision->getDate();
$from->modify('first day of this month');
$to = clone $comision->getDate();
$to->modify('last day of this month');
$credit = $this->em->getRepository("ComisionsBundle:Comision")->comisionsByDate($comision,$from, $to);
...
One way would be to group your constraints as described in the docs.
This way your can define two groups of contraints whereas the second group will be validated only if all constraints in the first group are valid.
Regarding your use case, you could put your custom constraint in a different group than the default one to be sure your have a proper $comision DateTime object.
To do this, you can use the GroupSequence feature. In this case, an object defines a group sequence, which determines the order groups should be validated.
https://symfony.com/doc/current/validation/sequence_provider.html
The solution should look like this:
/**
* #MyValidation\TotalHours(groups={"Strict"})
* #Assert\GroupSequence({"Comision", "Strict"})
*/
class Comision
In this way, it will first validate all constraints in the group Comision (which is the same as the Default group). Only if all constraints in that group are valid, the second group, Strict, will be validated, making sure $comision->getDate() will have a DateTime instance.
IIRC data transformers run before validation, you can go with data transformer for your date:
https://symfony.com/doc/current/form/data_transformers.html
and then in data transformer check if the date is valid and if not throw an error.

Map a form's text field to an entity's ArrayCollection

I am using tags on a form using tagsinput :
This plugin ends-up with a single text field containing tags separated by a comma (eg: tag1,tag2,...)
Those tags are currently managed on a non-mapped form field:
$builder
// ...
->add('tags', 'text', array(
'mapped' => false,
'required' => false,
))
;
And finally, they are stored on an ArrayCollection, as this is a bad practice to store multiple values in a database field:
/**
* #var ArrayCollection[FiddleTag]
*
* #ORM\OneToMany(targetEntity="FiddleTag", mappedBy="fiddle", cascade={"all"}, orphanRemoval=true)
*/
protected $tags;
To map my form to my entity, I can do some code in my controller like this:
$data->clearTags();
foreach (explode(',', $form->get('tags')->getData()) as $tag)
{
$fiddleTag = new FiddleTag();
$fiddleTag->setTag($tag);
$data->addTag($fiddleTag);
}
But this looks the wrong way at first sight.
I am wondering what is the best practice to map my entity to my form, and my form to my entity.
This is tricky since you aren't just embedding a collection of Tag forms that are say, all separate text fields. I suppose you could do that with some trickery, but what about using a data transformer instead? You could convert a comma-separated list of tags to an ArrayCollection and pass that back to the form, and on the flip-side, take the collection and return the tags as a comma-separated string.
Data transformer
FiddleTagsTransformer.php
<?php
namespace Fuz\AppBundle\Transformer;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Form\DataTransformerInterface;
use Fuz\AppBundle\Entity\FiddleTag;
class FiddleTagTransformer implements DataTransformerInterface
{
public function transform($tagCollection)
{
$tags = array();
foreach ($tagCollection as $fiddleTag)
{
$tags[] = $fiddleTag->getTag();
}
return implode(',', $tags);
}
public function reverseTransform($tags)
{
$tagCollection = new ArrayCollection();
foreach (explode(',', $tags) as $tag)
{
$fiddleTag = new FiddleTag();
$fiddleTag->setTag($tag);
$tagCollection->add($fiddleTag);
}
return $tagCollection;
}
}
Note: you cannot specify ArrayCollection type to public function transform($tagCollection) because your implementation should match the interface.
Form type
The second step is to replace your form field declaration so it will use the data transformer transparently, you'll not even need to do anything in your controller:
FiddleType.php
$builder
// ...
->add(
$builder
->create('tags', 'text', array(
'required' => false,
))
->addModelTransformer(new FiddleTagTransformer())
)
;
Validation
You can use #Assert\Count to limit the number of allowed tags, and #Assert\Valid if your FiddleTag entity has some validation constraints itself.
Fiddle.php
/**
* #var ArrayCollection[FiddleTag]
*
* #ORM\OneToMany(targetEntity="FiddleTag", mappedBy="fiddle", cascade={"all"}, orphanRemoval=true)
* #Assert\Count(max = 5, maxMessage = "You can't set more than 5 tags.")
* #Assert\Valid()
*/
protected $tags;
Further reading
See the Symfony2 doc about data transformers: http://symfony.com/doc/current/cookbook/form/data_transformers.html
See these posts for some other ideas:
Parsing comma separated string into multiple database entries (eg. Tags)
How does Symfony 2 find custom form types?

Auto-completion for Zend Form Elements

When creating form elements with Zend (using Zend Studio for Eclipse), I'd like some auto completion or hints. Here's what I'm thinking. I'm sure these exist, but I don't know how to get them.
I type createElement and auto-completes gives me the signature createElement($type, $name). Great, I select it.
but when I try to set the $type I don't get any hints like DateTextBox or ValidationTextBox. Being new, I see how this can be useful. What do you do to remember all the options?
for the array of attributes like require, invalidMessage, I'd like to get a list of those to choose from, and/or auto-complete when I start typing one.
// Date field
$date = $this->createElement('DateTextBox', 'date',
array('require' => 'true', 'invalidMessage' => 'Invalid date format')
);
$date->setLabel('date')->setRequired(true);
You have few options to help yourself, without waiting for any plugin:
learn it and remember ;)
extend your phpDoc blocks with all available options:
Example (to be honest I don't know if Eclipse supports html in phpDoc or even any text after variable name in #param, but it works fine in Netbeans):
/**
* [...]
* #param string $type Can be: <ul><li>DateTextBox</li><li>ValidationTextBox</li></ul>
* #param string $name Whatever
* #param array|Zend_Config $options Array with following keys: <ul><li>require</li><li>invalidMessage</li></ul>
* #return Zend_Form_Element
*/
public function createElement($type, $name, $options = null)
extend Zend class and create your own methods to simplify your work
Example:
class My_Zend_Form_Element extends Zend_Form_Element
{
public function createDateTextBox($name, $options = null)
{
return $this->createElement('DateTextBox', $name, $options);
}
}
declare some well named constants and provide some hint in phpDoc
Example: (type ZFE_OPTIONS and IDE should show hint with some constants to use as array keys)
/**
* Can be true or false
*/
define('ZFE_OPTIONS_REQUIRE','require');
create your own helper classes with methods to produce valid options array
Example:
class ZFE_Options
{
protected $opts = array();
/**
* #param bool $req
* #return ZFE_Options
*/
public function setRequired($req){
$this->opts['require'] = (bool)$req;
return $this;
}
/**
* #param string $txt
* #return ZFE_Options
*/
public function setInvalidMessage($txt){
$this->opts['invalidMessage'] = (string)$txt;
return $this;
}
/**
* #return array
*/
public function toArray(){
return $this->opts;
}
}
$zfe_options = new ZFE_Options();
$opts = $zfe_options
->setRequired(true)
->setInvalidMessage('Please provide valid email address')
->toArray();
That's not possible. It's not how autocompletion works. The hints you get are taken directly from ZF's code documentation. Nothing more, nothing less. Everything you see as hints is taken directly from the DocBlock and method signature, e.g.
/**
* Create an element
*
* Acts as a factory for creating elements. Elements created with this
* method will not be attached to the form, but will contain element
* settings as specified in the form object (including plugin loader
* prefix paths, default decorators, etc.).
*
* #param string $type
* #param string $name
* #param array|Zend_Config $options
* #return Zend_Form_Element
*/
public function createElement($type, $name, $options = null)
Eclipse can tell you to insert a string or an array and it will know that the method returns a Zend_Form_Element, but it cannot tell you what these strings should be.
The only place where I know something like what you describe exists is for CSS files. For some reason, when I type in display: it will give me an autocomplete box with possible values for this declaration. If you want more sophisticated autocomplete like this, consider filing this as a feature request to Zend.

Categories