The OptionsResolver component and extra options - symfony

I have an array which can have various keys. However, always exists two keys which are required. I use the OptionsResolver component now. It is works fine until there is no any extra keys. I also considered the Validator component and as I understood there is the same behaviour. So I need always set the full list of keys but as I wrote above I need validate only some of them. Is there a way to solve this problem?
Thanks!

Hello you can define required, optional and defaults values in OptionResolver.
Maybe I will give you some example so it will be easier than describing it:
$resolver = new Symfony\Component\OptionsResolver\OptionsResolver;
$resolver
->setRequired(['required1', 'required2'])
->setOptional(['optional1', 'optional2'])
->setDefaults(['defaultValue' => '123'])
;
$options = $resolver->resolve(
[
'required1' => 'test',
'required2' => 'test123',
'optional1' => 'opt'
]
);
then options will be looks like that
[
'defaultValue' => '123',
'required1' => 'test',
'required2' => 'test123',
'optional1' => 'opt',
]
if we do not set required1 or required2 in resolved array then we gotSymfony\Component\OptionsResolver\Exception\MissingOptionsException exception.
If we give not know option (not defined in setRequired, setOptional or setDefaults) then we got Symfony\Component\OptionsResolver\Exception\InvalidOptionsException exception.
I also considered the Validator component and as I understood there is the same behaviour
You can decide which values should be "required"... but not sure if I got what you mean exactly

Related

Symfony: Index in collection Type

I am trying to set the label of an embedded form type (collection type).
These labels have to be different for the different collections.
For example I have two collections and an array with the labels ['label1','label2'] in die form type.
I think I need the index of the collection iteration to get the right label.
Like below i need the index for the entry_options label to get the right value of the $labels array.
Thats my buildForm Method
public function buildForm(FormBuilderInterface $builder, array $options)
{
$labels = ['label1', 'label2'];
$builder
->add('otherForms', CollectionType::class, [
'entry_type' => otherFormType::class,
'entry_options' => ['label' => $labels[$index]],
])
...
;
}
After reading your comments, I must agree that translation bundles are sometimes heavy weight and quite inconvenient.
The CollectionType usually ignores the index/key of collection items given to it, though.
Now I see two scenarios: you want to give the labels as an option to your form or the form shall be dependent on the data (using form events). Your code so far suggests, that languages are set globally and will not be added on the fly in some other manner.
In the first scenario it's very simple (see below), the second scenario requires some event listener in your form, but apart from that, it's quite similar.
The essential idea is to just create the forms you need (optionally inside the pre-submit handler), since you apparently already know which forms you need from the label array:
foreach($labels as $index => $label) {
$builder->add('otherForms'.$index, otherFormType::class, [
'label' => $label,
'property_path' => 'otherForms['.$index.']',
]);
}
The use of property_path will help you access the proper elements (however, changing the language (= key) of a translation might be difficult, but that should not happen, usually. And even if it happens, only some copy-pasta is necessary).
Your otherForms keys probably will be differently, but I guess you'll figure it out.

Populate 3 selection lists using queries on the same entity, using Symfony2 buildForm()

I have an entity Machine which has a relation MM with other entity Piece. The pieces can be from 3 different types. Currently the form Machine is built with a selection list in which the whole array collection Machine.pieces is fetched. My idea is to build 3 different selection lists with a subset of Machine.pieces each.
I have tried two different approaches but I have no been able to accomplish it.
Use a MachineRepository class where a method
public function findPiecesByPieceType($pieceTypeID)
returns the proper query->getResult().
Then I add a choiceType in MachineType but I am not able to populate it from MachineController. I have used $form->get('pieces')->setData($arrcollectPieces) and other methods to add choices but I always get error.
How could I add choices from the controller to a Form?
In the form I use a queryBuilder
->add('pieces', EntityType::class, array(
'label' => 'label_pieces',
'class' => 'AppBundle\Entity\Piece',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('p')
->where('p.pieceType = :pieceType')
->setParameter('pieceType', 1);
},
)
)
this works but when I try to add more queryBuilders (->add('pieces2'... and so on) I have the error because
Neither the property pieces2 nor one of the methods getPiecess2(), pieces2(), isPieces2(), hasPieces2(), __get() exist and have public access in class AppBundle\Entity\Machine.
How can I use the various queryBuilders not bounded to a method name in that way?
Maybe both approaches are incorrect and I should solve this in a different way?
(Posted on behalf of the OP).
How to make it using 1.
In MachineController forget setData(), instead turn the arrayCollection into 2 arrays (arKeys, arValues) and send them to the form as the 3rd parameter in createForm().
$form = $this->createForm(<type>, <data>,
array ('p_keys' => array(...), 'p_values' => array(...)));
From MachineType.ConfigureOptions() we can fetch them
$resolver->setDefined(["p_keys",'p_values']);
and they will be available in MachineType.buildForm()
$options['p_keys'];
$options['p_values'];

Symfony2: How to filter the options of an entity-choice form field by a certain attribute?

1.) The Situation (simplified)
I have two entities: a Container-entity, which has exactly 1 Content-entity. The content_id is stored in the Container-entity.
2.) Soft-Delete Content-entities
I implemented a function to soft-delete Content-entities, so I added a "deleted"-attribute to the Content-entity. Everything works fine.
3.) The problem
Now, when I want to create a new Container-entity, the auto-generated choices show ALL the Content-entities - even those ones, which I "marked as deleted" (delete-attribute = 1).
4.) The question
Where is the correct place to add a "filter"/"query" to only show the elements which are not marked as deleted? (delete != 1)
5.) What I've tried
a.) view/twig approach: I tried to modify the rendering of the {{ form_widget(form.contentId) }} with no success
b.) controller approach: I tried to manipulate the form-data in the newAction where the form is being created ($form = $this->createCreateForm($entity)) with no success
c.) type/buildForm approach: I tried to change the buildForm()-method ... again, no success
If would be GREAT if you could give me a hint and/or a short code example of where I could hook into the action to remove the soft-deleted choices.
Thank you very much in advance!
You're looking for the query_builder option of the entity field.
You can create a custom query that filters the resultset in there.
example:
$builder->add('users', 'entity', array(
'class' => 'AcmeHelloBundle:User',
'query_builder' => function(EntityRepository $repository) {
$qb = $repository->createQueryBuilder('u');
// the function returns a QueryBuilder object
return $qb
// find all users where 'deleted' is NOT '1'
->where($qb->expr()->neq('u.deleted', '?1'))
->setParameter('1', '1')
->orderBy('u.username', 'ASC')
;
},
));
You could go for a more general approach that filters all select statements using doctrine filters, too.

