Symfony 空_数据不适用于复合表单,或实体未被实例化(ArgumentCountError:参数太少,无法运行)

Symfony 空_数据不适用于复合表单,或实体未被实例化(ArgumentCountError:参数太少,无法运行),symfony,symfony-forms,Symfony,Symfony Forms,我有一家公司,有许多员工。在我的表单中,我希望用户能够动态添加员工(非常简单)EmployeeType(一个AbstractType)是复合的,包含名字和姓氏。在提交表单时,Symfony似乎没有将表单中的数据传递到“新”员工的构造函数中。我犯了一个错误 ArgumentCountError:函数Employee::_构造()的参数太少。。。0已传入。。。预计会有3个 显示和编辑现有员工的作品,因此我相信我的人际关系等都是正确的 缩写代码: 单位 雇员 公司类型 雇员类型表 公司控制器 上述方法

我有一家
公司
,有许多
员工
。在我的表单中,我希望用户能够动态添加员工(非常简单)
EmployeeType
(一个
AbstractType
)是复合的,包含名字和姓氏。在提交表单时,Symfony似乎没有将表单中的数据传递到“新”员工的构造函数中。我犯了一个错误

ArgumentCountError:函数Employee::_构造()的参数太少。。。0已传入。。。预计会有3个

显示和编辑现有员工的作品,因此我相信我的人际关系等都是正确的

缩写代码:

单位 雇员 公司类型 雇员类型表 公司控制器 上述方法在修改现有员工时有效,但在创建新员工时无效。通过在Symfony代码中进行调试,我可以看到新员工没有数据,因此它试图在
CompanyType
中找到
empty\u data
的闭包或定义。我已经尝试过各种方法(通过
configureOptions
,以及在构建
CompanyType::buildForm
表单时的
empty\u data
选项),例如。我的直觉告诉我,我甚至不需要这样做,因为表单数据不应该是空的(我明确地填写了字段)

我也试着用模型变压器。在这种情况下,表单(传递给
newcallbacktransformer
的第二个函数参数)的转换甚至不会被命中

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

我还有一个
CollectionType
TextType
s,用于
CompanyType
中的其他内容,这些都很好用。因此,这个问题似乎与
EmployeeType
是复合的(包含多个字段)这一事实有关

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

更新:


问题似乎在于Symfony没有可以使用的
Employee
实例。在内部,每个字段都被传递到
Symfony\Component\Form\Form::submit()
。对于现有员工,还传入了一个
Employee
。对于新的,它是
null
。这就解释了为什么它在寻找
空数据
,但我不知道为什么我不能让
空数据
工作。

解决方案是在复合表单中定义
空数据
,而不是
集合类型
表单

我的情况有点奇怪,因为在我的
EmployeeType
中,我还需要
Company
的实例,因为它必须传递给
Employee
的构造函数。我通过将公司作为表单选项传递到
configureOptions
(由控制器提供),然后传递到
entry\u options
,实现了这一点。我不知道这是否是最佳实践,但它是有效的:

公司控制器 确保传入公司实例,以便在构建新的
员工时可以在
EmployeeType
中使用该实例:

$form = $this->createForm(CompanyType::class, $this->company, [
    // This is a form option, valid because it's in CompanyType::configureOptions()
    'company' => $this->company,
]);
公司类型 雇员类型表 在这里,我们要确保
empty_data
正确地从表单数据构建员工

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(),
                );
            },
        ]);
    }
}
中提琴!我现在可以添加新员工了

希望这能帮助其他人

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