Skip to content

Commit 55ea0c7

Browse files
committed
[BUGFIX] Do not let a detached <project> break the configuration
Two defects in the previous commit of this branch, both found by review and both reproduced before fixing. Detaching <project> can leave an empty root element behind, and `XmlUtils::convertDomElementToArray()` returns null rather than an empty array for one. `assert(is_array($rootConfig))` then fails for any `guides.xml` that holds nothing but a project — which the `version-from-guides-xml` fixtures are. It stays invisible under the default and the CI php.ini, both of which run with `zend.assertions=-1`, where the following write to `$rootConfig['project']` auto-vivifies null; with `zend.assertions=1` those two data sets fail. `getElementsByTagName('project')` searches the whole subtree, and the schema lets an `<extension>` carry arbitrary child elements. A nested `<project>` was therefore read as the project configuration and removed from that extension, while the real one at root level was dropped. Look at direct children only. The new `version-from-guides-xml-nested-project` fixture covers the second defect and fails against the previous state. The first one is only observable with assertions enabled, and the existing fixtures already cover it there. Assisted-by: claude-code:claude-opus-5 Agent-Session: https://claude.ai/code/session_015QXXkquh2eQNBiTYA39Wss Signed-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>
1 parent 555a67e commit 55ea0c7

4 files changed

Lines changed: 54 additions & 3 deletions

File tree

packages/guides-cli/src/Config/XmlFileLoader.php

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ public function load(mixed $resource, string|null $type = null): array
4646
// become 1). Reading them straight from the DOM and detaching <project>
4747
// beforehand keeps the version exactly as written.
4848
$projectConfig = null;
49-
$project = $element->getElementsByTagName('project')->item(0);
50-
if ($project instanceof DOMElement) {
49+
$project = $this->firstChildElement($element, 'project');
50+
if ($project !== null) {
5151
$projectConfig = [];
5252
foreach ($project->attributes as $attribute) {
5353
if (!($attribute instanceof DOMAttr)) {
@@ -71,8 +71,12 @@ public function load(mixed $resource, string|null $type = null): array
7171
$project->parentNode?->removeChild($project);
7272
}
7373

74+
// Detaching <project> can leave an otherwise empty root element behind, and
75+
// convertDomElementToArray() returns null rather than an empty array for that.
7476
$rootConfig = XmlUtils::convertDomElementToArray($element);
75-
assert(is_array($rootConfig));
77+
if (!is_array($rootConfig)) {
78+
$rootConfig = [];
79+
}
7680

7781
if ($projectConfig !== null) {
7882
$rootConfig['project'] = $projectConfig;
@@ -95,6 +99,24 @@ public function load(mixed $resource, string|null $type = null): array
9599
return $configs;
96100
}
97101

102+
/**
103+
* Returns the first direct child element of the given name.
104+
*
105+
* `getElementsByTagName()` would search the whole subtree, and the schema lets an <extension>
106+
* carry arbitrary child elements, so a nested <project> could be read as the project configuration
107+
* and removed from that extension.
108+
*/
109+
private function firstChildElement(DOMElement $element, string $name): DOMElement|null
110+
{
111+
foreach ($element->childNodes as $child) {
112+
if ($child instanceof DOMElement && $child->localName === $name) {
113+
return $child;
114+
}
115+
}
116+
117+
return null;
118+
}
119+
98120
public function supports(mixed $resource, string|null $type = null): bool
99121
{
100122
return $type === 'xml' && is_string($resource);
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<!-- content start -->
2+
<div class="section" id="some-document">
3+
<h1>Some Document</h1>
4+
5+
<p>Project Render guides in version 0.10, release 3.0.0.</p>
6+
7+
</div>
8+
<!-- content end -->
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<guides
3+
xmlns="https://www.phpdoc.org/guides"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="https://www.phpdoc.org/guides vendor/phpdocumentor/guides-cli/resources/schema/guides.xsd"
6+
>
7+
<!-- The schema lets an extension carry arbitrary child elements. A <project> among them
8+
must not be read as the project configuration of the documentation. -->
9+
<extension class="phpDocumentor\Guides\Bootstrap">
10+
<project title="Nested" version="9.9" release="9.9.9"/>
11+
</extension>
12+
<project
13+
title="Render guides"
14+
version="0.10"
15+
release="3.0.0"
16+
/>
17+
</guides>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Some Document
2+
=============
3+
4+
Project |project| in version |version|, release |release|.

0 commit comments

Comments
 (0)