学说无法删除 1:M 的 M 侧的实体

doctrine silently fails to delete entities on M side of 1:M

我开始怀疑这行不通,因为在我的用例中它根本行不通——而不是我遗漏了一些东西——但我必须咨询你的专业知识来确定,并看看如果有人可以建议解决方法。

我有一个多对多的情况,我正在使用一个关联 class 来实现,所以我们在 3 个参与的 class 之间有 one-to-many/many-to-one 个关联。有一个代表人的解释器实体和一个代表口语的语言实体(实际上是一对工作语言,但在这个以英语为中心的应用程序中,其中一半被理解为英语)。一个 Interpreter 可以有多种语言,Language 是多个 Interpreter 的工作语言之一。我们需要管理解释器语言的其他属性,因此需要 InterpreterLanguage class.

当我调用 $interpreter->removeInterpreterLanguage($interpreterLanguage); 后跟 $entityManager->flush() 时,内存中解释器实体的 $interpreterLanguages 集合中的元素少了一个,正如您所期望的那样,并且没有错误或抛出异常,但在数据库中发生了以下情况:nothing.

我已经在 MVC 上下文中尝试过此操作,使用 ZendFramework 3 和绑定 Zend\Form\Form 字段集,当这让我抓狂时,我编写了一个 CLI 脚本来尝试检查问题——同样的结果。也许值得注意的是,对于更新标量属性,它工作正常。

我很抱歉没有将 link 包含在我之前阅读的关于这个问题的讨论中——由于某种原因现在找不到了。但我记得有人说它只是行不通,因为 Doctrine 看到另一边的 M:1,因此不会删除,你必须说 $entityManager->remove($object) 才能完成。我的实验性 CLI 脚本似乎证实了这一点。不过,我想排除我做错事的可能性。

有什么想法吗?解决建议?

这是我的语言实体:

/** module/InterpretersOffice/src/Entity/Language.php */

namespace InterpretersOffice\Entity;

use Doctrine\ORM\Mapping as ORM;
use Zend\Form\Annotation;
use Doctrine\Common\Collections\ArrayCollection;

/**
 * Entity class representing a language used by an Interpreter.
 *
 * @Annotation\Name("language")
 * @ORM\Entity(repositoryClass="InterpretersOffice\Entity\Repository\LanguageRepository")
 * @ORM\Table(name="languages",uniqueConstraints={@ORM\UniqueConstraint(name="unique_language",columns={"name"})})
 */
class Language
{
    /**
     * entity id.
     *     
     * @ORM\Id
     * @ORM\GeneratedValue @ORM\Column(type="smallint",options={"unsigned":true})
     */
    protected $id;

    /**
     * name of the language.
     *
     * @ORM\Column(type="string",length=50,nullable=false)
     *
     * @var string
     */
    protected $name;

    /**
     * comments.
     *
     * @ORM\Column(type="string",length=300,nullable=false,options={"default":""})
     *
     * @var string
     */
    protected $comments = '';

    /**
     *
     * @ORM\OneToMany(targetEntity="InterpreterLanguage",mappedBy="language")
     */
    protected $interpreterLanguages;

    /**
     * constructor
     */
    public function __construct()
    {
        $this->interpreterLanguages = new ArrayCollection();
    }
    // setters and getters omitted for brevity

}

这是解释器实体:

<?php
/** module/InterpretersOffice/src/Entity/Interpreter.php */

namespace InterpretersOffice\Entity;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;

/**
 * Entity representing an Interpreter.
 *
 * @ORM\Entity(repositoryClass="InterpretersOffice\Entity\Repository\InterpreterRepository")
 * @ORM\Table(name="interpreters")
 */
class Interpreter extends Person
{
    /**
     * entity id.
     *
     * @ORM\Id @ORM\GeneratedValue @ORM\Column(type="smallint",options={"unsigned":true})
     */
    protected $id;

