Symfony 实体字段:manyToMany with multiple = false - 字段未正确填充
Symfony entity field : manyToMany with multiple = false - field not populated correctly
我正在使用 symfony2 和 doctrine 2。
我在两个实体之间存在多对多关系:
/**
* @ORM\ManyToMany(targetEntity="\AppBundle\Entity\Social\PostCategory", inversedBy="posts")
* @ORM\JoinTable(
* name="post_postcategory",
* joinColumns={@ORM\JoinColumn(name="postId", referencedColumnName="id", onDelete="CASCADE")},
* inverseJoinColumns={@ORM\JoinColumn(name="postCategoryId", referencedColumnName="id", onDelete="CASCADE")}
* )
*/
private $postCategories;
现在我想让用户只select一个类别。为此,我在表单中使用选项 'multiple' => false。
我的表格:
->add('postCategories', 'entity', array(
'label'=> 'Catégorie',
'required' => true,
'empty_data' => false,
'empty_value' => 'Sélectionnez une catégorie',
'class' => 'AppBundle\Entity\Social\PostCategory',
'multiple' => false,
'by_reference' => false,
'query_builder' => $queryBuilder,
'position' => array('before' => 'name'),
'attr' => array(
'data-toggle'=>"tooltip",
'data-placement'=>"top",
'title'=>"Choisissez la catégorie dans laquelle publier le feedback",
)))
这首先让我在保存时出错,我不得不按如下方式更改 setter :
/**
* @param \AppBundle\Entity\Social\PostCategory $postCategories
*
* @return Post
*/
public function setPostCategories($postCategories)
{
if (is_array($postCategories) || $postCategories instanceof Collection)
{
/** @var PostCategory $postCategory */
foreach ($postCategories as $postCategory)
{
$this->addPostCategory($postCategory);
}
}
else
{
$this->addPostCategory($postCategories);
}
return $this;
}
/**
* Add postCategory
*
* @param \AppBundle\Entity\Social\PostCategory $postCategory
*
* @return Post
*/
public function addPostCategory(\AppBundle\Entity\Social\PostCategory $postCategory)
{
$postCategory->addPost($this);
$this->postCategories[] = $postCategory;
return $this;
}
/**
* Remove postCategory
*
* @param \AppBundle\Entity\Social\PostCategory $postCategory
*/
public function removePostCategory(\AppBundle\Entity\Social\PostCategory $postCategory)
{
$this->postCategories->removeElement($postCategory);
}
/**
* Get postCategories
*
* @return \Doctrine\Common\Collections\Collection
*/
public function getPostCategories()
{
return $this->postCategories;
}
/**
* Constructor
* @param null $user
*/
public function __construct($user = null)
{
$this->postCategories = new \Doctrine\Common\Collections\ArrayCollection();
}
现在,在编辑 post 时,我也遇到了一个问题,因为它使用 getter 输出一个集合,而不是单个实体,而且我的类别字段没有正确填写。
/**
* Get postCategories
*
* @return \Doctrine\Common\Collections\Collection
*/
public function getPostCategories()
{
return $this->postCategories;
}
如果我设置 'multiple' => true 就可以工作,但我不想要这个,我希望用户只 select 一个类别,我不想只用断言。
当然有些情况下我想让用户select很多字段所以我想保持manyToMany关系。
我能做什么?
您可以将表单类型设置为不使用 PostCategory
by reference(将 by_reference
选项设置为 false)
这将强制 symfony 表单使用 addPostCategory
和 removePostCategory
而不是 setPostCategories。
UPD
1) 您正在混合使用普通数组和 ArrayCollection。选择一种策略。 Getter 将始终输出一个 ArrayCollection,因为它应该这样做。如果你想强制它成为普通数组,将 ->toArray()
方法添加到 getter
2) 我也理解 multiple=false
return 一个实体的选择,而 multiple=true
return 数组独立于映射关系(*toMany 或 *toOne) .因此,只需尝试从 class 中删除 setter,如果您希望在不同情况下有类似的行为,则仅使用加法器和去除器。
/** @var ArrayCollection|PostCategory[] */
private $postCategories;
public function __construct()
{
$this->postCategories = new ArrayCollection();
}
public function addPostCategory(PostCategory $postCategory)
{
if (!$this->postCategories->contains($postCategory) {
$postCategory->addPost($this);
$this->postCategories->add($postCategory);
}
}
public function removePostCategory(PostCategory $postCategory)
{
if ($this->postCategories->contains($postCategory) {
$postCategory->removePost($this);
$this->postCategories->add($postCategory);
}
}
/**
* @return ArrayCollection|PostCategory[]
*/
public function getPostCategories()
{
return $this->postCategories;
}
如果要在添加到 ManyToMany
集合时将 multiple
选项设置为 false
,可以在 "fake" 属性通过创建几个新的 getter 和 setter 并更新您的表单构建代码来创建实体。
(有趣的是,我在升级到 Symfony 2.7 后才在我的项目中看到这个问题,这迫使我设计了这个解决方案。)
这是一个使用您的实体的示例。该示例假设您需要验证(因为这有点复杂,所以希望这个答案对其他人更有用!)
将以下内容添加到您的 Post
class:
public function setSingleCategory(PostCategory $category = null)
{
// When binding invalid data, this may be null
// But it'll be caught later by the constraint set up in the form builder
// So that's okay!
if (!$category) {
return;
}
$this->postCategories->add($category);
}
// Which one should it use for pre-filling the form's default data?
// That's defined by this getter. I think you probably just want the first?
public function getSingleCategory()
{
return $this->postCategories->first();
}
现在更改表单中的这一行:
->add('postCategories', 'entity', array(
成为
->add('singleCategory', 'entity', array(
'constraints' => [
new NotNull(),
],
即我们更改了它引用的字段,还添加了一些内联验证 - 您无法通过注释设置验证,因为在您的 class 上没有 属性 调用 singleCategory
,只有一些使用该短语的方法。
在我的例子中,原因是 Doctrine 与 Join Table 没有一对多、单向的关系。在文档示例中显示山楂我们可以通过 ManyToMany 来完成这种关系(在第二列添加标志 unique=true)。
这种方式可以,但是Form组件自己混了
解决方案是更改实体中的 geter 和 seter class...即使是那些自动生成的。
这是我的案例(希望有人需要)。假设:classic 一对多关系,单向连接 Table
实体class:
/**
* @ORM\ManyToMany(targetEntity="B2B\AdminBundle\Entity\DictionaryValues")
* @ORM\JoinTable(
* name="users_responsibility",
* joinColumns={@ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="CASCADE")},
* inverseJoinColumns={@ORM\JoinColumn(name="responsibility_id", referencedColumnName="id", unique=true, onDelete="CASCADE")}
* )
*/
private $responsibility;
/**
* Constructor
*/
public function __construct()
{
$this->responsibility = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add responsibility
*
* @param \B2B\AdminBundle\Entity\DictionaryValues $responsibility
*
* @return User
*/
public function setResponsibility(\B2B\AdminBundle\Entity\DictionaryValues $responsibility = null)
{
if(count($this->responsibility) > 0){
foreach($this->responsibility as $item){
$this->removeResponsibility($item);
}
}
$this->responsibility[] = $responsibility;
return $this;
}
/**
* Remove responsibility
*
* @param \B2B\AdminBundle\Entity\DictionaryValues $responsibility
*/
public function removeResponsibility(\B2B\AdminBundle\Entity\DictionaryValues $responsibility)
{
$this->responsibility->removeElement($responsibility);
}
/**
* Get responsibility
*
* @return \Doctrine\Common\Collections\Collection
*/
public function getResponsibility()
{
return $this->responsibility->first();
}
表格:
->add('responsibility', EntityType::class,
array(
'required' => false,
'label' => 'Obszar odpowiedzialności:',
'class' => DictionaryValues::class,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('n')
->where('n.parent = 2')
->orderBy('n.id', 'ASC');
},
'choice_label' => 'value',
'placeholder' => 'Wybierz',
'multiple' => false,
'constraints' => array(
new NotBlank()
)
)
)
我知道这是一个很老的问题,但这个问题今天仍然有效。
使用简单的内联数据转换器对我有用。
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('profileTypes', EntityType::class, [
'multiple' => false,
'expanded' => true,
'class' => ProfileType::class,
]);
// data transformer so profileTypes does work with multiple => false
$builder->get('profileTypes')
->addModelTransformer(new CallbackTransformer(
// return first item from collection
fn ($data) => $data instanceof Collection && $data->count() ? $data->first() : $data,
// convert single ProfileType into collection
fn ($data) => $data && $data instanceof ProfileType ? new ArrayCollection([$data]) : $data
));
}
PS:Array functions 在 PHP 7.4 及更高版本中可用。
我正在使用 symfony2 和 doctrine 2。 我在两个实体之间存在多对多关系:
/**
* @ORM\ManyToMany(targetEntity="\AppBundle\Entity\Social\PostCategory", inversedBy="posts")
* @ORM\JoinTable(
* name="post_postcategory",
* joinColumns={@ORM\JoinColumn(name="postId", referencedColumnName="id", onDelete="CASCADE")},
* inverseJoinColumns={@ORM\JoinColumn(name="postCategoryId", referencedColumnName="id", onDelete="CASCADE")}
* )
*/
private $postCategories;
现在我想让用户只select一个类别。为此,我在表单中使用选项 'multiple' => false。
我的表格:
->add('postCategories', 'entity', array(
'label'=> 'Catégorie',
'required' => true,
'empty_data' => false,
'empty_value' => 'Sélectionnez une catégorie',
'class' => 'AppBundle\Entity\Social\PostCategory',
'multiple' => false,
'by_reference' => false,
'query_builder' => $queryBuilder,
'position' => array('before' => 'name'),
'attr' => array(
'data-toggle'=>"tooltip",
'data-placement'=>"top",
'title'=>"Choisissez la catégorie dans laquelle publier le feedback",
)))
这首先让我在保存时出错,我不得不按如下方式更改 setter :
/**
* @param \AppBundle\Entity\Social\PostCategory $postCategories
*
* @return Post
*/
public function setPostCategories($postCategories)
{
if (is_array($postCategories) || $postCategories instanceof Collection)
{
/** @var PostCategory $postCategory */
foreach ($postCategories as $postCategory)
{
$this->addPostCategory($postCategory);
}
}
else
{
$this->addPostCategory($postCategories);
}
return $this;
}
/**
* Add postCategory
*
* @param \AppBundle\Entity\Social\PostCategory $postCategory
*
* @return Post
*/
public function addPostCategory(\AppBundle\Entity\Social\PostCategory $postCategory)
{
$postCategory->addPost($this);
$this->postCategories[] = $postCategory;
return $this;
}
/**
* Remove postCategory
*
* @param \AppBundle\Entity\Social\PostCategory $postCategory
*/
public function removePostCategory(\AppBundle\Entity\Social\PostCategory $postCategory)
{
$this->postCategories->removeElement($postCategory);
}
/**
* Get postCategories
*
* @return \Doctrine\Common\Collections\Collection
*/
public function getPostCategories()
{
return $this->postCategories;
}
/**
* Constructor
* @param null $user
*/
public function __construct($user = null)
{
$this->postCategories = new \Doctrine\Common\Collections\ArrayCollection();
}
现在,在编辑 post 时,我也遇到了一个问题,因为它使用 getter 输出一个集合,而不是单个实体,而且我的类别字段没有正确填写。
/**
* Get postCategories
*
* @return \Doctrine\Common\Collections\Collection
*/
public function getPostCategories()
{
return $this->postCategories;
}
如果我设置 'multiple' => true 就可以工作,但我不想要这个,我希望用户只 select 一个类别,我不想只用断言。
当然有些情况下我想让用户select很多字段所以我想保持manyToMany关系。
我能做什么?
您可以将表单类型设置为不使用 PostCategory
by reference(将 by_reference
选项设置为 false)
这将强制 symfony 表单使用 addPostCategory
和 removePostCategory
而不是 setPostCategories。
UPD
1) 您正在混合使用普通数组和 ArrayCollection。选择一种策略。 Getter 将始终输出一个 ArrayCollection,因为它应该这样做。如果你想强制它成为普通数组,将 ->toArray()
方法添加到 getter
2) 我也理解 multiple=false
return 一个实体的选择,而 multiple=true
return 数组独立于映射关系(*toMany 或 *toOne) .因此,只需尝试从 class 中删除 setter,如果您希望在不同情况下有类似的行为,则仅使用加法器和去除器。
/** @var ArrayCollection|PostCategory[] */
private $postCategories;
public function __construct()
{
$this->postCategories = new ArrayCollection();
}
public function addPostCategory(PostCategory $postCategory)
{
if (!$this->postCategories->contains($postCategory) {
$postCategory->addPost($this);
$this->postCategories->add($postCategory);
}
}
public function removePostCategory(PostCategory $postCategory)
{
if ($this->postCategories->contains($postCategory) {
$postCategory->removePost($this);
$this->postCategories->add($postCategory);
}
}
/**
* @return ArrayCollection|PostCategory[]
*/
public function getPostCategories()
{
return $this->postCategories;
}
如果要在添加到 ManyToMany
集合时将 multiple
选项设置为 false
,可以在 "fake" 属性通过创建几个新的 getter 和 setter 并更新您的表单构建代码来创建实体。
(有趣的是,我在升级到 Symfony 2.7 后才在我的项目中看到这个问题,这迫使我设计了这个解决方案。)
这是一个使用您的实体的示例。该示例假设您需要验证(因为这有点复杂,所以希望这个答案对其他人更有用!)
将以下内容添加到您的 Post
class:
public function setSingleCategory(PostCategory $category = null)
{
// When binding invalid data, this may be null
// But it'll be caught later by the constraint set up in the form builder
// So that's okay!
if (!$category) {
return;
}
$this->postCategories->add($category);
}
// Which one should it use for pre-filling the form's default data?
// That's defined by this getter. I think you probably just want the first?
public function getSingleCategory()
{
return $this->postCategories->first();
}
现在更改表单中的这一行:
->add('postCategories', 'entity', array(
成为
->add('singleCategory', 'entity', array(
'constraints' => [
new NotNull(),
],
即我们更改了它引用的字段,还添加了一些内联验证 - 您无法通过注释设置验证,因为在您的 class 上没有 属性 调用 singleCategory
,只有一些使用该短语的方法。
在我的例子中,原因是 Doctrine 与 Join Table 没有一对多、单向的关系。在文档示例中显示山楂我们可以通过 ManyToMany 来完成这种关系(在第二列添加标志 unique=true)。
这种方式可以,但是Form组件自己混了
解决方案是更改实体中的 geter 和 seter class...即使是那些自动生成的。
这是我的案例(希望有人需要)。假设:classic 一对多关系,单向连接 Table
实体class:
/**
* @ORM\ManyToMany(targetEntity="B2B\AdminBundle\Entity\DictionaryValues")
* @ORM\JoinTable(
* name="users_responsibility",
* joinColumns={@ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="CASCADE")},
* inverseJoinColumns={@ORM\JoinColumn(name="responsibility_id", referencedColumnName="id", unique=true, onDelete="CASCADE")}
* )
*/
private $responsibility;
/**
* Constructor
*/
public function __construct()
{
$this->responsibility = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add responsibility
*
* @param \B2B\AdminBundle\Entity\DictionaryValues $responsibility
*
* @return User
*/
public function setResponsibility(\B2B\AdminBundle\Entity\DictionaryValues $responsibility = null)
{
if(count($this->responsibility) > 0){
foreach($this->responsibility as $item){
$this->removeResponsibility($item);
}
}
$this->responsibility[] = $responsibility;
return $this;
}
/**
* Remove responsibility
*
* @param \B2B\AdminBundle\Entity\DictionaryValues $responsibility
*/
public function removeResponsibility(\B2B\AdminBundle\Entity\DictionaryValues $responsibility)
{
$this->responsibility->removeElement($responsibility);
}
/**
* Get responsibility
*
* @return \Doctrine\Common\Collections\Collection
*/
public function getResponsibility()
{
return $this->responsibility->first();
}
表格:
->add('responsibility', EntityType::class,
array(
'required' => false,
'label' => 'Obszar odpowiedzialności:',
'class' => DictionaryValues::class,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('n')
->where('n.parent = 2')
->orderBy('n.id', 'ASC');
},
'choice_label' => 'value',
'placeholder' => 'Wybierz',
'multiple' => false,
'constraints' => array(
new NotBlank()
)
)
)
我知道这是一个很老的问题,但这个问题今天仍然有效。 使用简单的内联数据转换器对我有用。
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('profileTypes', EntityType::class, [
'multiple' => false,
'expanded' => true,
'class' => ProfileType::class,
]);
// data transformer so profileTypes does work with multiple => false
$builder->get('profileTypes')
->addModelTransformer(new CallbackTransformer(
// return first item from collection
fn ($data) => $data instanceof Collection && $data->count() ? $data->first() : $data,
// convert single ProfileType into collection
fn ($data) => $data && $data instanceof ProfileType ? new ArrayCollection([$data]) : $data
));
}
PS:Array functions 在 PHP 7.4 及更高版本中可用。