I have a form that is bound to an entity, but it also has an extra unmapped field:
(from the FormType class)
$builder
->add('name')
->add('qoh')
->add('serialNumber', 'text', array('mapped' => false, 'required' => false))
I want to pre-populate the serialNumber field from the controller with information taken from the request URL. The closest method I have found would be:
$form->setData(mixed $modelData)
but the API does not specify what form '$modelData' takes and nothing I've tried has had any effect.
Someone on Symfony's IRC channel gave me this answer, and they declined to post it here:
$form->get('serialNumber')->setData($serial_number);
You can use Form Events. For example if you want to set the data from the database to a non mapped field you can use POST_SET_DATA:
class AddNonMappedDataSubscriber implements EventSubscriberInterface
{
protected $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public static function getSubscribedEvents()
{
return array(
FormEvents::POST_SET_DATA => 'postSetData'
);
}
public function postSetData(FormEvent $event){
$form = $event->getForm();
$myEntity = $event->getData();
if($myEntity){
$serialNumber = $myEntity->getNumber();
$form->get('serialNumber')->setData($serialNumber);
}
}
}
You can pre-populate the field in twig (Set default value of Symfony 2 form field in Twig).
...
{{ form_widget(form.serialNumber, { value : serialNumber }) }}
...
Related
I'm experiencing an odd error with my form validation in Symfony 4. It is a simple contact form represented by this entity:
class ContactRequest
{
/** #var int */
private $id;
/** #var string */
private $fullName;
//...
/**
* #return string
*/
public function getFullName() : string
{
return $this->fullName;
}
In my controller I'm handling the submission as per Symfony website but there is something I'm missing for I'm getting the following error:
Type error: Return value of App\Entity\ContactRequest::getFullName() must be of the type string, null returned
Now, I know what is the meaning of that: it expects a string to be returned by the method getFullName whereas null is actually returned but I don't understand why.
This is my controller
public function contactSubmit(Request $request, ValidatorInterface $validator)
{
$form = $this->createForm(ContactType::class);
$form->handleRequest($request);
if($form->isValid()){
//...
}
$errors = $validator->validate($form);
Shouldn't the handleRequest() method set the values in the entity for me?
To my surprise, when I have initialised the entity before, it worked well notwithstanding the entity is already set in the configureOptions() method in the form:
$contact = new ContactRequest;
$contact
->setFullName($request->request->get('contact')['fullName'])
//...
$form = $this->createForm(
ContactType::class
$contact
);
$form->setData($contact);
$form->handleRequest($request);
However, shouldn't the scope of using the handleRequest() be to avoid setting the entity's values manually? Shouldn't the handleRequest() method take care of setting those values?
I know I could validate the submitted data against the entity too (thing that I've successfully tried), without using the handleRequest() at all but it would piss me off a little. Why would I need to set a form in such a case?
This is the ContactType form:
//...
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('fullName', TextType::class, [
'required' => true,
'empty_data' => 'a',
'attr' => [
'placeholder' => 'Full Name',
'class' => 'form-control'
]
])
//...
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => ContactRequest::class
]);
}
In order to find out where your getFullName gets called you could (atleast in dev environment) print the backtrace on the call:
/**
* #return string
*/
public function getFullName() : string
{
if ($this->fullName === null)
{
echo "<pre>getFullName on uninitialized entity:\n";
debug_print_backtrace();
die();
}
return $this->fullName;
}
But as said in the comments: Initializing the entity with a null value in that field and not allowing the getter to return a null value seems kinda odd to me, so : ?string to allow for nullable return values (as of PHP 7.1) seems to be the next best option.
I have a Blog entity who has a slug, i want this slug to be saved in a custom data attribute inside the form tag like this: <form data-article-slug="my-slug">.
This is what i expect to achieve:
class BlogTypeSubscriber implements EventSubscriberInterface
{
public function onPreSetData(FormEvent $event)
{
$data = $event->getData();
$form = $event->getForm();
$slug = $data->getSlug();
if (!empty($slug)) /* the bellow method does not exist */
$form->setAttr([ 'data-article-slug' => $slug ]);
}
public static function getSubscribedEvents()
{
return [ FormEvents::PRE_SET_DATA => 'onPreSetData' ];
}
}
It is possible to do almost the same thing inside of a twig template such as: {{ form_start(form, { 'attr': { 'data-article-slug' : form.vars.value.slug } }) }} but i find this way very redundent and not symfony friendly, how can i achieve this?
Unfortunately it's not possible. Once $builder->getForm() has been called the form is locked. It's part of the design, allowing to modify the form after it has been built could lead to issues because options has to be resolved and this can only be achevied through the form building process.
Aditionally you can read this:
Symfony 2 - modifying form configuration after initializing a form object
And maybe this: https://github.com/symfony/symfony/issues/20311
Basically the idea is next:
You need to create(or you already have) form type:
for example
class YourEntityType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->setAttribute('somefield')
->setAttribute('slug');
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($builder){
/** #var YOURENTITY $data */
$data = $builder->getData();
if ($data->getSlug()) {
$builder->setAttribute('data-article-slug', $data->getSlug());
}
}
);
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(['data_class' => YOURENTITYCLASSNAMEHERE::class]);
}
}
}
So in this example we have created an entity form type, with event listener. On set data, if you have a slug, then data-article-slug attribute will be added to a form.
Further reading:
1.forms (see data_class)
2.pre set data event
I'm trying by overriding the controller and the formtype, to show the roles from the selected group in my view, but I don't find the right way. I've followed the steps to override everything, that works, but problem comes when I try to say to the service that I'm passing to the constructor an entity object.
As the formtype has to be overridden, how to pass through the service that you need to implement, my Group entity?
Does anyone have an idea of how to achieve that?
Here's what I've done:
Override the controller, and when creating the Form, pass the $group entity
$formFactory = $this->container->get('fos_user.group.form.factory');
$form = $formFactory->createForm($group); //Here
Override the form, and use a custom __construct method where I can pass my entity (maybe here is my error and that should be done in a better or other way)
public function __construct(Container $container, Groups $group)
{
$this->container = $container;
$this->roles = array_keys($this->container->getParameter('security.role_hierarchy.roles'));
$this->group = $group; #How?
}
The container to get the keys for the roles is passed without errors, that works.
Create the service as the documentation says (here comes the real problem and the exceptions)
x_s_cosmos.group.form.type:
class: X\S\CosmosBundle\Form\Type\GroupFormType
arguments: [#service_container, here_should_be_my_entity?]
tags:
- { name: form.type, alias: kosmos_group_form }
I'm really stacked with that and don't have any idea of how to go on.
Finally, after overriding the GroupController.php and adding a choice field type to my form, I could achieve my goal.
$form->add('roles', 'choice', array(
'choices' => $this->getExistingRoles(),
'data' => $group->getRoles(),
'label' => 'Roles',
'expanded' => true,
'multiple' => true,
'mapped' => true,
));
Where getExistingRoles() is:
$roleHierarchy = $this->container->getParameter('security.role_hierarchy.roles');
$roles = array_keys($roleHierarchy);
foreach ($roles as $role) {
$theRoles[$role] = $role;
}
return $theRoles;
I was just going in the wrong way, it was not so difficult to get the roles of a group and show them in an admin interface so that you can choose one of your system roles and give it to the group. No need to override the FormType, just the controller to add your own fields to the form.
Hope it helps, as it has given lot of headache to me.
You should not pass an entity to the constructor. If you need to access entity inside the form, you have to add an event listener to the form builder, like this:
public function buildForm(FormBuilder $builder, array $options)
{
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$form = $event->getForm();
$entity = $event->getData();
// This is your Group entity, now do something with it...
if ($entity instanceof Group) {
// $form->add ...
}
});
}
I am learning to use Symfony2 and in the documentation I have read, all entities being used with Symfony forms have empty constructors, or none at all. (examples)
http://symfony.com/doc/current/book/index.html Chapter 12
http://symfony.com/doc/current/cookbook/doctrine/registration_form.html
I have parametrized constructors in order to require certain information at time of creation. It seems that Symfony's approach is to leave that enforcement to the validation process, essentially relying on metadata assertions and database constraints to ensure that the object is properly initialized, forgoing constructor constraints to ensure state.
Consider:
Class Employee {
private $id;
private $first;
private $last;
public function __construct($first, $last)
{ .... }
}
...
class DefaultController extends Controller
{
public function newAction(Request $request)
{
$employee = new Employee(); // Obviously not going to work, KABOOM!
$form = $this->createFormBuilder($employee)
->add('last', 'text')
->add('first', 'text')
->add('save', 'submit')
->getForm();
return $this->render('AcmeTaskBundle:Default:new.html.twig', array(
'form' => $form->createView(),
));
}
}
Should I not be using constructor arguments to do this?
Thanks
EDIT : Answered Below
Found a solution:
Looking into the API for the Controllers "createForm()" method I found something that is not obvious from the examples. It seems that the second argument is not necessarily an object:
**Parameters**
string|FormTypeInterface $type The built type of the form
mixed $data The initial data for the form
array $options Options for the form
So rather than pass in an instance of the Entity, you can simply pass in an Array with the appropriate field values:
$data = array(
'first' => 'John',
'last' => 'Doe',
);
$form = $this->createFormBuilder($data)
->add('first','text')
->add('last', 'text')
->getForm();
Another option (which may be better), is to create an empty data set as a default option in your Form Class.
Explanations here and here
class EmployeeType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('first');
$builder->add('last');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'empty_data' => new Employee('John', 'Doe'),
));
}
//......
}
class EmployeeFormController extends Controller
{
public function newAction(Request $request)
{
$form = $this->createForm(new EmployeeType());
}
//.........
}
Hope this saves others the head scratching.
I'm trying to display a form with a collection. The collection should display an empty sub-form. Due to the projects nature I can't rely on JavaScript to do so.
Googling didn't help and I does not seem to work by adding an empty entity to the collection field.
What I have so far:
public function indexAction($id)
{
$em = $this->getDoctrine()->getManager();
$event = $em->getRepository('EventBundle:EventDynamicForm')->find($id);
$entity = new Booking();
$entity->addParticipant( new Participant() );
$form = $this->createForm(new BookingType(), $entity);
return array(
'event' => $event,
'edit_form' => $form->createView()
);
}
In BookingType.php buildForm()
$builder
->add('Participants', 'collection')
In the Twig template
{{ form_row(edit_form.Participants.0.companyName) }}
If I put the line $entity->addParticipant( new Participant() ); in indexAction() I get an error saying:
The form's view data is expected to be of type scalar, array or an
instance of \ArrayAccess, but is an instance of class
Yanic\EventBundle\Entity\Participant. You can avoid this error by
setting the "data_class" option to
"Yanic\EventBundle\Entity\Participant" or by adding a view transformer
that transforms an instance of class
Yanic\EventBundle\Entity\Participant to scalar, array or an instance
of \ArrayAccess.
If I delete the said line Twig complains:
Method "0" for object "Symfony\Component\Form\FormView" does not exist in
/Applications/MAMP/htdocs/symfony-standard-2.1/src/Yanic/EventBundle/Resources/views/Booking/index.html.twig
at line 27
EDIT: The addParticipant is the default methos generated by the doctrine:generate:entities command
/**
* Add Participants
*
* #param \Yanic\EventBundle\Entity\Participant $participants
* #return Booking
*/
public function addParticipant(\Yanic\EventBundle\Entity\Participant $participants)
{
$this->Participants[] = $participants;
return $this;
}
I'm sure that I'm doing something wrong, but can't find the clue :-(
I guess you are a bit lost on Symfony2 form collection, though I think you already read http://symfony.com/doc/current/cookbook/form/form_collections.html.
Here I will just emphasize the doc, help other SO readers, and exercise myself a bit on answering question.. :)
First, you must have at least two entities. In your case, Booking and Participant. In Booking entity, add the following. Because you use Doctrine, Participant must be wrapped in ArrayCollection.
use Doctrine\Common\Collections\ArrayCollection;
class Booking() {
// ...
protected $participants;
public function __construct()
{
$this->participants = new ArrayCollection();
}
public function getParticipants()
{
return $this->participants;
}
public function setParticipants(ArrayCollection $participants)
{
$this->participants = $participants;
}
}
Second, your Participant entity could be anything. Just for example:
class Participant
{
private $name;
public function setName($name) {
$this->name = $name;
return $this;
}
public function getName() {
return $this->name;
}
}
Third, your BookingType should contain collection of ParticipantType, something like this:
// ...
$builder->add('participants', 'collection', array('type' => new ParticipantType()));
Fourth, the ParticipantType is straightforward. According to my example before:
// ...
$builder->add('name', 'text', array('required' => true));
Last, in BookingController, add the necessary amount of Participant to create a collection.
// ...
$entity = new Booking();
$participant1 = new Participant();
$participant1->name = 'participant1';
$entity->getParticipants()->add($participant1); // add entry to ArrayCollection
$participant2 = new Participant();
$participant2->name = 'participant2';
$entity->getParticipants()->add($participant2); // add entry to ArrayCollection
think you have to add here the type:
->add('Participants', 'collection', array('type' => 'YourParticipantType'));
Could you also paste in here the declaration of your addParticipant function from the model? Seems that there's something fishy too.