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.
Related
I have a table that contains SMS verification message and i have a last sent column and number of tries column
So in the database they're not nullable but upon creating the entity in code, the values are initially null, so should i call setters before getters and make the getters return the correct value ( returns DateTime, returns int) or make the getter return or null even if the value in the database is not nullable ( this question is about best practice )
is it okay if it's
getNumberOfTries(): ?int
or should it be strictly
getNumberOfTries(): int
So should i say since the database value is not null then the getter should always return a value ? or is it okay that the getter returns null if i create a new entity without calling the setter for this value ?
This is really a matter of opinion.
Heres the PHP DOC BLOCK that came out of my ORM. This is part of a larger autogenerated code ORM I wrote code from a MySQL dump using a php script.
PHP ORM Program
/**
*
* $argv = [
* 'select' => [
* '*column name array*', 'etc..'
* ],
*
* 'where' => [
* 'Column Name' => 'Value To Constrain',
* 'Defaults to AND' => 'Nesting array switches to OR',
* [
* 'Column Name' => 'Value To Constrain',
* 'This array is OR'ed togeather' => 'Another sud array would `AND`'
* [ etc... ]
* ]
* ],
*
* 'pagination' => [
* 'limit' => (int) 90, // The maximum number of rows to return,
* setting the limit explicitly to 1 will return a key pair array of only the
* singular result. SETTING THE LIMIT TO NULL WILL ALLOW INFINITE RESULTS (NO LIMIT).
* The limit defaults to 100 by design.
*
* 'order' => '*column name* [ASC|DESC]', // i.e. 'username ASC' or 'username, email DESC'
*
*
* ],
*
* ];
*
*
* #param array &$return
* #param string|null $primary
* #param array $argv
* #return bool
*/
public static function Get(array &$return, string $primary = null, array $argv) : bool
{
... SEE LINK ...
}
With this use case the calling function or method actually gets two responses. The contents of the $return variable was passed by reference so if could now have different data in it and the success of the database to deliver a response was given by the bool return.
In your case, if I was constrained to make a method such as the one presented, I would not give the nullable option. I would assume the data was returned successfully from the DB and should it not be an INT I would go ahead and make it a 0. I would choose this option because it means less type checking in the calling functions. If null were to symbolize something else than I could see this giving reason to allow : ?bool. However, I hope with the code snippet above you can deduce a better alternative for the ORM.
I only personally use a possibly nullable response when I have three possible returns for a function. This happens almost never in web dev, but all the time in recursive programming.
I'm with Symfony 3.2, and I would like to have a form type for an entity that is associated to another one:
class Aliment
// ...
private enseigneId;
// ...
and
class Enseigne
// ...
private nomEnseigne
// ...
and Aliment.enseigneId refers to Enseigne.id, but what I want to make in my form (CRUD, for the creation one) is to let the user choising an ID but I want to display the value of that ID instead, so it's easier for the user.
This is what I have in my form type (Ticket) :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('date', DateTimeType::class, array(
'data' => new \DateTime()
))
->add('aliments')
->add('prixTotal')
->add('recapitulatif')
->add('modePaiement')
->add('enseigneId', EntityType::class, array(
'class' => 'AppBundle\Entity\Enseigne',
'choice_label' => function ($enseigne) {
/** #var $enseigne Enseigne */
return $enseigne->getNomEnseigne();
}
));
}
But it keeps displaying the integer 0 instead (while getNomEnseigne() return a string and the database is well filled with correct dataset), I don't know how to do..
I want it to be stored as an integer, but I want to display (in the choice field) as the value of the Id it's referring to.
Id | nomEnseigne | ...
-----------------------
0 | LECLERC | ...
1 | LIDL | ...
With the above example, the user would see the string "LECLERC" but the integer 0 would be stored instead.
Thank you for your help.
you can try other ways like
'choice_label' => 'nomEnseigne',
or realizing __toString() method of Enseigne entity
/**
* #return string
*/
public function __toString()
{
if (empty($this->nomEnseigne) {
return 'enseigne ' . $this->id;
}
return (string) $this->nomEnseigne;
}
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.
I'm using Laravel 5.1
I got a model:
class ExampleModel extends Model {
// ....
protected $dateFormat = 'Y.m.d';
protected $dates = ['first_date', 'second_date'];
// ...
}
So when I'm indexing ExampleModel elements, the date format is correct (ex 2015.07.31)
But on an edit form it uses the default format: 2015-07-31 00:00:00
I'm using Form::model() binding.
I know I could use getFirstDateAttribute() but it's not the solution I'm looking for. Because it's not elegant at all and once I defined the $dates array, it should work automatically in every case.
So is it a bug maybe? Or am I doing something wrong?
I solved my problem by overriding Carbon's default date format:
Carbon\Carbon::setToStringFormat('Y.m.d');
But Kelly's answer is much better and more elegant, I just post this one as well, maybe someone will find this useful once.
I've never done this before, but it seems to work on a basic example I put together. Note that I'm just calling the toArray method on the model in the form opening tag.
{!! Form::model($exampleModel->toArray(), ['route' => ['example-models.update', $exampleModel->id]]) !!}
{!! Form::label('first_date', 'First Date') !!}
{!! Form::text('first_date') !!}
{!! Form::close() !!}
The docs say that the dateFormat property determines the date format when the object is cast to json or an array.
I don't fully advise this as a fix because when you update the core you'll lose this fix, but maybe this should be posted as a pull request to Laravel's next version.
In \Illuminate\Database\Eloquent\Concerns\HasAttributes update the asDate function as follows. As you can see from the comments the asDate function still returns a timestamp even though it's 00:00:00.
/**
* Return a timestamp as DateTime object with time set to 00:00:00.
*
* #param mixed $value
* #return \Illuminate\Support\Carbon
*/
protected function asDate($value)
{
$date = $this->asDateTime($value)->startOfDay();
$date->setToStringFormat($this->getDateFormat());
return $date;
}
This allows you to control the format of a date from the model (note I'm differentiating between a date and datetime). Then use the casts variable to cast your variable to date format.
protected $casts = [
'start_date' => 'date',
'end_date' => 'date'
];
protected $dateFormat = 'Y-m-d';
The $dates variable cannot differentiate between a date and a datetime.
UPDATE
The real problem is the form model binding skips the helper function of the FormBuilder class. As you can see if you inspect \Collective\Html\FormBuilder::date
/**
* Create a date input field.
*
* #param string $name
* #param string $value
* #param array $options
*
* #return \Illuminate\Support\HtmlString
*/
public function date($name, $value = null, $options = [])
{
if ($value instanceof DateTime) {
$value = $value->format('Y-m-d');
}
return $this->input('date', $name, $value, $options);
}
It is correctly formatting date to 'Y-m-d' as specified in HTML5 spec. However at this point $value is actually null. So you have to update the generic input function.
/**
* Create a form input field.
*
* #param string $type
* #param string $name
* #param string $value
* #param array $options
*
* #return \Illuminate\Support\HtmlString
*/
public function input($type, $name, $value = null, $options = [])
{
...
//$value is null here
if (! in_array($type, $this->skipValueTypes)) {
//$value is fetched based on hierarchy set by
$value = $this->getValueAttribute($name, $value);
//necessary duplicate code to format date value
if ($type == 'date' && $value instanceof DateTime) {
$value = $value->format('Y-m-d');
}
}
...
}
UPDATE There is no need to update vendor code. Check out FormModelAccessors
https://laravelcollective.com/docs/5.2/html#form-model-binding
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).