简体   繁体   中英

I can't display embedded, nested form in Symfony 3

I have a big issue because I can't find any solutions for my problem on Internet. I work on a Post Entity from which I want to add Images Entities. The idea is when I log in my app, I can write a post and upload one or n images. After that, the user will allow to see the images from his posts for example.

I am at the step one, It seems I can't display my CollectionClass::Class of my ImageType::Class in my PostType.php . I haven't errors, it's simply not displayed in the view. Thanks in advance!

PostType.php

<?php

    namespace UserBundle\Form;

    use Symfony\Component\Form\AbstractType;
    use Symfony\Component\Form\FormBuilderInterface;
    use Symfony\Component\OptionsResolver\OptionsResolver;
    use Symfony\Component\Form\Extension\Core\Type\CollectionType;
    use UserBundle\Entity\Post;


    class PostType extends AbstractType
    {
        /**
         * {@inheritdoc}
         */
        public function buildForm(FormBuilderInterface $builder, array $options)
        {
            $builder
                ->add('title')
                ->add('content')
                ->add('images', CollectionType::class, [
                    'entry_type' => ImageType::class,
                    'entry_options' => array('label' => false),
                    'allow_add' => true,
                ]);
            ;
        }
        
        /**
         * {@inheritdoc}
         */
        public function configureOptions(OptionsResolver $resolver)
        {
            $resolver->setDefaults(array(
                'data_class' => Post::class,
            ));
        }

        /**
         * {@inheritdoc}
         */
        public function getBlockPrefix()
        {
            return 'userbundle_post';
        }
    }

ImageType.php

<?php

    namespace UserBundle\Form;

    use Symfony\Component\Form\AbstractType;
    use Symfony\Component\Form\FormBuilderInterface;
    use Symfony\Component\OptionsResolver\OptionsResolver;
    use Symfony\Component\Form\Extension\Core\Type\FileType;
    use UserBundle\Entity\Image;

    class ImageType extends AbstractType
    {
        /**
         * {@inheritdoc}
         */
        public function buildForm(FormBuilderInterface $builder, array $options)
        {
            $builder
                ->add('file', FileType::class, ['required' => false, 'data_class' => null]);
        }
        
        /**
         * {@inheritdoc}
         */
        public function configureOptions(OptionsResolver $resolver)
        {
            $resolver->setDefaults(array(
                'data_class' => Image::class,
            ));
        }

        /**
         * {@inheritdoc}
         */
        public function getBlockPrefix()
        {
            return 'userbundle_image';
        }
    }

Post.php

<?php

    namespace UserBundle\Entity;

    use Doctrine\ORM\Mapping as ORM;
    use Symfony\Component\Validator\Constraints as Assert;
    use Doctrine\Common\Collections\ArrayCollection;

    /**
     * Post
     *
     * @ORM\Table(name="post")
     * @ORM\Entity(repositoryClass="UserBundle\Repository\PostRepository")
     */
    class Post
    {
        /**
         * @var int
         *
         * @ORM\Column(name="id", type="integer")
         * @ORM\Id
         * @ORM\GeneratedValue(strategy="AUTO")
         */
        private $id;

        /**
         * @var string
         *
         * @ORM\Column(name="Title", type="string", length=255)
         */
        private $title;

        /**
         * @var string
         *
         * @ORM\Column(name="Content", type="text")
         */
        private $content;


        /**
         * @ORM\ManyToOne(targetEntity="User", inversedBy="posts")
         * @ORM\JoinColumn(name="user_id", referencedColumnName="id")
        */
        private $user;

        /**
         * @ORM\OneToMany(targetEntity="Image", mappedBy="post", cascade={"persist"})
         */
        private $images;

        /**
         * Get id
         *
         * @return int
         */
        public function getId()
        {
            return $this->id;
        }

        /**
         * Set title
         *
         * @param string $title
         *
         * @return Post
         */
        public function setTitle($title)
        {
            $this->title = $title;

            return $this;
        }

        /**
         * Get title
         *
         * @return string
         */
        public function getTitle()
        {
            return $this->title;
        }

        /**
         * Set content
         *
         * @param string $content
         *
         * @return Post
         */
        public function setContent($content)
        {
            $this->content = $content;

            return $this;
        }

        /**
         * Get content
         *
         * @return string
         */
        public function getContent()
        {
            return $this->content;
        }

        /**
         * Set post
         *
         * @param \
         *
         * @return Post
         */
        public function setUser($user)
        {
            $this->user = $user;

            return $this;
        }

        /**
         * Get post
         *
         * @return \?
         */
        public function getUser()
        {
            return $this->user;
        }
        /**
         * Constructor
         */
        public function __construct()
        {
            $this->images = new \Doctrine\Common\Collections\ArrayCollection();
        }

        /**
         * Add image
         *
         * @param \UserBundle\Entity\Image $image
         *
         * @return Post
         */
        public function addImage(\UserBundle\Entity\Image $image)
        {
            $this->images[] = $image;

            return $this;
        }

        /**
         * Remove image
         *
         * @param \UserBundle\Entity\Image $image
         */
        public function removeImage(\UserBundle\Entity\Image $image)
        {
            $this->images->removeElement($image);
        }

        /**
         * Get images
         *
         * @return \Doctrine\Common\Collections\Collection
         */
        public function getImages()
        {
            return $this->images;
        }
    }

