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

Не можем ли мы иметь скрытые поля объектов? Почему нет? Должен ли я поместить другое скрытое поле для получения идентификатора объекта?

РЕДАКТИРОВАТЬ:

В основном то, что я пытаюсь сделать, это гидратировать форму перед ее отображением, но не позволяет пользователю изменять одно из своих полей (здесь родительский). Это потому, что мне нужно передать идентификатор в качестве параметра, и я не могу это сделать в URL-адресе формы.

Ответы

Ответ 1

Я думаю, вы просто сбиты с толку о типах полей и о том, что они представляют.

Поле entity является типом поля choice. Поля выбора зависят от значений, выбираемых пользователем в форме. Когда эта форма отображается, Symfony будет генерировать список возможных вариантов, основанных на базовом классе поля сущности, а значение каждого выбора в списке - это идентификатор соответствующего объекта. После отправки формы Symfony увлажнит объект для вас, представляющий выбранный объект. Поле entity обычно используется для отображения ассоциаций сущностей (например, список roles вы можете выбрать для назначения user).

Если вы просто пытаетесь создать местозаполнитель для поля идентификатора объекта, то вы будете использовать hidden ввод. Но это работает только в том случае, если класс формы, который вы создаете, представляет собой сущность (т.е. форма data_class относится к сущности, которую вы определили). Затем поле идентификатора правильно сопоставляет идентификатор объекта типа, определенного формой data_class.

EDIT: одним из решений вашей конкретной ситуации, описанным ниже, было бы создание нового типа поля (позвольте ему назвать EntityHidden), который расширяет тип hidden поля, но обрабатывает преобразование данных для преобразования в/из объекта /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 будет генерировать скрытый ввод HTML должным образом с идентификатором объекта как его значение. Надеюсь, это поможет.

Ответ 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 <[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 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;
    }
}

Поэтому идея состоит в том, что вы вызываете ее, передавая там объект-менеджер, объект, который вы хотите использовать, а затем имя поля, чтобы получить идентификатор объекта.

В основном так:

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

Это :-)

Ответ 3

Быстрое решение без создания новых классов трансформаторов и типов. Если вы хотите предварительно сформировать связанный объект из 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

Это может быть достигнуто достаточно чисто с помощью формы, используя стандартную тему hidden поля вместо того, что для объекта. Я думаю, что использование трансформаторов, вероятно, слишком велико, учитывая, что скрытые и выбранные поля будут иметь тот же формат.

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