There is an example of how to do what you're looking to achieve in the Symfony Documentation on How to Embed a Collection of Forms.
For your specific use case, you will want to create a UserCartsForm
and a Separate CartsForm
.
In your UserCart
add the carts
field as a CollectionType
.
Symfony will then process the field as a series of forms.
src/AppBundle/Form/UserCart.php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type as FormType;
class UserCartsForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('carts', FormType\CollectionType::class, [
'label' => false,
'entry_type' => CartsForm::class,
'entry_options' => array('label' => false),
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => User::class,
]);
}
}
Add the fields you want editable on your form to the CartsForm
src/AppBundle/Form/CartsForm.php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type as FormType;
class CartsForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('quantity', FormType\IntegerType::class, [
'label' => false
//...
]);
//...
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Cart::class,
]);
}
}
In your controller, reference the user entity as your UserCartsForm
data.
src/AppBundle/Controller/DefaultController.php
namespace AppBundle\Controller;
use AppBundle\Form\UserCartsForm;
class DefaultController extends Controller
{
/**
* @Route('/{id}/user-carts')
*/
public function userCartsAction(Request $request, User $user)
{
$form = $this->createForm(UserCartsForm::class, $user);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid())
{
//... process entity
//$this->getDoctrine()->getManager()->flush($carts);
return $this->redirectToRoute('some_route');
}
return $this->render('user_carts_form.html.twig', [
'form' => $form
]);
}
}
Then you should be able to easily retrieve the data from your twig template to render as you would like.
app/Resources/views/user_carts_form.html.twig
{% form_start(form) %}
<table>
<thead>
<tr>
<td>Name</td>
<td>Quantity</td>
<td></td>
</tr>
</thead>
<tbody>
{% for cart in form.carts %}
{% set cartEntity = cart.vars.data %}
<tr>
<td>{{ cartEntity.product.name }}</td>
<td>{{ form_widget(cart.quantity) }}</td>
<td><a class="button" href="{{ path('remove_cart_action', { id: cartEntity.id }) }}">Delete <icon/></a></td>
<tr>
{% endfor %}
</tbody>
</table>
<button type="submit">Submit</button>
{% form_end(form) %}
Update for Entity Constraints
By default Symfony will use all constraints (Default
) assigned to the entity when validating your form causing $form->isValid()
to return false.
https://symfony.com/doc/3.4/validation/groups.html
If no groups are specified, all constraints that belong to the group
Default
will be applied.
To resolve the issue, use Validation Groups to separate the entity constraints and declare the desired groups on their respective form(s).
Example:
src/AppBundle/Entity/User.php
namespace AppBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
/**
* @ORM\Entity
*/
class User
{
/**
* @Assert\NotBlank(groups={"registration"})
*/
private $username;
//...
}
Then to use your validation group(s) on the desired form, in this case RegistrationForm
, you declare the desired group in the AbstractTye::configureOptions
as one of the OptionsResolver:$defaults
.
src/AppBundle/Form/RegistrationForm.php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
class RegistrationForm extends AbstractType
{
//...
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => User::class,
'validation_groups' => ['registration']
]);
}
}
Now the User::NotBlank
constraint will only apply for the RegistrationForm::isValid()
or any other form that declare the registration validation group.