A Symfony 4 application has a User entity using Joined InheritanceType with types Admin, Staff and Volunteer. The Staff entity has a OneToOne relationship with an Organization entity. So the Organization form type includes a child staff, which has a custom form type NewUserType. (Relevant excerpts appear below.) If an empty OrganizationType form is submitted all of the expected form errors are rendered EXCEPT the name and email errors. The password form error field of NewUserType IS rendered. Symfony's profiler does not show errors for name or email. error_bubbling in NewUserType makes no difference. #Assert\Valid on the staff property of Organization entity makes no difference.
registerOrganization:
public function registerOrganiztion(Request $request) {
$form = $this->createForm(OrganizationType::class);
$templates = [
'Registration/organization.html.twig',
'Registration/new_user.html.twig',
'Registration/focuses.html.twig',
];
...
return $this->render('Default/formTemplates.html.twig', [
'form' => $form->createView(),
'headerText' => 'Add an organization',
'userHeader' => 'Staff Member',
'orgHeader' => 'Organization',
'focusHeader' => "Organization's Focus",
'templates' => $templates,
]);
}
new_user.html.twig:
{% if form.staff is defined %}{% set user = form.staff %}{% else %}{% set user = form %}{% endif %}
<div class="text-center text-bold">
{{ userHeader }}
</div>
{{ form_row(user.fname) }}
{{ form_row(user.sname) }}
{{ form_row(user.email) }}
{{ form_row(user.plainPassword.first) }}
{{ form_row(user.plainPassword.second) }}
OrganizationType:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
...
->add('staff', NewUserType::class)
}
NewUserType:
class NewUserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('sname', null, [
'attr' => [
'class' => 'mb-2',
'size' => '15',
],
'label' => 'Last name: ',
'label_attr' => ['class' => 'mr-2'],
])
->add('fname', null, [
'attr' => [
'class' => 'mb-2',
'size' => '15',
],
'label' => 'First name: ',
'label_attr' => ['class' => 'mr-2'],
])
->add('email', null, [
'attr' => [
'class' => 'mb-2',
'size' => '15',
],
'label' => 'Email: ',
'label_attr' => ['class' => 'mr-2'],
])
->add('plainPassword', RepeatedType::class, array(
'type' => PasswordType::class,
'mapped' => false,
'constraints' => [new NotBlank(['message' => "Password may not empty"])],
'invalid_message' => 'Passwords do not match',
'first_options' => [
'attr' => [
'class' => 'mb-2',
'size' => '15',
],
'label' => 'Password:',
'label_attr' => ['class' => 'mr-2'],
'required' => true,
],
'second_options' => [
'attr' => [
'class' => 'mb-2',
'size' => '15',
],
'label' => 'Confirm:',
'label_attr' => ['class' => 'mr-2'],
'required' => true,
],
))
;
if (Volunteer::class === $options['data_class']) {
$builder
->add('focuses', FocusFieldType::class)
->add('skills', SkillFieldType::class)
;
}
}
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults([
'data_class' => Staff::class,
'required' => false,
'error_bubbling' => true,
]);
}
}
Organization entity
/**
* Organization
*
* #ORM\Table(name="organization")
* #ORM\Entity
*/
class Organization
{
...
/**
* #ORM\OneToOne(targetEntity="Staff", inversedBy="organization")
* #ORM\JoinColumn(name="staff_id", referencedColumnName="id")
* #Assert\Valid
*/
protected $staff;
...
}
Staff entity
/**
* Staff
*
* #ORM\Table(name="staff")
* #ORM\Entity
*/
class Staff extends User
{
...
/**
* #ORM\OneToOne(targetEntity="Organization", mappedBy="staff")
*/
protected $organization;
public function getOrganization()
{
return $this->organization;
}
public function setOrganization(Organization $organization = null)
{
$this->organization = $organization;
return $this;
}
}
While I don't know why the name & email form errors are not rendered, they can be forced by adding the constraint to the form type. Just need then to remove the #Assert\... from those fields in the User entity.
Related
I have an employee entity, a society entity and a site entity,
A society can have several sites (OneToMany),
The employee has a form. Inside, we have the choice of society.
I would like to nest the Site FormType in that of society.
So when I choose a society under EmployeeForm, it only suggests sites linked to society.
Hope I am clear. I followed the symfony documentation on how to embed an ArrayCollection.
https://symfony.com/doc/5.1/form/form_collections.html
and I looked https://symfony.com/doc/current/form/data_transformers.html
I'm not sure which is the best approach ?
I still get the error
The object of class Doctrine\ORM\PersistentCollection could not be
converted to a string
and I am unable in SocietyForm to isolate the "name" attribute of each Site. I don't want a collection, but a list of name (string).
EmployeeType
class EmployeeType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', NULL, [
'label' => 'Nom',
])
->add('firstname', NULL, [
'label' => 'Prénom',
])
->add('societe', EntityType::class, [
'label' => 'Société',
'class' => RefSociety::class,
'choice_label' => 'Name',
])
->add('site', EntityType::class, [
'label' => 'Site',
'class' => RefSociety::class,
'choice_label' => 'refSites'
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Employee::class,
]);
}
}
RefSocietyType
class RefSocietyType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('Name', NULL, [
'label' => 'Nom de société'
])
->add('refSites', CollectionType::class, array(
'type' => RefSiteType::class,
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'by_reference' => false,
))
->add('Adress', NULL, [
'label' => 'Adresse'
])
->add('PostalCode', NULL, [
'label' => 'Code postal'
])
->add('Country', NULL, [
'label' => 'Pays'
])
->add('City', NULL, [
'label' => 'Ville'
])
->add('save', SubmitType::class, [
'attr' => ['class' => 'btn btn-primary btn-block btn-radius'],
'label' => 'Sauvegarder'
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => RefSociety::class,
]);
}
}
RefSiteType
class RefSiteType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => RefSite::class,
]);
}
}
index.html.twig
{ form_start(employeeForm) }}
{{form_row(employeeForm.name, {'attr' : {'class' : ""} }) }}
{{form_row(employeeForm.firstname, {'attr' : {'class' : ""} }) }}
{{form_row(employeeForm.societe) }}
<ul class="sites">
{% for site in employeeForm.refSites %}
<li>{{ form_row(site.name) }}</li>
{% endfor %}
</ul>
{{ form_end(employeeForm) }}
->add('site', EntityType::class, [
'label' => 'Site',
'class' => RefSociety::class,
'choice_label' => 'refSites'
])
'choice_label' => 'refSites' means that the method getRefSites() are called by PropertyAccessor to get a label name, so the method getRefSites returns the collection (because one to many), but the label must be a string.
Maybe should you use 'choice_label' => 'name' ?
I want to create multiples forms in one page with one submit buttons with symfony. I checked the documentation and i saw i will need to make a form with a collectiontype field but i don't know how to proceed. I have two forms in two page, the first page the user tell how many forms the next page will have . I already create my first form and its working fine, i have the data of the first form but now how i can create the second page that contain multiple forms depending of the submited data.
Here is my first form :
class OrdersType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add ('tickets', CollectionType::class, [
'entry_type' => TicketsType::class,
'allow_add' => true
])
->add('numberOfTickets', ChoiceType::class, [
'attr' => [
'class' => 'form-control'
],
'choices' => [
'1' => 1,
'2' => 2,
'3' => 3,
'4' => 4,
'5' => 5,
]
])
->add('date', DateType::class, [
'attr' => [
'class' => 'form-control'
],
'widget' => 'single_text'
])
->add('type', ChoiceTYpe::class, [
'attr' => [
'class' => 'form-control'
],
'choices' => [
'Journée' => 1,
'Demi-journée(à partir de 14 heures)' => 0,
]
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Orders::class,
]);
}
}
And my second form (the one i want to duplicate in my second page) :
class TicketsType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('category', CheckboxType::class, [
'attr' => [
'class' => 'form-control'
]
])
->add('firstname', TextType::class, [
'attr' => [
'class' => 'form-control'
]
])
->add('lastname', TextType::class, [
'attr' => [
'class' => 'form-control'
]
])
->add('country', TextType::class, [
'attr' => [
'class' => 'form-control'
]
])
->add('dateOfBirth', DateType::class, [
'attr' => [
'class' => 'form-control'
],
'widget' => 'single_text'
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Tickets::class,
]);
}
}
and my controller :
/**
* #Route("/", name="home")
*/
public function home(Request $request, SessionInterface $session)
{
$Orders = new Orders();
$form = $this->createForm(OrdersType::class, $Orders);
$form->handleRequest($request);
if($form->isSubmitted()){
$formData = $form->getData();
$session->set('orders', $formData);
return $this->redirectToRoute('ticket');
}
$repo = $this->getDoctrine()->getRepository(Tickets::class);
$tickets = $repo->findAll();
return $this->render('louvre/home.html.twig', [
'form' => $form->createView()
]);
}
/**
* #Route("/ticket", name="ticket")
*/
public function ticket(Request $request, SessionInterface $session)
{
$data = $session->get('orders');
$Orders = new Orders()
for ($i=0; $i<$data->getNumberOfTickets() ;$i++){
echo 'test';
$tickets = new Tickets();;
$form = $this->createForm(OrdersType::class, $Orders);
$form->handleRequest($request);
};
$repo = $this->getDoctrine()->getRepository(Tickets::class);
$tickets = $repo->findAll();
return $this->render('louvre/ticket.html.twig', [
'form' => $form->createView()
]);
}
Here's an example from symfony 3.4 that shows two different forms on the same page with a single submit button. Note that the head of household is a Member entity and the household is a Household entity:
$form = $this->createForm(HouseholdType::class, $household);
$formHead = $this->createForm(MemberType::class, $head);
$form->handleRequest($request);
$formHead->handleRequest($request);
if ($form->isSubmitted() && $form->isValid() && $formHead->isSubmitted() && $formHead->isValid()) {
//relevant logic
}
return $this->render(
'Household/new.html.twig',
array(
'formType' => 'New Household',
'form' => $form->createView(),
'formHead' => $formHead->createView(),
'title' => 'New Household',
)
);
So the solution was to make the CollectionType not in my form but in my controller, so I deleted:
->add(
'tickets',
CollectionType::class,
[
'entry_type' => TicketsType::class,
'allow_add' => true
]
)
Then when I want to make a CollectionType of my form I write this in my controller:
$form = $this->createForm(
CollectionType::class,
$tickets,
['entry_type' => TicketsType::class]
);
Whole error is missiong namespace Symfony\Component\Form which is replaced with 3 dots, due to title maximum characters.
So, I am following the steps, that are presented in the docs and I'm unable to find source of the error I'm getting. If anyone could help, I'd greatly appreciate it.
Here is the method from my AuthController
/**
* #Route("/register", name="registrationPage")
*/
public function showRegistrationPage(Request $request)
{
return $this->render('auth/register.html.twig', [
'register_form' => $this->createForm(RegisterType::class, (new UserInformation()))
]);
}
And here is the method, where I declare the form
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('firstname', TextType::class, ['attr' => ['class' => 'form-control']])
->add('secondname', TextType::class, ['attr' => ['class' => 'form-control']])
->add('email', EmailType::class, ['attr' => ['class' => 'form-control']])
->add('password', PasswordType::class, ['attr' => ['class' => 'form-control']])
->add('password_confirmation', PasswordType::class, [
'label' => 'Confirm Password',
'attr' => ['class' => 'form-control'],
'mapped' =>false
])
->add('Register', SubmitType::class, ['attr' => ['class' => 'btn btn-primary']]);
}
/**
* #Route("/register", name="registrationPage")
*/
public function showRegistrationPage(Request $request)
{
$form = $this->createForm(RegisterType::class, (new UserInformation()));
return $this->render('auth/register.html.twig', [
'register_form' => $form->createView()
]);
}
http://symfony.com/doc/current/forms.html#building-the-form
the missing part was createView() method
/**
* #Route("/register", name="registrationPage")
*/
public function showRegistrationPage(Request $request)
{
return $this->render('auth/register.html.twig', [
'register_form' => $this->createForm(RegisterType::class, (new UserInformation()))->createView()
]);
}
I have for with fields type entity, drop down choice and required true but when submit form have error in console
An invalid form control with name='inbound_invoice_row[costObject]' is not focusable.
new:1 An invalid form control with name='inbound_invoice_row[accountingAccount]' is not focusable.
new:1 An invalid form control with name='inbound_invoice_row[user]' is not focusable.
Another field validate fine, like vat or price but for accountingAccount user costObject have this error in console
why not understand
my form
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('location', EntityType::class, [
'class' => Location::class,
'empty_value' => 'select_default_value',
'query_builder' => self::getLocations(),
'required' => false,
'label' => 'locations',
'translation_domain' => 'invoicing'
])
->add('costObject', EntityType::class, [
'class' => CostObject::class,
'empty_value' => 'select_default_value',
'choices' => self::getCostObjectHierarchy(),
'required' => true,
'label' => 'cost_object',
'translation_domain' => 'invoicing'
])
->add('accountingAccount', EntityType::class, [
'class' => AccountingAccount::class,
'empty_value' => 'select_default_value',
'query_builder' => self::getAccountingAccount(),
'required' => true,
'label' => 'accounting_account',
'translation_domain' => 'invoicing'
])
->add('user', EntityType::class, [
'class' => User::class,
'empty_value' => 'select_default_value',
'choices' => self::getR(),
'required' => true,
'label' => 'employee',
'translation_domain' => 'invoicing'
])
->add('description', TextType::class, [
'label' => 'description',
'required' => false,
'translation_domain' => 'invoicing'
])
->add('vat', ChoiceType::class, [
'choices' => $this->vatClasses,
'required' => true,
'label' => 'vat',
'translation_domain' => 'common'
])
->add('price', TextType::class, [
'label' => 'price',
'required' => true,
'translation_domain' => 'invoicing'
]);
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'EconomyBundle\Entity\InboundInvoiceRow',
'locations' => [],
'employees' => [],
'accounts' => [],
'vat' => [],
'cost' => [],
'ajax' => true,
'csrf_protection' => true
));
}
public function getName()
{
return 'inbound_invoice_row';
}
create form in action
$form = $this->createForm(
$this->get('economy.form.type.in_bound_invoice_row'),
$inboundInvoiceRow,
[
'validation_groups' => [InboundInvoiceRow::GROUP_POST],
'cascade_validation' => true,
'action' => $this->generateUrl('inbound_invoices_row_create', ['id' => $inboundInvoice->getId()]),
'method' => 'POST',
]
);
$form->add('submit', 'submit', array('label' => 'save', 'translation_domain' => 'invoicing'));
You probably have some js library that is used when rendering those fields (e.g. Select2 or Chosen). When there's some HTML validation error (e.g. the field is required but there is no value) on a field, but it's not visible - it might have display property set to none - then the browser is unable to attach error message to that field. This is what most likely triggers your error.
Simplest solution is to set 'required' => false in form type options and rely on backend validation (e.g. using Symfony Validation component) rather than on basic HTML validation.
So I am following the documentation and I am making a form inside its own class:
<?php
namespace Mp\ShopBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\PropertyAccess\PropertyAccess;
class RegisterFormType extends AbstractType
{
public function registerForm(FormBuilderInterface $builder, array $options) {
$builder
>add('title', 'choice', array(
'choices' => array('-' => '-', 'mr' => 'Mr.', 'mrs' => 'Mrs.', 'mss' => 'Miss.'),
'label' => 'Title * ',
'attr' => array('class' => 'span1')))
->add('firstName', 'text', array(
'label' => 'First Name * ',
'attr' => array('placeholder' => 'First Name')))
->add('lastName', 'text', array(
'label' => 'Last Name * ',
'attr' => array('placeholder' => 'Last Name')))
->add('Email', 'email', array(
'label' => 'Email * ',
'attr' => array('placeholder' => 'Email')))
->add('Password', 'password', array(
'label' => 'Password * ',
'attr' => array('placeholder' => 'Password')))
->add('DateOfBirth', 'date', array(
'label' => 'Date Of Birth * ',
'widget' => 'choice'))
->add('Company', 'text', array(
'label' => 'Company ',
'attr' => array('placeholder' => 'Company')))
->add('Adress', 'text', array(
'label' => 'Adress * ',
'attr' => array('placeholder' => 'Adress')))
->add('Country', 'country', array(
'label' => 'Country * ',
'attr' => array('placeholder' => 'Country')))
->add('State', 'text', array(
'label' => 'State * ',
'attr' => array('placeholder' => 'State')))
->add('City', 'text', array(
'label' => 'City * ',
'attr' => array('placeholder' => 'City')))
->add('ZipPostalCode', 'text', array(
'label' => 'Zip / Postal Code *',
'attr' => array('placeholder' => 'Zip / Postal Code')))
->add('AdditionalInformation', 'textarea', array(
'label' => 'Additional Information ',
'attr' => array('placeholder' => 'Additional Information')))
->add('HomePhone', 'number', array(
'label' => 'Home phone ',
'attr' => array('placeholder' => 'Home Phone')))
->add('MobilePhone', 'number', array(
'label' => 'Mobile phone ',
'attr' => array('placeholder' => 'Mobile Phone')))
->add('save', 'submit', array('label' => 'Register'));
}
public function getName()
{
return 'register_form_users';
}
}
It looks like a simple form. Now in my controller I want to show it:
use Mp\ShopBundle\Form\Type\RegisterFormType;
public function registerAction()
{
$em = $this->getDoctrine()->getManager();
$products = $em->getRepository('MpShopBundle:Product')->findAll();
$form = $this->createForm(new RegisterFormType());
return $this->render('MpShopBundle:Frontend:registration.html.twig', array(
'products'=>$products,
'form'=>$form->createView(),
));
}
My twig:
<h3>Your personal information</h3>
{{ dump(form) }}
{% form_theme form _self %}
{{ form(form) }}
The thing is im not getting my form. The page and the template loads fine, but not my form.
When I do {{ dump(form) }} I get something:
FormView {#2110 ▼
+vars: array:33 [▶]
+parent: null
+children: array:1 [▶]
-rendered: true
}
As you can see I am getting the form? But it is not displaying?... Why is that?
You must change your method
public function registerForm(FormBuilderInterface $builder, array $options) {
to
public function buildForm(FormBuilderInterface $builder, array $options)