如何将Doctrine ODM的Document数组存入Doctrine ORM实体并实现批量懒加载?
Nice question! I’ve dealt with exactly this scenario when blending Doctrine ORM and ODM, so let me break down how to implement batch lazy loading for your documents—since DocumentManager::getReference() only handles single documents, we need a custom approach to make this work at scale.
Step 1: Adjust Your ORM Entity Structure
First, update your ORM entity to store the comma-separated IDs and hold a lazy-loaded collection of ODM documents (we’ll use a custom collection class for this):
use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity */ class YourEntity { /** * @ORM\Column(type="string", nullable=true) */ private $documentIds; /** * @var LazyDocumentCollection|null */ private $documents; // Getter/Setter for documentIds public function getDocumentIds(): ?string { return $this->documentIds; } public function setDocumentIds(?string $documentIds): void { $this->documentIds = $documentIds; } // Getter for the lazy collection public function getDocuments(): LazyDocumentCollection { return $this->documents; } // Setter to inject the lazy collection (used in the event listener) public function setDocuments(LazyDocumentCollection $documents): void { $this->documents = $documents; } }
Step 2: Create a Lazy-Loaded Collection Class
This class will handle the batch loading logic, only fetching documents when you first access them. It supports two approaches: loading proxy references (true lazy loading per document) or batch-fetching all documents in one query (better performance for most cases):
use Doctrine\ODM\MongoDB\DocumentManager; class LazyDocumentCollection implements \IteratorAggregate, \Countable { private $documentManager; private $documentClass; private $documentIds; private $loadedDocuments; public function __construct(DocumentManager $dm, string $documentClass, array $documentIds) { $this->documentManager = $dm; $this->documentClass = $documentClass; // Filter out empty IDs to avoid invalid queries $this->documentIds = array_filter($documentIds); $this->loadedDocuments = null; } /** * Load documents only once, on first access */ private function loadDocuments(): void { if ($this->loadedDocuments !== null) { return; } // Option 1: Load proxy references (individual lazy loading) // Each document is a proxy that only fetches data when you access its properties $this->loadedDocuments = array_map(function ($id) { return $this->documentManager->getReference($this->documentClass, $id); }, $this->documentIds); // Option 2: Batch-fetch all documents in one query (better performance) // Uncomment this and comment Option 1 if you want to minimize database calls // $this->loadedDocuments = $this->documentManager // ->getRepository($this->documentClass) // ->findBy(['_id' => $this->documentIds]); // Optional: Re-sort documents to match the original ID order // $idOrder = array_flip($this->documentIds); // usort($this->loadedDocuments, function ($a, $b) use ($idOrder) { // return $idOrder[$a->getId()] - $idOrder[$b->getId()]; // }); } // Implement IteratorAggregate to support looping public function getIterator(): \Traversable { $this->loadDocuments(); return new \ArrayIterator($this->loadedDocuments); } // Implement Countable to support count() public function count(): int { $this->loadDocuments(); return count($this->loadedDocuments); } // Add other collection methods as needed (e.g., get(), contains()) public function get(int $index) { $this->loadDocuments(); return $this->loadedDocuments[$index] ?? null; } }
Step 3: Set Up a PostLoad Event Subscriber
Use an ORM event subscriber to split the comma-separated ID string into an array and inject the lazy collection into your entity when it’s loaded from the database:
use Doctrine\Common\EventSubscriber; use Doctrine\ORM\Event\PostLoadEventArgs; use Doctrine\ODM\MongoDB\DocumentManager; class YourEntityPostLoadSubscriber implements EventSubscriber { private $documentManager; public function __construct(DocumentManager $dm) { $this->documentManager = $dm; } public function getSubscribedEvents(): array { return ['postLoad']; } public function postLoad(PostLoadEventArgs $args): void { $entity = $args->getObject(); if (!$entity instanceof YourEntity) { return; } $documentIdsString = $entity->getDocumentIds(); $documentIds = $documentIdsString ? explode(',', $documentIdsString) : []; $lazyCollection = new LazyDocumentCollection( $this->documentManager, YourDocument::class, // Replace with your ODM document class $documentIds ); $entity->setDocuments($lazyCollection); } }
Key Notes
- Proxy vs Batch Fetch: Option 1 (proxies) is true lazy loading—each document only hits the database when you access its properties. Option 2 (batch fetch) makes one database call to load all documents at once, which is better for performance if you expect to access most or all documents.
- Order Preservation: If the order of your documents matters, uncomment the sorting code in the
loadDocuments()method to reorder the fetched documents to match the original ID sequence. - Caching: The collection only loads documents once—subsequent calls to
getDocuments()will use the cached instances.
内容的提问来源于stack exchange,提问作者Nicolas Appriou

