如何在Symfony中结合Doctrine与User、Restaurant、Table实体创建新预订
在Symfony中结合Doctrine ORM实现预订创建功能
以下是基于User、Restaurant、Table(命名为RestaurantTable避免SQL关键字冲突)实体的完整实现步骤:
1. 定义实体及关联关系
明确核心实体的关联逻辑:
- User 与 Reservation:一对多(单个用户可创建多个预订)
- Restaurant 与 RestaurantTable:一对多(单个餐厅包含多张桌子)
- Reservation 关联 User、Restaurant、RestaurantTable:多对一(每个预订归属一个用户、对应一个餐厅和一张桌子)
User实体
// src/Entity/User.php namespace App\Entity; use Doctrine\ORM\Mapping as ORM; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; #[ORM\Entity] class User { #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column(type: 'integer')] private $id; // 补充name、email等业务字段 #[ORM\OneToMany(mappedBy: 'user', targetEntity: Reservation::class, cascade: ['persist', 'remove'])] private $reservations; public function __construct() { $this->reservations = new ArrayCollection(); } // 生成id、reservations的getter/setter,以及addReservation、removeReservation方法 }
Restaurant实体
// src/Entity/Restaurant.php namespace App\Entity; use Doctrine\ORM\Mapping as ORM; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; #[ORM\Entity] class Restaurant { #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column(type: 'integer')] private $id; #[ORM\Column(type: 'string', length: 255)] private $name; // 补充address、phone等字段 #[ORM\OneToMany(mappedBy: 'restaurant', targetEntity: RestaurantTable::class, cascade: ['persist', 'remove'])] private $tables; #[ORM\OneToMany(mappedBy: 'restaurant', targetEntity: Reservation::class, cascade: ['persist', 'remove'])] private $reservations; public function __construct() { $this->tables = new ArrayCollection(); $this->reservations = new ArrayCollection(); } // 生成getter/setter及关联操作方法 }
RestaurantTable实体
// src/Entity/RestaurantTable.php namespace App\Entity; use Doctrine\ORM\Mapping as ORM; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; #[ORM\Entity] #[ORM\Table(name: 'restaurant_table')] // 规避SQL关键字冲突 class RestaurantTable { #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column(type: 'integer')] private $id; #[ORM\Column(type: 'integer')] private $number; // 桌号 #[ORM\Column(type: 'integer')] private $capacity; // 容纳人数 #[ORM\ManyToOne(targetEntity: Restaurant::class, inversedBy: 'tables')] #[ORM\JoinColumn(nullable: false)] private $restaurant; #[ORM\OneToMany(mappedBy: 'table', targetEntity: Reservation::class)] private $reservations; public function __construct() { $this->reservations = new ArrayCollection(); } // 生成getter/setter及关联操作方法 }
Reservation实体
// src/Entity/Reservation.php namespace App\Entity; use Doctrine\ORM\Mapping as ORM; #[ORM\Entity] class Reservation { #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column(type: 'integer')] private $id; #[ORM\Column(type: 'datetime')] private $reservationTime; #[ORM\Column(type: 'integer')] private $guestsCount; // 用餐人数 #[ORM\ManyToOne(targetEntity: User::class, inversedBy: 'reservations')] #[ORM\JoinColumn(nullable: false)] private $user; #[ORM\ManyToOne(targetEntity: Restaurant::class, inversedBy: 'reservations')] #[ORM\JoinColumn(nullable: false)] private $restaurant; #[ORM\ManyToOne(targetEntity: RestaurantTable::class, inversedBy: 'reservations')] #[ORM\JoinColumn(nullable: false)] private $table; // 生成所有字段的getter/setter方法 }
2. 生成数据库表
执行以下命令创建迁移文件并同步到数据库:
php bin/console make:migration php bin/console doctrine:migrations:migrate
3. 创建预订表单
创建表单类型处理前端提交的预订数据:
// src/Form/ReservationType.php namespace App\Form; use App\Entity\Reservation; use App\Entity\Restaurant; use App\Entity\RestaurantTable; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Form\Extension\Core\Type\DateTimeType; use Symfony\Component\Form\Extension\Core\Type\IntegerType; class ReservationType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add('reservationTime', DateTimeType::class, [ 'widget' => 'single_text', 'label' => '预订时间' ]) ->add('guestsCount', IntegerType::class, [ 'label' => '用餐人数' ]) ->add('restaurant', EntityType::class, [ 'class' => Restaurant::class, 'choice_label' => 'name', 'label' => '选择餐厅', 'placeholder' => '请选择餐厅' ]) ->add('table', EntityType::class, [ 'class' => RestaurantTable::class, 'choice_label' => 'number', 'label' => '选择桌号', 'placeholder' => '请先选择餐厅', 'disabled' => true ]); } public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'data_class' => Reservation::class, ]); } }
4. 编写控制器处理预订逻辑
创建控制器处理表单提交、验证预订冲突并保存数据:
// src/Controller/ReservationController.php namespace App\Controller; use App\Entity\Reservation; use App\Form\ReservationType; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; class ReservationController extends AbstractController { #[Route('/reservation/new', name: 'app_reservation_new')] public function new(Request $request, EntityManagerInterface $em): Response { $reservation = new Reservation(); $reservation->setUser($this->getUser()); // 关联当前登录用户 $form = $this->createForm(ReservationType::class, $reservation); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { // 检查桌号在指定时间是否已被预订 $existingReservation = $em->getRepository(Reservation::class)->findOneBy([ 'table' => $reservation->getTable(), 'reservationTime' => $reservation->getReservationTime() ]); if ($existingReservation) { $this->addFlash('error', '该桌号在所选时间已被占用'); return $this->redirectToRoute('app_reservation_new'); } $em->persist($reservation); $em->flush(); $this->addFlash('success', '预订创建成功'); return $this->redirectToRoute('app_reservation_list'); // 替换为你的预订列表路由 } return $this->render('reservation/new.html.twig', [ 'form' => $form->createView() ]); } // 动态加载餐厅桌号的API接口 #[Route('/api/restaurants/{id}/tables', name: 'api_restaurant_tables', methods: ['GET'])] public function getRestaurantTables(Restaurant $restaurant): Response { $data = []; foreach ($restaurant->getTables() as $table) { $data[] = [ 'id' => $table->getId(), 'number' => $table->getNumber(), 'capacity' => $table->getCapacity() ]; } return $this->json($data); } }
5. 编写前端模板
创建Twig模板渲染表单,并添加JS实现餐厅桌号的动态加载:
{# templates/reservation/new.html.twig #} {% extends 'base.html.twig' %} {% block body %} <h1>创建新预订</h1> {% for flashError in app.flashes('error') %} <div class="alert alert-danger">{{ flashError }}</div> {% endfor %} {% for flashSuccess in app.flashes('success') %} <div class="alert alert-success">{{ flashSuccess }}</div> {% endfor %} {{ form_start(form) }} {{ form_widget(form) }} <button type="submit" class="btn btn-primary mt-3">提交预订</button> {{ form_end(form) }} <script> const restaurantSelect = document.querySelector('select[name="reservation[restaurant]"]'); const tableSelect = document.querySelector('select[name="reservation[table]"]'); restaurantSelect.addEventListener('change', () => { const restaurantId = restaurantSelect.value; tableSelect.innerHTML = '<option value="">请先选择餐厅</option>'; tableSelect.disabled = true; if (!restaurantId) return; fetch(`{{ path('api_restaurant_tables', {id: 0}) }}`.replace('0', restaurantId)) .then(res => res.json()) .then(tables => { tableSelect.innerHTML = '<option value="">选择桌号</option>'; tables.forEach(table => { const option = document.createElement('option'); option.value = table.id; option.textContent = `桌号 ${table.number} (可容纳 ${table.capacity} 人)`; tableSelect.appendChild(option); }); tableSelect.disabled = false; }); }); </script> {% endblock %}
内容的提问来源于stack exchange,提问作者NyntjuhTheBunny
相关产品推荐
相关产品推荐