    /**
     * phone number.
     *
     * @ORM\Column(type="string",length=16,nullable=true)
     *
     * @var string
     */
    protected $phone;

    /**
     * date of birth.
     *
     * @ORM\Column(type="date",nullable=true)
     *
     * @var string
     */
    protected $dob;

    /**
     * working languages.
     *
     * @ORM\OneToMany(targetEntity="InterpreterLanguage",mappedBy="interpreter", cascade={"persist", "remove"})
     * 
     *
     * @var ArrayCollection of InterpreterLanguage
     */
    protected $interpreterLanguages;

    /**
     * Constructor.
     */
    public function __construct()
    {
        $this->interpreterLanguages = new ArrayCollection();
    }

    // some boring setters and getters omitted.... 

    /**
     * Add interpreterLanguage.
     *
     * @param InterpreterLanguage $interpreterLanguage
     *
     * @return Interpreter
     */
    public function addInterpreterLanguage(InterpreterLanguage $interpreterLanguage)
    {
        $this->interpreterLanguages->add($interpreterLanguage);
        return $this;
    }

    /**
     * Remove interpreterLanguage.
     *
     * @param \InterpretersOffice\Entity\InterpreterLanguage $interpreterLanguage
     *
     * @return Interpreter
     */
    public function removeInterpreterLanguage(InterpreterLanguage $interpreterLanguage)
    {       

        $this->interpreterLanguages->removeElement($interpreterLanguage);
        //$interpreterLanguage->setInterpreter(null)->setLanguage(null);
        return $this;
    }

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

    /*
     because "AllowRemove strategy for DoctrineModule hydrator requires both addInterpreterLanguages and  removeInterpreterLanguages to be defined in InterpretersOffice\Entity\Interpreter entity domain code, but one or both 
     [seemed] to be missing"
     */


    public function addInterpreterLanguages(Collection $interpreterLanguages)
    {
        foreach ($interpreterLanguages as $interpreterLanguage) {

            $interpreterLanguage->setInterpreter($this);
            $this->interpreterLanguages->add($interpreterLanguage);
        }
    }

    public function removeInterpreterLanguages(Collection $interpreterLanguages)
    {
        foreach ($interpreterLanguages as $interpreterLanguage) {

            $this->interpreterLanguages->removeElement($interpreterLanguage);

        }
    }

}

和协会 class:

/** module/InterpretersOffice/src/Entity/InterpreterLanguage.php  */

namespace InterpretersOffice\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * Entity representing an Interpreter's Language.
 *
 * Technically, it is a language *pair*, but in this system it is understood that
 * the other language of the pair is English. There is a many-to-many relationship
 * between interpreters and languages. But because there is also metadata to record
 * about the language (federal certification), it is implemented as a Many-To-One
 * relationship on either side.
 *
 * @ORM\Entity
 * @ORM\Table(name="interpreters_languages")
 */
class InterpreterLanguage
{
    /**
     * constructor.
     *
     * @param Interpreter $interpreter
     * @param Language    $language
     *
     * @todo a lifecycle callback to ensure certified languages have a boolean
     * $federalCertification set
     */
    public function __construct(
        Interpreter $interpreter = null,
        Language $language = null
    ) {
        if ($interpreter) {
            $this->setInterpreter($interpreter);
        }
        if ($language) {
            $this->setLanguage($language);
        }
    }

    /**
     * The Interpreter who works in this language.
     *
     * @ORM\ManyToOne(targetEntity="Interpreter",inversedBy="interpreterLanguages")
     * @ORM\Id
     *
     * @var Interpreter
     */
    protected $interpreter;

    /**
     * The language in which this interpreter works.
     *
     * @ORM\ManyToOne(targetEntity="Language",inversedBy="interpreterLanguages")
     * @ORM\Id
     *
     * @var Language
     */
    protected $language;

