调用一个非对象的成员函数removeElement()

3
我正在创建一个表单,以便向与特定课程相关联的一组学生发送电子邮件消息。默认情况下,必须选择给定课程的所有学生,但消息的发送者必须能够取消选择学生以将其排除在接收消息之外。向整个组发送没有问题。问题出现在从选择中删除学生时。 我正在使用Sonata Admin的sonata_type_model和自定义查询。在生成的表单上,如果我不更改选择选项并提交表单,则一切正常。当我从列表中删除项目时,在提交表单后会出现错误: 错误:在/xxx/xxx/xxx/vendor/sonata-project/doctrine-orm-admin-bundle/Model/ModelManager.php行607上调用非对象的removeElement()成员函数 经过两天的搜索,希望这里的某个人可以帮助我找到正确的方向。这是我使用的一些代码: 管理员:
$em = $this->modelManager->getEntityManager('Stnu\EduBundle\Entity\DealItem');
    $query = $em->createQueryBuilder('d')
            ->select('d')
            ->from('StnuEduBundle:DealItem', 'd')
            ->innerJoin('d.deal', 'de')
            ->where('d.course = :course')
            ->andWhere('de.status = :status')
            ->setParameter('course',$course)
            ->setParameter('status','order');

    $defaults = $query->getQuery()->getResult();


    $formMapper
            ->with('Certificaten verzenden cursus \''. $title .'\'', array('description' => 'Begeleidende tekst e-mail'))
                ->add('dealItems', 'sonata_type_model', array(
                    'required' => true,
                    'expanded' => false,
                    'btn_add' => false,
                    'multiple' => true,
                    'label' => 'Verzenden aan',
                    'query' => $query,
                    'property' => 'deal.user',
                    'data' => $defaults,
                    'validation_groups' => false
                ))
                ->add('subject', 'text', array('required' => true, 'label' => 'Onderwerp', 'data' => $subject))
                ->add('body', 'textarea', array('label' => 'Bericht', 'required' => false, 'data' => $body, 'attr' => array('class' => 'tinymce', 'data-theme' => 'fullpage', 'style' => 'height: 350px')));

控制器:

/**
 * Create action
 *
 * @return Response
 *
 * @throws AccessDeniedException If access is not granted
 */
