Symfony 3.3 как добавить ограничения в collectionType для двух вложенных

У меня есть форма symfony 3.3 с двумя уровнями коллекции типов вложенности. как отследить проверку полей со второго уровня до первого. как отследить валидацию последнего уровня вложенности, то есть поля $ amount в PricingMissionTypeEquipmentType. only @Assert\valid() не работает. Вот мой код

BillingSetting

namespace AppBundle\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * BillingSetting.
 *
 * @ORM\Table(name = "billing_setting")
 * @ORM\Entity(repositoryClass = "AppBundle\Repository\BillingSettingRepository")
 */
class BillingSetting
{
    /**
     * @var int
     *
     * @ORM\Column(name = "id", type = "integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy = "AUTO")
     */
    private $id;

    /**
     * @ORM\OneToOne(targetEntity = "Pricing")
     * @ORM\JoinColumn(name = "pricing_id", referencedColumnName = "id")
     * @Assert\Valid()
     */
    private $pricing;

    /**
     * @ORM\OneToMany(targetEntity = "PricingMissionType")
     *@Assert\Valid()
     */
    private $pricingMissionTypes;

    /**
     * Constructor.
     */
    public function __construct()
    {
        $this->pricingMissionTypes = new ArrayCollection();
    }

}

Цена

use AppBundle\Entity\Traits\MigratedAtTrait;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * PricingMissionType.
 *
 * @ORM\Table(name = "pricing_mission_type")
 * @ORM\Entity(repositoryClass = "AppBundle\Repository\PricingMissionTypeRepository")
 */
class PricingMissionType
{
    /**
     * @var int
     *
     * @ORM\Column(type = "integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy = "AUTO")
     */
    private $id;

    /**
     * @ORM\ManyToOne(targetEntity = "Pricing")
     */
    private $pricing;

    /**
     * @ORM\ManyToOne(targetEntity = "MissionType")
     */
    private $missionType;

    /**
     * @ORM\OneToMany(targetEntity = "PricingMissionTypeEquipmentType")
     * @Assert\Valid()
     */
    private $pricingMissionTypeEquipmentTypes;

    /**
     * @var float
     *
     * @ORM\Column(type = "float")
     * @Assert\NotBlank()
     */
    private $displacementCost;

    /**
     * @ORM\ManyToOne(targetEntity = "BillingSetting")
     */
    private $billingSetting;

    /**
     * Constructor.
     */
    public function __construct()
    {
        $this->pricingMissionTypeEquipmentType = new ArrayCollection();
    }

Цена

    <?php

namespace AppBundle\Entity;

use AppBundle\Entity\Traits\MigratedAtTrait;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * PricingMissionTypeEquipmentType.
 * @ORM\Entity(repositoryClass = "AppBundle\Repository\PricingMissionTypeEquipmentTypeRepository")
 */
class PricingMissionTypeEquipmentType
{
    /**
     * @var int
     *
     * @ORM\Column(type = "integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy = "AUTO")
     */
    private $id;

    /**
     * @var int
     *
     * @ORM\Column(type = "integer", nullable=true)
     */
    private $sqlServerTarifClientId;

    /**
     * @ORM\ManyToOne(targetEntity = "PricingMissionType")
     */
    private $pricingMissionType;

    /**
     * @var float
     *
     * @ORM\Column(type = "float")
     * @Assert\NotBlank()
     * @Assert\GreaterThan(0)
     */
    private $amount;

    /**
     * @ORM\ManyToOne(targetEntity = "EquipementType")
     */
    private $equipementType;

BillingSettingType

    <?php

namespace AppBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class BillingSettingType extends AbstractType
{
    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('pricing', PricingType::class)
        ->add('pricingMissionTypes', CollectionType::class,
            [
                'entry_type' => PricingMissionTypeType::class,
            ])
        ;
    }

    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => 'AppBundle\Entity\BillingSetting',
        ]);
    }

    /**
     * {@inheritdoc}
     */
    public function getBlockPrefix()
    {
        return 'appbundle_billingsetting';
    }
}

PricingMissionTypeType

