嵌入表单集合:检查实体是否存在
Embedding Collection of Forms: check if entity exists
我正在实施 this tutorial。但是在我的例子中 Tag
实体有一个独特的 name
属性:
/**
* @ORM\Entity()
*/
class Tag
{
/**
* @var string
* @ORM\Column(name="name", type="string", length=63, unique=true)
*/
private $name;
/**
* @ORM\ManyToMany(targetEntity="Task", cascade={"persist"})
* @ORM\JoinTable(name="tasks_tags")
*/
protected $tasks;
}
如何检查 Tag
实体是否已存在于 new
和 edit
控制器中,以便我可以决定创建新实体。如果 Tag
存在,我可以将其添加到新任务中。
public function newAction(Request $request)
{
$task = new Task();
$form = $this->createForm(TaskType::class, $task);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$tags = $form->get('tags')->getData();
$em = $this->getDoctrine()->getManager();
foreach ($tags as $tagName) {
$tag = $this->getDoctrine()->getRepository('AppBundle:Tag')->findOneBy(array('name' => $tagName));
if (!$tag) {
$newTag = new Tag();
$newTag->setName($tagName);
$em->persist($newTag);
}
}
$em->persist($task);
$em->flush();
return $this->redirectToRoute('tasks_list');
}
}
提交后出现这个错误:
Catchable Fatal Error: Object of class AppBundle\Entity\Tag
could not be converted to string
我认为这是因为 Tag
实体在 Task
表单提交之前被持久化了。
我该如何处理这部分?
不,那是因为您正在重定向到 tasks_list
,这将显示 Tag
您已坚持但您没有该对象的字符串表示形式。
为了避免这个错误,在Tag
实体中引入如下方法
public function __toString()
{
// This is only an example; you can insert
// here whatever you want to display
// as tag's string representation
return $this->name;
}
您在 Tag
实体的 $tasks
上有 cascade={"persist"}
;但是,您在 $tag
之后坚持 $task
。删除 $task
上的 cascade={"persist"}
并尝试将 cascade={"persist"}
放在 Task
实体中的 $tags
上并检查是否有任何差异。
因为你还没有发布 TaskType::class,我猜你已经使用了这个代码片段来获取标签:
$builder->add('tags', CollectionType::class, array(
'entry_type' => TagType::class
));
你看,Symfony 表单的工作方式是,它们将你从用户那里得到的数据序列化回实体。当你创建一个标签集合时,你实际上在做的是在内部将标签名称序列化回标签对象。所以这部分代码:
$tags = $form->get('tags')->getData();
实际上获取的是标签对象数组,而不仅仅是字符串。这就是为什么当您循环遍历数组以检查现有标签时:
foreach ($tags as $tagName) {}
$tagName 实际上不是标签名称。它是 class 标签的一个对象。当您尝试通过 tagName:
查找标签时
->findOneBy(array('name' => $tagName))
,您实际上正在做的是向学说存储库传递一个 Tag 对象以查询名称。现在内部它当然会尝试将其转换为字符串,因为您无法将对象与字符串进行比较。这就是您收到错误的原因。
为了实现您的目标,您首先必须:
->findOneBy(array('name' => $tagName->getName()))
(如果此时更改 tagName 的命名也很好,因为它实际上不是一个名称,它是一个对象),那么如果您在数据库中找到一个标签,您应该删除您的对象在这种情况下,已通过 $em->remove() 保存在 tagName 变量中,并将新找到的标签添加到任务中。
您应该使用数据转换器。参考:http://symfony.com/doc/current/form/data_transformers.html
// src/AppBundle/Form/DataTransformer/TagToNameTransformer.php
namespace AppBundle\Form\DataTransformer;
use AppBundle\Entity\Tag;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
class TagToNameTransformer implements DataTransformerInterface
{
private $manager;
public function __construct(ObjectManager $manager)
{
$this->manager = $manager;
}
/**
* Transforms an object (tag) to a string (name).
*
* @param Tag|null $tag
* @return string
*/
public function transform($tag)
{
if (null === $tag) {
return '';
}
return $tag->getName();
}
/**
* Transforms a string (name) to an object (tag).
*
* @param string $tagName
* @return Tag|null
* @throws TransformationFailedException if object (tag) is not found.
*/
public function reverseTransform($tagName)
{
// empty tag name? It's optional, so that's ok
if (empty($tagName)) {
return;
}
$tag = $this->manager
->getRepository('AppBundle:Tag')
->find($tagName)
;
if (null === $tag) {
// causes a validation error
// this message is not shown to the user
// see the invalid_message option
throw new TransformationFailedException(sprintf(
'An tag with name "%s" does not exist!',
$tagName
));
}
return $tag;
}
}
在TagType
class中添加这段代码:
class TagType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
// ... your code
$builder->get('name')
->addModelTransformer(new TagToNameTransformer($this->manager));
}
}
瞧!如果您的标签已经存在,它将被找到并链接到您的任务。
我正在实施 this tutorial。但是在我的例子中 Tag
实体有一个独特的 name
属性:
/**
* @ORM\Entity()
*/
class Tag
{
/**
* @var string
* @ORM\Column(name="name", type="string", length=63, unique=true)
*/
private $name;
/**
* @ORM\ManyToMany(targetEntity="Task", cascade={"persist"})
* @ORM\JoinTable(name="tasks_tags")
*/
protected $tasks;
}
如何检查 Tag
实体是否已存在于 new
和 edit
控制器中,以便我可以决定创建新实体。如果 Tag
存在,我可以将其添加到新任务中。
public function newAction(Request $request)
{
$task = new Task();
$form = $this->createForm(TaskType::class, $task);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$tags = $form->get('tags')->getData();
$em = $this->getDoctrine()->getManager();
foreach ($tags as $tagName) {
$tag = $this->getDoctrine()->getRepository('AppBundle:Tag')->findOneBy(array('name' => $tagName));
if (!$tag) {
$newTag = new Tag();
$newTag->setName($tagName);
$em->persist($newTag);
}
}
$em->persist($task);
$em->flush();
return $this->redirectToRoute('tasks_list');
}
}
提交后出现这个错误:
Catchable Fatal Error: Object of class
AppBundle\Entity\Tag
could not be converted to string
我认为这是因为 Tag
实体在 Task
表单提交之前被持久化了。
我该如何处理这部分?
不,那是因为您正在重定向到 tasks_list
,这将显示 Tag
您已坚持但您没有该对象的字符串表示形式。
为了避免这个错误,在Tag
实体中引入如下方法
public function __toString()
{
// This is only an example; you can insert
// here whatever you want to display
// as tag's string representation
return $this->name;
}
您在 Tag
实体的 $tasks
上有 cascade={"persist"}
;但是,您在 $tag
之后坚持 $task
。删除 $task
上的 cascade={"persist"}
并尝试将 cascade={"persist"}
放在 Task
实体中的 $tags
上并检查是否有任何差异。
因为你还没有发布 TaskType::class,我猜你已经使用了这个代码片段来获取标签:
$builder->add('tags', CollectionType::class, array(
'entry_type' => TagType::class
));
你看,Symfony 表单的工作方式是,它们将你从用户那里得到的数据序列化回实体。当你创建一个标签集合时,你实际上在做的是在内部将标签名称序列化回标签对象。所以这部分代码:
$tags = $form->get('tags')->getData();
实际上获取的是标签对象数组,而不仅仅是字符串。这就是为什么当您循环遍历数组以检查现有标签时:
foreach ($tags as $tagName) {}
$tagName 实际上不是标签名称。它是 class 标签的一个对象。当您尝试通过 tagName:
查找标签时->findOneBy(array('name' => $tagName))
,您实际上正在做的是向学说存储库传递一个 Tag 对象以查询名称。现在内部它当然会尝试将其转换为字符串,因为您无法将对象与字符串进行比较。这就是您收到错误的原因。
为了实现您的目标,您首先必须:
->findOneBy(array('name' => $tagName->getName()))
(如果此时更改 tagName 的命名也很好,因为它实际上不是一个名称,它是一个对象),那么如果您在数据库中找到一个标签,您应该删除您的对象在这种情况下,已通过 $em->remove() 保存在 tagName 变量中,并将新找到的标签添加到任务中。
您应该使用数据转换器。参考:http://symfony.com/doc/current/form/data_transformers.html
// src/AppBundle/Form/DataTransformer/TagToNameTransformer.php
namespace AppBundle\Form\DataTransformer;
use AppBundle\Entity\Tag;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
class TagToNameTransformer implements DataTransformerInterface
{
private $manager;
public function __construct(ObjectManager $manager)
{
$this->manager = $manager;
}
/**
* Transforms an object (tag) to a string (name).
*
* @param Tag|null $tag
* @return string
*/
public function transform($tag)
{
if (null === $tag) {
return '';
}
return $tag->getName();
}
/**
* Transforms a string (name) to an object (tag).
*
* @param string $tagName
* @return Tag|null
* @throws TransformationFailedException if object (tag) is not found.
*/
public function reverseTransform($tagName)
{
// empty tag name? It's optional, so that's ok
if (empty($tagName)) {
return;
}
$tag = $this->manager
->getRepository('AppBundle:Tag')
->find($tagName)
;
if (null === $tag) {
// causes a validation error
// this message is not shown to the user
// see the invalid_message option
throw new TransformationFailedException(sprintf(
'An tag with name "%s" does not exist!',
$tagName
));
}
return $tag;
}
}
在TagType
class中添加这段代码:
class TagType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
// ... your code
$builder->get('name')
->addModelTransformer(new TagToNameTransformer($this->manager));
}
}
瞧!如果您的标签已经存在,它将被找到并链接到您的任务。