所以我今天花了大约 5 到 6 个小时与 Symfony2 表格作斗争,现在我想从社区的其他成员那里得到一些建议。我已经尝试了超过 3 种不同的方法来实现我所追求的目标,但没有成功。我通读了文档,搜索了所有内容,询问了其他人,但我只比刚开始时好一点。
我的用例
我正在建立一个系统,您可以在其中订购门票。但核心问题是如何设计系统的订单部分。
- 票有名称,以及可用的开始和结束日期(还有其他内容,但让示例保持简单。
- 一个订单可能选择了多个工单,每个工单都有一个数量。
- 一个订单有一个客户。 这部分很好,效果很好!
在阅读并尝试了不同的东西之后,我收集到代表订单的票和数量,我需要另一个实体OrderTicket对应于来自https://github.com/beberlei/AcmePizzaBundle的 OrderItem并且比萨是我的票。
- OrderTicket有一个 Ticket 和一个数量。
在创建订单的订单页面上,我想要以下内容:
- 客户详细信息表格- 姓名、电子邮件、地址。 这部分工作正常。
- 门票的表格。我希望在文本框甚至字符串中显示票证名称;不在选择框中(这就是现在正在发生的事情)。我希望在票名旁边指定数量。如果没有设置数量,则表示该票未被选中。
- 应该根据今天的日期在可用的地方过滤票证-这可以通过在具有查询构建器闭包的表单类型上使用自定义存储库方法在其他地方(在创建票证的后端管理员中)实现。
我的后端
Order/OrderTicket/Ticket 设计主要基于https://github.com/beberlei/AcmePizzaBundle
票
/**
* @ORM\Entity(repositoryClass="Foo\BackendBundle\Entity\TicketsRepository")
* @ORM\HasLifecycleCallbacks
* @ORM\Table(name="tickets")
*/
class Tickets
{
// id fields and others
/**
* @Assert\NotBlank
* @ORM\Column(type="string", nullable=true)
*/
protected $name;
/**
* @ORM\Column(type="date", name="available_from", nullable=true)
*/
protected $availableFrom;
/**
* @ORM\Column(type="date", name="available_to", nullable=true)
*/
protected $availableTo;
}
订单票
/**
* @ORM\Table()
* @ORM\Entity
*/
class OrderTicket
{
// id field
/**
* @ORM\Column(name="quantity", type="integer")
*/
private $quantity;
/**
* @ORM\ManyToOne(targetEntity="Tickets")
*/
protected $ticket;
/**
* @ORM\ManyToOne(targetEntity="Orders", inversedBy="tickets")
*/
protected $order;
// getters and setters for quantity, ticket and order
}
命令
/**
* @ORM\Entity
* @ORM\HasLifecycleCallbacks
* @ORM\Table(name="orders")
*/
class Orders
{
// id field and other stuff
/**
* @ORM\OneToMany(targetEntity="OrderTicket", mappedBy="order", cascade={"persist"})
**/
protected $tickets;
/**
* @ORM\ManyToOne(targetEntity="Customer", cascade={"persist"})
*/
protected $customer;
public function __construct()
{
$this->tickets = new \Doctrine\Common\Collections\ArrayCollection();
}
// getters, setters, add for Tickets and Customer
}
顾客
/**
* @ORM\Table()
* @ORM\Entity
*/
class Customer
{
// id, name, email, address fields
}
这会创建一个这样的模式(表命名差异来自自动生成):
CREATE TABLE `tickets` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`available_from` date DEFAULT NULL,
`available_to` date DEFAULT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `Customer` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`email` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`address` longtext COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `OrderTicket` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`ticket_id` int(11) DEFAULT NULL,
`order_id` int(11) DEFAULT NULL,
`quantity` int(11) NOT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `orders` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`customer_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
);
形式
class CustomerType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email')
->add('name')
->add('address')
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Foo\BackendBundle\Entity\Customer'
));
}
public function getName()
{
return 'foo_backendbundle_customertype';
}
}
class OrderTicketType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('quantity', 'integer')
->add('ticket')
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Foo\BackendBundle\Entity\OrderTicket'
));
}
public function getName()
{
return 'foo_backendbundle_ordertickettype';
}
}
class OrdersType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('customer', new CustomerType())
->add('tickets', 'collection', array(
'type' => new OrderTicketType(),
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
))
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Foo\BackendBundle\Entity\Orders',
));
}
public function getName()
{
return 'foo_backendbundle_orderstype';
}
}
表格
<form action="{{ path('index') }}" method="post" {{ form_enctype(form) }}>
<h3>Tickets</h3>
{{ form_errors(form) }}
<table>
<thead>
<tr>
<td>Ticket</td>
<td>Quantity</td>
</thead>
<tbody>
{% for ticketrow in form.tickets %}
<tr>
<td>{{ form_widget(ticketrow.ticket) }}</td>
<td>{{ form_widget(ticketrow.quantity) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<h3>Customer</h3>
{% for customer in form.customer %}
{{ form_row(customer) }}
{% endfor %}
</form>
最后是控制器
class DefaultController extends Controller
{
/**
* @Route("/", name="index")
* @Template()
*/
public function indexAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
// IMPORTANT - the Tickets are prefiltered for active Tickets, these have to be injected into the Order atm. In other places I use this method on the query builder
$tickets = $em->getRepository('FooBackendBundle:Tickets')->findActive();
// check no tickets
$order = new Orders();
// To prepopulate the order with the available tickets, we have to do it like this, due to it being a collection,
// rather than using the forms query_builder like everywhere else
foreach($tickets as $ticket) {
$ot = new OrderTicket();
$ot->setTicket($ticket);
$ot->setQuantity(0);
$ot->setOrder($order);
$order->addTicket($ot);
}
$form = $this->createForm(new OrdersType(), $order);
if ($request->isMethod('POST')) {
$form->bind($request);
// IMPORTANT here I have to remove the previously added Tickets where the quantity is 0 - as they're not wanted in the Order. Is there a better way to do this?
// if the quantity of Ticket is 0, do not add to order
// note we use the validation callback in Order to check total quantity of OrderTickets is > 0
$order->removeTicketsWithNoQuantity();
if ($form->isValid()) {
$em->persist($order);
$em->flush();
return $this->redirect($this->generateUrl('order_show', array('id' => $order->getId())));
}
}
return array('form' => $form->createView());
}
}
概括
这可以正常工作,并且可以正确保存订单,但我不确定这是做我想做的事情的正确方法,而且它不会按我想要的方式显示。
您可以在下面的图片中看到它的外观以及订单是如何通过的。值得注意的是,在每个工单下拉菜单中是剩余工单但未激活。
订单页面:
保存后的订单摘要页面:
显示的 3 个工单是已过滤的工单,我只希望表单上有这些工单。 我只想查看门票名称,而不是可编辑的下拉菜单。
核心问题是它们显示为可编辑的下拉菜单。我可能只想要一个门票名称的文本字符串,或者甚至可能是未来的门票价格。我不确定如何实现这一目标。我知道必须以某种方式呈现票证字段和关系,以便可以将其绑定在控制器中。所以基本上我希望能够使用 Ticket 实体,它的字段与数量文本框位于同一行。
因此,让我们跳出 Symfony2 表单的废话并正确看待这一点——在正常的世界中,显然我只是检索门票,然后对于每张门票,我会打印门票名称,我想要的任何其他内容,a hidden Ticket id,然后是 Ticket 数量的输入。稍微回到 SF2 - 我想在循环 OrderTicket 集合时我需要可用的 Ticket 实体。
请帮我!