【问题标题】:Symfony 2.7 - multiple nested/embedded FormsSymfony 2.7 - 多个嵌套/嵌入式表单
【发布时间】:2016-12-08 23:56:31
【问题描述】:

如果有人可以帮助我,我将不胜感激。我花了几个小时来解决这个问题,但找不到解决方案。

只要我不需要 PersonType 类中的第二个 AddressType 类,代码就可以正常工作(见下文)。这样的事情不起作用:

// PersonType:
       $builder
            ->add('firstname', 'text')
            ->add('middlename', 'text')
            ->add('lastname', 'text')
            ->add('address', new AddressType())
            ->add('address', new AddressType());

相同类型的多个 FormType 将具有相同的 HTML-ID,因此 Symfony 不会呈现第二个地址。我有几个关于该主题的问题:

  1. 我是否需要 CollectionType,即使我只需要两个地址还是有其他方法(我不需要用户能够动态添加另一个地址)?

  2. 假设我需要 CollectionType。一开始 CollectionType 是空的。但我从一开始就需要两个地址。我在 Symfony-Documentation 中发现以下内容从一开始就创建了一些集合项:Form Collections

    // Controller:
    $task = new Task();
    // dummy code - this is here just so that the Task has some tags
    // otherwise, this isn't an interesting example
    $tag1 = new Tag();
    $tag1->setName('tag1');
    $task->getTags()->add($tag1);
    $tag2 = new Tag();
    $tag2->setName('tag2');
    $task->getTags()->add($tag2);
    

现在,请看看我的控制器。我什至不创建实体。我只创建嵌入了所有其他表单类型的主表单类型。我如何或在哪里可以创建这两个地址?甚至我的整个控制器代码都错了?

  1. 有没有办法告诉 symfony 第一个地址和第二个地址不一样?如何设置每个 AddressType 的名称/ID? (这个solution 已经失效了。)

  2. 在我的控制器中生成表单真的可以吗?我不会将实体或数组传递给表单。它有效,但我想知道为什么....否则我不知道如何将我需要的所有实体传递给表单(合同、人员、地址、客户、付款等)。

提前致谢!

控制器:

class DefaultController extends Controller{
/**
 * @return array
 * @Route("/private")
 * @Template("DmmGenericFormBundle:Default:index.html.twig")
 */
public function indexAction(Request $request)
{
    // This formtype class has all other form type classes
    $privateRentDepositForm = new PrivateRentDepositType();

    $form = $this->createForm($privateRentDepositForm);

    $form->handleRequest($request);

    if($form->isValid()){

        $contract =          $form->get('contract')->getData();
        $privateContractDetails = $form->get('privateContractDetails')->getData();

        $customer =          $form->get('customer')->getData();
        $customerPerson =    $form->get('customer')->get('person')->getData();

        $privateRealEstate =          $form->get('privateRealEstate')->getData();
        $privateRealEstateAddress =   $form->get('privateRealEstate')->get('address')->getData();

        $landlord =          $form->get('privateRealEstate')->get('landlord')->getData();
        $landlordPerson =    $form->get('privateRealEstate')->get('landlord')->get('person')->getData();

        $caretakerPerson =   $form->get('privateRealEstate')->get('landlord')->get('caretaker')->get('person')->getData();

        $payment =           $form->get('payment')->getData();

        $contract->addPerson($customerPerson);
        $contract->addPerson($landlordPerson);
        $contract->addPerson($caretakerPerson);
        $contract->setPrivateContractDetails($privateContractDetails);

        $customer->setPayment($payment);

        $landlord->setPrivateRealEstate($privateRealEstate);

        $privateRealEstate->addCustomer($customer);
        $privateRealEstate->setLandlord($landlord);


        $em = $this->get('doctrine')->getManager();
        $em->persist($contract);
        $em->flush();
    }
    return array('form' => $form->createView());
}

表单类型类:

class PrivateRentDepositType extends AbstractType
{
    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {

        $builder
            ->add('contract', new ContractType())
            ->add('privateContractDetails', new PrivateContractDetailsType())
            ->add('customer', new CustomerType())
            ->add('privateRealEstate', new PrivateRealEstateType())
            ->add('payment', new PaymentType())
            ->add('save', 'submit');
    }

    /**
     * @param OptionsResolverInterface $resolver
     */
    public function configureOptions(OptionsResolver $resolver)
    {  
        $resolver->setDefaults(array(
            'data_class' => null,
            'csrf_protection' => false
        ));   
    }

    /**
     * @return string
     */
    public function getName()
    {
        return 'FormStep';
    }
}

人物类型类:

class PersonType extends AbstractType
{
    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('firstname', 'text')
            ->add('middlename', 'text')
            ->add('lastname', 'text')
            ->add('address', new AddressType());

    }

    /**
     * @param OptionsResolverInterface $resolver
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(
            [
                'data_class' => 'Dmm\Bundle\GenericFormBundle\Entity\Person',
                'config' => null,
            ]
        );
    }

    /**
     * @return string
     */
    public function getName()
    {
        return 'dmm_bundle_genericformbundle_person';
    }
}

地址类型类:

class AddressType extends AbstractType
{

    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('street', 'text')
            ->add('housenumber', 'text')
            ->add('zip', 'text')
            ->add('city', 'text')
            ->add('extra', 'text')
            ->add('country', 'text')
            ->add('postOfficeBox', 'integer')
        ;
    }

    /**
     * @param OptionsResolverInterface $resolver
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(
            [
                'data_class' => 'Dmm\Bundle\GenericFormBundle\Entity\Address',
                'config'     => null,
            ]
        );
    }

    /**
     * @return string
     */
    public function getName()
    {
        return 'dmm_bundle_genericformbundle_address';
    }
}

客户类型类:

class CustomerType extends AbstractType
{
    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('person', new PersonType())
            ->add('birthplace', 'text')
            ->add('email', 'email')
            ->add('phone', 'text');

    }

    /**
     * @param OptionsResolverInterface $resolver
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(
            [
                'data_class' => 'Dmm\Bundle\GenericFormBundle\Entity\Customer',
                'config'     => null,
            ]
        );
    }

    /**
     * @return string
     */
    public function getName()
    {
        return 'dmm_bundle_genericformbundle_customer';
    }
}

数据库关联

Contract:Person = 1:n (Owning Side: Person, ArrayCollection for Person in Contract)

Person:Address = 1:n (Owning Side: Address, ArrayCollection for Address in Person)

Person:Customer = 1:1(拥有方:客户,客户是一个人)

编辑: Cerad 对解决方案 2 的代码编辑 控制器 - 创建所有对象

   $contract = new Contract();
   $contractDetails = new PrivateContractDetails();
   $contract->setPrivateContractDetails($contractDetails);


   $customer1Person = new Person();
   $customer1Address1 = new Address();
   $customer1Address1->setAddressType('someAddress');
   $customer1Address2 = new Address();
   $customer1Address2->setAddressType('someAddress');
   $customer1Person->addAddress($customer1Address1); 
   $customer1Person->addAddress($customer1Address2);
   $contract->addPerson($customer1Person);
   $customer1 = new Customer();
   $customer1->setPerson($customer1Person); 

   $customer2Person = new Person();
   $customer2Address1 = new Address();
   $customer2Address1->setAddressType('someAddress');
   $customer2Address2 = new Address();
   $customer2Address2->setAddressType('someAddress');
   $customer2Person->addAddress($customer2Address1);
   $customer2Person->addAddress($customer2Address2);
   $contract->addPerson($customer2Person);
   $customer2 = new Customer();
   $customer2->setPerson($customer2Person); 


   $landlordPerson = new Person();
   $landlordPersonAddress = new Address();
   $landlordPersonAddress->setAddressType('someAddress');
   $landlordPerson->addAddress($landlordPersonAddress);
   $contract->addPerson($landlordPerson);
   $landlord = new Landlord();
   $landlord->setPerson($landlordPerson);


   $prEstate = new PrivateRealEstate();
   $prEstateAddress = new Address();
   $prEstateAddress->setAddressType('someAddress');
   $prEstate->setAddress($prEstateAddress); 
   $landlord->setPrivateRealEstate($prEstate); 
   $prEstate->addCustomer($customer1); 
   $prEstate->addCustomer($customer2);


   $caretakerPerson = new Person();
   $caretakerAddress = new Address();
   $caretakerAddress->setAddressType('someAddress');
   $caretakerPerson->addAddress($caretakerAddress);
   $contract->addPerson($caretakerPerson);
   $caretaker = new Caretaker();
   $caretaker->addLandlord($landlord);
   $caretaker->setPerson($caretakerPerson);

   $payment = new Payment();
   $customer1->setPayment($payment);

    $privateRentDepositForm = new PrivateRentDepositType();


    $form = $this->createForm($privateRentDepositForm, $contract);

我换了

->add('address', new AddressType());

->add('address', 'collection', array(
    'type' => new AddressType()
))

现在我得到以下异常: 表单的视图数据应为标量、数组或 \ArrayAccess 的实例类型,但它是 Dmm\Bundle\GenericFormBundle\Entity\Contract 类的实例。您可以通过将“data_class”选项设置为“Dmm\Bundle\GenericFormBundle\Entity\Contract”或添加一个视图转换器来将 Dmm\Bundle\GenericFormBundle\Entity\Contract 类的实例转换为标量、数组或\ArrayAccess 的一个实例。

在 ContractType 中,data_class 设置为正确的类。只有在 PrivateRentDepositType 中,data_class 才设置为 null。但是 PrivateRentDepositType 是不同类型的集合。我试图将 data_class 设置为 'Dmm\Bundle\GenericFormBundle\Entity\Contract' 但这会导致另一个异常:属性“contract”和方法之一“getContract()”、“contract()”、“isContract ()"、"hasContract()"、"__get()" 存在并且在类 "Dmm\Bundle\GenericFormBundle\Entity\Contract" 中具有公共访问权限。

【问题讨论】:

    标签: forms symfony nested-forms


    【解决方案1】:

    您可以使用两种基本方法。

    鉴于每个人需要两个且只有两个地址,那么最快的方法是:

       // Form type
       $builder
            ->add('firstname', 'text')
            ->add('middlename', 'text')
            ->add('lastname', 'text')
            ->add('address1', new AddressType(),
            ->add('address2', new AddressType());
    

    将 address1/address2 getter/setter 添加到您的 Person 实体。在内部,Person 仍然可以将地址存储在任何数组中。但就表单类型而言,每个表单都可以单独访问。其他一切都应该工作。

    第二种方法有点 Symfonyish,基本上涉及创建和初始化一个新合约并将其传递给表单。这将消除提交表单后进行大量处理的需要。因此,在您的控制器中,您将拥有:

    $contract = new Contract();
    $customerPerson = new CustomerPersion();
    $contract->addPerson($customerPerson);
    ...
    $privateContractDetails = new PrivateContractDetails()
    ...
    $privateRentDeposit = [
        'contract' => $contract,
        'privateContractDetails' => new $privateContractDetails,
        ... rest of objects ...
    ];
    ...
    $form = $this->createForm($privateRentDepositForm,$privateRentDeposit);
    ---
    if ($form->isValid()) {
        $em->persist($contract);
    

    当然,创建您的合同(也就是聚合根)比所显示的内容要多得多,但您应该明白这一点。我会把它全部移到某个地方的 createContract 工厂方法中。这种方法允许您向您的人员实体添加两个地址,这反过来意味着您可以使用开箱即用的表单集合。

    最后一点:表单在 S3.x 中发生了显着变化。 new FormType() 不再有效。考虑至少更新到 S2.8(其中包含新的表单内容),否则如果您确实迁移到 S3+,您将面临重大的重写。

    【讨论】:

    • 非常感谢!第一个解决方案工作正常。但我对第二个有一个例外。我编辑了我的帖子。如果您能再次帮助我,那就太好了。提前致谢。
    • 看起来 $privateRentDeposit 只是一个附加对象数组。更新了我的答案。我刚刚扫描了您的一些问题编辑。这是一个相当复杂的工作形式。可能会尝试一次添加一个对象,而不是尝试调试整个表单。
    • 谢谢你,塞拉德!你帮了我很多!看来,我的代码需要一些重构。但是将对象数组传递给表单类型是有效的!
    猜你喜欢
    • 1970-01-01
    • 2017-03-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-04
    相关资源
    最近更新 更多