簡體   English   中英

Symfony2更新“子表單”中的項目

[英]Symfony2 updating items in “subform”

我的問題的簡短版本:

如何在Symfony2中編輯子表單的實體?

= - = - = - = - = - = - =長而詳細的版本= - = - = - = - = - = - = - =

我有一個實體訂單

<?php

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

    /**
     * @ORM\ManyToOne(targetEntity="Customer")
     * @ORM\JoinColumn(name="customer_id", referencedColumnName="id", nullable=false)
     **/
    private $customer;

    /**
     * @var \DateTime
     *
     * @ORM\Column(name="date", type="date")
     */
    private $date;

    /**
     * @ORM\ManyToOne(targetEntity="\AppBundle\Entity\OrderStatus")
     * @ORM\JoinColumn(name="order_status_id", referencedColumnName="id", nullable=false)
     **/
    private $orderStatus;

    /**
     * @var string
     *
     * @ORM\Column(name="reference", type="string", length=64)
     */
    private $reference;

    /**
     * @var string
     *
     * @ORM\Column(name="comments", type="text")
     */
    private $comments;

    /**
     * @var array
     *
     * @ORM\OneToMany(targetEntity="OrderRow", mappedBy="Order", cascade={"persist"})
     */
    private $orderRows;

    ...
}

MySQL的

_____________________________________________________________
|id                           | order id                    |
|customer_id                  | fk customer.id NOT NULL     |
|date                         | order date                  |
|order_status_id              | fk order_status.id NOT NULL |
|reference                    | varchar order reference     |
|comments                     | text comments               |
|___________________________________________________________|

和實體OrderRow(訂單可以有一行或多行)

<?php

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

    /**
     * @ORM\ManyToOne(targetEntity="Order", inversedBy="orderRows", cascade={"persist"})
     * @ORM\JoinColumn(name="order_id, referencedColumnName="id", nullable=false)
     **/
    private $order;

    /**
     * @ORM\ManyToOne(targetEntity="[MyShop\Bundle\ProductBundle\Entity\Product")
     * @ORM\JoinColumn(name="product_id", referencedColumnName="id", nullable=true)
     **/
    private $product;

    /**
     * @var string
     *
     * @ORM\Column(name="description", type="string", length=255)
     */
    private $description;

    /**
     * @var integer
     *
     * @ORM\Column(name="count", type="integer")
     */
    private $count = 1;

    /**
     * @var \DateTime
     *
     * @ORM\Column(name="date", type="date")
     */
    private $date;

    /**
     * @var decimal
     *
     * @ORM\Column(name="amount", type="decimal", precision=5, scale=2)
     */
    private $amount;

    /**
     * @var string
     *
     * @ORM\Column(name="tax_amount", type="decimal", precision=5, scale=2)
     */
    private $taxAmount;

    /**
     * @var string
     *
     * @ORM\Column(name="discount_amount", type="decimal", precision=5, scale=2)
     */
    private $discountAmount;

    ...
}

MySQL的

_____________________________________________________________
|id                           | order id                    |
|order_id                     | fk order.id NOT NULL        |
|product_id                   | fk product.id               |
|description                  | varchar product description |
|count                        | int count                   |
|date                         | date                        |
|amount                       | amount                      |
|taxAmount                    | tax amount                  |
|discountAmount               | discount amount             |
|___________________________________________________________|

我想創建一個允許編輯一個訂單及其行的表單。

OrderType.php

class OrderType extends AbstractType
{
    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('customer', 'entity', array(
                'class' => 'Customer',
                'multiple' => false
            ))
            ->add('orderStatus', 'entity', array(
                'class' => 'AppBundle\Entity\OrderStatus',
                'multiple' => false
            ))
            ->add('date')
            ->add('reference')
            ->add('comments')
            ->add('orderRows', 'collection', [
                'type' => new OrderRowType(),
                'allow_add' => true,
                'by_reference' => false,
            ])
        ;
    }

    ...
}

OrderRowType.php

