如何用Doctrine注解将对象持久化到两张不同数据表?
Hey there! As someone who's worked with Doctrine for a while, let's break down how to solve this problem step by step. It's actually straightforward once you split the problem into defining your entities and handling the dual persistence logic.
Step 1: Define Your Two Entities with Doctrine Annotations
First, you'll need two separate entity classes: one for the current vehicle tracking state, and another for the tour history. This keeps your data models clean and aligned with each table's purpose.
1.1 CarTracking Entity (Current State Table)
This entity maps to your tracking table, holds the current vehicle state, and maintains a relationship to the Company entity (using companyId as the foreign key):
<?php namespace App\Entity; use Doctrine\ORM\Mapping as ORM; use App\Entity\Company; /** * @ORM\Entity(repositoryClass="App\Repository\CarTrackingRepository") * @ORM\Table(name="car_tracking") */ class CarTracking { /** * @ORM\Id * @ORM\GeneratedValue * @ORM\Column(type="integer") */ private $id; /** * @ORM\Column(type="string", length=255) */ private $tourNumber; /** * @ORM\Column(type="datetime") */ private $startTime; /** * @ORM\Column(type="datetime", nullable=true) */ private $endTime; /** * @ORM\Column(type="string", length=255) */ private $currentPosition; /** * @ORM\ManyToOne(targetEntity=Company::class) * @ORM\JoinColumn(nullable=false, name="company_id") */ private $company; // Getters and Setters public function getId(): ?int { return $this->id; } public function getTourNumber(): ?string { return $this->tourNumber; } public function setTourNumber(string $tourNumber): self { $this->tourNumber = $tourNumber; return $this; } public function getStartTime(): ?\DateTimeInterface { return $this->startTime; } public function setStartTime(\DateTimeInterface $startTime): self { $this->startTime = $startTime; return $this; } // Add getters/setters for endTime, currentPosition, and company public function getCompany(): ?Company { return $this->company; } public function setCompany(?Company $company): self { $this->company = $company; return $this; } }
1.2 TourHistory Entity (History Table)
This entity maps to your tourHistory table, stores historical records, and directly holds companyName (no foreign key to Company, since we want to preserve the name at the time of the record):
<?php namespace App\Entity; use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity(repositoryClass="App\Repository\TourHistoryRepository") * @ORM\Table(name="tour_history") */ class TourHistory { /** * @ORM\Id * @ORM\GeneratedValue * @ORM\Column(type="integer") */ private $id; /** * @ORM\Column(type="string", length=255) */ private $tourNumber; /** * @ORM\Column(type="datetime") */ private $startTime; /** * @ORM\Column(type="datetime", nullable=true) */ private $endTime; /** * @ORM\Column(type="string", length=255) */ private $currentPosition; /** * @ORM\Column(type="string", length=255) */ private $companyName; // Getters and Setters public function getId(): ?int { return $this->id; } public function getTourNumber(): ?string { return $this->tourNumber; } public function setTourNumber(string $tourNumber): self { $this->tourNumber = $tourNumber; return $this; } public function getCompanyName(): ?string { return $this->companyName; } public function setCompanyName(string $companyName): self { $this->companyName = $companyName; return $this; } // Add getters/setters for startTime, endTime, currentPosition }
Step 2: Handle Dual Persistence
Now you need to save both entities whenever you create or update the CarTracking record. There are two simple ways to do this:
Option 1: Manual Persistence (Great for Beginners)
This approach is straightforward—you explicitly create and save the TourHistory object alongside CarTracking in your business logic (e.g., a controller or service):
<?php namespace App\Controller; use App\Entity\CarTracking; use App\Entity\TourHistory; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\HttpFoundation\Response; class CarTrackingController { public function saveOrUpdateTracking(EntityManagerInterface $em): Response { // Fetch an existing company (or create a new one) $company = $em->getRepository(Company::class)->find(1); // Create or update your CarTracking entity $carTracking = new CarTracking(); $carTracking->setTourNumber('TOUR-2024-001'); $carTracking->setStartTime(new \DateTime()); $carTracking->setCurrentPosition('Downtown Chicago'); $carTracking->setCompany($company); // Create a TourHistory record and copy data from CarTracking $tourHistory = new TourHistory(); $tourHistory->setTourNumber($carTracking->getTourNumber()); $tourHistory->setStartTime($carTracking->getStartTime()); $tourHistory->setCurrentPosition($carTracking->getCurrentPosition()); $tourHistory->setCompanyName($company->getName()); // Grab the current company name // Persist both entities and flush to the database $em->persist($carTracking); $em->persist($tourHistory); $em->flush(); return new Response('Tracking state and history record saved!'); } }
Option 2: Doctrine Lifecycle Callback (Automated)
If you want the history record to be created automatically whenever CarTracking is saved/updated, use a Doctrine lifecycle callback. This keeps your business logic cleaner:
First, update the CarTracking entity to include the callback and inject the Entity Manager:
/** * @ORM\Entity(repositoryClass="App\Repository\CarTrackingRepository") * @ORM\Table(name="car_tracking") * @ORM\HasLifecycleCallbacks() */ class CarTracking { // ... existing properties and methods private $entityManager; // Inject the Entity Manager via dependency injection public function setEntityManager(EntityManagerInterface $em): void { $this->entityManager = $em; } /** * @ORM\PostPersist() * @ORM\PostUpdate() */ public function createHistoryRecord(): void { $tourHistory = new TourHistory(); $tourHistory->setTourNumber($this->tourNumber); $tourHistory->setStartTime($this->startTime); $tourHistory->setEndTime($this->endTime); $tourHistory->setCurrentPosition($this->currentPosition); $tourHistory->setCompanyName($this->getCompany()->getName()); $this->entityManager->persist($tourHistory); $this->entityManager->flush(); } }
Then, configure the entity to autowire the Entity Manager in your services.yaml:
# config/services.yaml services: App\Entity\CarTracking: autowire: true calls: - method: setEntityManager arguments: ['@doctrine.orm.entity_manager']
Key Notes
- Make sure your
Companyentity has agetName()method to retrieve the company name. - If you want to create a new history record every time
CarTrackingis updated (not just created), use both@ORM\PostPersist()and@ORM\PostUpdate()annotations in the lifecycle callback. - Run
php bin/console make:migrationandphp bin/console doctrine:migrations:migrateto generate and apply the database tables from your entities.
内容的提问来源于stack exchange,提问作者naptoon

