@@ -47,6 +47,9 @@ final class ProjectNode extends CompoundNode
4747 /** @var array<string, array<string, InternalTarget>> */
4848 private array $ internalLinkTargets = [];
4949
50+ /** Cached root document entry for O(1) lookup */
51+ private DocumentEntryNode |null $ rootDocumentEntry = null ;
52+
5053 /** @var DocumentEntryNode[] */
5154 private array $ documentEntries = [];
5255 private DateTimeImmutable $ lastRendered ;
@@ -182,6 +185,11 @@ public function getAllInternalTargets(): array
182185
183186 public function addDocumentEntry (DocumentEntryNode $ documentEntry ): void
184187 {
188+ // Cache root document entry for O(1) lookup
189+ if ($ documentEntry ->isRoot ()) {
190+ $ this ->rootDocumentEntry = $ documentEntry ;
191+ }
192+
185193 $ this ->documentEntries [$ documentEntry ->getFile ()] = $ documentEntry ;
186194 }
187195
@@ -193,8 +201,15 @@ public function getAllDocumentEntries(): array
193201
194202 public function getRootDocumentEntry (): DocumentEntryNode
195203 {
204+ if ($ this ->rootDocumentEntry !== null ) {
205+ return $ this ->rootDocumentEntry ;
206+ }
207+
208+ // Fallback: scan and cache if set via setDocumentEntries()
196209 foreach ($ this ->documentEntries as $ documentEntry ) {
197210 if ($ documentEntry ->isRoot ()) {
211+ $ this ->rootDocumentEntry = $ documentEntry ;
212+
198213 return $ documentEntry ;
199214 }
200215 }
@@ -205,10 +220,8 @@ public function getRootDocumentEntry(): DocumentEntryNode
205220 /** @throws DocumentEntryNotFound */
206221 public function getDocumentEntry (string $ file ): DocumentEntryNode
207222 {
208- foreach ($ this ->documentEntries as $ documentEntry ) {
209- if ($ documentEntry ->getFile () === $ file ) {
210- return $ documentEntry ;
211- }
223+ if (isset ($ this ->documentEntries [$ file ])) {
224+ return $ this ->documentEntries [$ file ];
212225 }
213226
214227 throw new DocumentEntryNotFound ('No document Entry found for file ' . $ file );
@@ -218,6 +231,7 @@ public function getDocumentEntry(string $file): DocumentEntryNode
218231 public function setDocumentEntries (array $ documentEntries ): void
219232 {
220233 $ this ->documentEntries = $ documentEntries ;
234+ $ this ->rootDocumentEntry = null ;
221235 }
222236
223237 public function findDocumentEntry (string $ filePath ): DocumentEntryNode |null
@@ -228,6 +242,7 @@ public function findDocumentEntry(string $filePath): DocumentEntryNode|null
228242 public function reset (): void
229243 {
230244 $ this ->documentEntries = [];
245+ $ this ->rootDocumentEntry = null ;
231246 }
232247
233248 public function getLastRendered (): DateTimeImmutable
0 commit comments