class OrderRowType extends AbstractType
{
    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('order', 'entity', array(
                'class' => 'MyShop\Bundle\OrderBundle\Entity\Order',
                'multiple' => false
            ))
            ->add('product', 'product_selector') // service
            ->add('orderRowStatus', 'entity', array(
                'class' => 'AppBundle\Entity\OrderRowStatus',
                'multiple' => false
            ))
            ->add('description')
            ->add('count')
            ->add('startDate')
            ->add('endDate')
            ->add('amount')
            ->add('taxAmount')
            ->add('discountAmount')
        ;
    }

    ...
}

通過向我的API發送請求來更新訂單:

  • 請求網址: https//api.example.net/admin/orders/update/37
  • 請求方法:POST
  • 狀態代碼:200

     Params: { "order[customer]": "3", "order[orderStatus]": "1", "order[date][month]:": "5", "order[date][day]": "18", "order[date][year]": "2015", "order[reference]": "Testing", "order[comments]": "I have nothing to say!", "order[orderRows][0][order]": "32", "order[orderRows][0][product]": "16721", "order[orderRows][0][orderRowStatus]:1": "1", "order[orderRows][0][description]": "8 GB memory", "order[orderRows][0][count]": "12", "order[orderRows][0][startDate][month]": "5", "order[orderRows][0][startDate][day]": "18", "order[orderRows][0][startDate][year]": "2015", "order[orderRows][0][endDate][month]": "5", "order[orderRows][0][endDate][day]": "18", "order[orderRows][0][endDate][year]": "2015", "order[orderRows][0][amount]": "122.03", "order[orderRows][0][taxAmount]": "25.63", "order[orderRows][0][discountAmount]": "0", "order[orderRows][1][order]": "32", "order[orderRows][1][product]": "10352", "order[orderRows][1][orderRowStatus]": "2", "order[orderRows][1][description]": "12 GB MEMORY", "order[orderRows][1][count]": "1", "order[orderRows][1][startDate][month]": "5", "order[orderRows][1][startDate][day]": "18", "order[orderRows][1][startDate][year]": "2015", "order[orderRows][1][endDate][month]": "5", "order[orderRows][1][endDate][day]": "18", "order[orderRows][1][endDate][year]": "2015", "order[orderRows][1][amount]": "30.8", "order[orderRows][1][taxAmount]": "6.47", "order[orderRows][1][discountAmount]": "0", "order[orderRows][2][order]": "32", "order[orderRows][2][product]": "2128", "order[orderRows][2][orderRowStatus]": "3", "order[orderRows][2][description]": "4GB MEMORY", "order[orderRows][2][count]": "5", "order[orderRows][2][startDate][month]": "5", "order[orderRows][2][startDate][day]": "18", "order[orderRows][2][startDate][year]": "2015", "order[orderRows][2][endDate][month]": "5", "order[orderRows][2][endDate][day]": "18", "order[orderRows][2][endDate][year]": "2015", "order[orderRows][2][amount]": "35.5", "order[orderRows][2][taxAmount]": "7.46", "order[orderRows][2][discountAmount]": "0" } 

上面的請求編輯訂單詳細信息並創建新的order_rows,因為沒有提供order_row_id。 現在在Symfony2中,我發現我應該只將$ builder-> add('id')添加到我的OrderRowType中,我的實體也沒有為列ID設置setter。

經過大量的信息,我有一個很短的問題。 我該如何更新此表單中的order_rows記錄?

如果您不了解內部,處理收集和Doctrine有時會很復雜。 我將首先向您提供有關內部結構的一些信息,以便您更清楚地了解引擎蓋下的內容。

從您提供的詳細信息中很難估計實際問題,但我會給您一些建議,可以幫助您調試問題。 我給出了一個廣泛的答案,所以它可能會幫助別人。

TL; DR版本

以下是我的猜測:您正在通過引用修改實體,即使您將by_reference設置為false也是如此。 這可能是因為您沒有定義addOrderRowremoveOrderRow方法(兩者都有)或者因為您沒有使用doctrine集合對象

一些內部

