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;
}
Related
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.
I can't seem to update an entity when making a PUT request.
It works fine when I want to modify single elements belonging to the entity but not anymore as soon as I try to update an array or an object within it.
This works fine
{
"order_number": "TEST NAME"
}
As soon as an array is involved nothing seems to happen.
The quantities in the request are different from the one in the original entity.
{
"order_number": "TEST NAME",
"items": [
{
"id": 248,
"quantity": 0,
"quantity_shipped": 252
}
]
}
EDIT:
Foo and Items have a OneToMany relationship as shown in the code below.
class Foo implements FooInterface
{
/**
* One Foo has Many Items.
* #ORM\OneToMany(targetEntity="Item", mappedBy="foo", cascade={"persist","remove"})
* #JMSSerializer\Groups({"list_orders", "order_details"})
* #JMSSerializer\MaxDepth(1)
* #JMSSerializer\Expose
*/
private $items;
}
I also use a Form to validate the data in the request, and the Items are defined as a CollectionType as expected.
class FooType extends AbstractType implements FormTypeInterface
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('items',CollectionType::class, array(
'entry_type'=>ItemType::class,
'entry_options' =>array('label'=>false),
'allow_add' => true,
'by_reference' => false,
))
}
}
The same was done for the Items:
class SalesOrderLineItemType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('quantity',TextType::class,array(
'constraints' =>array(
new NotBlank())
)),
->add('quantityShipped',TextType::class,array(
'constraints' =>array(
new NotBlank())
))
->add('Foo',EntityType::class, array(
'class'=>'App:Api\Foo',
'constraints' =>array(
new Required(),
),
))
}
}
Does anyone know why this is happening?
Do I need to use a forEach loop to check each element?
I'm sure there's a more direct and efficient way of doing this.
Doctrine has hard time to detect changes on object and array, you need to reset the child object in order to update it again.
You can check how to reset the child here: How to force Doctrine to update array type fields?
If i understand you correctly you looking for cascade persist analog for updating. Doctrine hasn't such opportunities, so if you want update order item you need do this apart from the order, send PUT request to, for example, /api/order//items/ and then update your item.
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.
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).
Let’s say I have a nullable datetime field in my entity:
class Foo
{
/**
* #ORM\Column(name="due", type="datetime", nullable=true)
*/
private $due;
public function getDue()
{
return $this->due;
}
public function setDue($due)
{
$this->due = $due;
return $this;
}
}
And now in the FooType.php I define a form field for this:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// ...
->add('due', 'datetime')
// ...
;
}
However, even though the field is nullable, the form field rendered will allow the user only to enter a date/time, but not to say that it should be null.
I would like to have either a radio button UI like this:
( ) No due date
( ) Due date: [ control ]
or perhaps a checkbox:
[ ] Due date: [ control ]
Ideally, if “no due date” is selected/the checkbox is unchecked, the control should be disabled.
Is it possible to do this elegantly in Symfony?
Afaik theres no symfony provided solution
i would
$builder
// ...
->add('due', 'hidden')
and in view you implement the logic with html and js
so two checkboxes on click on due-date you show a datetime input then
you just need to set the value (null || thedatetimeinputvalue) to the hidden field before submit