Image.php

<?php
    // src/UserformBundle/Entity/Image

    namespace UserBundle\Entity;

    use Doctrine\ORM\Mapping as ORM;

    /**
     * @ORM\Entity(repositoryClass="UserBundle\Entity\ImageRepository")
     */
    class Image
    {
      /**
       * @ORM\Column(name="id", type="integer")
       * @ORM\Id
       * @ORM\GeneratedValue(strategy="AUTO")
       */
      private $id;

      /**
       * @ORM\Column(name="url", type="string", length=255)
       */
      private $url;

      /**
       * @ORM\Column(name="alt", type="string", length=255)
       */
      private $alt;

      private $file;
      
      public function getFile()
      {
        return $this->file;
      }

      public function setFile(UploadedFile $file = null)
      {
        $this->file = $file;
      }
      

      /**
       * @ORM\ManyToOne(targetEntity="Post", inversedBy="images")
       * @ORM\JoinColumn(name="post_id", referencedColumnName="id")
      */
      private $post;
      

      /**
       * Get id
       *
       * @return integer
       */
      public function getId()
      {
          return $this->id;
      }

      /**
       * Set url
       *
       * @param string $url
       *
       * @return Image
       */
      public function setUrl($url)
      {
          $this->url = $url;

          return $this;
      }

      /**
       * Get url
       *
       * @return string
       */
      public function getUrl()
      {
          return $this->url;
      }

      /**
       * Set alt
       *
       * @param string $alt
       *
       * @return Image
       */
      public function setAlt($alt)
      {
          $this->alt = $alt;

          return $this;
      }

      /**
       * Get alt
       *
       * @return string
       */
      public function getAlt()
      {
          return $this->alt;
      }

      /**
       * Set post
       *
       * @param \UserBundle\Entity\Post $post
       *
       * @return Image
       */
      public function setPost(\UserBundle\Entity\Post $post = null)
      {
          $this->post = $post;

          return $this;
      }

      /**
       * Get post
       *
       * @return \UserBundle\Entity\Post
       */
      public function getPost()
      {
          return $this->post;
      }
    }

