使用 EntityType 显示 select 时,无法从 Symfony 表单保存实体

When using EntityType to show a select, can't save entity from Symfony form

我的 symfony 项目中有两个相关的实体——它们是使用 Doctrine 存储的。一个实体是 "Advertiser",它有一个 ID 和一个名称。导致问题的另一个实体是 "Report",它有一个自己的 ID 以及一个名为 "advertiser_id".

的字段

在 Symfony 表单中为广告商添加报告时,我将 EntityType 用于 advertiser_id 字段,以便我可以显示 select 个广告商。该部分运行良好,但是当我尝试提交表单时,出现错误,因为它将 Advertiser 对象而不是广告商的 ID 传递给 advertiser_id 字段。

这是我的表单生成器中的内容:

$builder
    ->add('advertiser_id', EntityType::class, [
        'class'        => Advertiser::class,
        'query_builder' => $this->advertiserRepository->findAllNotDeletedUnpaginated(),
        'choice_label' => 'name',

    ])
    ->add('submit', SubmitType::class, [
        'label' => 'Submit',
    ])
;

当我提交表单时,出现以下错误: 给定 "integer"、"App\Entity\Advertiser" 类型的预期参数。

关于如何强制 symfony 仅尝试保存被 select 编辑的广告商 ID 而不是传递整个广告商的任何想法?

UPDATE: Now that I've refactored it so that the advertiser is a related entity to report, I'm trying to figure out how to make the advertiser a hidden field and getting nowhere.

我已经用回调转换器尝试了 iiirxs 之前提到的代码 - 将 'advertiser_id' 更改为 'advertiser' - 但我没有成功。我一直在阅读这样的帖子 Symfony hiddenType using data_class for entity instead of transformer,但我无法获得 'advertiser' 的值,就像他们在该示例中获得 $options['selected_course'] 的方式一样。

当我尝试(出于测试目的)将广告商的值硬编码为 1,然后将其放在表单中时,表单会显示,但在提交时出现错误:

    $advertiser=1;

    $builder
        ->add('advertiser', HiddenType::class,['data' => $advertiser, 'data_class' => null])

提交表单时出现的错误是: 给定 "App\Entity\Advertiser or null"、"string" 类型的预期参数。

我很抱歉为此打了一个死马。这似乎是一件 common/easy 要做的事情,但我很难找到如何将其设为隐藏字段的方法。任何想法将不胜感激!

问题是您没有在报告实体中正确定义与广告商实体的关联。您应该定义这样的关联:

// inside Report.php class

/**
 * @ORM\ManyToOne(targetEntity="App\Entity\Advertiser")
 */
private $advertiser;

而不是定义一个包含外键的字段advertiser_id。 Doctrine 足够聪明,可以自行将广告商字段映射到数据库中的 advertiser_id 外键,因此最好使用关联映射。您可以在 documentation.

中找到更多信息

但是,如果出于您自己的原因,您确实必须只使用一个整数来将 advertiser_id 存储为一个整数,您应该使用 Symfony 的数据转换器将广告商实体转换为一个整数,如下所示:

$advertiserRepository = $this->advertiserRepository;
$builder->get('advertiser_id')
        ->addModelTransformer(new CallbackTransformer(
            function ($advertiserAsInteger) use ($advertiserRepository) {
                // transform the integer to an entity object
                return $advertiserRepository->find($advertiserAsInteger);
            },
            function ($advertiserAsEntity) {
                // transform the entity back to an integer
                return $advertiserAsEntity->getId();
            }
        ))
    ;

在我上面的代码中,我使用了 CallbackTransformer 来实现转换,但您也可以使用转换器 class 来实现。您还可以在 data transformers documentation.

中找到更多相关信息

我想用我刚刚根据模型转换器建议实现的自定义类型添加到@iiirxs 的部分答案中。

此类型扩展了内置 EntityType,因此在很多情况下,您可以将 EntityType 替换为 EntityReferenceType 并改为映射 ID。

这在表单不直接映射到实体时很有用,例如使用命令模式,因此您更喜欢标识符。

use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\CallbackTransformer;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\PropertyAccess\PropertyAccess;

class EntityReferenceType extends AbstractType
{
    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'id_property' => 'id'
        ]);
    }

    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder->addModelTransformer(
            new CallbackTransformer(
                function ($entityIdentifier) use ($options) {
                    if ($entityIdentifier === null) {
                        return null;
                    }
                    return $options['em']->getRepository($options['class'])->find($entityIdentifier);
                },
                function ($entity) use ($options) {
                    if ($entity === null) {
                        return null;
                    }
                    return PropertyAccess::createPropertyAccessor()->getValue($entity, $options['id_property']);
                }
            )
        );
    }


    public function getParent(): string
    {
        return EntityType::class;
    }
}