web-dev-qa-db-fra.com

symfony: ne pouvons-nous pas avoir un champ d'entité caché?

Je rend un formulaire avec un champ d'entité dans symfony.

Cela fonctionne bien lorsque je choisis un champ d'entité régulier.

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

Cela génère l'erreur suivante lorsque je choisis -> ajouter ('parent', 'caché'):

Les données de vue du formulaire sont censées être de type scalaire, tableau ou une instance de\ArrayAccess, mais il s'agit d'une instance de la classe AppBundle\Entity\FoodAnalytics\Recipe. Vous pouvez éviter cette erreur en définissant l'option "data_class" sur "AppBundle\Entity\FoodAnalytics\Recipe" ou en ajoutant un transformateur de vue qui transforme une instance de la classe AppBundle\Entity\FoodAnalytics\Recipe en scalaire, tableau ou une instance de\ArrayAccess. 500 Erreur de serveur interne - LogicException

Ne pouvons-nous pas avoir des champs d'entité cachés ?? Pourquoi pas? Suis-je obligé de mettre un autre champ caché pour récupérer l'ID d'entité?

ÉDITER :

Fondamentalement, ce que j'essaie de faire est d'hydrater le formulaire avant de l'afficher mais d'empêcher l'utilisateur de modifier l'un de ses champs (le parent ici). C'est parce que je dois passer l'ID en tant que paramètre et je ne peux pas le faire dans l'url d'action de formulaire.

16
Sébastien

Je pense que vous êtes simplement confus au sujet des types de champs et de ce qu'ils représentent chacun.

Un champ entity est un type de champ choice. Les champs de choix sont censés contenir des valeurs sélectionnables par un utilisateur dans un formulaire. Lorsque ce formulaire est rendu, Symfony générera une liste de choix possibles en fonction de la classe sous-jacente du champ d'entité, et la valeur de chaque choix dans la liste est l'id de l'entité respective. Une fois le formulaire soumis, Symfony hydratera pour vous un objet représentant l'entité sélectionnée. Le champ entity est généralement utilisé pour le rendu des associations d'entités (comme par exemple une liste de roles que vous pouvez sélectionner pour affecter à un user).

Si vous essayez simplement de créer un espace réservé pour un champ ID d'une entité, vous utiliserez l'entrée hidden. Mais cela ne fonctionne que si la classe de formulaire que vous créez représente une entité (c'est-à-dire que le data_class Du formulaire fait référence à une entité que vous avez définie). Le champ ID sera alors correctement mappé à l'ID d'une entité du type défini par le data_class Du formulaire.

EDIT: Une solution à votre situation particulière décrite ci-dessous serait de créer un nouveau type de champ (appelons-le EntityHidden) qui étend le type de champ hidden mais gère la transformation des données pour la conversion vers/depuis une entité/id. De cette façon, votre formulaire contiendra l'ID d'entité en tant que champ masqué, mais l'application aura accès à l'entité elle-même une fois le formulaire soumis. Bien entendu, la conversion est effectuée par le transformateur de données.

Voici un exemple d'une telle implémentation, pour la postérité:

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

Notez simplement que dans la classe de type de formulaire, tout ce que vous avez à faire est d'assigner votre entité cachée à sa propriété de champ de formulaire correspondante (dans le modèle de formulaire/classe de données) et Symfony générera correctement le HTML d'entrée caché avec l'ID de l'entité comme Sa valeur. J'espère que cela pourra aider.

17
Haig Bedrosian

Je viens de le faire sur Symfony et j'ai réalisé que c'est un peu différent de ce qui a déjà été posté ici, donc j'ai pensé que ça valait le coup d'être partagé.

Je viens de créer un transformateur de données générique qui pourrait être facilement réutilisable sur tous vos types de formulaires. Il vous suffit de passer votre type de formulaire et c'est tout. Pas besoin de créer un type de formulaire personnalisé.

Jetons d'abord un coup d'œil au transformateur de données:

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

L'idée est donc que vous l'appeliez en y passant le gestionnaire d'objets, l'entité que vous souhaitez utiliser, puis le nom du champ pour obtenir l'ID d'entité.

Fondamentalement, comme ceci:

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

Mettons tout cela ensemble maintenant. Nous avons juste besoin du type de formulaire et d'un peu de configuration YAML, puis nous sommes prêts.

<?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',
        ]);
    }
}

Et puis dans votre services.yml fichier:

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

Et dans votre contrôleur:

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

C'est tout :-)

14
Francesco Casula

Ceci peut être réalisé assez proprement avec le thème du formulaire, en utilisant le thème de champ standard hidden à la place de celui de l'entité. Je pense que l'utilisation de transformateurs est probablement exagérée, étant donné que les champs masqués et sélectionnés donneront le même format.

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

Une solution rapide sans créer de nouveaux transformateurs et classes de types. Lorsque vous souhaitez préremplir une entité associée à partir de la base de données.

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

Il en résulte une seule sélection masquée comme:

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

Mais oui, je suis d'accord qu'avec un peu plus d'efforts en utilisant un DataTransformer vous pouvez réaliser quelque chose comme:

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