Skip to content

Commit 8bba205

Browse files
authored
Merge pull request #1293 from CybotTM/perf/projectnode-document-lookup-optimization
perf: O(1) document entry lookups in ProjectNode
2 parents 5820131 + a571546 commit 8bba205

File tree

6 files changed

+30
-19
lines changed

6 files changed

+30
-19
lines changed

packages/guides/src/Compiler/Passes/GlobalMenuPass.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,9 @@ public function run(array $documents, CompilerContextInterface $compilerContext)
6262
}
6363

6464
$rootDocument = null;
65+
$rootFile = $rootDocumentEntry->getFile();
6566
foreach ($documents as $document) {
66-
if ($document->getDocumentEntry() === $rootDocumentEntry) {
67+
if ($document->getDocumentEntry()->getFile() === $rootFile) {
6768
$rootDocument = $document;
6869
break;
6970
}

packages/guides/src/Compiler/Passes/ToctreeValidationPass.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ public function run(array $documents, CompilerContextInterface $compilerContext)
6868

6969
public function isMissingInToctree(DocumentEntryNode $documentEntry, ProjectNode $projectNode): bool
7070
{
71-
return $documentEntry->getParent() === null && $documentEntry !== $projectNode->getRootDocumentEntry();
71+
return $documentEntry->getParent() === null
72+
&& $documentEntry->getFile() !== $projectNode->getRootDocumentEntry()->getFile();
7273
}
7374
}

packages/guides/src/Nodes/ProjectNode.php

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,10 @@ final class ProjectNode extends CompoundNode
4747
/** @var array<string, array<string, InternalTarget>> */
4848
private array $internalLinkTargets = [];
4949

50-
/** @var DocumentEntryNode[] */
50+
/** Cached root document entry for O(1) lookup */
51+
private DocumentEntryNode|null $rootDocumentEntry = null;
52+
53+
/** @var array<string, DocumentEntryNode> */
5154
private array $documentEntries = [];
5255
private DateTimeImmutable $lastRendered;
5356

@@ -182,6 +185,11 @@ public function getAllInternalTargets(): array
182185

183186
public function addDocumentEntry(DocumentEntryNode $documentEntry): void
184187
{
188+
// Cache root for O(1) lookup
189+
if ($documentEntry->isRoot()) {
190+
$this->rootDocumentEntry = $documentEntry;
191+
}
192+
185193
$this->documentEntries[$documentEntry->getFile()] = $documentEntry;
186194
}
187195

@@ -193,10 +201,8 @@ public function getAllDocumentEntries(): array
193201

194202
public function getRootDocumentEntry(): DocumentEntryNode
195203
{
196-
foreach ($this->documentEntries as $documentEntry) {
197-
if ($documentEntry->isRoot()) {
198-
return $documentEntry;
199-
}
204+
if ($this->rootDocumentEntry !== null) {
205+
return $this->rootDocumentEntry;
200206
}
201207

202208
throw new Exception('No root document entry was found');
@@ -205,10 +211,9 @@ public function getRootDocumentEntry(): DocumentEntryNode
205211
/** @throws DocumentEntryNotFound */
206212
public function getDocumentEntry(string $file): DocumentEntryNode
207213
{
208-
foreach ($this->documentEntries as $documentEntry) {
209-
if ($documentEntry->getFile() === $file) {
210-
return $documentEntry;
211-
}
214+
// O(1) lookup by file path
215+
if (isset($this->documentEntries[$file])) {
216+
return $this->documentEntries[$file];
212217
}
213218

214219
throw new DocumentEntryNotFound('No document Entry found for file ' . $file);
@@ -217,7 +222,12 @@ public function getDocumentEntry(string $file): DocumentEntryNode
217222
/** @param DocumentEntryNode[] $documentEntries */
218223
public function setDocumentEntries(array $documentEntries): void
219224
{
220-
$this->documentEntries = $documentEntries;
225+
$this->documentEntries = [];
226+
$this->rootDocumentEntry = null;
227+
228+
foreach ($documentEntries as $entry) {
229+
$this->addDocumentEntry($entry);
230+
}
221231
}
222232

223233
public function findDocumentEntry(string $filePath): DocumentEntryNode|null
@@ -228,6 +238,7 @@ public function findDocumentEntry(string $filePath): DocumentEntryNode|null
228238
public function reset(): void
229239
{
230240
$this->documentEntries = [];
241+
$this->rootDocumentEntry = null;
231242
}
232243

233244
public function getLastRendered(): DateTimeImmutable

packages/guides/src/RenderContext.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,9 @@ public function getProjectNode(): ProjectNode
222222

223223
public function getDocumentNodeForEntry(DocumentEntryNode $entryNode): DocumentNode
224224
{
225+
$file = $entryNode->getFile();
225226
foreach ($this->allDocuments as $child) {
226-
if ($child->getDocumentEntry() === $entryNode) {
227+
if ($child->getDocumentEntry()->getFile() === $file) {
227228
return $child;
228229
}
229230
}

packages/guides/src/Renderer/DocumentTreeIterator.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,9 @@ public function __construct(
4141

4242
public function current(): DocumentNode
4343
{
44+
$file = $this->levelNodes[$this->position]->getFile();
4445
foreach ($this->documents as $document) {
45-
if ($document->getDocumentEntry() === $this->levelNodes[$this->position]) {
46+
if ($document->getDocumentEntry()->getFile() === $file) {
4647
return $document;
4748
}
4849
}

tests/Functional/FunctionalTest.php

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,8 @@
2525
use phpDocumentor\Guides\Compiler\Compiler;
2626
use phpDocumentor\Guides\Compiler\CompilerContext;
2727
use phpDocumentor\Guides\NodeRenderers\NodeRenderer;
28-
use phpDocumentor\Guides\Nodes\DocumentTree\DocumentEntryNode;
2928
use phpDocumentor\Guides\Nodes\Node;
3029
use phpDocumentor\Guides\Nodes\ProjectNode;
31-
use phpDocumentor\Guides\Nodes\TitleNode;
3230
use phpDocumentor\Guides\Parser;
3331
use phpDocumentor\Guides\RenderContext;
3432
use phpDocumentor\Guides\Settings\ProjectSettings;
@@ -110,13 +108,11 @@ public function testFunctional(
110108

111109
$parser = $this->getContainer()->get(Parser::class);
112110
assert($parser instanceof Parser);
113-
$document = $parser->parse($rst);
114-
$documentEntry = new DocumentEntryNode($document->getFilePath(), $document->getTitle() ?? TitleNode::fromString(''), true);
111+
$document = $parser->parse($rst)->withIsRoot(true);
115112

116113
$compiler = $this->getContainer()->get(Compiler::class);
117114
assert($compiler instanceof Compiler);
118115
$projectNode = new ProjectNode();
119-
$projectNode->setDocumentEntries([$documentEntry]);
120116
$compiler->run([$document], new CompilerContext($projectNode));
121117

122118
$inputFilesystem = FlySystemAdapter::createFromFileSystem(new Filesystem(new InMemoryFilesystemAdapter()));

0 commit comments

Comments
 (0)