2011-10-12 38 views
7

當保存表單提交數據時,我遇到了一個新的實體實例,其中該實體與另一個實體具有可爲空的關聯時遇到問題,我試圖將其設置爲null。爲表單創建一個新的實體實例,結合提交的請求的形式和堅持和沖洗的實體實例,這取決於我如何填充爲關聯實體的財產之後,我要麼得到Symfony2形式:我如何堅持一個可爲空的關聯的實體?

  1. UnexpectedTypeException: Expected argument of type "object or array", "NULL" given(如果設置爲空),或
  2. InvalidArgumentException: A new entity was found through the relationship 'AccessLog#document' that was not configured to cascade persist operations for entity(如果設置爲相關實體的新的空實例,我不想堅持)。

如果我設置了級聯堅持,它試圖創建的相關表中的記錄(這是不是在數據庫中的數據模型允許的),即使沒有數據爲它堅持。如果設置cascade persist是要走的路,那麼我該如何防止它嘗試創建新記錄?處理這個問題的最好方法是什麼?

注意,無論關聯設置爲單向還是雙向,行爲都是相同的。

詳情:

我有一個多到一的關聯實體與另一個實體(簡稱):

/** @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... 
} 

我有新的ACCESSLOG記錄輸入數據的Symfony的形式:

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實體而不需要保留一個新的Document實體?

+0

爲什麼你需要做'setDocument(null)'?爲什麼不不叫'setDocument'? – greg0ire

回答

0

你讀過的the Doctrine2 documentation about Transitive persistence/Cascade Operations的頁面?我認爲,如果該文件存在,您首先需要remove它,如果沒有,那麼你應該不叫setDocument()。爲了避免第二個錯誤,§解釋瞭如何配置級聯(請參閱最後一個代碼片段)。

+0

我已經讀過,但我不想使用雙向關聯。無論如何,我只是試了一下,仍然得到了第二個錯誤,沒有調用set_document(),在Document實體上設置了cascade persist。這是令人困惑的,因爲錯誤使得它聽起來像沒有看到級聯持續我剛剛設置: –

+0

爲了清楚起見,我添加了一個'$ access_logs'屬性(和getter/setter)給Document,註釋爲@OneToMany( targetEntity =「AccessLog」,mappedBy =「document」,cascade = {「persist」})',並且我在AccessLog中的$ document屬性上的ManyToOne註釋中添加了'inversedBy =「access_logs」'。 –

+0

我認爲'cascade = {「persist」}'應該在另一個關係上,在AccessLog – greg0ire

3

看來解決方法是在持久操作之前將set_document(null)調用移動到右側,因爲將請求綁定到窗體會導致將空Document對象附加到AccessLog對象的$ document屬性。

此解決方案也可以在刪除cascade persist並使關聯單向之後生效。

感謝@ greg0ire的幫助。

[更新]這也成了必要添加@ChangeTrackingPolicy("DEFERRED_EXPLICIT")到文檔的實體定義,因爲它移除現有關聯(這引起了$ name屬性時繼續嘗試更新與ACCESSLOG記錄相關聯的文檔記錄,例如在Document中設置爲null,然後在db中設置爲null)。令人沮喪的是,即使沒有指定cascade persist,當刪除關聯時,默認行爲是擦除/修改數據。

+0

很高興你知道了。 +1 – greg0ire

2

我認爲您的問題可能是您在AccessLog中定義文檔設置器的方式。

,如果你不喜歡這樣寫道:

setDocument(Document $document) 

你將永遠無法setDocument(null),因爲null不是文件的一個實例。要做到這一點,

setDocument($document) or setDocument(Document $docuement = null) 
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 
     )); 

這允許你保存你的實體無需設置文件。

1

這已在Symfony2 master中解決。現在,空表單不再導致空對象,而是返回null。