Symfony实体字段:manyToMany,其中multiple = false - 字段未正确填充

时间:2015-02-20 09:46:34

标签: php forms symfony doctrine-orm entity

我正在使用带有教义2的symfony2。 我在两个实体之间有很多关系:

/**
 * @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;

现在我想让用户只选择一个类别。为此,我使用选项'multiple'=>我的形式是假的。

我的表格:

        ->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();
}

现在,在编辑帖子时,我也有一个问题,因为它使用了一个输出集合而不是单个实体的getter,并且我的类别字段未正确填充。

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

如果我设置'multiple'=>它会工作是的,但我不想要这个,我希望用户只选择一个类别,我不希望仅用断言约束它。

当然有些情况下我想让用户选择多个字段,所以我想保持manyToMany关系。

我该怎么办?

4 个答案:

答案 0 :(得分:2)

如果您想在添加到multiple集合时将false选项设置为ManyToMany,则可以通过创建一些新的getter来在实体上使用“假”属性和setter,并更新您的表单构建代码。

(有趣的是,我在升级到Symfony 2.7之后才在我的项目中看到了这个问题,这迫使我设计这个解决方案。)

以下是使用您的实体的示例。该示例假设您需要验证(因为这有点复杂,所以这个答案希望对其他人更有用!)

将以下内容添加到Post课程中:

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(),
    ],

即。我们已经更改了它引用的字段,并且还添加了一些内联验证 - 您无法通过注释设置验证,因为您的类上没有名为singleCategory的属性,只有一些方法使用该短语。

答案 1 :(得分:0)

您可以将表单类型设置为不使用PostCategory by reference(将by_reference选项设置为false)

这会强制symfony表单使用addPostCategoryremovePostCategory而不是setPostCategories。

<强> UPD

1)您正在使用plain array和ArrayCollection进行混合。选择一种策略。 Getter将始终输出一个ArrayCollection,因为它应该这样做。如果你想强制它为普通数组,请将->toArray()方法添加到getter

2)我也理解用multiple=false选择返回实体,而multiple=true返回映射关系数组(* toMany或* toOne)。因此,如果您想在不同情况下采取类似的行为,只需尝试从类中删除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;
}

答案 2 :(得分:0)

在我的情况下,原因是Doctrine与Join Table没有一对多,单向关系。在“文档”示例中,展示了如何通过ManyToMany进行这种关联(在第二列上添加标志unique = true)。

这样可以,但是Form组件会自己混音。

解决方案是更改实体类中的获取器和设置器,甚至是自动生成的获取器和设置器。

这是我的情况(我希望有人会需要它)。假设:经典的一对多关系,带有联接表的单向关系

实体类:

/**
 * @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()
        )
    )
)

答案 3 :(得分:0)

我知道这是一个很老的问题,但这个问题今天仍然有效。 使用简单的内联数据转换器对我有用。

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 及更高版本中可用。