<?php

namespace AppBundle\Form;

use AppBundle\Entity\PricingMissionTypeEquipmentType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class PricingMissionTypeType extends AbstractType
{
    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('displacementCost', IntegerType::class, [
            'label' => 'billing_settings.displacement_cost',
            'error_bubbling' => true,
             'scale' => 2,
             'required' => true,
            'attr' => [
                'min' => 0,
                'step' => 0.1,
            ],
        ])
            ->add('pricingMissionTypeEquipmentType', CollectionType::class,
                [
                    'entry_type' => PricingMissionTypeEquipmentTypeType::class,
                    'error_bubbling' => true,
                    'entry_options' => [
                        'label' => false,
                    ],
                ]);
    }

    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => 'AppBundle\Entity\PricingMissionType',
        ]);
    }

    /**
     * {@inheritdoc}
     */
    public function getBlockPrefix()
    {
        return 'appbundle_pricingmissiontype';
    }
}

ЦенаMissionTypeEquipmentTypeType

    <?php

namespace AppBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class PricingMissionTypeEquipmentTypeType extends AbstractType
{
    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('amount', IntegerType::class,
            [
                'label' => '(€)',
                'required' => true,
                'scale' => 3,
                'attr' => [
                    'min' => 0,
                    'step' => 0.1,
                ],
            ]);
    }

    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => 'AppBundle\Entity\PricingMissionTypeEquipmentType',
        ]);
    }

    /**
     * {@inheritdoc}
     */
    public function getBlockPrefix()
    {
        return 'appbundle_pricingmissiontypeequipmenttype';
    }
}

В моем контроллере

    /**
 * @Route("/add", name = "billing_settings_add")
 */
public function buildBillingSettingForm(Request $request)
{
    $form = $this->createForm(BillingSettingType::class, $this->addFormFields());
    $form->handleRequest($request);
    if ($form->isSubmitted() and $form->isValid()) {
        $em = $this->getDoctrine()->getManager();
        /** @var BillingSetting $billingSetting */
        $billinSetting = $form->getData();
        foreach ($billinSetting->getPricingMissionTypes() as $pricingMissionType) {
            /* @var PricingMissionType $pricingMissionType */
            $pricingMissionType->setBillingSetting($billinSetting);
            $pricingMissionType->setPricing($billinSetting->getPricing());
            $em->persist($pricingMissionType);
            foreach ($pricingMissionType->getPricingMissionTypeEquipmentType() as $pricingMissionTypeEquipmentType) {
                /* @var PricingMissionTypeEquipmentType $pricingMissionTypeEquipmentTypes */
                $pricingMissionTypeEquipmentType->setPricingMissionType($pricingMissionType);
                $em->persist($pricingMissionTypeEquipmentType);
            }
            $em->persist($form->getData());
            $em->flush();
            $message = $this->get('translator')->trans('billing_settings.success.add_billing_setting');
            $this->get('session')->getFlashBag()->set('success', $message);

            return $this->redirectToRoute('billing_setting_list');
        }
    }

    return $this->render(
        '@App/page/billingSettings/edit.html.twig',
        [
            'form' => $form->createView(),
            'missionTypeLabels' => $this->getLabels()['missionTypeLabels'],
            'pricingMissionTypeEquipmentTypeLabels' => $this->getLabels()['pricingMissionTypeEquipmentTypeLabels'],
        ]
    );
}

/**
 * @return BillingSetting
 */
public function addFormFields()
{
    $billingSetting = new BillingSetting();
    $missionTypes = $this->getDoctrine()->getRepository(MissionType::class)->findAll();
    $equipementTypes = $this->getDoctrine()->getRepository(EquipementType::class)->findAll();
    foreach ($missionTypes as $missionType) {
        $pricingMissionType = new PricingMissionType();
        $pricingMissionType->setMissionType($missionType);
        foreach ($equipementTypes as $equipementType) {
            $pricingMissionTypeEquipmentType = new PricingMissionTypeEquipmentType();
            $pricingMissionTypeEquipmentType->setEquipementType($equipementType);
            $pricingMissionType->addPricingMissionTypeEquipmentType($pricingMissionTypeEquipmentType);
        }
        $pricingMissionType->setBillingSetting($billingSetting);
        $billingSetting->addPricingMissionType($pricingMissionType);
    }

    return $billingSetting;
}

