diff --git a/packages/guides/src/Compiler/Passes/GlobalMenuPass.php b/packages/guides/src/Compiler/Passes/GlobalMenuPass.php index dac74b564..cbe9f4b18 100644 --- a/packages/guides/src/Compiler/Passes/GlobalMenuPass.php +++ b/packages/guides/src/Compiler/Passes/GlobalMenuPass.php @@ -62,8 +62,9 @@ public function run(array $documents, CompilerContextInterface $compilerContext) } $rootDocument = null; + $rootFile = $rootDocumentEntry->getFile(); foreach ($documents as $document) { - if ($document->getDocumentEntry() === $rootDocumentEntry) { + if ($document->getDocumentEntry()->getFile() === $rootFile) { $rootDocument = $document; break; } diff --git a/packages/guides/src/Compiler/Passes/ToctreeValidationPass.php b/packages/guides/src/Compiler/Passes/ToctreeValidationPass.php index 75a69bb9b..ec93f9774 100644 --- a/packages/guides/src/Compiler/Passes/ToctreeValidationPass.php +++ b/packages/guides/src/Compiler/Passes/ToctreeValidationPass.php @@ -68,6 +68,7 @@ public function run(array $documents, CompilerContextInterface $compilerContext) public function isMissingInToctree(DocumentEntryNode $documentEntry, ProjectNode $projectNode): bool { - return $documentEntry->getParent() === null && $documentEntry !== $projectNode->getRootDocumentEntry(); + return $documentEntry->getParent() === null + && $documentEntry->getFile() !== $projectNode->getRootDocumentEntry()->getFile(); } } diff --git a/packages/guides/src/Nodes/ProjectNode.php b/packages/guides/src/Nodes/ProjectNode.php index 7690c434c..2afce43e2 100644 --- a/packages/guides/src/Nodes/ProjectNode.php +++ b/packages/guides/src/Nodes/ProjectNode.php @@ -47,7 +47,10 @@ final class ProjectNode extends CompoundNode /** @var array> */ private array $internalLinkTargets = []; - /** @var DocumentEntryNode[] */ + /** Cached root document entry for O(1) lookup */ + private DocumentEntryNode|null $rootDocumentEntry = null; + + /** @var array */ private array $documentEntries = []; private DateTimeImmutable $lastRendered; @@ -182,6 +185,11 @@ public function getAllInternalTargets(): array public function addDocumentEntry(DocumentEntryNode $documentEntry): void { + // Cache root for O(1) lookup + if ($documentEntry->isRoot()) { + $this->rootDocumentEntry = $documentEntry; + } + $this->documentEntries[$documentEntry->getFile()] = $documentEntry; } @@ -193,10 +201,8 @@ public function getAllDocumentEntries(): array public function getRootDocumentEntry(): DocumentEntryNode { - foreach ($this->documentEntries as $documentEntry) { - if ($documentEntry->isRoot()) { - return $documentEntry; - } + if ($this->rootDocumentEntry !== null) { + return $this->rootDocumentEntry; } throw new Exception('No root document entry was found'); @@ -205,10 +211,9 @@ public function getRootDocumentEntry(): DocumentEntryNode /** @throws DocumentEntryNotFound */ public function getDocumentEntry(string $file): DocumentEntryNode { - foreach ($this->documentEntries as $documentEntry) { - if ($documentEntry->getFile() === $file) { - return $documentEntry; - } + // O(1) lookup by file path + if (isset($this->documentEntries[$file])) { + return $this->documentEntries[$file]; } throw new DocumentEntryNotFound('No document Entry found for file ' . $file); @@ -217,7 +222,12 @@ public function getDocumentEntry(string $file): DocumentEntryNode /** @param DocumentEntryNode[] $documentEntries */ public function setDocumentEntries(array $documentEntries): void { - $this->documentEntries = $documentEntries; + $this->documentEntries = []; + $this->rootDocumentEntry = null; + + foreach ($documentEntries as $entry) { + $this->addDocumentEntry($entry); + } } public function findDocumentEntry(string $filePath): DocumentEntryNode|null @@ -228,6 +238,7 @@ public function findDocumentEntry(string $filePath): DocumentEntryNode|null public function reset(): void { $this->documentEntries = []; + $this->rootDocumentEntry = null; } public function getLastRendered(): DateTimeImmutable diff --git a/packages/guides/src/RenderContext.php b/packages/guides/src/RenderContext.php index bc8e52b64..5d797667e 100644 --- a/packages/guides/src/RenderContext.php +++ b/packages/guides/src/RenderContext.php @@ -222,8 +222,9 @@ public function getProjectNode(): ProjectNode public function getDocumentNodeForEntry(DocumentEntryNode $entryNode): DocumentNode { + $file = $entryNode->getFile(); foreach ($this->allDocuments as $child) { - if ($child->getDocumentEntry() === $entryNode) { + if ($child->getDocumentEntry()->getFile() === $file) { return $child; } } diff --git a/packages/guides/src/Renderer/DocumentTreeIterator.php b/packages/guides/src/Renderer/DocumentTreeIterator.php index 30266415d..d2904dc5c 100644 --- a/packages/guides/src/Renderer/DocumentTreeIterator.php +++ b/packages/guides/src/Renderer/DocumentTreeIterator.php @@ -41,8 +41,9 @@ public function __construct( public function current(): DocumentNode { + $file = $this->levelNodes[$this->position]->getFile(); foreach ($this->documents as $document) { - if ($document->getDocumentEntry() === $this->levelNodes[$this->position]) { + if ($document->getDocumentEntry()->getFile() === $file) { return $document; } } diff --git a/tests/Functional/FunctionalTest.php b/tests/Functional/FunctionalTest.php index f0e5d53df..bcac5797f 100644 --- a/tests/Functional/FunctionalTest.php +++ b/tests/Functional/FunctionalTest.php @@ -25,10 +25,8 @@ use phpDocumentor\Guides\Compiler\Compiler; use phpDocumentor\Guides\Compiler\CompilerContext; use phpDocumentor\Guides\NodeRenderers\NodeRenderer; -use phpDocumentor\Guides\Nodes\DocumentTree\DocumentEntryNode; use phpDocumentor\Guides\Nodes\Node; use phpDocumentor\Guides\Nodes\ProjectNode; -use phpDocumentor\Guides\Nodes\TitleNode; use phpDocumentor\Guides\Parser; use phpDocumentor\Guides\RenderContext; use phpDocumentor\Guides\Settings\ProjectSettings; @@ -110,13 +108,11 @@ public function testFunctional( $parser = $this->getContainer()->get(Parser::class); assert($parser instanceof Parser); - $document = $parser->parse($rst); - $documentEntry = new DocumentEntryNode($document->getFilePath(), $document->getTitle() ?? TitleNode::fromString(''), true); + $document = $parser->parse($rst)->withIsRoot(true); $compiler = $this->getContainer()->get(Compiler::class); assert($compiler instanceof Compiler); $projectNode = new ProjectNode(); - $projectNode->setDocumentEntries([$documentEntry]); $compiler->run([$document], new CompilerContext($projectNode)); $inputFilesystem = FlySystemAdapter::createFromFileSystem(new Filesystem(new InMemoryFilesystemAdapter()));