Symfony 形成与外国实体的隐藏关系

Symfony form hidden relation to foreign entity

我正在尝试在 symfony 3 中创建一个表单。它有一个隐藏字段用于与另一个实体的关系。它被定义为

$builder->add('course', HiddenType::class, array('property_path' => 'course.id'))

当我提交表单时这样做,我得到一个错误,因为 id 属性 不可见并且显然没有 setter。

Could not determine access type for property "id".

我该怎么办?为 id 添加 setter 可能不是一个好主意。默认情况下 none 是有原因的。除 id 之外的其他字段不是唯一的,因此我不能使用其中任何一个。

我想一个选项可能是自定义类型,它在数据库中查询引用实体。但是对于这个相当标准的用例有更简单的方法吗?

编辑

好吧,显然我的问题被误解了。所以我会解释得更好。这与教义无关。我的数据模型很好,该字段是一个适当的关系,而不是别的东西。问题仅与 Symfony Form 组件有关,特别是 HiddenField 似乎默认无法处理关系。首先,它会在显示表单 Cannot serialize type Model to string 时导致错误,我可以使用用于显示表单的 property_path 指令来解决。但是一旦提交,它就无法创建相关实体,因为它无法设置相关学说实体的 id 属性(参见 property_path)。

一个有效的解决方案是使用 DataTransformerInterface class 而不是 属性 路径。但是必须为每个需要的实体实施一个。所以我想知道是否有更简单的解决方案,因为在我看来它是一个非常标准的用例。

这是我所做的:

我在现场使用了 Symfony\Component\Form\DataTransformerInterface 来序列化模型,然后在表单传输时通过学说从数据库中再次加载它。可能不是每个人在任何情况下都想要的,也意味着表单需要实体管理器作为一个选项。

形式

/**
 * Some form
 */
class SomeType extends AbstractType
{
    // Configure 
    public function configureOptions(OptionsResolver $resolver) {
        [...]
        $resolver->setRequired('entity_manager');
    }

    public function buildForm(FormBuilderInterface $builder, array $options) {
        $builder->add('course', HiddenType::class);

        [...]

        $builder->get('course')
           ->addModelTransformer(new CourseTransformer($options['entity_manager']));
    }
}

这是 CourseTransformer

class CourseTransformer implements DataTransformerInterface
{
    // The object manager
    private $manager;

    //Constructor
    public function __construct(ObjectManager $manager) {
        $this->manager = $manager;
    }

    /**
     * Transforms an object (course) to a string (id).
     */
    public function transform($course)
    {
        if (null === $course) return '';
        return $course->getId();
    }

    /**
     * Transforms a string (id) to an object (course).
     */
    public function reverseTransform($courseId)
    {
        // no course id? It's optional, so that's ok
        if (!$courseId) {
            return;
        }

        $course = $this->manager
            ->getRepository('MyBundle:Course')
            // query for the issue with this id
            ->find($courseId)
        ;

        if (null === $course) {
            // causes a validation error
            // this message is not shown to the user
            throw new TransformationFailedException(sprintf(
                'An course with id "%s" does not exist!',
                $courseId
            ));
        }

        return $course;
    }
}

Symfony 4 方法

您需要为您的实体创建一个 DataTransformer class,此 class 将告诉 Symfony 在将实体呈现为字符串(例如作为隐藏输入)时如何序列化您的实体。

我是这样做的:

首先,我在 "Form" 文件夹中创建了一个文件夹 "DataTransformer",并在其中创建了我的实体 DataTransformer class。

您的 class 必须实现 DataTransformerInterface 和两个方法:transform 和 reverseTransform。您可以在接口 class.

的 PHPDoc 中阅读有关这些方法的更多信息

这是 DataTransformer 的工作示例 class。

<?php


namespace App\Form\DataTransformer;


use App\Repository\CompetitionRepository;
use Symfony\Component\Form\DataTransformerInterface;

class CompetitionTransformer implements DataTransformerInterface
{

    private $repository;

    public function __construct(CompetitionRepository $repository) {
        $this->repository = $repository;
    }

    /**
     * {@inheritdoc}
     */
    public function transform($competition): ?int
    {
        return ( $competition !== null ) ? $competition->getId() : null;
    }

    /**
     * {@inheritdoc}
     */
    public function reverseTransform($competitionId)
    {

        if ('' === $competitionId || null === $competitionId) {
            return '';
        }

        $competition = $this->repository->find( $competitionId );
        return $competition;
    }
}

您将在表单中使用 DataTransformer class。

class EventType extends AbstractType
{
    [...]
    private $competitionRepository;

    public function __construct(CompetitionRepository $competitionRepository)
    {
        $this->competitionRepository = $competitionRepository;
    }

    [...]

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        [...]
        // This is how you use your DataTransformer class
        $builder->add('competition', HiddenType::class);
        $builder->get('competition')->addModelTransformer(new CompetitionTransformer( $this->competitionRepository ));
    }

    [...]
}