/**
 * @return array
 */
private function getLabels()
{
    $missionTypes = $this->getDoctrine()->getRepository(MissionType::class)->findAll();
    $EquipementTypes = $this->getDoctrine()->getRepository(EquipementType::class)->findAll();
    $missionTypeLabels = [];
    $pricingMissionTypeEquipmentTypeLabels = [];
    foreach ($missionTypes as $missionType) {
        $missionTypeLabels[] = $missionType->getName();
    }
    foreach ($EquipementTypes as $EquipementType) {
        $pricingMissionTypeEquipmentTypeLabels[] = $EquipementType->getCode();
    }

    return [
        'pricingMissionTypeEquipmentTypeLabels' => $pricingMissionTypeEquipmentTypeLabels,
        'missionTypeLabels' => $missionTypeLabels,
        ];
}

Проверка:

 /**
 * @Route("/edit/{id}", name = "billing_setting_edit")
 */
public function editAction(Request $request, BillingSetting $billinSetting)
{
    $entityManager = $this->getDoctrine()->getManager();
    $oldName = $billinSetting->getPricing()->getName();
    $oldMinPrice = $billinSetting->getPricing()->getMinPrice();
    $oldUnnecessaryDisplacements = $billinSetting->getPricing()->getUnnecessaryDisplacements();
    $oldDiscountByEquipement = $billinSetting->getPricing()->getDiscountByEquipement();
    $oldMaxEquipmentDiscount = $billinSetting->getPricing()->getMaxEquipmentDiscount();

    $editForm = $this->createForm(BillingSettingType::class, $billinSetting);
    $editForm->handleRequest($request);

    if ($editForm->isValid()) {
        $entityManager->flush();
        $message = $this->get('translator')->trans('billing_settings.update.update_billing_setting');
        $this->get('session')->getFlashBag()->set('success', $message);
        // Force doctrine to refresh object
        $entityManager->refresh($billinSetting);
        // For updating Elasticsearch data
        ...

        return $this->redirectToRoute('billing_setting_list');
    }

    return $this->render(
        '@App/page/billingSettings/edit.html.twig',
        [
            'billingSetting' => $billinSetting,
            'form' => $editForm->createView(),
            'missionTypeLabels' => $this->getLabels()['missionTypeLabels'],
            'pricingMissionTypeEquipmentTypeLabels' => $this->getLabels()['pricingMissionTypeEquipmentTypeLabels'],
        ]
    );
}

Как именно вы применяете валидацию?

lxg 05.07.2018 08:33

lxg - у меня есть проверка поля суммы, расположенного на последнем уровне объекта PricingMissionTypeEquipementType, я хочу, чтобы он применялся к этому объекту без обязательной повторной сборки родительского объекта BillingSetting. что он действительно показывает мне ошибку в этом поле там сумма.

Moccine 05.07.2018 11:45

Можете ли вы опубликовать точный код, в котором вы запускаете валидатор?

lxg 05.07.2018 12:31

Я обновил свой вопрос выше, код построения моей формы и ее проверки

Moccine 05.07.2018 13:34

Хорошо спасибо. Итак, вы используете валидатор внутри компонента формы. Ха, странно ... похоже, это должно сделать глубокую проверку. Но, видимо, это не так. :(

lxg 05.07.2018 14:05
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Валидация полей ввода для базовой формы React
Валидация полей ввода для базовой формы React
В одном из моих проектов MERN Stack есть форма с именем, фамилией, контактным номером, адресом, электронной почтой, датой рождения, номером NIC, весом...
Пользовательские правила валидации в Laravel
Пользовательские правила валидации в Laravel
Если вы хотите создать свое собственное правило валидации, Laravel предоставляет возможность сделать это. Создайте правило с помощью следующей...
0
5
312
0

Другие вопросы по теме