empty_data not working for compound forms, or entity is not being instantiated (ArgumentCountError: too few arguments to function)

empty_data not working for compound forms, or entity is not being instantiated (ArgumentCountError: too few arguments to function)

我有一个 Company 有很多 Employee。在我的表单中,我希望用户能够动态添加员工(足够简单)。 EmployeeTypeAbstractType)是复合词,包含名字和姓氏。在提交表单时,Symfony 似乎没有将表单中的数据转移到“新”员工的构造函数中。我得到一个错误

ArgumentCountError: Too few arguments to function Employee::__construct() ... 0 passed in ... and exactly 3 expected

显示和编辑现有员工有效,所以我确信我的关系等都是正确的。

缩写代码:

公司

class Company
{
    protected $employees;

    public function __construct()
    {
        $this->employees = new ArrayCollection();
    }

    public function addEmployee(Employee $employee)
    {
        if ($this->employees->contains($employee)) {
            return;
        }
        $this->employees->add($employee);
    }

    public function removeEmployee(Employee $employee)
    {
        if (!$this->employees->contains($employee)) {
            return;
        }
        $this->employees->removeElement($employee);
    }
}

员工

class Employee
{
    // ... firstName and lastName properties...

    public function __construct(Company $company, $firstName, $lastName)
    {
        $this->company = $company;
        $this->company->addEmployee($this);
    }

    // ...getter and setter for firstName / lastName...
}

公司类型

class CompanyType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('employees', CollectionType::class, [
            'entry_type' => EmployeeType::class,
            'allow_add' => true,
            'allow_delete' => false,
            'required' => false,
        ]);
        // ...other fields, some are CollectionType of TextTypes that work correctly...
    }
}

员工类型

class EmployeeType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('firstName')
            ->add('lastName');
    }

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

公司控制人

class CompanyController
{
    // Never mind that this is a show and not edit, etc.
    public function showAction()
    {
        // Assume $this->company is a new or existing Company
        $form = $this->createForm(CompanyType::class, $this->company);

        $form->handleRequest($this->request);

        if ($form->isSubmitted() && $form->isValid()) {
            $company = $form->getData();

            $entityManager = $this->getDoctrine()->getManager();
            $entityManager->persist($company);
            $entityManager->flush();
        }

        // set flash message, redirect, etc.
    }

    // ...render view...
}

以上内容在修改现有员工时有效,但在创建新员工时无效。从 Symfony 代码中调试,我可以看到新员工的数据不存在,因此它试图在 CompanyType 中找到 empty_data 的闭包或定义。我已经尝试了各种方法(在构建 CompanyType::buildForm 表单时通过 configureOptionsempty_data 选项),例如https://symfony.com/doc/current/form/use_empty_data.html。我的直觉告诉我,我什至不需要这样做,因为表单数据不应该为空(我明确填写了这些字段)。

我也尝试过使用模型转换器。在这种情况下,甚至不会命中来自表单的转换(传递给 new CallbackTransformer 的第二个函数参数)。

视图在添加新员工字段时正确设置名称属性,例如form[employees][1][firstName],等等。这不是问题所在。它还将正确的数据发送到控制器。我通过 CompanyType::onPreSubmit(使用事件侦听器)检查表单提交数据来确认这一点。

我还有一个 CollectionTypeTextType 用于 CompanyType 中的其他东西,这些工作正常。所以这个问题似乎与 EmployeeType 是复合的(包含多个字段)有关。

希望以上足以说明问题。有什么想法吗?

更新:

似乎问题是没有 Employee 的实例化供 Symfony 使用。在内部,每个字段都传递给 Symfony\Component\Form\Form::submit()。对于现有员工,还有一个 Employee 传入。对于新员工,它是 null。这解释了为什么它正在寻找 empty_data,但我不知道为什么我无法让 empty_data 工作。

解决方案是以复合形式而不是CollectionType形式定义empty_data

我的情况有点奇怪,因为我的 EmployeeType 中还需要 Company 的实例,因为它必须传递给 Employee 的构造函数。我通过将 Company 作为表单选项传递到 configureOptions(由控制器提供),然后传递到 entry_options 来完成此操作。我不知道这是否是最佳做法,但它确实有效:

公司控制人

确保我们传入 Company 实例,以便在构建新 Employee 时可以在 EmployeeType 中使用它:

$form = $this->createForm(CompanyType::class, $this->company, [
    // This is a form option, valid because it's in CompanyType::configureOptions()
    'company' => $this->company,
]);

公司类型

class CompanyType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('employees', CollectionType::class, [
            // ...

            // Pass the Company instance to the EmployeeType.
            'entry_options' => [ 'company' => $options['company'] ],

            // This was also needed, apparently.
            'by_reference' => false,
        ]);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            // Allows the controller to pass in a Company instance.
            'company' => null,
        ]);
    }
}

员工类型

这里我们确保 empty_data 从表单数据正确构建一个 Employee。

class EmployeeType extends AbstractType
{
    private $company;

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('firstName')
            ->add('lastName');

        // A little weird to set a class property here, but we need the Company
        // instance in the 'empty_data' definition in configureOptions(),
        // at which point we otherwise wouldn't have access to the Company.
        $this->company = $options['company'];
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => Employee::class,
            'empty_data' => function (FormInterface $form) use ($resolver) {
                return new Employee(
                    $this->company,
                    $form->get('firstName')->getData(),
                    $form->get('lastName')->getData(),
                );
            },
        ]);
    }
}

中提琴!我现在可以添加新员工了。

希望这对其他人有帮助!