EDIT
My Post New action view: new.html.twig

    {% extends 'base.html.twig' %}
        {% block javascripts %}
            <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
            {# Voici le script en question : #}
            <script type="text/javascript">
                $(document).ready(function() {
                    // On récupère la balise <div> en question qui contient l'attribut « data-prototype » qui nous intéresse.
                    var $container = $('div#userbundle_post_images');

                    // On définit un compteur unique pour nommer les champs qu'on va ajouter dynamiquement
                    var index = $container.find(':input').length;

                    // On ajoute un nouveau champ à chaque clic sur le lien d'ajout.
                    $('#add_category').click(function(e) {
                        addImage($container);
                        e.preventDefault(); // évite qu'un # apparaisse dans l'URL
                        return false;
                    });
                    // On ajoute un premier champ automatiquement s'il n'en existe pas déjà un (cas d'une nouvelle annonce par exemple).
                    if (index == 0) {
                        addImage($container);
                    } else {
                        // S'il existe déjà des catégories, on ajoute un lien de suppression pour chacune d'entre elles
                        $container.children('div').each(function() {
                            addDeleteLink($(this));
                        });
                    }

                    // La fonction qui ajoute un formulaire CategoryType
                    function addImage($container) {
                        // Dans le contenu de l'attribut « data-prototype », on remplace :
                        // - le texte "__name__label__" qu'il contient par le label du champ
                        // - le texte "__name__" qu'il contient par le numéro du champ
                        var template = $container.attr('data-prototype')
                            .replace(/__name__label__/g, 'Catégorie n°' + (index+1))
                            .replace(/__name__/g,        index)
                        ;

                        // On crée un objet jquery qui contient ce template
                        var $prototype = $(template);

                        // On ajoute au prototype un lien pour pouvoir supprimer la catégorie
                        addDeleteLink($prototype);

                        // On ajoute le prototype modifié à la fin de la balise <div>
                        $container.append($prototype);

                        // Enfin, on incrémente le compteur pour que le prochain ajout se fasse avec un autre numéro
                        index++;
                    }

                    // La fonction qui ajoute un lien de suppression d'une catégorie
                    function addDeleteLink($prototype) {
                        // Création du lien
                        var $deleteLink = $('<a href="#" class="btn btn-danger">Supprimer l\'image</a>');

                        // Ajout du lien
                        $prototype.append($deleteLink);

                        // Ajout du listener sur le clic du lien pour effectivement supprimer la catégorie
                        $deleteLink.click(function(e) {
                            $prototype.remove();

                            e.preventDefault(); // évite qu'un # apparaisse dans l'URL
                            return false;
                        });
                    }
                });
            </script>
        {% endblock %}

        {% block body %}
            <h1>Post creation</h1>
            {{ form_start(form) }}
                {{ form_row(form.title) }}
                {{ form_row(form.content) }}
                <a href="#" id="add_category" class="btn btn-default">Ajouter une Image</a>
                {{ form_row(form.images) }}
                <br/>
                <input type="submit" value="Create" />
            {{ form_end(form) }}

            <ul>
                <li>
                    <a href="{{ path('post_index') }}">Back to the list</a>
                </li>
            </ul>
        {% endblock %}

EDIT: PostController.php

    /**
     * Creates a new post entity.
     *
     * @Route("/new", name="post_new")
     * @Method({"GET", "POST"})
     */
    public function newAction(Request $request)
    {
        $post = new Post();
        //dump($post);
        
        $user = $this->getUser();
        if (!is_object($user) || !$user instanceof UserInterface) {
            throw new AccessDeniedException('This user does not have access to this section.');
        }

        $form = $this->createForm('UserBundle\Form\PostType', $post);
        dump($form);
        //die();
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            
            // relate this post to the owner user
            $user->addPost($post);
            // relate the owner user to this post
            $post->setUser($user);

            $em = $this->getDoctrine()->getManager();
            $em->persist($post);
            $em->persist($user);
            $em->flush();

            return $this->redirectToRoute('post_show', array('id' => $post->getId()));
        }

        return $this->render('post/new.html.twig', array(
            'post' => $post,
            'form' => $form->createView(),
        ));
    }

It would be useful to see your view but just a reminder: to be able to add new images your form needs some javascript, please look here: https://symfony.com/doc/current/form/form_collections.html#allowing-new-tags-with-the-prototype

Without javascript, by default it would only render already added collection objects. If you have none in your entity, it will display nothing by default.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM