[英]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發送請求來更新訂單:
狀態代碼: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有時會很復雜。 我將首先向您提供有關內部結構的一些信息,以便您更清楚地了解引擎蓋下的內容。
從您提供的詳細信息中很難估計實際問題,但我會給您一些建議,可以幫助您調試問題。 我給出了一個廣泛的答案,所以它可能會幫助別人。
以下是我的猜測:您正在通過引用修改實體,即使您將by_reference
設置為false也是如此。 這可能是因為您沒有定義addOrderRow
和removeOrderRow
方法(兩者都有)或者因為您沒有使用doctrine集合對象
在控制器中創建Form對象時, 將它與從數據庫中檢索的實體(即帶有ID)或剛剛創建的實體綁定 :這意味着表單DO不需要主實體的id,它也不需要集合對象的id。 為方便起見,您可以將其添加到表單中,但如果確實它們是不可變的(例如,帶有disabled => true
選項的hidden
類型)。
創建Collection表單時,Symfony會自動為實體集合中已存在的每個實體創建一個子表單; 這就是為什么在entity/<id>/edit
操作中,您(應該)總是看到已經存在的集合元素的可編輯形式。
allow_add
和allow_delete
選項控制是否可以動態調整生成的子表單大小,刪除集合的某些元素,或者添加新元素(請參閱ResizeFormListener
類)。 請注意,當您使用javascript prototype
時,必須小心使用__prototype__
占位符:這是用於重新映射對象服務器端的實際key
,因此如果您更改它,Form將在集合中創建一個新元素。
在Doctrine中,你需要好好照顧映射的owning side
和inverse side
。 owning
方是將關聯持久保存到數據庫的實體,反方是另一方實體。 持久化時, owning
方是唯一觸發關系保存的方。 在對象修改期間保持兩個關系同步是一種模型責任。
當一個一對多的關系處理中, owning
一邊是many
(如OrderRow
在你的情況下),而one
是inverse
方。
最后,應用程序需要明確標記要保留的實體。 關系的兩端都可以標記為persist cascading
,因此通過關系的所有可到達實體也會被持久化。 在此過程中,所有新實體都會自動保留,並且(在標准配置中)所有“臟”實體都會更新。
臟實體的概念在官方文檔中得到了很好的解釋。 默認情況下,Doctrine會通過將每個屬性與原始狀態進行比較來自動檢測更新的實體,並在刷新期間生成UPDATE
語句。 如果明確提高性能(即@ChangeTrackingPolicy("DEFERRED_EXPLICIT")
),則必須手動@ChangeTrackingPolicy("DEFERRED_EXPLICIT")
所有實體,即使關系標記為級聯。
還要注意,當從DB重新加載實體時,Doctrine使用PersistenCollection
實例來處理集合,因此您需要使用doctrine集合接口來處理實體集合。
總而言之,這里有一個(希望是完整的)列表,用於檢查正確的集合更新。
Doctrine\\Common\\Collection
,而不是簡單的數組; 在你的情況下:
<?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;
}
}
id
(但在模板中包含路由器操作的正確GET
參數) order
不存在,因為它將由模型類自動更新 by_reference
設置為false
Order
類中定義addOrderRow
和removeOrderRow
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
;
}
}
Form::handleRequest
確保HTTP方法與Form方法屬性匹配 在你的情況下,你應該有這樣的行動:
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.