我无法在 Symfony 3 中显示嵌入的嵌套表单
I can't display embedded, nested form in Symfony 3
我遇到了一个大问题,因为我在 Internet 上找不到解决我问题的方法。我在一个 Post 实体上工作,我想从中添加图像实体。这个想法是当我登录我的应用程序时,我可以写一个 post 并上传一张或 n 张图片。之后,用户将允许查看来自他 post 的图像。例如。
我在第一步,似乎无法显示我的 CollectionClass::Class 和 我的 PostType.php 中的 ImageType::Class。我没有错误,它只是没有显示在视图中。提前致谢!
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;
}
}
编辑
我的 Post 新操作视图: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 %}
编辑:
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(),
));
}
看到您的视图会很有用,但只是提醒:为了能够添加新图像,您的表单需要一些 javascript,请看这里:https://symfony.com/doc/current/form/form_collections.html#allowing-new-tags-with-the-prototype
没有javascript,默认情况下它只会渲染已经添加的集合对象。如果您的实体中有 none,默认情况下它不会显示任何内容。
我遇到了一个大问题,因为我在 Internet 上找不到解决我问题的方法。我在一个 Post 实体上工作,我想从中添加图像实体。这个想法是当我登录我的应用程序时,我可以写一个 post 并上传一张或 n 张图片。之后,用户将允许查看来自他 post 的图像。例如。
我在第一步,似乎无法显示我的 CollectionClass::Class 和 我的 PostType.php 中的 ImageType::Class。我没有错误,它只是没有显示在视图中。提前致谢!
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;
}
}
编辑
我的 Post 新操作视图: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 %}
编辑: 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(),
));
}
看到您的视图会很有用,但只是提醒:为了能够添加新图像,您的表单需要一些 javascript,请看这里:https://symfony.com/doc/current/form/form_collections.html#allowing-new-tags-with-the-prototype
没有javascript,默认情况下它只会渲染已经添加的集合对象。如果您的实体中有 none,默认情况下它不会显示任何内容。