Массив для CollectionType в формах Symfony 3.4

У меня есть массив корзины сущностей, и я хочу сгенерировать общую форму, которая выглядит на экране.

введите здесь описание изображения

Как видите, я хочу, чтобы в каждой строке было редактируемое поле Количество, представляющее сущность Корзина, и я хочу иметь возможность обновлять их все сразу.

class Cart
{
/**
 * @ORM\Id
 * @ORM\Column(type="integer")
 * @ORM\GeneratedValue(strategy="AUTO")
 */
private $id;

/**
 * @ORM\ManyToOne(targetEntity="User", inversedBy="carts")
 */
private $userId;

/**
 * @ORM\ManyToOne(targetEntity="Product", inversedBy="carts")
 */
protected $product;

/**
 * @ORM\Column(type="integer")
 */
private $quantity;

/*gettes & setters */
}

На данный момент у меня есть форма, которая хочет получить CollectionType, чтобы работать с ней, но у меня есть только массив сущностей, поэтому она сбрасывает LogicalException.

Что мне нужно сделать - есть ли способ разобрать массив на CollectionType, или, может быть, я мог бы взять группу объектов корзины из базы данных другим способом?:

$carts=$this->getDoctrine()->getRepository(Cart::class)->findByUserId($user);

person D_Jedrzejewski    schedule 25.04.2018    source источник


Ответы (1)


Пример того, как добиться желаемого, есть в Документация Symfony на Как встроить коллекцию форм.

Для вашего конкретного случая использования вы захотите создать UserCartsForm и отдельный CartsForm.

В UserCart добавьте поле carts как CollectionType. Затем Symfony обработает поле как серию форм.

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,
        ]);
    }
}

Добавьте поля, которые вы хотите редактировать в своей форме, в 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,
        ]);
    }
}

В вашем контроллере укажите объект пользователя как ваши UserCartsForm данные.

src/AppBundle/Controller/DefaultController.php

namespace AppBundle\Controller;

use AppBundle\Form\UserCartsForm;

class DefaultController extends Controller
{

   /**
    * @Route('/user/{id}/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();
         return $this->redirectToRoute('some_route');
      }

      return $this->render('user_carts_form.html.twig', [
          'form' => $form
      ]);
   }
}

Затем вы сможете легко получить данные из своего шаблона ветки, чтобы отобразить их так, как вам хочется.

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) %}

Обновление ограничений объектов

По умолчанию Symfony будет использовать все ограничения (Default), назначенные объекту, при проверке вашей формы, в результате чего $form->isValid() вернет false.

https://symfony.com/doc/3.4/validation/groups.html

Если группы не указаны, будут применены все ограничения, принадлежащие группе Default.

Чтобы решить эту проблему, используйте группы проверки, чтобы разделить ограничения объектов и объявить желаемые группы в соответствующих формах.

Пример:

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;
    
    //...
}

Затем, чтобы использовать группу(ы) проверки в нужной форме, в этом случае RegistrationForm вы объявляете нужную группу в AbstractTye::configureOptions как одну из 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']
         ]);
     }

}

Теперь ограничение User::NotBlank будет применяться только к RegistrationForm::isValid() или любой другой форме, объявляющей группу проверки регистрации.

person Will B.    schedule 25.04.2018
comment
нужно было изменить несколько вещей, чтобы они соответствовали моему проекту на 100%, но, по крайней мере, он отлично работает. Спасибо, чувак, я должен тебе огромное огромное пиво :D - person D_Jedrzejewski; 26.04.2018
comment
Но еще одно - как правильно отправить и сохранить его? Форма не попадает в операторы isValid && isSubmitted, поэтому она только локально изменяет сущность корзины, не сохраняя ее в базе данных. - person D_Jedrzejewski; 26.04.2018
comment
Проверьте ошибки формы, чтобы понять, почему она не считается действительной. Я не уверен, какие у вас ограничения на форму или объект. Вам нужно будет создать группы, если ограничения находятся на объекте. Также убедитесь, что у вас есть токен CSRF в вашей форме, если они включены, так как это не позволит форме быть действительной. - person Will B.; 26.04.2018
comment
Он выдает ошибку с полем plainPassword из сущности пользователя, у которой есть утверждение NotBlank. Есть ли способ правильно повиноваться ему? - person D_Jedrzejewski; 26.04.2018
comment
И после удаления этого утверждения все работает нормально, но если бы вы могли помочь мне справиться с этой ошибкой, было бы здорово... Может быть, во время отправки формы? Я не знаю... - person D_Jedrzejewski; 26.04.2018
comment
@D_Jedrzejewski обновил ответ с примером группы проверки. - person Will B.; 26.04.2018