Commit e826bf80363 for php.net
commit e826bf80363f307f7e461f06edc335000cfec3df
Author: Máté Kocsis <kocsismate@woohoolabs.com>
Date: Sun Apr 5 07:31:58 2026 +0200
Add support for generating enum pages for the manual (#21469)
diff --git a/build/gen_stub.php b/build/gen_stub.php
index 396541272c3..67be5c2f7b9 100755
--- a/build/gen_stub.php
+++ b/build/gen_stub.php
@@ -945,6 +945,22 @@ public function getDeclarationName(): string
}
}
+class EnumCaseName {
+ public /* readonly */ Name $enum;
+ public /* readonly */ string $case;
+
+ public function __construct(Name $enum, string $case)
+ {
+ $this->enum = $enum;
+ $this->case = $case;
+ }
+
+ public function __toString(): string
+ {
+ return "$this->enum::$this->case";
+ }
+}
+
interface FunctionOrMethodName {
public function getDeclaration(): string;
public function getArgInfoName(): string;
@@ -3286,17 +3302,19 @@ protected function addModifiersToFieldSynopsis(DOMDocument $doc, DOMElement $fie
}
class EnumCaseInfo {
- private /* readonly */ string $name;
+ private /* readonly */ EnumCaseName $name;
private /* readonly */ ?Expr $value;
+ private /* readonly */ ?string $valueString;
- public function __construct(string $name, ?Expr $value) {
+ public function __construct(EnumCaseName $name, ?Expr $value, ?string $valueString) {
$this->name = $name;
$this->value = $value;
+ $this->valueString = $valueString;
}
/** @param array<string, ConstInfo> $allConstInfos */
public function getDeclaration(array $allConstInfos): string {
- $escapedName = addslashes($this->name);
+ $escapedName = addslashes($this->name->case);
if ($this->value === null) {
$code = "\n\tzend_enum_add_case_cstr(class_entry, \"$escapedName\", NULL);\n";
} else {
@@ -3309,6 +3327,55 @@ public function getDeclaration(array $allConstInfos): string {
return $code;
}
+
+ /** @param array<string, ConstInfo> $allConstInfos */
+ public function getEnumSynopsisItemElement(DOMDocument $doc, array $allConstInfos, int $indentationLevel): DOMElement
+ {
+ $indentation = str_repeat(" ", $indentationLevel);
+
+ $itemElement = $doc->createElement("enumitem");
+
+ $identifierElement = $doc->createElement("enumidentifier", $this->name->case);
+
+ $itemElement->appendChild(new DOMText("\n$indentation "));
+ $itemElement->appendChild($identifierElement);
+
+ $valueString = $this->getEnumSynopsisValueString($allConstInfos);
+ if ($valueString) {
+ $itemElement->appendChild(new DOMText("\n$indentation "));
+ $valueElement = $doc->createElement("enumvalue", $valueString);
+ $itemElement->appendChild($valueElement);
+ }
+
+ $descriptionElement = $doc->createElement("enumitemdescription", "Description.");
+ $itemElement->appendChild(new DOMText("\n$indentation "));
+ $itemElement->appendChild($descriptionElement);
+
+ $itemElement->appendChild(new DOMText("\n$indentation"));
+
+ return $itemElement;
+ }
+
+ /** @param array<string, ConstInfo> $allConstInfos */
+ public function getEnumSynopsisValueString(array $allConstInfos): ?string
+ {
+ if ($this->value === null) {
+ return null;
+ }
+
+ $value = EvaluatedValue::createFromExpression($this->value, null, null, $allConstInfos);
+ if ($value->isUnknownConstValue) {
+ return null;
+ }
+
+ if ($value->originatingConsts) {
+ return implode("\n", array_map(function (ConstInfo $const) use ($allConstInfos) {
+ return $const->getFieldSynopsisValueString($allConstInfos);
+ }, $value->originatingConsts));
+ }
+
+ return $this->valueString;
+ }
}
// Instances of AttributeInfo are immutable and do not need to be cloned
@@ -3748,8 +3815,12 @@ public function getClassSynopsisDocument(array $classMap, array $allConstInfos):
* @param array<string, ConstInfo> $allConstInfos
*/
public function getClassSynopsisElement(DOMDocument $doc, array $classMap, array $allConstInfos): ?DOMElement {
- $classSynopsis = $doc->createElement("classsynopsis");
- $classSynopsis->setAttribute("class", $this->type === "interface" ? "interface" : "class");
+ if ($this->type === "enum") {
+ $classSynopsis = $doc->createElement("enumsynopsis");
+ } else {
+ $classSynopsis = $doc->createElement("classsynopsis");
+ $classSynopsis->setAttribute("class", $this->type === "interface" ? "interface" : "class");
+ }
$namespace = $this->getNamespace();
if ($namespace) {
@@ -3769,108 +3840,120 @@ public function getClassSynopsisElement(DOMDocument $doc, array $classMap, array
$classSynopsisIndentation = str_repeat(" ", $classSynopsisIndentationLevel);
}
- $exceptionOverride = $this->type === "class" && $this->isException($classMap) ? "exception" : null;
- $ooElement = self::createOoElement($doc, $this, $exceptionOverride, true, null, $classSynopsisIndentationLevel + 1);
- if (!$ooElement) {
- return null;
- }
$classSynopsis->appendChild(new DOMText("\n$classSynopsisIndentation "));
- $classSynopsis->appendChild($ooElement);
- foreach ($this->extends as $k => $parent) {
- $parentInfo = $classMap[$parent->toString()] ?? null;
- if ($parentInfo === null) {
- throw new Exception("Missing parent class " . $parent->toString());
- }
+ if ($this->type === "enum") {
+ $enumName = $doc->createElement("enumname", $this->getClassName());
+ $classSynopsis->appendChild($enumName);
- $ooElement = self::createOoElement(
- $doc,
- $parentInfo,
- null,
- false,
- $k === 0 ? "extends" : null,
- $classSynopsisIndentationLevel + 1
- );
+ foreach ($this->enumCaseInfos as $enumCaseInfo) {
+ $classSynopsis->appendChild(new DOMText("\n\n$classSynopsisIndentation "));
+ $enumItemElement = $enumCaseInfo->getEnumSynopsisItemElement($doc, $allConstInfos, $classSynopsisIndentationLevel + 1);
+ $classSynopsis->appendChild($enumItemElement);
+ }
+ } else {
+ $exceptionOverride = $this->type === "class" && $this->isException($classMap) ? "exception" : null;
+ $ooElement = self::createOoElement($doc, $this, $exceptionOverride, true, null, $classSynopsisIndentationLevel + 1);
if (!$ooElement) {
return null;
}
-
- $classSynopsis->appendChild(new DOMText("\n\n$classSynopsisIndentation "));
$classSynopsis->appendChild($ooElement);
- }
- foreach ($this->implements as $k => $interface) {
- $interfaceInfo = $classMap[$interface->toString()] ?? null;
- if (!$interfaceInfo) {
- throw new Exception("Missing implemented interface " . $interface->toString());
+ foreach ($this->extends as $k => $parent) {
+ $parentInfo = $classMap[$parent->toString()] ?? null;
+ if ($parentInfo === null) {
+ throw new Exception("Missing parent class " . $parent->toString());
+ }
+
+ $ooElement = self::createOoElement(
+ $doc,
+ $parentInfo,
+ null,
+ false,
+ $k === 0 ? "extends" : null,
+ $classSynopsisIndentationLevel + 1
+ );
+ if (!$ooElement) {
+ return null;
+ }
+
+ $classSynopsis->appendChild(new DOMText("\n\n$classSynopsisIndentation "));
+ $classSynopsis->appendChild($ooElement);
}
- $ooElement = self::createOoElement($doc, $interfaceInfo, null, false, $k === 0 ? "implements" : null, $classSynopsisIndentationLevel + 1);
- if (!$ooElement) {
- return null;
+ foreach ($this->implements as $k => $interface) {
+ $interfaceInfo = $classMap[$interface->toString()] ?? null;
+ if (!$interfaceInfo) {
+ throw new Exception("Missing implemented interface " . $interface->toString());
+ }
+
+ $ooElement = self::createOoElement($doc, $interfaceInfo, null, false, $k === 0 ? "implements" : null, $classSynopsisIndentationLevel + 1);
+ if (!$ooElement) {
+ return null;
+ }
+ $classSynopsis->appendChild(new DOMText("\n\n$classSynopsisIndentation "));
+ $classSynopsis->appendChild($ooElement);
}
- $classSynopsis->appendChild(new DOMText("\n\n$classSynopsisIndentation "));
- $classSynopsis->appendChild($ooElement);
- }
- /** @var array<string, Name> $parentsWithInheritedConstants */
- $parentsWithInheritedConstants = [];
- /** @var array<string, Name> $parentsWithInheritedProperties */
- $parentsWithInheritedProperties = [];
- /** @var array<int, array{name: Name, types: int[]}> $parentsWithInheritedMethods */
- $parentsWithInheritedMethods = [];
+ /** @var array<string, Name> $parentsWithInheritedConstants */
+ $parentsWithInheritedConstants = [];
+ /** @var array<string, Name> $parentsWithInheritedProperties */
+ $parentsWithInheritedProperties = [];
+ /** @var array<int, array{name: Name, types: int[]}> $parentsWithInheritedMethods */
+ $parentsWithInheritedMethods = [];
- $this->collectInheritedMembers(
- $parentsWithInheritedConstants,
- $parentsWithInheritedProperties,
- $parentsWithInheritedMethods,
- $this->hasConstructor(),
- $classMap
- );
+ $this->collectInheritedMembers(
+ $parentsWithInheritedConstants,
+ $parentsWithInheritedProperties,
+ $parentsWithInheritedMethods,
+ $this->hasConstructor(),
+ $classMap
+ );
- $this->appendInheritedMemberSectionToClassSynopsis(
- $doc,
- $classSynopsis,
- $parentsWithInheritedConstants,
- "&Constants;",
- "&InheritedConstants;",
- $classSynopsisIndentationLevel + 1
- );
+ $this->appendInheritedMemberSectionToClassSynopsis(
+ $doc,
+ $classSynopsis,
+ $parentsWithInheritedConstants,
+ "&Constants;",
+ "&InheritedConstants;",
+ $classSynopsisIndentationLevel + 1
+ );
- if (!empty($this->constInfos)) {
- $classSynopsis->appendChild(new DOMText("\n\n$classSynopsisIndentation "));
- $classSynopsisInfo = $doc->createElement("classsynopsisinfo", "&Constants;");
- $classSynopsisInfo->setAttribute("role", "comment");
- $classSynopsis->appendChild($classSynopsisInfo);
+ if (!empty($this->constInfos)) {
+ $classSynopsis->appendChild(new DOMText("\n\n$classSynopsisIndentation "));
+ $classSynopsisInfo = $doc->createElement("classsynopsisinfo", "&Constants;");
+ $classSynopsisInfo->setAttribute("role", "comment");
+ $classSynopsis->appendChild($classSynopsisInfo);
- foreach ($this->constInfos as $constInfo) {
- $classSynopsis->appendChild(new DOMText("\n$classSynopsisIndentation "));
- $fieldSynopsisElement = $constInfo->getFieldSynopsisElement($doc, $allConstInfos, $classSynopsisIndentationLevel + 1);
- $classSynopsis->appendChild($fieldSynopsisElement);
+ foreach ($this->constInfos as $constInfo) {
+ $classSynopsis->appendChild(new DOMText("\n$classSynopsisIndentation "));
+ $fieldSynopsisElement = $constInfo->getFieldSynopsisElement($doc, $allConstInfos, $classSynopsisIndentationLevel + 1);
+ $classSynopsis->appendChild($fieldSynopsisElement);
+ }
}
- }
- if (!empty($this->propertyInfos)) {
- $classSynopsis->appendChild(new DOMText("\n\n$classSynopsisIndentation "));
- $classSynopsisInfo = $doc->createElement("classsynopsisinfo", "&Properties;");
- $classSynopsisInfo->setAttribute("role", "comment");
- $classSynopsis->appendChild($classSynopsisInfo);
+ if (!empty($this->propertyInfos)) {
+ $classSynopsis->appendChild(new DOMText("\n\n$classSynopsisIndentation "));
+ $classSynopsisInfo = $doc->createElement("classsynopsisinfo", "&Properties;");
+ $classSynopsisInfo->setAttribute("role", "comment");
+ $classSynopsis->appendChild($classSynopsisInfo);
- foreach ($this->propertyInfos as $propertyInfo) {
- $classSynopsis->appendChild(new DOMText("\n$classSynopsisIndentation "));
- $fieldSynopsisElement = $propertyInfo->getFieldSynopsisElement($doc, $allConstInfos, $classSynopsisIndentationLevel + 1);
- $classSynopsis->appendChild($fieldSynopsisElement);
+ foreach ($this->propertyInfos as $propertyInfo) {
+ $classSynopsis->appendChild(new DOMText("\n$classSynopsisIndentation "));
+ $fieldSynopsisElement = $propertyInfo->getFieldSynopsisElement($doc, $allConstInfos, $classSynopsisIndentationLevel + 1);
+ $classSynopsis->appendChild($fieldSynopsisElement);
+ }
}
- }
- $this->appendInheritedMemberSectionToClassSynopsis(
- $doc,
- $classSynopsis,
- $parentsWithInheritedProperties,
- "&Properties;",
- "&InheritedProperties;",
- $classSynopsisIndentationLevel + 1
- );
+ $this->appendInheritedMemberSectionToClassSynopsis(
+ $doc,
+ $classSynopsis,
+ $parentsWithInheritedProperties,
+ "&Properties;",
+ "&InheritedProperties;",
+ $classSynopsisIndentationLevel + 1
+ );
+ }
if (!empty($this->funcInfos)) {
$classSynopsis->appendChild(new DOMText("\n\n$classSynopsisIndentation "));
@@ -3954,7 +4037,7 @@ private static function createOoElement(
$indentation = str_repeat(" ", $indentationLevel);
if ($classInfo->type !== "class" && $classInfo->type !== "interface") {
- echo "Class synopsis generation is not implemented for " . $classInfo->type . "\n";
+ echo "Warning: Class synopsis generation is not implemented for " . $classInfo->type . " of type " . $classInfo->name . "\n";
return null;
}
@@ -4491,7 +4574,10 @@ private function handleStatements(array $stmts, PrettyPrinterAbstract $prettyPri
);
} else if ($classStmt instanceof Stmt\EnumCase) {
$enumCaseInfos[] = new EnumCaseInfo(
- $classStmt->name->toString(), $classStmt->expr);
+ new EnumCaseName($className, $classStmt->name->toString()),
+ $classStmt->expr,
+ $classStmt->expr ? $prettyPrinter->prettyPrintExpr($classStmt->expr) : null,
+ );
} else {
throw new Exception("Not implemented {$classStmt->getType()}");
}
@@ -5741,7 +5827,7 @@ function replaceClassSynopses(
continue;
}
- if (stripos($xml, "<classsynopsis") === false) {
+ if (stripos($xml, "<classsynopsis") === false && stripos($xml, "<enumsynopsis") === false) {
continue;
}
@@ -5757,36 +5843,48 @@ function replaceClassSynopses(
continue;
}
- $classSynopsisElements = [];
+ $synopsisElements = [];
foreach ($doc->getElementsByTagName("classsynopsis") as $element) {
- $classSynopsisElements[] = $element;
+ $synopsisElements[] = $element;
}
- foreach ($classSynopsisElements as $classSynopsis) {
- if (!$classSynopsis instanceof DOMElement) {
- continue;
- }
+ foreach ($doc->getElementsByTagName("enumsynopsis") as $element) {
+ $synopsisElements[] = $element;
+ }
- $child = $classSynopsis->firstElementChild;
- if ($child === null) {
+ foreach ($synopsisElements as $synopsis) {
+ if (!$synopsis instanceof DOMElement) {
continue;
}
- $child = $child->lastElementChild;
- if ($child === null) {
- continue;
+
+ if ($synopsis->nodeName === "classsynopsis") {
+ $child = $synopsis->firstElementChild;
+ if ($child === null) {
+ continue;
+ }
+ $child = $child->lastElementChild;
+ if ($child === null) {
+ continue;
+ }
+ } elseif ($synopsis->nodeName === "enumsynopsis") {
+ $child = $synopsis->firstElementChild;
+ if ($child === null) {
+ continue;
+ }
}
+
$className = $child->textContent;
- if ($classSynopsis->parentElement->nodeName === "packagesynopsis" &&
- $classSynopsis->parentElement->firstElementChild->nodeName === "package"
+ if ($synopsis->parentElement->nodeName === "packagesynopsis" &&
+ $synopsis->parentElement->firstElementChild->nodeName === "package"
) {
- $package = $classSynopsis->parentElement->firstElementChild;
+ $package = $synopsis->parentElement->firstElementChild;
$namespace = $package->textContent;
$className = $namespace . "\\" . $className;
- $elementToReplace = $classSynopsis->parentElement;
+ $elementToReplace = $synopsis->parentElement;
} else {
- $elementToReplace = $classSynopsis;
+ $elementToReplace = $synopsis;
}
if (!isset($classMap[$className])) {
@@ -5797,17 +5895,32 @@ function replaceClassSynopses(
$classInfo = $classMap[$className];
- $newClassSynopsis = $classInfo->getClassSynopsisElement($doc, $classMap, $allConstInfos);
- if ($newClassSynopsis === null) {
+ $newSynopsis = $classInfo->getClassSynopsisElement($doc, $classMap, $allConstInfos);
+ if ($newSynopsis === null) {
continue;
}
// Check if there is any change - short circuit if there is not any.
- if (replaceAndCompareXmls($doc, $elementToReplace, $newClassSynopsis)) {
+ if (replaceAndCompareXmls($doc, $elementToReplace, $newSynopsis)) {
continue;
}
+ if ($synopsis->nodeName === "enumsynopsis") {
+ $oldEnumCaseDescriptionElements = collectEnumSynopsisItemDescriptions($className, $elementToReplace);
+ $newEnumCaseDescriptionElements = collectEnumSynopsisItemDescriptions($className, $newSynopsis);
+
+ foreach ($newEnumCaseDescriptionElements as $key => $newEnumCaseDescriptionElement) {
+ if (isset($oldEnumCaseDescriptionElements[$key]) === false) {
+ continue;
+ }
+
+ $oldEnumCaseDescriptionElement = $oldEnumCaseDescriptionElements[$key];
+
+ $newEnumCaseDescriptionElement->parentElement->replaceChild($oldEnumCaseDescriptionElement, $newEnumCaseDescriptionElement);
+ }
+ }
+
// Return the updated XML
$replacedXml = $doc->saveXML();
@@ -5843,6 +5956,28 @@ function replaceClassSynopses(
return $classSynopses;
}
+/**
+ * @return array<string, DOMElement>
+ */
+function collectEnumSynopsisItemDescriptions(string $className, DOMElement $synopsis): array
+{
+ $enumCaseDescriptionElements = [];
+
+ $enumCaseDescriptions = $synopsis->getElementsByTagName("enumitemdescription");
+ foreach ($enumCaseDescriptions as $enumItemDescription) {
+ $enumCaseNames = $enumItemDescription->parentElement->getElementsByTagName("enumidentifier");
+ if (empty($enumCaseNames)) {
+ continue;
+ }
+
+ $enumCaseName = $enumCaseNames[0]->textContent;
+
+ $enumCaseDescriptionElements["$className::$enumCaseName"] = $enumItemDescription;
+ }
+
+ return $enumCaseDescriptionElements;
+}
+
function getReplacedSynopsisXml(string $xml): string
{
return preg_replace(