web-dev-qa-db-ja.com

symfony:非表示のエンティティフィールドを持つことはできませんか?

私は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内部サーバーエラー-LogicException

エンティティフィールドを非表示にできませんか?何故なの?エンティティIDを取得するために別の非表示フィールドを配置する必要がありますか?

編集:

基本的に、私がやろうとしていることは、フォームを表示する前にハイドレイトするが、ユーザーがそのフィールドの1つ(ここでは親)を変更できないようにすることです。これは、Idをパラメーターとして渡す必要があり、フォームアクションURLでそれを行うことができないためです。

16
Sébastien

フィールドの種類とそれぞれが何を表しているのか、混乱していると思います。

entityフィールドはchoiceフィールドの一種です。選択フィールドは、ユーザーがフォームで選択できる値を含むことを目的としています。このフォームがレンダリングされると、symfonyはエンティティフィールドの基になるクラスに基づいて可能な選択肢のリストを生成し、リストの各選択肢の値はそれぞれのエンティティのIDです。フォームが送信されると、symfonyは選択されたエンティティを表すオブジェクトをハイドレートします。 entityフィールドは、通常、エンティティの関連付けのレンダリングに使用されます(たとえば、rolesに割り当てるために選択できるuserのリストなど)。

エンティティのIDフィールドのプレースホルダーを作成するだけの場合は、hidden入力を使用します。ただし、これは、作成しているフォームクラスがエンティティを表す場合にのみ機能します(つまり、フォームのdata_classは、定義したエンティティを参照します)。次に、IDフィールドは、フォームのdata_classで定義されたタイプのエンティティのIDに適切にマッピングされます。

編集:以下で説明する特定の状況に対する1つの解決策は、hiddenフィールドタイプを拡張するが、エンティティ/ IDへ/から変換するためのデータ変換を処理する新しいフィールドタイプ(EntityHiddenと呼びましょう)を作成することです。このように、フォームには非表示フィールドとしてエンティティIDが含まれますが、フォームが送信されると、アプリケーションはエンティティ自体にアクセスできます。もちろん、変換はデータトランスフォーマーによって実行されます。

後世のための、そのような実装の例を次に示します。

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がエンティティのIDを使用して非表示の入力HTMLを適切に生成することに注意してくださいその価値。お役に立てば幸いです。

17
Haig Bedrosian

Symfonyでこれを作成したところ、すでにここに投稿されているものとは少し異なることに気付いたので、共有する価値があると考えました。

すべてのフォームタイプで簡単に再利用できる汎用データトランスフォーマーを作成しました。あなたはあなたのフォームタイプを渡す必要があります、そしてそれだけです。カスタムフォームタイプを作成する必要はありません。

まず、データトランスフォーマーを見てみましょう。

<?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 <[email protected]>
 */
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 <[email protected]>
 */
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);

それでおしまい :-)

14

これは、エンティティのテーマの代わりに標準のhiddenフィールドテーマを使用して、フォームのテーマを設定することでかなりきれいに実現できます。非表示フィールドと選択フィールドが同じ形式になるため、トランスフォーマーの使用は多分やり過ぎだと思います。

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

新しいトランスフォーマーと型クラスを作成する迅速な解決策。データベースから関連エンティティを事前入力する場合。

// 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'
    )
));

これにより、次のような1つの非表示の選択が行われます。

<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]"/>
2
Fabian Picone