形成

在控制器中創建Form對象時, 它與從數據庫中檢索的實體(即帶有ID)或剛剛創建的實體綁定 :這意味着表單DO不需要主實體的id,它也不需要集合對象的id。 為方便起見,您可以將其添加到表單中,但如果確實它們是不可變的(例如,帶有disabled => true選項的hidden類型)。

創建Collection表單時,Symfony會自動為實體集合中已存在的每個實體創建一個子表單; 這就是為什么在entity/<id>/edit操作中,您(應該)總是看到已經存在的集合元素的可編輯形式。

allow_addallow_delete選項控制是否可以動態調整生成的子表單大小,刪除集合的某些元素,或者添加新元素(請參閱ResizeFormListener類)。 請注意,當您使用javascript prototype時,必須小心使用__prototype__占位符:這是用於重新映射對象服務器端的實際key ,因此如果您更改它,Form將在集合中創建一個新元素。

教義

在Doctrine中,你需要好好照顧映射的owning sideinverse side owning方是將關聯持久保存到數據庫的實體,反方是另一方實體。 持久化時, owning方是唯一觸發關系保存的方。 在對象修改期間保持兩個關系同步是一種模型責任。

當一個一對多的關系處理中, owning一邊是many (如OrderRow在你的情況下),而oneinverse方。

最后,應用程序需要明確標記要保留的實體。 關系的兩端都可以標記為persist cascading ,因此通過關系的所有可到達實體也會被持久化。 在此過程中,所有新實體都會自動保留,並且(在標准配置中)所有“臟”實體都會更新。

臟實體的概念在官方文檔中得到很好解釋。 默認情況下,Doctrine會通過將每個屬性與原始狀態進行比較來自動檢測更新的實體,並在刷新期間生成UPDATE語句。 如果明確提高性能(即@ChangeTrackingPolicy("DEFERRED_EXPLICIT") ),則必須手動@ChangeTrackingPolicy("DEFERRED_EXPLICIT")所有實體,即使關系標記為級聯。

還要注意,當從DB重新加載實體時,Doctrine使用PersistenCollection實例來處理集合,因此您需要使用doctrine集合接口來處理實體集合。

怎么檢查

總而言之,這里有一個(希望是完整的)列表,用於檢查正確的集合更新。

理論關系的兩個方面都設置得恰當

  1. 擁有方和反方都應標記為級聯持久(如果不是控制器必須手動級聯...不推薦,除非太慢);
  2. 集合屬性必須是Doctrine\\Common\\Collection ,而不是簡單的數組;
  3. 模型必須在每次更改時相互更新,因此這意味着
  4. 集合對象不應該按原樣返回,以避免通過引用進行修改。

在你的情況下:

<?php

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

    /**
     * @var \Doctrine\Common\Collections\Collection
     * @ORM\OneToMany(targetEntity="OrderRow", mappedBy="Order", cascade={"persist"})
     */
    private $orderRows;

    public function __construct()
    {
        // this is required, as Doctrine will replace it by a PersistenCollection on load
        $this->orderRows = new \Doctrine\Common\Collections\ArrayCollection();
    }

    /**
     * Add order row
     *
     * @param  OrderRow $row
     */
    public function addOrderRow(OrderRow $row)
    {
        if (! $this->orderRows->contains($row))
            $this->orderRows[] = $row;

        $row->setOrder($this);
    }

    /**
     * Remove order row
     *
     * @param OrderRow $row
     */
    public function removeOrderRow(OrderRow $row)
    {
        $removed = $this->orderRows->removeElement($row);
        /*
        // you may decide to allow your domain to have spare rows, with order set to null
        if ($removed)
            $row->setOrder(null);
        */

        return $removed;
    }

    /**
     * Get order rows
     * @return OrderRow[]
     */
    public function getOrders()
    {
        // toArray prevent edit by reference, which breaks encapsulation
        return $this->orderRows->toArray();
    }
}

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

    /**
     * @var Order
     * @ORM\ManyToOne(targetEntity="Order", inversedBy="orderRows", cascade={"persist"})
     * @ORM\JoinColumn(name="order_id, referencedColumnName="id", nullable=false)
     */
    private $order;


    /**
     * Set order
     *
     * @param  Order $order
     */
    public function setOrder(Order $order)
    {
        // avoid infinite loops addOrderRow -> setOrder -> addOrderRow
        if ($this->order === $order) {
            return;
        }

        if (null !== $this->order) {
            // see the comment above about spare order rows
            $this->order->removeOrderRow($this);
        }

        $this->order = $order;
    }

    /**
     * Get order
     *
     * @return Order
     */
    public function getOrder()
    {
        return $this->order;
    }
}

