Symfony 渲染一个集合表单类型原型

Posted

技术标签:

【中文标题】Symfony 渲染一个集合表单类型原型【英文标题】:Symfony Render a Collection Form Type Prototype 【发布时间】:2021-10-08 14:51:48 【问题描述】:

我尝试使用嵌入集合表单呈现我创建的集合类型原型。 我阅读了 Symonfy、Github 上的文档......但没有成功理解使用块呈现表单的方式。 在这里,我有一个基于 User 实体的表单 (RegistrationFormType),它具有 Adress (AdressFormType) 的嵌入集合表单。 我成功地添加了按钮并在表单上生成了新地址,但我希望我的原型具有与我的第一个地址相同的布局(从数据库中检索)。 你能帮我理解我做错了什么吗?

这是我的 RegistrationFormType

<?php

namespace App\Form;

use App\Entity\User;
//use Doctrine\DBAL\Types\TextType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\TelType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\IsTrue;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Form\CallbackTransformer;


class RegistrationFormType extends AbstractType

    public function buildForm(FormBuilderInterface $builder, array $options)
    
        $builder
            ->add('roles',ChoiceType::class,[
                'choices'=>[
                'Specialist'=>'Role_Specialist',
                'Parent'=>'Role_Parent',
                'Center'=>'Role_Center',],
                'label'=>"Je m'inscris en tant que"])
            ->add('name')
            ->add('firstname',TextType::class, [
                'label' => 'Firstname',
                'row_attr' => [
                    'id' => 'firstname'
                ],])
            ->add('email')
            ->add('phone',TelType::class)
            ->add('NISS',TextType::class, [
                'label' => 'NISS',
                'row_attr' => [
                    'id' => 'NISS'
                ]
                ,
                'required'=>'false',
                ])
            ->add('BCE',TextType::class, [
                'label' => 'BCE',
                'row_attr' => [
                    'id' => 'BCE'
                ],
                'required'=>'false'])
            ->add('agreeTerms', CheckboxType::class, [
                'mapped' => false,
                'constraints' => [
                    new IsTrue([
                        'message' => 'You should agree to our terms.',
                    ]),
                ],
            ])
            ->add('plainPassword', PasswordType::class, [
                // instead of being set onto the object directly,
                // this is read and encoded in the controller
                'mapped' => false,
                'attr' => ['autocomplete' => 'new-password'],
                'constraints' => [
                    new NotBlank([
                        'message' => 'Please enter a password',
                    ]),
                    new Length([
                        'min' => 6,
                        'minMessage' => 'Your password should be at least  limit  characters',
                        // max length allowed by Symfony for security reasons
                        'max' => 4096,
                    ]),
                ],
            ])
            //imbrication de adress dans le formulaire user afin de retrouver toutes les adresses qui lui sont référées
            ->add('adress',CollectionType::class,[
                'entry_type' => AdressType::class,
                'entry_options' => ['label' => false],
                'allow_add' => true,
                'block_name' => 'adress'

            ])
        ;
        // Data transformer
        $builder->get('roles')
            ->addModelTransformer(new CallbackTransformer(
                function ($rolesArray) 
                    // transform the array to a string
                    return count($rolesArray)? $rolesArray[0]: null;
                ,
                function ($rolesString) 
                    // transform the string back to an array
                    return [$rolesString];
                
            ));
    

    public function configureOptions(OptionsResolver $resolver)
    
        $resolver->setDefaults([
            'data_class' => User::class,
        ]);
    

地址类型

<?php

namespace App\Form;

use App\Entity\Adress;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class AdressType extends AbstractType

    public function buildForm(FormBuilderInterface $builder, array $options)
    
        $builder
            ->add('type',ChoiceType::class,[
                'choices' =>[
                    'Adresse Principale' => 'Principale',
                    'Adresse Secondaire' => 'Secondaire'
                    ]
            ])
            ->add('housenumber',TextType::class,[
                'label'=>'N°'
            ])
            ->add('additional_info',TextType::class,[
                'label'=>'Apt/Etage/...',
                'required'=>false
            ])
            ->add('street', TextType::class, [
                'label' => 'Rue',
                'row_attr' => [
                    'id' => 'Adress2'
                ],])
            ->add('postalcode')
            ->add('city')
            ->add('region')
            ->add('country')
            ->add('latitude', HiddenType::class)
            ->add('longitude', HiddenType::class)
//            ->add('users')

        ;
    

    public function configureOptions(OptionsResolver $resolver)
    
        $resolver->setDefaults([
            'data_class' => Adress::class,
        ]);
    

树枝模板

