Symfony2 calling controller function from buildform function - symfony

How do I call a custom function in the controller class of my bundle from the buildForm function of the AbstractType class of the same bundle?
My AbstractType:buildForm function works fine and generates my desired form but I have to add an extra field which will be a dropdown field of selectable options.
I need to dynamically generate the options for the dropdown list from data in the database - which I am already generating in the controller class.

Thanks to #sjagr, I've found a working solution.
Previously I had tried the following:
$form = $this->createForm(new SalesType(), new Sale(),
array(
'action' => $this->generateUrl('sales_add'),
'method' => 'POST',
'arguments' => array(1,2,3,4,5,6,7)
)
)
But I hadn't paid enough attention to the resulting error message: The option "arguments" does not exist. Known options are: "action", "allow_extra_fields" ...
I changed the arguments index of the array above to allow_extra_fields and my array arguments data was then available in the $options parameter of the buildForm function

Related

Symfony 5 / Easy Admin 3 - FormBuilder added field not displaying appropiate input

I am building a form using Easy Admin's FormBuilder. My goal is to have an AssociationField which represents a OneToMany relationship, for example, to assign multiple products to a shop. Additionally, I only want some filtered products to be listed, so I overrode the createEditFormBuilder method in the CrudController, I used this question as reference, and this is the code for the overridden function :
public function createEditFormBuilder(EntityDto $entityDto, KeyValueStore $formOptions, AdminContext $context): FormBuilderInterface
{
$formBuilder = parent::createEditFormBuilder($entityDto, $formOptions, $context);
$filteredProducts = $context->getEntity()->getInstance()->getFilteredProducts();
$formBuilder->add('products', EntityType::class, ['class' => 'App\Entity\Product', 'choices' => $filteredProducts, 'multiple' => true]);
return $formBuilder;
}
I expected an Association field as the ones configured in the configureFields() function, however, the displayed field doesn't allow text search or autocomplete features, plus has incorrect height.
Expected:
Actual:
I tried to change the second argument in the $formBuilder->Add() function, but all specific EasyAdmin types threw errors.
UPDATE: I also tried using EasyAdmin's CrudFormType instead of EntityType, which doesn't support the 'choice' parameter. Still, the result was the same.
There is setQueryBuilder on the field, you can use it for filtering entities like this
<?php
// ...
public function configureFields(string $pageName): iterable
{
// ...
yield new AssociationField::new('products')->setQueryBuilder(function($queryBuilder) {
$queryBuilder
->andWhere('entity.id IN (1,2,3)')
;
})
;
// ...
}

The method name must start with either findBy or findOneBy! error

I have a table which has relationship with Application\Sonata\MediaBundle\Entity\Media (SonataMediaBundle Entity) as 'media'
Normally I can make the form for Media like this below,
$form = $this->createFormBuilder($myMedia)
->add('name')
->add('media') // make the selectbox
->add('save', SubmitType::class, array('label' => 'Create Post'))
->getForm();
However I want to restrict to some medias from all medias, then I made this.
$form = $this->createFormBuilder($myMedia)
->add('name')
->add('media','entity',array(
'class' => "Application\Sonata\MediaBundle\Entity\Media",
'query_builder' => function(EntityRepository $er) {
return $er->createQuery('SELECT r FROM ApplicationSonataMediaBundle:Media');
}))
->add('save', SubmitType::class, array('label' => 'Create Post'))
->getForm();
However it shows the error like this.
Undefined method 'createQuery'. The method name must start with either findBy or findOneBy!
I have found some articles and understood it is related with Repository.
But I am not sure which Repository should I point. THere is no Repository class under
Sonata\MediaBundle\ either Application\Sonata\MediaBundle
namespace Application\Sonata\MediaBundle\Entity;
use Sonata\MediaBundle\Entity\BaseMedia as BaseMedia;
#ORM\Entity(repositoryClass="Where is my repository???")
class Media extends BaseMedia
{
/**
* #var int $id
*/
protected $id;
BTW, my first code shows only select box for pictures(medias)
It is not useful enough to select pictures,
Is there a more suitable way for selecting pictures?
Look at the error, the createQuery method does not exist.
If you take a look at the EntityRepository class you will see that the right method is createQueryBuilder().
If you look at the content of the method you'll see that it returns a QueryBuilder instance with already the right select from statement since you are supposed to get the right repository for your media entity from the Entity form type since you pass the class of your entity in the class option.
You have defined $er as $this->getDoctrine()->getRepository('Application\Sonata\MediaBundle\Entity:Media') which is the EntityRepository. What you need rather is the EntityManager which is $this->getDoctrine()->getManager() and then use the select statement that you have in the piece of code. Hope it helps!

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'];

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