表單集合已正確配置

  1. 確保表單不公開訂單id (但在模板中包含路由器操作的正確GET參數)
  2. 確保OrderRow order不存在,因為它將由模型類自動更新
  3. 確保將by_reference設置為false
  4. 確保在Order類中定義addOrderRowremoveOrderRow
  5. 為了加快調試速度,請確保Order::getOrderRows不直接返回集合

這里的片段:

class OrderType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('orderRows', 'collection', [
                'type'           => new OrderRowType(),
                'allow_add'      => true,  // without, new elements are ignored
                'allow_delete'   => true,  // without, deleted elements are not updated
                'by_reference'   => false, // hint Symfony to use addOrderRow and removeOrderRow
                                          // NOTE: both method MUST exist, or Symfony will ignore the option
            ])
        ;
    }
}

class OrderRowType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            // ->add('order') NOT required, the model will handle the setting
            ->add('product', 'product_selector') // service
        ;
    }
}

控制器必須正確更新實體

  1. 確保正確創建表單;
  2. 如果使用Form::handleRequest確保HTTP方法與Form方法屬性匹配
  3. 如果表單有效,請處理集合中已刪除的元素
  4. 如果表單有效,則保持實體然后刷新

在你的情況下,你應該有這樣的行動:

public function updateAction(Request $request, $id)
{
    $em = $this->getDoctrine()->getManager();

    $order = $em->getRepository('YourBundle:Order')->find($id);

    if (! $order) {
        throw $this->createNotFoundException('Unable to find Order entity.');
    }

    $previousRows = $order->getOrderRows();

// is a PUT request, so make sure that <input type="hidden" name="_method" value="PUT" /> is present in the template
    $editForm = $this->createForm(new OrderType(), $order, array(
        'method' => 'PUT',
        'action' => $this->generateUrl('order_update', array('id' => $id))
    ));
    $editForm->handleRequest($request);

    if ($editForm->isValid()) {
        // removed rows = previous rows - current rows
        $rowsRemoved = array_udiff($previousRows, $order->getOrderRows(), function ($a, $b) { return $a === $b ? 0 : -1; });

        // removed rows must be deleted manually
        foreach ($rowsRemoved as $row) {
            $em->remove($row);
        }

        // if not cascading, all rows must be persisted as well
        $em->flush();
    }

    return $this->render('YourBundle:Order:edit.html.twig', array(
        'entity'      => $order,
        'edit_form'   => $editForm->createView(),
    ));
}

希望這可以幫助!

我不相信這是可能的,原因如下:

OrderRows僅由其ID標識,因此為了使Doctrine知道實際更新了哪個實體,需要知道id。 但是,您需要將OrderRow id添加為字段,您不想這樣做,因為這將允許更改不屬於該訂單的“外部”OrderRows。 (沒有復雜的權限檢查)

解決方案是完全刪除舊的OrderRows並插入新的OrderRows。 插入已經有效:-)。

Doctrine:確保數據庫持久性的cookbook中描述了刪除實體

只有一個小缺點:OrderRows在訂單更新時獲得新的ID。

該的mappedBy應刻申,而不是即刻申請,因為它指向一個屬性,而不是一個類名。

    /**
 * @var array
 *
 * @ORM\OneToMany(targetEntity="OrderRow", mappedBy="order", cascade={"persist"})
 */
private $orderRows;

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM