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

时间:2014-11-23 22:02:39

标签: php symfony entity formbuilder

我正在使用symfony中的实体字段渲染表单。

当我选择常规实体字段时效果很好。

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

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

  

表单的视图数据应该是标量,数组或类型   \ ArrayAccess的实例,但是是类的实例   的appbundle \实体\ FoodAnalytics \配方。你可以避免这个错误   将“data_class”选项设置为   “AppBundle \ Entity \ FoodAnalytics \ Recipe”或添加视图   转换类实例的转换器   AppBundle \ Entity \ FoodAnalytics \ Recipe到标量,数组或实例   \ ArrayAccess。 500内部服务器错误 - LogicException

我们不能有隐藏的实体字段吗?为什么不?我是否有义务将另一个隐藏字段用于检索实体ID?

编辑:

基本上,我想要做的是在显示之前保持表单,但是阻止用户更改其中一个字段(这里的父项)。 这是因为我需要将Id作为参数传递,我不能在表单操作URL中执行此操作。

6 个答案:

答案 0 :(得分:16)

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

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

如果您只是尝试为实体的ID字段创建占位符,那么您将使用user输入。但这仅适用于您正在创建的表单类表示实体(即表单hidden引用您已定义的实体)。然后,ID字段将正确映射到表单data_class定义的类型的实体的ID。

编辑:下面描述的特定情况的一种解决方案是创建一个新的字段类型(让它称之为EntityHidden),它扩展data_class字段类型,但处理数据转换以转换为/从实体/身份证。这样,您的表单将包含实体ID作为隐藏字段,但是一旦提交表单,应用程序就可以访问实体本身。当然,转换由数据转换器执行。

以下是这种实施的一个例子,供后人使用:

hidden

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

答案 1 :(得分:13)

刚刚在 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);

它是: - )

答案 2 :(得分:3)

使用标准hidden字段主题代替实体,可以使用表单主题相当干净地实现这一点。我认为使用变换器可能有点过分,因为隐藏和选择字段将提供相同的格式。

{% block _recipe_parent_widget %}
    {%- set type = 'hidden' -%}
    {{ block('form_widget_simple') }}
{% endblock %}

答案 3 :(得分:2)

快速解决方案whitout创建新的转换器和类型类。如果要从db中预填充相关实体。

// Hidden selected single group
$builder->add('idGroup', 'entity', array(
    'label' => false,
    'class' => 'MyVendorCoreBundle:Group',
    'query_builder' => function (EntityRepository $er) {
        $qb = $er->createQueryBuilder('c');
        return $qb->where($qb->expr()->eq('c.groupid', $this->groupId()));
    },
    'attr' => array(
        'class' => 'hidden'
    )
));

这会产生一个隐藏的选择,如:

<select id="mytool_idGroup" name="mytool[idGroup]" class="hidden">
    <option value="1">MyGroup</option>
</select>

但是,是的,我同意通过使用DataTransformer稍加努力,您可以实现以下目标:

<input type="hidden" value="1" id="mytool_idGroup" name="mytool[idGroup]"/>

答案 4 :(得分:1)

在Symfony 5中,我使用实现了DataTransformerInterface接口的Hidden类型的解决方案。

<?php

namespace App\Form\Type;

use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface;

/**
 * Defines the custom form field type used to add a hidden entity
 *
 * See https://symfony.com/doc/current/form/create_custom_field_type.html
 */
class EntityHiddenType extends HiddenType implements DataTransformerInterface
{

    /** @var ManagerRegistry $dm */
    private $dm;

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

    /**
     *
     * @param ManagerRegistry $doctrine
     */
    public function __construct(ManagerRegistry $doctrine)
    {
        $this->dm = $doctrine;
    }

    /**
     *
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        // Set class, eg: App\Entity\RuleSet
        $this->entityClass = sprintf('App\Entity\%s', ucfirst($builder->getName()));
        $builder->addModelTransformer($this);
    }

    public function transform($data): string
    {
        // Modified from comments to use instanceof so that base classes or interfaces can be specified
        if (null === $data || !$data instanceof $this->entityClass) {
            return '';
        }

        $res = $data->getId();

        return $res;
    }

    public function reverseTransform($data)
    {
        if (!$data) {
            return null;
        }

        $res = null;
        try {
            $rep = $this->dm->getRepository($this->entityClass);
            $res = $rep->findOneBy(array(
                "id" => $data
            ));
        }
        catch (\Exception $e) {
            throw new TransformationFailedException($e->getMessage());
        }

        if ($res === null) {
            throw new TransformationFailedException(sprintf('A %s with id "%s" does not exist!', $this->entityClass, $data));
        }

        return $res;
    }
}

并使用以下格式的字段:

use App\Form\Type\EntityHiddenType;

public function buildForm(FormBuilderInterface $builder, array $options): void
{
    // Field name must match entity class, eg 'ruleSet' for App\Entity\RuleSet
    $builder->add('ruleSet', EntityHiddenType::class);
}

答案 5 :(得分:0)

这将满足您的需求:

$builder->add('parent', 'hidden', array('property_path' => 'parent.id'));