Forms Symfony表格与外部实体的隐藏关系

Forms Symfony表格与外部实体的隐藏关系,forms,symfony,symfony-3.1,Forms,Symfony,Symfony 3.1,我正在尝试在symfony3中创建一个表单。它有一个隐藏字段,用于表示与另一个实体的关系。它被定义为 $builder->add('course', HiddenType::class, array('property_path' => 'course.id')) 当我提交表单时,会出现一个错误,因为id属性不可见,而且显然没有setter Could not determine access type for property "id". 我该怎么办?为id添加setter可能

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

$builder->add('course', HiddenType::class, array('property_path' => 'course.id'))
当我提交表单时,会出现一个错误,因为id属性不可见,而且显然没有setter

Could not determine access type for property "id".
我该怎么办?为id添加setter可能不是一个好主意。默认情况下没有的原因是存在的。id以外的其他字段不是唯一的,因此我不能使用其中任何字段

我猜其中一个选项可能是自定义类型,它在数据库中查询引用实体。但是对于这个非常标准的用例,有没有更简单的方法呢

编辑

很明显我的问题被误解了。所以我会解释得更好。这与教义无关。我的数据模型很好,字段是正确的关系,而不是其他关系。问题只与Symfony表单组件有关,特别是HiddenField在默认情况下似乎无法处理关系。首先,它会在显示表单时导致一个错误,无法将类型模型序列化为字符串,我可以使用用于显示表单的property_path指令来解决这个问题。但一旦提交,它就无法创建相关实体,因为它无法设置id属性,请查看相关实体的属性路径

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

以下是我所做的:

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

在形式上

/**
 * 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']));
    }
}
这是课程转换器

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\Component\Form\DataTransformerInterface来序列化模型,然后在表单传输时通过doctrine从数据库再次加载它。可能不是每个人在任何情况下都想要的,这也意味着表单需要实体管理器作为选项

在形式上

/**
 * 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']));
    }
}
这是课程转换器

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类,该类将告诉Symfony在将实体呈现为字符串(例如作为隐藏输入)时如何序列化实体

我就是这样做的:

首先,我在表单文件夹中创建一个文件夹DataTransformer,并在其中创建实体DataTransformer类

类必须实现DataTransformerInterface和两个方法:transform和reverseTransform。您可以在接口类的PHPDoc中阅读有关这些方法的更多信息

这是DataTransformer类的一个工作示例

<?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;
    }
}
Symfony 4进近

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

我就是这样做的:

首先,我在表单文件夹中创建一个文件夹DataTransformer,并在其中创建实体DataTransformer类

类必须实现DataTransformerInterface和两个方法:transform和reverseTransform。您可以在接口类的PHPDoc中阅读有关这些方法的更多信息

这是DataTransformer类的一个工作示例

<?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;
    }
}

可能重复使用属性路径的原因?字段“course”应该映射到其他实体-然后隐藏字段中的值将是该外部实体的ID。@AndrzejPiszczek可能这就是我在文档中遗漏的内容。有这样的选择吗?目前我正在使用一个自定义transformerread。这里有关于实体连接的信息。好吧,那么你误解我了。理论联系很好,并且正在发挥作用。我只是不知道表单组件是如何工作的,顺便说一句,但我想知道是否有一个更简单的解决方案可能重复您为什么要使用property_path?字段“course”应该映射到其他实体-然后隐藏字段中的值将是该外部实体的ID。@AndrzejPiszczek可能这就是我在文档中遗漏的内容。有这样的选择吗?目前我正在使用一个自定义transformerread。这里有关于实体连接的信息。好吧,那么你误解我了。理论联系很好,并且正在发挥作用。我只是不知道表单组件是如何工作的,顺便说一句,但我想知道是否有一个更简单的解决方案