Doctrine orm Symfony2表单:如何持久化具有可空关联的实体?

Doctrine orm Symfony2表单:如何持久化具有可空关联的实体?,doctrine-orm,symfony,symfony-forms,Doctrine Orm,Symfony,Symfony Forms,在保存表单提交数据时,我在持久化一个新实体实例时遇到了问题,其中该实体与另一个实体具有可为null的关联,并且我尝试将其设置为null。在为表单创建新实体实例、将提交的请求绑定到表单并持久化和刷新实体实例之后,根据我为关联实体填充属性的方式,我可以 UnexpectedTypeException:类型为“object或array”的预期参数,给定为NULL(如果设置为NULL),或 InvalidArgumentException:通过关系“AccessLog#document”找到了一个新实体

在保存表单提交数据时,我在持久化一个新实体实例时遇到了问题,其中该实体与另一个实体具有可为null的关联,并且我尝试将其设置为null。在为表单创建新实体实例、将提交的请求绑定到表单并持久化和刷新实体实例之后,根据我为关联实体填充属性的方式,我可以

  • UnexpectedTypeException:类型为“object或array”的预期参数,给定为NULL(如果设置为NULL),或
  • InvalidArgumentException:通过关系“AccessLog#document”找到了一个新实体,该关系未配置为级联实体的持久化操作
    (如果设置为相关实体的新的空实例,我不想持久化)
  • 如果我设置了cascade persist,它会尝试在相关表中创建一条记录(数据库中的数据模型不允许这样做),即使没有数据可供其保存。如果设置cascade persist是一种方法,那么如何防止它尝试创建新记录?最好的处理方法是什么

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

    详细信息:

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

    相关实体并不奇怪:

    /** @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()
        ));
    }
    
    当更新现有的访问日志条目或创建新条目并且在表单中选择了文档时,上述代码可以正常工作,但如果未选择任何文档,则不能正常工作


    假设数据模型无法更改,如何在不保留新文档实体的情况下保留新的AccessLog实体?

    您是否阅读了的页面?我认为,如果文档存在,您首先需要
    删除它,如果它不存在,那么您就不应该调用
    setDocument()
    。为了避免第二个错误,§解释了如何配置级联(请参阅最后一个代码段)。

    解决方案似乎是在持久化操作之前将set_document(null)调用移到右侧,因为将请求绑定到表单导致将空文档对象附加到AccessLog对象的$Document属性

    此解决方案在移除级联并使关联单向后也可以工作

    感谢@greg0ire的帮助


    [更新]还需要在文档实体定义中添加
    @ChangeTrackingPolicy(“DEFERRED\u EXPLICIT”)
    ,因为它会继续尝试更新与AccessLog记录关联的文档记录,例如在删除现有关联时(这导致文档上的$name属性设置为null,然后在db中设置为null)。当删除关联时,默认行为是删除/修改数据,即使没有指定cascade persist。

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

    如果您这样做:

    setDocument(Document $document)
    
    您将永远无法
    设置文档(null)
    ,因为null不是文档的实例。为此

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

    在AccessLogFormType中,您应该更改

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


    这允许您在不设置文档的情况下保存实体。

    这在Symfony2 master中已修复。空表单现在不再生成空对象,而是返回null。

    为什么需要执行
    设置文档(null)
    ?为什么不直接调用
    setDocument
    ?我已经读过了,但我不想使用双向关联。我只是尝试了一下,但还是出现了第二个错误,没有调用set\u document(),在文档实体上设置了cascade persist。这令人困惑,因为错误使它听起来好像没有看到我刚刚设置的cascade persist:为了清楚起见,我添加了一个
    $access\u logs
    属性(和getter/setter)到带有注释
    @OneToMany(targetEntity=“AccessLog”,mappedBy=“Document”,cascade)的文档中={“persist”}
    ,我在AccessLog中$document属性的manytone注释中添加了
    inversedBy=“access\u logs”
    。我认为
    cascade={“persist”}
    应该在AccessLogOk中的另一个关系上,该关系清除了错误,但现在它正在尝试对document表进行插入(这是我的数据模型在数据库级别不允许的)即使没有新文档。我如何防止它尝试创建空记录?似乎假设即使联接列可为空,也必须存在相关实体。请使用新代码更新您的问题,这将使我更容易回答。
    $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
            ));