Symfony2表单:如何持久化具有可空连接的实体?

时间:2011-10-12 23:24:40

标签: doctrine-orm symfony symfony-forms

保存表单提交数据时,我无法持久保存新实体实例,其中实体与另一个实体具有可空的关联,并且我尝试将其设置为null。在为表单创建新的实体实例后,将提交的请求绑定到表单并持久化并刷新实体实例,具体取决于我如何填充关联实体的属性,我得到

  1. UnexpectedTypeException: Expected argument of type "object or array", "NULL" given(如果设置为null)或
  2. InvalidArgumentException: A new entity was found through the relationship 'AccessLog#document' that was not configured to cascade persist operations for entity(如果设置为相关实体的新的空实例,我不想继续这样做。)
  3. 如果我设置了cascade persist,它会尝试在相关表中创建一条记录(db中的数据模型不允许),即使没有数据可以保留。如果设置级联持续是要走的路,我该如何阻止它尝试创建新记录?处理这个问题的最佳方法是什么?

    注意,无论关联是设置为单向还是双向,行为都是相同的。

    详细信息:

    我有一个与另一个实体(缩写)有多对一关联的实体:

    /** @Entity */
    class AccessLog
    {
        /** @Id @Column(type="integer") */
        private $access_log_id;
    
        /** @Column(type="integer", nullable=true) */
        private $document_id;
    
        /**
         * @ManyToOne(targetEntity="Document", inversedBy="access_logs", cascade={"persist"})
         * @JoinColumn(name="document_id", referencedColumnName="document_id")
         */
        private $document;
    
        // plus other fields
        // plus getters and setters for all of the above...
    }
    

    相关实体没什么特别的:

    /** @Entity */
    class Document
    {
        /** @Id @Column(type="integer") */
        private $document_id;
    
        /** @Column(length=255) */
        private $name;
    
        /** @OneToMany(targetEntity="AccessLog", mappedBy="document") */
        private $access_logs;
    
        // plus other fields
        // plus getters and setters for all of the above...
    }
    

    我有一个Symfony表单,用于输入新的AccessLog记录的数据:

    class AccessLogFormType extends AbstractType
    {
        public function getName()
        {
            return 'access_log_form';
        }
    
        public function getDefaultOptions(array $options)
        {
            return array('data_class' => 'AccessLog');
        }
    
        public function buildForm(FormBuilder $builder, array $options)
        {
            $builder->add('access_log_id', 'hidden');
            $builder->add('document_id', 'hidden', array(
                'required' => false
            ));
            $builder->add('document', new DocumentType(), array(
                'label' => 'Document',
                'required' => false
            ));
            //...
        }
    }
    

    使用以下支持类型定义:

    class DocumentType extends AbstractType
    {
        public function getName()
        {
            return 'document';
        }
    
        public function getDefaultOptions(array $options)
        {
            return array('data_class' => 'Document');
        }
    
        public function buildForm(FormBuilder $builder, array $options)
        {
            $builder->add('name', 'text', array(
                'required' => false
            ));
        }
    }
    

    我的控制器包括以下内容:

    public function save_access_log_action()
    {
        $request = $this->get('request');
        $em = $this->get('doctrine.orm')->getEntityManager();
    
        $access_log = null;
    
        if ($request->getMethod() === 'POST') {
            $data = $request->request->get('access_log_form');
    
            if (is_numeric($data['access_log_id'])) {
                $access_log = $em->find('AccessLog', $data['access_log_id']);
            } else {
                $access_log = new AccessLog();
            }
    
            if (is_numeric($data['document_id'])) {
                $document = $em->find('Document', $data['document_id']);
                $access_log->set_document($document);
    
            } else {
                // Not calling set_document() since there shouldn't be a
                // related Document entity.
            }
    
            $form = $this->get('form.factory')
                ->createBuilder(new AccessLogFormType(), $access_log)
                ->getForm();
    
            $form->bindRequest($request);
    
            if ($form->isValid()) {
                $em->persist($access_log);
                $em->flush();
            }
    
        } else {
            // ... (handle get request)
        }
    
        return $this->render('access_log_form.tpl', array(
            'form' => $form->createView()
        ));
    }
    

    上述代码在更新现有访问日志条目或创建新条目并在表单中选择文档时工作正常,但如果没有选择文档则不行。

    假设无法更改数据模型,如何在不持久保存新Document实体的情况下持久保存新的AccessLog实体?

5 个答案:

答案 0 :(得分:3)

似乎解决方案是在persist操作之前将set_document(null)调用移到右边,因为将请求绑定到表单会导致将空Document对象附加到AccessLog对象的$ document属性。 / p>

此解决方案在删除级联持久性并使关联单向后也可以使用。

感谢@ greg0ire的帮助。

[更新]有必要将@ChangeTrackingPolicy("DEFERRED_EXPLICIT")添加到Document实体定义,因为它继续尝试更新与AccessLog记录关联的Document记录,例如在删除现有关联时(导致$要在Document上设置为null的name属性,然后在db中。当删除关联时,默认行为是删除/修改数据,这是非常令人沮丧的,即使未指定级联持久性也是如此。

答案 1 :(得分:2)

我认为您的问题可能就是您在AccessLog中定义文档设置器的方式。

如果你这样做:

setDocument(Document $document)

您永远无法setDocument(null),因为null不是Document的实例。为此,

setDocument($document) or setDocument(Document $docuement = null)

答案 2 :(得分:1)

这是在Symfony2 master中修复的。现在,空表单不再导致空对象,而是返回null。

答案 3 :(得分:0)

您是否阅读了the Doctrine2 documentation about Transitive persistence / Cascade Operations的页面?我认为如果文档存在,您首先需要remove,如果没有,那么您就不应该调用setDocument()。为了避免第二个错误,§解释了如何配置级联(请参阅最后一个代码段)。

答案 4 :(得分:0)

在您的AccessLogFormType中,您应该更改

$builder->add('document', new DocumentType(), array(
            'label' => 'Document',
            'required' => false
        ));

为:

$builder->add('document', new DocumentType(), array(
            'label' => 'Document',
            'required' => false,
            'empty_value' => '', 
            'empty_data' => null
        ));

这使您可以在不设置文档的情况下保存实体。