    /**
     * Whether the Interpreter holds federal court interpreter certification in this language.
     *
     * The only certified languages in the US District Court system are Spanish,
     * Navajo and Haitian Creole. Of these, only the Spanish certification
     * program is active. This field should be a boolean for the certified
     * languages and null for everything else.
     *
     * @link http://www.uscourts.gov/services-forms/federal-court-interpreters/federal-court-interpreter-certification-examination the federal court certification program
     *
     * @ORM\Column(name="federal_certification",type="boolean",nullable=true)
     *
     * @var bool
     */
    protected $federalCertification;

    /**
     * Set interpreter.
     *
     * @param \InterpretersOffice\Entity\Interpreter $interpreter
     *
     * @return InterpreterLanguage
     */
    public function setInterpreter(Interpreter $interpreter = null)
    {
        $this->interpreter = $interpreter;

        return $this;
    }

    /**
     * Get interpreter.
     *
     * @return Interpreter
     */
    public function getInterpreter()
    {
        return $this->interpreter;
    }

    /**
     * Set language.
     *
     * @param Language $language
     *
     * @return InterpreterLanguage
     */
    public function setLanguage(Language $language = null)
    {
        $this->language = $language;

        return $this;
    }

    /**
     * Get language.
     *
     * @return Language
     */
    public function getLanguage()
    {
        return $this->language;
    }

    /**
     * Set federalCertification.
     *
     * @param bool $federalCertification
     *
     * @return InterpreterLanguage
     */
    public function setFederalCertification($federalCertification)
    {
        $this->federalCertification = $federalCertification;

        return $this;
    }

    /**
     * Get federalCertification.
     *
     * @return bool
     */
    public function getFederalCertification()
    {
        return $this->federalCertification;
    }
}

为了简洁起见,我将省略 Form 和 Fieldset classes 的代码——它们似乎工作正常(看起来也很有品位。谢谢 Bootstrap) .我加载表单,删除其中一个 InterpreterLanguages 并提交...这是控制器操作:

/**
 * updates an Interpreter entity.
 */
public function editAction()
{
    $viewModel = (new ViewModel())
            ->setTemplate('interpreters-office/admin/interpreters/form.phtml')
            ->setVariable('title', 'edit an interpreter');
    $id = $this->params()->fromRoute('id');

    $entity = $this->entityManager->find('InterpretersOffice\Entity\Interpreter', $id);
    if (!$entity) {
        return $viewModel->setVariables(['errorMessage' => "interpreter with id $id not found"]);
    }
    $form = new InterpreterForm($this->entityManager, ['action' => 'update']);
    $form->bind($entity);
    $viewModel->setVariables(['form' => $form, 'id' => $id ]);

    $request = $this->getRequest();
    if ($request->isPost()) {
        $form->setData($request->getPost());
        if (!$form->isValid()) {
            return $viewModel;
        }
        $this->entityManager->flush();
        $this->flashMessenger()
              ->addSuccessMessage(sprintf(
                  'The interpreter <strong>%s %s</strong> has been updated.',
                  $entity->getFirstname(),
                  $entity->getLastname()
              ));
        // dump the entity and see how it looksa after update
        echo "NOT redirecting. entity:<pre>";
        \Doctrine\Common\Util\Debug::dump($entity); echo "</pre>";
        //$this->redirect()->toRoute('interpreters');
    } else { 
        // dump the entity fresh from the database
        echo "loaded:<pre> "; \Doctrine\Common\Util\Debug::dump($entity);echo "</pre>";}

    return $viewModel;
}

同样,数据在转储到屏幕时看起来是正确的,但是您重新加载表单并且集合中的元素与以前一样多。

谢谢!

在 Interpreter.php 中,orphanRemoval=true !!

/**
 * working languages.
 *
 * @ORM\OneToMany(targetEntity="InterpreterLanguage",mappedBy="interpreter", 
 * cascade={"persist", "remove"},orphanRemoval=true)
 *
 * @var ArrayCollection of InterpreterLanguage
 */
protected $interpreterLanguages;