% extends 'base.html.twig' %

% block title %Register% endblock %
% block body %

    % for flashError in app.flashes('verify_email_error') %
        <div class="alert alert-danger" role="alert"> flashError </div>
    % endfor %
    <div class="container">
     form_start(registrationForm) 
    <div class="form-group"> form_row(registrationForm.roles) </div>
    <div class="form-row">
        <div class="form-group col-md-6"> form_row(registrationForm.name) </div>
        <div class="form-group col-md-6"> form_row(registrationForm.firstname) </div>
    </div>
    <div class="form-row">
        <div class="form-group col-md-6"> form_row(registrationForm.email) </div>
        <div class="form-group col-md-6"> form_row(registrationForm.plainPassword, 
                label: 'Password'
            ) </div>
    </div>
    <div class="form-row">
        <div class="form-group col-md-4"> form_row(registrationForm.phone) </div>
        <div class="form-group col-md-4"> form_row(registrationForm.NISS, label: 'NISS') </div>
        <div class="form-group col-md-4"> form_row(registrationForm.BCE, label: 'BCE') </div>
    </div>
#        @TODO formatter pour qu'on differencie bien les adresses entre elles par block. Cf block en dessous#
        <h3>Adresses</h3>
        % block _registration_form_adress_entry_widget %
        <div class="adress" data-prototype=" form_widget(registrationForm.adress.vars.prototype)|e('html_attr') ">
            % for adress in registrationForm.adress %
            <div class="form-group col-md-4"> form_row(adress.type) </div>
        <div class="form-row">
                <div class="form-group col-md-4"> form_row(adress.street) </div>
                <div class="form-group col-md-4"> form_row(adress.housenumber) </div>
                <div class="form-group col-md-4"> form_row(adress.additional_info) </div>
        </div>
        <div class="form-row">
            <div class="form-group col-md-3"> form_row(adress.postalcode) </div>
            <div class="form-group col-md-3"> form_row(adress.city) </div>
            <div class="form-group col-md-3"> form_row(adress.region) </div>
            <div class="form-group col-md-3"> form_row(adress.country) </div>
        </div>
            % endfor %
        </div>

        <button type="button" class="add_item_link" data-collection-holder-class="adress">Add an adress</button>



         form_end(registrationForm) 
        % endblock %
    </div>
    <script>
        const addFormToCollection = (e) => 
            const collectionHolder = document.querySelector('.' + e.currentTarget.dataset.collectionHolderClass);

            const item = document.createElement('li');

            item.innerHTML = collectionHolder
                .dataset
                .prototype
                .replace(
                    /__name__/g,
                    collectionHolder.dataset.index
                );

            collectionHolder.appendChild(item);

            collectionHolder.dataset.index++;
        ;
        document
            .querySelectorAll('.add_item_link')
            .forEach(btn => btn.addEventListener("click", addFormToCollection));

    </script>
% endblock %
#% block registration_form_adress_entry_row %#
#    <div class="form-row">#
#        <div class="form-group col-md-4"> form_row(adress.street) </div>#
#            <div class="form-group col-md-4"> form_row(adress.housenumber) </div>#
#            <div class="form-group col-md-4"> form_row(adress.additional_info) </div>#
#        </div>#
#        <div class="form-row">#
#            <div class="form-group col-md-3"> form_row(adress.postalcode) </div>#
#            <div class="form-group col-md-3"> form_row(adress.city) </div>#
#            <div class="form-group col-md-3"> form_row(adress.region) </div>#
#            <div class="form-group col-md-3"> form_row(adress.country) </div>#
#    </div>#
#% endblock %#

代码渲染

谢谢你:)

【问题讨论】:

【参考方案1】:

您可以定义一个宏来呈现表单的现有子字段和原型字符串。

% import _self as formMacros %
% macro address(item) %
... your code formatting the address subform ... 
% endmacro %

所以树枝中的这一行:

<div class="adress" data-prototype=" form_widget(registrationForm.adress.vars.prototype)|e('html_attr') ">

<div class="adress" data-prototype=" formMacros.address(registrationForm.adress.vars.prototype)|e('html_attr') ">

现有字段也是如此。

【讨论】:

以上是关于Symfony 渲染一个集合表单类型原型的主要内容,如果未能解决你的问题,请参考以下文章

Symfony 表单 - 内部带有复选框的集合类型

带有文件类型字段editAction的Symfony 3表单集合实体

如何处理包含 500 多个项目的 Symfony 表单集合

Symfony 集合表单控制器问题

从 Symfony2 中的控制器访问集合表单字段

枚举和 Symfony 表单的教义集合