symfony :我们不能有一个隐藏的实体字段吗?

2022-08-30 19:42:40

我正在以symfony格式呈现一个带有实体字段的表单。

当我选择常规实体字段时,它工作得很好。

$builder
    ->add('parent','entity',array(
            'class' => 'AppBundle:FoodAnalytics\Recipe',
            'attr' => array(
                'class' => 'hidden'
            )
        ))

当我选择->add('parent','hidden'))时,它会抛出以下错误:

窗体的视图数据应为标量、数组或 \ArrayAccess 的实例类型,但应是类 AppBundle\Entity\FoodAnalytics\Recipe 的实例。通过将“data_class”选项设置为“AppBundle\Entity\FoodAnalytics\Recipe”,或者添加一个视图转换器,将类 AppBundle\Entity\FoodAnalytics\Recipe 的实例转换为标量、数组或 \ArrayAccess 的实例,可以避免此错误。500 内部服务器错误 - 逻辑异常

我们不能有隐藏的实体字段吗?为什么不呢?我是否必须放置另一个隐藏字段来检索实体 ID?

编辑:

基本上,我试图做的是在显示表单之前对其进行水合,但阻止用户更改其字段之一(此处的父项)。这是因为我需要将 Id 作为参数传递,而我无法在表单操作 url 中执行此操作。


答案 1

我认为您只是对字段类型以及它们各自代表的内容感到困惑。

字段是一种字段类型。选项字段旨在包含用户在表单中选择的值。呈现此表单时,Symfony 将根据实体字段的基础类生成可能选择的列表,列表中每个选项的值是相应实体的 id。提交表单后,Symfony 将为您水合一个表示所选实体的对象。该字段通常用于呈现实体关联(例如,您可以选择分配给某个列表)。entitychoiceentityrolesuser

如果您只是尝试为实体的 ID 字段创建占位符,则可以使用输入。但这仅在您正在创建的表单类表示实体(即表单的引用您定义的实体)时才有效。然后,ID 字段将正确映射到表单的 类型定义的实体的 ID。hiddendata_classdata_class

编辑:下面描述的特定情况的一种解决方案是创建一个新的字段类型(我们称之为EntityHidden),该类型扩展了字段类型,但处理了与实体/id之间的转换。这样,您的表单将包含实体 ID 作为隐藏字段,但提交表单后,应用程序将有权访问实体本身。当然,转换是由数据转换器执行的。hidden

以下是此类实现的示例,供后代使用:

namespace My\Bundle\Form\Extension\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\DataTransformerInterface;

/**
 * Entity hidden custom type class definition
 */
class EntityHiddenType extends AbstractType
{
    /**
     * @var DataTransformerInterface $transformer
     */
     private $transformer;

    /**
     * Constructor
     *
     * @param DataTransformerInterface $transformer
     */
    public function __construct(DataTransformerInterface $transformer)
    {
        $this->transformer = $transformer;
    }

    /**
     * @inheritDoc
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        // attach the specified model transformer for this entity list field
        // this will convert data between object and string formats
        $builder->addModelTransformer($this->transformer);
    }

    /**
     * @inheritDoc
     */
    public function getParent()
    {
        return 'hidden';
    }

    /**
     * @inheritDoc
     */
    public function getName()
    {
        return 'entityhidden';
    }
}

请注意,在表单类型类中,您所要做的就是将隐藏的实体分配给其相应的表单字段属性(在表单模型/数据类中),Symfony将正确生成隐藏的输入HTML,并将实体的ID作为其值。希望有所帮助。


答案 2

刚刚在Symfony 3上做了这个,并意识到它与已经在这里发布的内容有点不同,所以我认为它值得分享。

我刚刚制作了一个通用数据转换器,可以在所有表单类型中轻松重用。您只需要传递表单类型即可。无需创建自定义表单类型。

首先,让我们看一下数据转换器:

<?php

namespace AppBundle\Form;

use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;

/**
 * Class EntityHiddenTransformer
 *
 * @package AppBundle\Form
 * @author  Francesco Casula <fra.casula@gmail.com>
 */
class EntityHiddenTransformer implements DataTransformerInterface
{
    /**
     * @var ObjectManager
     */
    private $objectManager;

    /**
     * @var string
     */
    private $className;

    /**
     * @var string
     */
    private $primaryKey;

    /**
     * EntityHiddenType constructor.
     *
     * @param ObjectManager $objectManager
     * @param string        $className
     * @param string        $primaryKey
     */
    public function __construct(ObjectManager $objectManager, $className, $primaryKey)
    {
        $this->objectManager = $objectManager;
        $this->className = $className;
        $this->primaryKey = $primaryKey;
    }

    /**
     * @return ObjectManager
     */
    public function getObjectManager()
    {
        return $this->objectManager;
    }

    /**
     * @return string
     */
    public function getClassName()
    {
        return $this->className;
    }

    /**
     * @return string
     */
    public function getPrimaryKey()
    {
        return $this->primaryKey;
    }

    /**
     * Transforms an object (entity) to a string (number).
     *
     * @param  object|null $entity
     *
     * @return string
     */
    public function transform($entity)
    {
        if (null === $entity) {
            return '';
        }

        $method = 'get' . ucfirst($this->getPrimaryKey());

        // Probably worth throwing an exception if the method doesn't exist
        // Note: you can always use reflection to get the PK even though there's no public getter for it

        return $entity->$method();
    }

    /**
     * Transforms a string (number) to an object (entity).
     *
     * @param  string $identifier
     *
     * @return object|null
     * @throws TransformationFailedException if object (entity) is not found.
     */
    public function reverseTransform($identifier)
    {
        if (!$identifier) {
            return null;
        }

        $entity = $this->getObjectManager()
            ->getRepository($this->getClassName())
            ->find($identifier);

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

        return $entity;
    }
}

因此,我们的想法是,您通过传递对象管理器(要使用的实体,然后传递字段名称来获取实体 ID)来调用它。

基本上是这样的:

new EntityHiddenTransformer(
    $this->getObjectManager(),
    Article::class, // in your case this would be FoodAnalytics\Recipe::class
    'articleId' // I guess this for you would be recipeId?
)

现在让我们把它们放在一起。我们只需要表单类型和一些YAML配置,然后我们就可以开始了。

<?php

namespace AppBundle\Form;

use AppBundle\Entity\Article;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\OptionsResolver\OptionsResolver;

/**
 * Class JustAFormType
 *
 * @package AppBundle\CmsBundle\Form
 * @author  Francesco Casula <fra.casula@gmail.com>
 */
class JustAFormType extends AbstractType
{
    /**
     * @var ObjectManager
     */
    private $objectManager;

    /**
     * JustAFormType constructor.
     *
     * @param ObjectManager $objectManager
     */
    public function __construct(ObjectManager $objectManager)
    {
        $this->objectManager = $objectManager;
    }

    /**
     * @return ObjectManager
     */
    public function getObjectManager()
    {
        return $this->objectManager;
    }

    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('article', HiddenType::class)
            ->add('save', SubmitType::class);

        $builder
            ->get('article')
            ->addModelTransformer(new EntityHiddenTransformer(
                $this->getObjectManager(),
                Article::class,
                'articleId'
            ));
    }

    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => 'AppBundle\Entity\MyEntity',
        ]);
    }
}

然后在您的文件中:services.yml

app.form.type.article:
    class: AppBundle\Form\JustAFormType
    arguments: ["@doctrine.orm.entity_manager"]
    tags:
        - { name: form.type }

在您的控制器中:

$form = $this->createForm(JustAFormType::class, new MyEntity());
$form->handleRequest($request);

就是这样:-)


推荐