Cannot remove on collection item (using CollectionType, OneToMany bidirectional)
Symfony2.8.10 と Doctrine を使って、コレクションを永続化することについて質問があります。
以下の2つのクラス Article を ArticleAttribute を書きました。
/**
* Article
*
* @ORM\Table(name="article")
* @ORM\Entity(repositoryClass="AppBundle\Repository\ArticleRepository")
* @ORM\HasLifecycleCallbacks
* */
class Article
{
/** @ORM\Id @ORM\Column(type="integer") */
private $id;
/** @ORM\Column(type="string") */
private $title;
/**
* @var ArticleAttribute[]
* @ORM\OneToMany(targetEntity="ArticleAttribute", mappedBy="article", cascade={"ALL"})
*/
private $attributes;
/**
* Constructor
*/
public function __construct()
{
$this->attributes = new ArrayCollection();
}
/**
* Get attributes
*
* @return ArticleAttribute[]
*/
public function getAttributes()
{
return $this->attributes;
}
/**
* @param $item ArticleAttribute
*/
public function addAttribute($item)
{
$this->attributes[$item->getAttribute()] = $item;
$item->setArticle($this);
}
/**
* Remove attributes
*
* @param ArticleAttribute $attribute
*/
public function removeAttribute(ArticleAttribute $attribute)
{
$this->attributes->removeElement($attribute);
}
...
/**
* @ORM\Table(name="article_attribute")
* @ORM\Entity
*/
class ArticleAttribute
{
/**
* @var Article
* @ORM\Id
* @ORM\ManyToOne(targetEntity="Article", inversedBy="attributes")
* @ORM\JoinColumn(name="article_id", referencedColumnName="id")
*/
private $article;
/**
* @ORM\Id
* @ORM\Column(type="string")
*/
private $attribute;
/**
* Get article
*
* @return Article
*/
public function getArticle()
{
return $this->article;
}
/**
* Set article
*
* @param Article $article
* @return ArticleAttribute
*/
public function setArticle(Article $article)
{
$this->article = $article;
return $this;
}
....
これらのクラスをフォームにバインドしました。
class ArticleType extends AbstractType
{
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('id')
->add('title')
->add('attributes', CollectionType::class, [
'entry_type' => ArticleAttributeType::class,
'allow_add' => true,
'allow_delete' => true,
]);
}
上記コードはフォームタイプの一部です。
いくつかの ArticleAttributes をページ上で削除します。
サブミットします。
handleRequest と $em->flush(); を呼び出します。
しかし、削除した項目はデータベースに残ったままです。
私は回避方法を見つけました。
FormResizeListener クラスの onSubmit メソッドを変更しました。
public function onSubmit(FormEvent $event)
{
/** @var FormBuilder $form */
$form = $event->getForm();
/** @var PersistentCollection $data */
$data = $event->getData();
// At this point, $data is an array or an array-like object that already contains the
// new entries, which were added by the data mapper. The data mapper ignores existing
// entries, so we need to manually unset removed entries in the collection.
if (null === $data) {
$data = [];
}
if (!is_array($data) && !($data instanceof \Traversable && $data instanceof \ArrayAccess)) {
throw new UnexpectedTypeException($data, 'array or (\Traversable and \ArrayAccess)');
}
if ($this->deleteEmpty) {
$previousData = $event->getForm()->getData();
foreach ($form as $name => $child) {
$isNew = !isset($previousData[$name]);
// $isNew can only be true if allowAdd is true, so we don't
// need to check allowAdd again
if ($child->isEmpty() && ($isNew || $this->allowDelete)) {
unset($data[$name]);
$form->remove($name);
}
}
}
// The data mapper only adds, but does not remove items, so do this
// here
if ($this->allowDelete) {
$arr = (array)$data; //added
/** @var EntityManager $em */ //added
$em = @$arr["\0Doctrine\\ORM\\PersistentCollection\0em"]; //added
$toDelete = [];
foreach ($data as $name => $child) {
if (!$form->has($name)) {
$toDelete[] = $name;
}
}
foreach ($toDelete as $name) {
if ($em) $em->remove($data[$name]); //added
unset($data[$name]);
}
}
$event->setData($data);
}
変更後は、削除した項目がデータベースから正しく削除されました。
しかし、これは汚いと考えます。
この問題を回避する正しい方法をお教えください。
あなたの助けを大いに評価します。