Add custom validator on unmapped field, but with context of whole form submission?

tl;dr:
I need a custom validation constraint to run on an unmapped form field
I need the ID of the object the form is manipulating to eliminate it from consideration doing my validation constraint
Attaching the validation to the form itself or the unmapped field doesn't give me enough context to run my validation query
I have an unmapped field on my Person entity form that I need to run a validation on. I've followed this great article on how to do this, but my use case is slightly different and not entirely covered by the article.
I am making my own Unique Constraint that needs to run a custom query to determine the uniqueness. To run the query, I need access to the field value that was submitted, as well as the original Person object (so I can get it's ID if it's an update operation). Without also having the that Person object I won't be able to eliminate it from consideration during the uniqueness query.
If I apply the validator on the PersonType class like so:
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver
->setDefaults(array(
'data_class' => 'CS\AcmeBundle\Entity\Person',
'constraints' => array(
new MyUniqueValidator(),
)
))
;
}
Then the validator gets passed the entire Person object to perform the validation on. This doesn't help me, because the submitted form data is not persisted to the Person object (it's an unmapped field that I handle after $form->isValid() is called in the controller).
If I apply the validator to the unmapped field directly instead:
$builder
->add('myUnmappedField', 'text', array(
'mapped' => false,
'constraints' => array(
new MyUniqueValidator(),
)
),
))
Then the object I get passed to the validator is just the standalone form text, and nothing else. I don't have the ID Person object (if it was an update operation) to perform by uniqueness query.
Hopefully I've explained this properly. Do I have any options to do this sort of validation gracefully?
You say you have unmapped field. Would it help, if you make it mapped to the Person entity? Just make a new property in the Person class with getter and setter methods, but not to the ORM, since you don't want it persisted.
If you do not want to polute your Person class, you can also make another composite class, which will hold your currently unmapped field and a Person object (you will then make it mapped). Ofcourse you will then set data_class to match the new object's namespace.
Both above solutions should work with the upper code you have there. Please let me know it it helped.
You can achieve this by using a callback constraint with a closure.
To access your Person entity, you will need to add the field via an event listener.
$builder->addEventListener(FormEvents::POST_SET_DATA, function (FormEvent $event) {
$form = $event->getForm();
$person = $event->getData();
$form->add('myUnmappedField', TextType::class, [
'mapped' => false,
'constraints' => [
new Symfony\Component\Validator\Constraints\Callback([
'callback' => function ($value, ExecutionContextInterface $context) use ($person) {
// Here you can use $person->getId()
// $value is the value of the unmapped field
}
])
],
]);
});

Validate a Collection Field Type in Symfony 2 with allowExtraFields=true

I'm trying to validate a collection form field:
$builder->add(
'autor',
'collection',
array(
'type' => 'text',
'options' => array('required' => false),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'error_bubbling' => false
)
);
I use JavaScript, as suggested in the Cookbook, to dynamically add more text fields to the collection. My problem is, that I don't know, how to validate these fields. The collection validator lets me validate specific fields of a collection by their name but not simply every field of it. How can I manage that?
Even cooler would be, if I could check, if at least one of the fields is notBlank instead of forcing it to every field.
Best regards
You can use the "constraints" option defined in form Field Type that are available on all fields.(http://symfony.com/doc/master/reference/forms/types/form.html#constraints).
In your case, you can add your constraint like this:
$builder->add('autor', 'collection', array(
'constraints' => new NotBlank()),
));
(in this case dont forget to include the constraints provided by the Validation component:
use Symfony\Component\Validator\Constraints\NotBlank; ...)
i didnt test but i think with this every input will be validate againts the constraint you assigned to the field, and as you have the option "error_bubbling" as false, an error message should be attached to the invalid element.
--EDIT--
Since you even use the 2.0 version of Symfony i think this solution solves your problem, however I strongly advise you to update to the 2.3 version.
You can create a Form Event Subscriber(http://symfony.com/doc/2.0/cookbook/form/dynamic_form_modification.html) that will be listening the POST_BIND event.(note that Post Bind event is Deprecated since version 2.3 and will be removed in 3.0);
In your subscriber class, you will validate each of your submited authors as you want and add an error to the form if something is wrong.
Your postBind method could be something like this:
public function postBind(DataEvent $event)
{
$data = $event->getData();
$form = $event->getForm();
if (null === $data) {
return;
}
// get the submited values for author
// author is an array
$author = $form['autor']->getData();
// now iterate over the authors and validate what you want
// if you find any error, you can add a error to the form like this:
$form->addError(new FormError('your error message'));
// now as the form have errors it wont pass on the isValid() method
// on your controller. However i think this error wont appear
// next to your invalid author input but as a form error, but with
// this you can unsure that non of the fields will be blank for example.
}
you can check the Symfony2 Form Component API if you have any doubt about a core method.
http://api.symfony.com/2.0/index.html

Resources