public function createAction()
{

    // the key used to lookup the template
    $templateKey = 'edit';

    if (false === $this->admin->isGranted('CREATE')) {
        throw new AccessDeniedException();
    }

    $object = $this->admin->getNewInstance();

    $this->admin->setSubject($object);


    /** @var $form \Symfony\Component\Form\Form */
    $form = $this->admin->getForm();
    $form->setData($object);


    if ($this->getRestMethod()== 'POST') {

        $object->setDealItems($object->getDealItems());

        $form->submit($this->get('request'));

这个错误出现在此点之后。

实体:

<?php

namespace Stnu\EduBundle\Entity;

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

/**
 * DocsEmail
 * 
 * @ORM\Entity
 */
class CertificateEmail {

    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;


    /**
     * @ORM\ManyToMany(targetEntity="DealItem")
     * @ORM\JoinTable(name="certificateemails_dealitems",
     *      joinColumns={@ORM\JoinColumn(name="certificateEmail_id", referencedColumnName="id")},
     *      inverseJoinColumns={@ORM\JoinColumn(name="dealItem_id", referencedColumnName="id")}
     *      )
     */
    private $dealItems;

    private $subject;

    private $body;

    private $extraEmailTo;

    public function __construct() {
        $this->dealItems = new ArrayCollection();
    }

    /**
     * Add dealItem
     *
     */
    public function addDealItem(\Stnu\EduBundle\Entity\DealItem $dealItem) {

        $this->dealItems->add($dealItem);
        //$this->dealItems[] = $dealItem;
        return $this;
    }

    /**
     * Remove dealItem
     */
    public function removeDealItem(\Stnu\EduBundle\Entity\DealItem $dealItem) {

        foreach ($this->dealItems as $item) {
            if ($dealItem === $item) {
                // manager of Stnu\EduBundle\Entity\DealItem
                $entityManager->remove($dealItem);
            }
        }

    }

    /**
     * Get dealItems
     *
     * @return \Doctrine\Common\Collections\Collection 
     */
    public function getDealItems() {

        return $this->dealItems;
    }

    public function setDealItems($dealItems) {
        $this->dealItems = new ArrayCollection();

        if (count($dealItems) > 0) {
            foreach ($dealItems as $dealItem) {
                $this->addDealItem($dealItem);
            }
        }

        return $this;
    }


    /**
     * Set subject
     *
     * @param string $subject
     */
    public function setSubject($subject) {
        $this->subject = $subject;

        return $this;
    }

    /**
     * Get subject
     *
     * @return string 
     */
    public function getSubject() {
        return $this->subject;
    }

    /**
     * Set body
     *
     * @param string $body
     */
    public function setBody($body) {
        $this->body = $body;

        return $this;
    }

    /**
     * Get body
     *
     * @return string 
     */
    public function getBody() {
        return $this->body;
    }

    /**
     * Set extraEmailTo
     *
     * @param string $extraEmailTo
     */
    public function setExtraEmailTo($extraEmailTo) {
        $this->extraEmailTo = $extraEmailTo;

        return $this;
    }

    /**
     * Get extraEmailTo
     *
     * @return string 
     */
    public function getExtraEmailTo() {
        return $this->extraEmailTo;
    }

}
希望有人能帮助我!

错误提示表明在调用_removeElement()_时,_dealItems_为空。我可以看到您在对象构造时初始化了集合 - 所以这对我来说意味着在某个地方,_dealItems_被销毁了...但我看不到是在哪里。 - Ragdata
这里发生了什么 - $object->setDealItems($object->getDealItems()); - Ragdata
这个也有问题 @Stijlnu - $form->submit($this->get('request')); 你在上面的评论中提到的Form类没有_submit_方法...你扩展了它吗? - Ragdata
我刚刚看到了你的评论,关于不需要将数据从电子邮件持久化。那么,使用你上面提到的查询来填充一个简单的“选择”字段类型怎么样?选择字段类型 如果你不需要完整的实体 - 我猜你只需要学生的电子邮件地址 - 那么这可能是一个好方法。如果你认为这有帮助,请告诉我,我会编写一个示例代码。 - Ragdata
@Stijlnu - 是的,它在那里...请查看我下面更新的答案。 - Ragdata
显示剩余5条评论
2个回答

5
我相信这已经接近了你问题的答案。但是,你问得完全错了。重要的东西在下面,“EDIT - 自定义多选字段”部分。 你的 removeDealItem 方法是错误的。尝试这样做:
    public function removeDealItem(\Stnu\EduBundle\Entity\DealItem $dealItem)
    {
        $this->dealItems->removeElement( $dealItem );

        return $this;
    }

这里不需要有$entityManager,也不需要用到。Doctrine会检查你要删除的实体是否存在,并在存在时将其删除。你不需要遍历集合中的现有元素,当然也不需要在数据库层面上做任何事情。

    public function addDealItem(\Stnu\EduBundle\Entity\DealItem $dealItem)
    {
        // Getting fancy - check if the item exists before adding it
        if( !$this->dealItems->contains($dealItem) )
        {
            $this->dealItems->add($dealItem);
        }
        return $this;
    }

添加一个项目就像是轻而易举的事情...我们甚至可以使用Doctrine ArrayCollection::contains()方法检查元素是否存在,然后再将其添加。你的addDealItem()方法没有任何问题 - 我只是想向你展示contains()作为让ArrayCollection类为你完成工作的更加显眼的例子。

编辑 - 自定义多选框字段

好的 - 在阅读了您关于不一定需要持久化数据的评论之后,我想提供这个简化的示例,演示如何创建自定义的多选框。请注意,这只是一个“裸基础”示例 - 但它应该引导您走向正确的方向。显然,由于不知道您的DealItem实体的结构,我只是猜测您需要访问哪些特定字段以获取所需的数据来发送电子邮件。

所以 - 在您的控制器中 - 首先获取数据:

$query = $em->createQueryBuilder('d')
            ->select('d')
            ->from('StnuEduBundle:DealItem', 'd')
            ->innerJoin('d.deal', 'de')
            ->where('d.course = :course')
            ->andWhere('de.status = :status')
            ->setParameter('course',$course)
            ->setParameter('status','order');

$defaults = $query->getQuery()->getResult();

$choices = array();

foreach( $defaults as $dealItem )
{
    $choices[ $dealItem->getEmailAddress() ] = $dealItem->getStudentName();
}
现在我们需要一个对象来接收数据。从您的评论中我了解到,您不想保留这些数据,并且只是为了构建表单而创建了一个CertificateEmail对象实体。这是一个不好的想法。您不需要一个实体 - 所以一开始就不要创建。为了证明这一点,我将使用stdClass对象完成它:
$certificateEmail = new \stdClass();

$certificateEmail->dealItems = array();
$certificateEmail->subject   = '';
$certificateEmail->body      = '';
然后我们构建表单:
$form = $this->createFormBuilder( $certificateEmail )
             ->add( 'dealItems', 'choice', array(
                         'choices'  => $choices,
                         'multiple' => true,
                         'required' => true,
                         'label'    => 'Verzenden aan' ) )
             ->add( 'subject', 'text', array( 'required' => true, 'label' => 'Onderwerp' ) )
             ->add( 'body', 'textarea', array( 'required' => false, 'label' => 'Bericht' ) )
             ->getForm();
最后,将其应用到模板中:
return $this->render( 'template.html.twig', array( 'form' => $form->createView() ) );
希望你能够理解并继续进行 :)

_removeDealItem_确实需要修复 - 但我开始觉得这不是你的问题所在。 - Ragdata
作为测试,我刚刚从头开始创建了一个新的实体和表单管理员类,没有大部分自定义编码。我只保留了sonata_type_collection的自定义查询。这次我让它持久化到数据库中。同样的问题发生了。只有在不删除选择项的情况下,持久化才能正常工作。如果我不使用自定义查询进行测试,那么就无法工作,因为我会收到OutOfMemoryException异常。 - Stijlnu
1
@Stijlnu - 试一下那个更新的答案,然后告诉我你的进展如何。 - Ragdata
非常感谢提供的脚本示例。我认为我会重写我的代码,让它更加简洁。这确实帮助我朝着正确的方向前进。我喜欢Symfony的许多方面,但对我来说学习曲线很陡峭。 - Stijlnu
2
我最终通过将$defaults = $query->getQuery()->getResult();更改为$defaults = new Collections\ArrayCollection($query->getQuery()->getResult());来解决它。 - Stijlnu
显示剩余8条评论

1

在执行删除操作之前,请检查您的收藏。

ldd($object->getDealItems()); // or var_dump();die();
我认为当调用删除操作时,您的属性dealItems为空。

当您创建NewInstance时,您使用$object->setDealItems($object->getDealItems());。您确定在删除操作时$object->getDealItems()返回集合吗? - Danila Ganchar
1
需要更多的代码...现在它并没有太多意义。不知何故,某个地方正在成功取消设置DealItem实体的_private_属性...这必须发生在实体本身的某个地方。 - Ragdata
1
我仍然想知道$object->setDealItems($object->getDealItems());这行代码的目的是什么。根据你发布的代码,它似乎并不重要,因为它看起来会导致一个空的ArrayCollection对象...但是我觉得它有点可疑。 - Ragdata
1
这对我来说真的没有意义 - 在您的实体中它被设置了两次 - 一次在对象初始化时,一次在执行_setDealItems()_方法时。您的实体类中必须有某些东西正在取消设置它...它是一个私有属性。 - Ragdata
1
我已经更新了我的问题,并附上了实体的完整代码。 - Stijlnu
显示剩余25条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,